diff --git a/src/main/compiler/CompilerScope.ts b/src/main/compiler/CompilerScope.ts index 28faa28..0be0510 100644 --- a/src/main/compiler/CompilerScope.ts +++ b/src/main/compiler/CompilerScope.ts @@ -233,6 +233,8 @@ export class CompilerScope { return this.emitParamNode(node, resSym); case '@system/Input': return this.emitInputNode(node, resSym); + case '@system/Scope': + return this.emitScopeNode(node, resSym); case '@system/Result': case '@system/Output': return this.emitOutputNode(node, resSym); @@ -265,7 +267,13 @@ export class CompilerScope { } private emitInputNode(node: NodeView, resSym: string) { - this.code.line(`${resSym} = params;`); + this.code.line(`${resSym} = { ...params, ...ctx.getScopeData() };`); + // TODO phase out the workaround after subgraphs are migrated to Scope + Param nodes + // this.code.line(`${resSym} = params;`); + } + + private emitScopeNode(node: NodeView, resSym: string) { + this.code.line(`${resSym} = ctx.getScopeData();`); } private emitOutputNode(node: NodeView, resSym: string) { @@ -367,14 +375,14 @@ export class CompilerScope { const sym = this.symbols.getNodeSym(rootNode.nodeUid); if (this.options.introspect) { return [ - `(params, ctx) => {`, - `ctx.scopeCaptured.emit({ nodeUid: ${JSON.stringify(node.nodeUid)}, params });`, + `(scopeData, ctx) => {`, + `ctx.scopeCaptured.emit({ nodeUid: ${JSON.stringify(node.nodeUid)}, params: scopeData });`, `ctx.checkPendingNode(${JSON.stringify(node.nodeUid)});`, - `return ${sym}(params, ctx);`, + `return ${sym}(params, ctx.setScopeData(scopeData));`, `}`, ].join(''); } - return sym; + return `(scopeData, ctx) => ${sym}(params, ctx.setScopeData(scopeData))`; } private emitNodeProps(node: NodeView) { diff --git a/src/main/runtime/GraphEvalContext.ts b/src/main/runtime/GraphEvalContext.ts index 2bf94e2..3455b86 100644 --- a/src/main/runtime/GraphEvalContext.ts +++ b/src/main/runtime/GraphEvalContext.ts @@ -25,6 +25,8 @@ export class GraphEvalContext implements t.GraphEvalContext { // Locals are stored per-context. Lookups delegate up the hierarchy. locals = new Map(); + scopeData: any = undefined; + constructor( readonly parent: GraphEvalContext | null = null, ) { @@ -68,6 +70,15 @@ export class GraphEvalContext implements t.GraphEvalContext { return new GraphEvalContext(this); } + getScopeData() { + return this.scopeData; + } + + setScopeData(data: any) { + this.scopeData = data; + return this; + } + convertType(value: unknown, schema: SchemaSpec) { return new Schema(schema as any).decode(value); } diff --git a/src/main/runtime/ModuleLoader.ts b/src/main/runtime/ModuleLoader.ts index 3b0d4d5..323c4de 100644 --- a/src/main/runtime/ModuleLoader.ts +++ b/src/main/runtime/ModuleLoader.ts @@ -25,6 +25,7 @@ export abstract class GenericModuleLoader implements ModuleLoader { this.addModule('@system/Output', systemModules.Output); this.addModule('@system/Param', systemModules.Param); this.addModule('@system/Result', systemModules.Output); + this.addModule('@system/Scope', systemModules.Scope); } abstract resolveComputeUrl(ref: string): string; diff --git a/src/main/system/Scope.ts b/src/main/system/Scope.ts new file mode 100644 index 0000000..654737d --- /dev/null +++ b/src/main/system/Scope.ts @@ -0,0 +1,17 @@ +import { ModuleSpecSchema } from '../schema/ModuleSpec.js'; + +export const Scope = ModuleSpecSchema.create({ + moduleName: 'Scope', + version: '0.0.0', + params: { + }, + result: { + schema: { type: 'any' }, + }, + cacheMode: 'never', + attributes: { + hidden: true, + hideEvalControls: true, + forceColor: 'cyan', + } +}); diff --git a/src/main/system/index.ts b/src/main/system/index.ts index c3a7a4e..0ec507e 100644 --- a/src/main/system/index.ts +++ b/src/main/system/index.ts @@ -9,3 +9,4 @@ export * from './Frame.js'; export * from './Input.js'; export * from './Output.js'; export * from './Param.js'; +export * from './Scope.js'; diff --git a/src/main/types/ctx.ts b/src/main/types/ctx.ts index 9a7dfe3..cd59e77 100644 --- a/src/main/types/ctx.ts +++ b/src/main/types/ctx.ts @@ -19,6 +19,8 @@ export interface GraphEvalContext { clear(): void; finalize(): Promise; newScope(): GraphEvalContext; + getScopeData(): any; + setScopeData(data: any): this; checkPendingNode(nodeUid: string): void; getLocal(key: string, defaultValue?: T): T | undefined;