Skip to content

Commit

Permalink
add eval export (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
seveibar authored Jan 23, 2025
1 parent d92f2b5 commit 40213a2
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 11 deletions.
128 changes: 128 additions & 0 deletions lib/eval/CircuitRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { AnyCircuitElement } from "circuit-json"
import type {
CircuitRunnerApi,
CircuitRunnerConfiguration,
} from "lib/shared/types"
import { createExecutionContext } from "../../webworker/execution-context"
import { normalizeFsMap } from "../../webworker/normalize-fs-map"
import type { RootCircuit } from "@tscircuit/core"
import * as React from "react"
import { importEvalPath } from "webworker/import-eval-path"

export class CircuitRunner implements CircuitRunnerApi {
_executionContext: ReturnType<typeof createExecutionContext> | null = null
_circuitRunnerConfiguration: CircuitRunnerConfiguration = {
snippetsApiBaseUrl: "https://registry-api.tscircuit.com",
cjsRegistryUrl: "https://cjs.tscircuit.com",
verbose: false,
}
_eventListeners: Record<string, ((...args: any[]) => void)[]> = {}

async executeWithFsMap(opts: {
entrypoint: string
fsMap: Record<string, string>
name?: string
}): Promise<void> {
if (this._circuitRunnerConfiguration.verbose) {
console.log("[CircuitRunner] executeWithFsMap called with:", {
entrypoint: opts.entrypoint,
fsMapKeys: Object.keys(opts.fsMap),
name: opts.name,
})
}

this._executionContext = createExecutionContext(
this._circuitRunnerConfiguration,
{
name: opts.name,
},
)
this._bindEventListeners(this._executionContext.circuit)

this._executionContext.fsMap = normalizeFsMap(opts.fsMap)
if (!this._executionContext.fsMap[opts.entrypoint]) {
throw new Error(`Entrypoint "${opts.entrypoint}" not found`)
}
;(globalThis as any).__tscircuit_circuit = this._executionContext.circuit

const entrypoint = opts.entrypoint.startsWith("./")
? opts.entrypoint
: `./${opts.entrypoint}`

await importEvalPath(entrypoint, this._executionContext)
}

async execute(code: string, opts: { name?: string } = {}) {
if (this._circuitRunnerConfiguration.verbose) {
console.log(
"[CircuitRunner] execute called with code length:",
code.length,
)
}

this._executionContext = createExecutionContext(
this._circuitRunnerConfiguration,
opts,
)
this._bindEventListeners(this._executionContext.circuit)
this._executionContext.fsMap["entrypoint.tsx"] = code
;(globalThis as any).__tscircuit_circuit = this._executionContext.circuit

await importEvalPath("./entrypoint.tsx", this._executionContext)
}

on(event: string, callback: (...args: any[]) => void) {
this._eventListeners[event] ??= []
this._eventListeners[event].push(callback)
this._executionContext?.circuit.on(event as any, callback)
}

async renderUntilSettled(): Promise<void> {
if (!this._executionContext) {
throw new Error("No circuit has been created")
}
await this._executionContext.circuit.renderUntilSettled()
}

async getCircuitJson(): Promise<AnyCircuitElement[]> {
if (!this._executionContext) {
throw new Error("No circuit has been created")
}
return this._executionContext.circuit.getCircuitJson()
}

clearEventListeners() {
if (this._executionContext?.circuit) {
for (const event in this._eventListeners) {
for (const listener of this._eventListeners[event]) {
const circuit = this._executionContext.circuit as unknown as {
// biome-ignore lint/complexity/noBannedTypes: <explanation>
removeListener?: (event: string, listener: Function) => void
}
circuit.removeListener?.(event, listener)
}
}
}

for (const event in this._eventListeners) {
delete this._eventListeners[event]
}
}

async kill() {
// Cleanup resources
this._executionContext = null
}

async setSnippetsApiBaseUrl(baseUrl: string) {
this._circuitRunnerConfiguration.snippetsApiBaseUrl = baseUrl
}

private _bindEventListeners(circuit: RootCircuit) {
for (const event in this._eventListeners) {
for (const listener of this._eventListeners[event]) {
circuit.on(event as any, listener as any)
}
}
}
}
1 change: 1 addition & 0 deletions lib/eval/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./CircuitRunner"
17 changes: 14 additions & 3 deletions lib/shared/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import type { AnyCircuitElement } from "circuit-json"

export interface WebWorkerConfiguration {
export interface CircuitRunnerConfiguration {
snippetsApiBaseUrl: string
cjsRegistryUrl: string
verbose?: boolean
}

export interface WebWorkerConfiguration extends CircuitRunnerConfiguration {
/**
* @deprecated, renamed to webWorkerBlobUrl
*/
webWorkerUrl?: URL | string
webWorkerBlobUrl?: URL | string
verbose?: boolean
}

export interface InternalWebWorkerApi {
/**
* API for the CircuitRunner class, used for eval'ing circuits
*/
export interface CircuitRunnerApi {
execute: (
code: string,
opts?: {
Expand All @@ -31,6 +37,11 @@ export interface InternalWebWorkerApi {
kill: () => Promise<void>
}

/**
* @deprecated, use CircuitRunnerApi instead
*/
export type InternalWebWorkerApi = CircuitRunnerApi

export type CircuitWebWorker = {
execute: (code: string) => Promise<void>
executeWithFsMap: (opts: {
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
"version": "0.0.77",
"type": "module",
"scripts": {
"build": "bun run build:lib && bun run build:webworker && bun run build:blob-url",
"build": "bun run build:lib && bun run build:webworker && bun run build:blob-url && bun run build:eval",
"build:lib": "tsup-node ./lib/index.ts --format esm --sourcemap inline --dts -d dist/lib",
"build:webworker": "tsup ./webworker/index.ts --platform browser --format esm --sourcemap inline -d dist/webworker",
"build:webworker:analyze": "tsup ./webworker/index.ts --platform browser --metafile ./metadata.json --format esm --sourcemap inline -d dist/webworker",
"build:blob-url": "bun run ./scripts/build-worker-blob-url.ts",
"build:eval": "tsup-node ./lib/eval --format esm --dts --platform browser -d dist/eval",
"format": "biome format --write .",
"format:check": "biome format ."
},
"exports": {
".": "./dist/lib/index.js",
"./worker": "./dist/webworker/index.js",
"./blob-url": "./dist/blob-url.js"
"./blob-url": "./dist/blob-url.js",
"./eval": "./dist/eval/index.js"
},
"devDependencies": {
"@babel/standalone": "^7.26.2",
Expand Down
27 changes: 27 additions & 0 deletions tests/circuit-runner/circuitrunner1-readme-example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createCircuitWebWorker } from "lib"
import { expect, test } from "bun:test"
import { CircuitRunner } from "lib/eval/CircuitRunner"

test("circuitrunner1-readme-example", async () => {
const circuitRunner = new CircuitRunner()

await circuitRunner.execute(`
import { RedLed } from "@tsci/seveibar.red-led"
circuit.add(
<board width="10mm" height="10mm">
<RedLed name="LED1" />
</board>
)
`)

await circuitRunner.renderUntilSettled()

const circuitJson = await circuitRunner.getCircuitJson()

expect(circuitJson).toBeDefined()

const led = circuitJson.find((el: any) => el.name === "LED1")
expect(led).toBeDefined()
expect(led?.type).toBe("source_component")
})
2 changes: 2 additions & 0 deletions webworker/execution-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function createExecutionContext(
name?: string
} = {},
): ExecutionContext {
globalThis.React = React

const circuit = new RootCircuit()

if (opts.name) {
Expand Down
12 changes: 6 additions & 6 deletions webworker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ globalThis.React = React

let executionContext: ExecutionContext | null = null

const webWorkerConfiguration: WebWorkerConfiguration = {
const circuitRunnerConfiguration: WebWorkerConfiguration = {
snippetsApiBaseUrl: "https://registry-api.tscircuit.com",
cjsRegistryUrl: "https://cjs.tscircuit.com",
verbose: false,
Expand All @@ -35,22 +35,22 @@ function bindEventListeners(circuit: RootCircuit) {

const webWorkerApi = {
setSnippetsApiBaseUrl: async (baseUrl: string) => {
webWorkerConfiguration.snippetsApiBaseUrl = baseUrl
circuitRunnerConfiguration.snippetsApiBaseUrl = baseUrl
},

async executeWithFsMap(opts: {
entrypoint: string
fsMap: Record<string, string>
name?: string
}): Promise<void> {
if (webWorkerConfiguration.verbose) {
if (circuitRunnerConfiguration.verbose) {
console.log("[Worker] executeWithFsMap called with:", {
entrypoint: opts.entrypoint,
fsMapKeys: Object.keys(opts.fsMap),
name: opts.name,
})
}
executionContext = createExecutionContext(webWorkerConfiguration, {
executionContext = createExecutionContext(circuitRunnerConfiguration, {
name: opts.name,
})
bindEventListeners(executionContext.circuit)
Expand All @@ -68,10 +68,10 @@ const webWorkerApi = {
},

async execute(code: string, opts: { name?: string } = {}) {
if (webWorkerConfiguration.verbose) {
if (circuitRunnerConfiguration.verbose) {
console.log("[Worker] execute called with code length:", code.length)
}
executionContext = createExecutionContext(webWorkerConfiguration, opts)
executionContext = createExecutionContext(circuitRunnerConfiguration, opts)
bindEventListeners(executionContext.circuit)
executionContext.fsMap["entrypoint.tsx"] = code
;(globalThis as any).__tscircuit_circuit = executionContext.circuit
Expand Down

0 comments on commit 40213a2

Please sign in to comment.