@milaboratories/pl-middle-layer 1.55.21 → 1.55.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js_render/service_injectors.cjs +1 -0
- package/dist/js_render/service_injectors.cjs.map +1 -1
- package/dist/js_render/service_injectors.js +1 -0
- package/dist/js_render/service_injectors.js.map +1 -1
- package/dist/model/args.cjs +44 -14
- package/dist/model/args.cjs.map +1 -1
- package/dist/model/args.js +44 -14
- package/dist/model/args.js.map +1 -1
- package/dist/service_factories.cjs +2 -1
- package/dist/service_factories.cjs.map +1 -1
- package/dist/service_factories.js +2 -1
- package/dist/service_factories.js.map +1 -1
- package/package.json +15 -15
- package/src/js_render/service_injectors.ts +4 -0
- package/src/model/args.test.ts +103 -0
- package/src/model/args.ts +64 -25
- package/src/service_factories.ts +1 -0
|
@@ -37,6 +37,7 @@ function getServiceInjectors() {
|
|
|
37
37
|
findTableColumn: (tableSpec, selector) => vm.exportSingleValue(driver.findTableColumn(vm.importObjectViaJson(tableSpec), vm.importObjectViaJson(selector)))
|
|
38
38
|
};
|
|
39
39
|
},
|
|
40
|
+
Dialog: () => ({}),
|
|
40
41
|
PFrame: ({ host, vm }) => ({
|
|
41
42
|
createPFrame: (def) => vm.exportSingleValue(host.createPFrame(vm.importObjectViaJson(def))),
|
|
42
43
|
createPTable: (def) => vm.exportSingleValue(host.createPTable(vm.importObjectViaJson(def))),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service_injectors.cjs","names":["Services","ServiceNotRegisteredError","PoolEntryGuard"],"sources":["../../src/js_render/service_injectors.ts"],"sourcesContent":["import type { QuickJSHandle, VmFunctionImplementation } from \"quickjs-emscripten\";\nimport type { InferServiceModel, ServiceBrand } from \"@milaboratories/pl-model-common\";\nimport { Services, ServiceNotRegisteredError } from \"@milaboratories/pl-model-common\";\nimport type {\n AxesId,\n AxesSpec,\n DataInfo,\n PColumn,\n PColumnSpec,\n PColumnValues,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n BuildQueryInput,\n DeleteColumnRequest,\n DiscoverColumnsRequest,\n PFrameDef,\n SpecQuery,\n PTableDef,\n PTableDefV2,\n SpecFrameHandle,\n} from \"@milaboratories/pl-model-common\";\nimport { PoolEntryGuard } from \"@milaboratories/pl-model-common\";\nimport type { JsExecutionContext } from \"./context\";\nimport type { ComputableContextHelper } from \"./computable_context\";\n\ntype VmMethod = VmFunctionImplementation<QuickJSHandle>;\n\nexport type ServiceInjectorContext = {\n host: ComputableContextHelper;\n vm: JsExecutionContext;\n};\n\n// Each injector returns a record of method name -> VM function implementation.\n// The framework automatically registers them with serviceFnKey(serviceId, methodName).\nexport type ServiceInjector<Methods extends string = string> = (\n ctx: ServiceInjectorContext,\n) => Record<Methods, VmMethod>;\n\n// Type-safe injector for a specific service — must return all methods from the model interface.\ntype ServiceInjectorFor<S extends keyof typeof Services> = ServiceInjector<\n string & keyof InferServiceModel<ServiceBrand<(typeof Services)[S]>>\n>;\n\n// Complete, type-checked injector map.\n// Adding a service to Services without an entry here is a compile-time error.\n// Missing a method from the interface is also a compile-time error.\ntype ServiceInjectorMap = { [K in keyof typeof Services]: ServiceInjectorFor<K> };\n\nexport function getServiceInjectors(): ServiceInjectorMap {\n return {\n PFrameSpec: ({ host, vm }: ServiceInjectorContext) => {\n const driver = host.serviceRegistry.get(Services.PFrameSpec);\n if (!driver)\n throw new ServiceNotRegisteredError(\n `Service \"${Services.PFrameSpec}\" has no factory in ModelServiceRegistry. Provide a non-null factory.`,\n );\n\n return {\n createSpecFrame: (specs: QuickJSHandle) => {\n using guard = new PoolEntryGuard(\n driver.createSpecFrame(vm.importObjectViaJson(specs) as Record<string, PColumnSpec>),\n );\n host.addOnDestroy(guard.entry.unref);\n const entry = guard.keep();\n // TODO: add [Symbol.dispose] once QuickJS supports ES2024 explicit resource management\n const obj = vm.vm.newObject();\n vm.vm.newString(entry.key).consume((k) => vm.vm.setProp(obj, \"key\", k));\n vm.vm\n .newFunction(\"unref\", () => {\n entry.unref();\n })\n .consume((fn) => vm.vm.setProp(obj, \"unref\", fn));\n return obj;\n },\n\n listColumns: (handle: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.listColumns(vm.vm.getString(handle) as SpecFrameHandle)),\n\n discoverColumns: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.discoverColumns(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DiscoverColumnsRequest,\n ),\n ),\n\n buildQuery: (input: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.buildQuery(vm.importObjectViaJson(input) as BuildQueryInput),\n ),\n\n deleteColumn: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.deleteColumn(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DeleteColumnRequest,\n ),\n ),\n\n evaluateQuery: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.evaluateQuery(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as SpecQuery,\n ),\n ),\n\n expandAxes: (spec: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.expandAxes(vm.importObjectViaJson(spec) as AxesSpec)),\n\n collapseAxes: (ids: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.collapseAxes(vm.importObjectViaJson(ids) as AxesId)),\n\n findAxis: (spec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findAxis(\n vm.importObjectViaJson(spec) as AxesSpec,\n vm.importObjectViaJson(selector) as SingleAxisSelector,\n ),\n ),\n\n findTableColumn: (tableSpec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findTableColumn(\n vm.importObjectViaJson(tableSpec) as PTableColumnSpec[],\n vm.importObjectViaJson(selector) as PTableColumnId,\n ),\n ),\n };\n },\n\n PFrame: ({ host, vm }: ServiceInjectorContext) => ({\n createPFrame: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPFrame(\n vm.importObjectViaJson(def) as PFrameDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTable: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTable(\n vm.importObjectViaJson(def) as PTableDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTableV2: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTableV2(\n vm.importObjectViaJson(def) as PTableDefV2<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n }),\n };\n}\n"],"mappings":";;;;AAiDA,SAAgB,sBAA0C;AACxD,QAAO;EACL,aAAa,EAAE,MAAM,SAAiC;GACpD,MAAM,SAAS,KAAK,gBAAgB,IAAIA,gCAAAA,SAAS,WAAW;AAC5D,OAAI,CAAC,OACH,OAAM,IAAIC,gCAAAA,0BACR,YAAYD,gCAAAA,SAAS,WAAW,uEACjC;AAEH,UAAO;IACL,kBAAkB,UAAyB;;;MACzC,MAAM,QAAA,YAAA,EAAQ,IAAIE,gCAAAA,eAChB,OAAO,gBAAgB,GAAG,oBAAoB,MAAM,CAAgC,CACrF,CAAA;AACD,WAAK,aAAa,MAAM,MAAM,MAAM;MACpC,MAAM,QAAQ,MAAM,MAAM;MAE1B,MAAM,MAAM,GAAG,GAAG,WAAW;AAC7B,SAAG,GAAG,UAAU,MAAM,IAAI,CAAC,SAAS,MAAM,GAAG,GAAG,QAAQ,KAAK,OAAO,EAAE,CAAC;AACvE,SAAG,GACA,YAAY,eAAe;AAC1B,aAAM,OAAO;QACb,CACD,SAAS,OAAO,GAAG,GAAG,QAAQ,KAAK,SAAS,GAAG,CAAC;AACnD,aAAO;;;;;;;IAGT,cAAc,WACZ,GAAG,oBAAoB,OAAO,YAAY,GAAG,GAAG,UAAU,OAAO,CAAoB,CAAC;IAExF,kBAAkB,QAAuB,YACvC,GAAG,oBACD,OAAO,gBACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,UACX,GAAG,oBACD,OAAO,WAAW,GAAG,oBAAoB,MAAM,CAAoB,CACpE;IAEH,eAAe,QAAuB,YACpC,GAAG,oBACD,OAAO,aACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,gBAAgB,QAAuB,YACrC,GAAG,oBACD,OAAO,cACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,SACX,GAAG,oBAAoB,OAAO,WAAW,GAAG,oBAAoB,KAAK,CAAa,CAAC;IAErF,eAAe,QACb,GAAG,oBAAoB,OAAO,aAAa,GAAG,oBAAoB,IAAI,CAAW,CAAC;IAEpF,WAAW,MAAqB,aAC9B,GAAG,kBACD,OAAO,SACL,GAAG,oBAAoB,KAAK,EAC5B,GAAG,oBAAoB,SAAS,CACjC,CACF;IAEH,kBAAkB,WAA0B,aAC1C,GAAG,kBACD,OAAO,gBACL,GAAG,oBAAoB,UAAU,EACjC,GAAG,oBAAoB,SAAS,CACjC,CACF;IACJ;;
|
|
1
|
+
{"version":3,"file":"service_injectors.cjs","names":["Services","ServiceNotRegisteredError","PoolEntryGuard"],"sources":["../../src/js_render/service_injectors.ts"],"sourcesContent":["import type { QuickJSHandle, VmFunctionImplementation } from \"quickjs-emscripten\";\nimport type { InferServiceModel, ServiceBrand } from \"@milaboratories/pl-model-common\";\nimport { Services, ServiceNotRegisteredError } from \"@milaboratories/pl-model-common\";\nimport type {\n AxesId,\n AxesSpec,\n DataInfo,\n PColumn,\n PColumnSpec,\n PColumnValues,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n BuildQueryInput,\n DeleteColumnRequest,\n DiscoverColumnsRequest,\n PFrameDef,\n SpecQuery,\n PTableDef,\n PTableDefV2,\n SpecFrameHandle,\n} from \"@milaboratories/pl-model-common\";\nimport { PoolEntryGuard } from \"@milaboratories/pl-model-common\";\nimport type { JsExecutionContext } from \"./context\";\nimport type { ComputableContextHelper } from \"./computable_context\";\n\ntype VmMethod = VmFunctionImplementation<QuickJSHandle>;\n\nexport type ServiceInjectorContext = {\n host: ComputableContextHelper;\n vm: JsExecutionContext;\n};\n\n// Each injector returns a record of method name -> VM function implementation.\n// The framework automatically registers them with serviceFnKey(serviceId, methodName).\nexport type ServiceInjector<Methods extends string = string> = (\n ctx: ServiceInjectorContext,\n) => Record<Methods, VmMethod>;\n\n// Type-safe injector for a specific service — must return all methods from the model interface.\ntype ServiceInjectorFor<S extends keyof typeof Services> = ServiceInjector<\n string & keyof InferServiceModel<ServiceBrand<(typeof Services)[S]>>\n>;\n\n// Complete, type-checked injector map.\n// Adding a service to Services without an entry here is a compile-time error.\n// Missing a method from the interface is also a compile-time error.\ntype ServiceInjectorMap = { [K in keyof typeof Services]: ServiceInjectorFor<K> };\n\nexport function getServiceInjectors(): ServiceInjectorMap {\n return {\n PFrameSpec: ({ host, vm }: ServiceInjectorContext) => {\n const driver = host.serviceRegistry.get(Services.PFrameSpec);\n if (!driver)\n throw new ServiceNotRegisteredError(\n `Service \"${Services.PFrameSpec}\" has no factory in ModelServiceRegistry. Provide a non-null factory.`,\n );\n\n return {\n createSpecFrame: (specs: QuickJSHandle) => {\n using guard = new PoolEntryGuard(\n driver.createSpecFrame(vm.importObjectViaJson(specs) as Record<string, PColumnSpec>),\n );\n host.addOnDestroy(guard.entry.unref);\n const entry = guard.keep();\n // TODO: add [Symbol.dispose] once QuickJS supports ES2024 explicit resource management\n const obj = vm.vm.newObject();\n vm.vm.newString(entry.key).consume((k) => vm.vm.setProp(obj, \"key\", k));\n vm.vm\n .newFunction(\"unref\", () => {\n entry.unref();\n })\n .consume((fn) => vm.vm.setProp(obj, \"unref\", fn));\n return obj;\n },\n\n listColumns: (handle: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.listColumns(vm.vm.getString(handle) as SpecFrameHandle)),\n\n discoverColumns: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.discoverColumns(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DiscoverColumnsRequest,\n ),\n ),\n\n buildQuery: (input: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.buildQuery(vm.importObjectViaJson(input) as BuildQueryInput),\n ),\n\n deleteColumn: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.deleteColumn(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DeleteColumnRequest,\n ),\n ),\n\n evaluateQuery: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.evaluateQuery(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as SpecQuery,\n ),\n ),\n\n expandAxes: (spec: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.expandAxes(vm.importObjectViaJson(spec) as AxesSpec)),\n\n collapseAxes: (ids: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.collapseAxes(vm.importObjectViaJson(ids) as AxesId)),\n\n findAxis: (spec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findAxis(\n vm.importObjectViaJson(spec) as AxesSpec,\n vm.importObjectViaJson(selector) as SingleAxisSelector,\n ),\n ),\n\n findTableColumn: (tableSpec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findTableColumn(\n vm.importObjectViaJson(tableSpec) as PTableColumnSpec[],\n vm.importObjectViaJson(selector) as PTableColumnId,\n ),\n ),\n };\n },\n\n // Dialog has no model-side surface — workflow scripts cannot open save dialogs.\n // Declared as an empty injector to satisfy the exhaustive ServiceInjectorMap.\n Dialog: () => ({}) as Record<never, VmMethod>,\n\n PFrame: ({ host, vm }: ServiceInjectorContext) => ({\n createPFrame: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPFrame(\n vm.importObjectViaJson(def) as PFrameDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTable: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTable(\n vm.importObjectViaJson(def) as PTableDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTableV2: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTableV2(\n vm.importObjectViaJson(def) as PTableDefV2<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n }),\n };\n}\n"],"mappings":";;;;AAiDA,SAAgB,sBAA0C;AACxD,QAAO;EACL,aAAa,EAAE,MAAM,SAAiC;GACpD,MAAM,SAAS,KAAK,gBAAgB,IAAIA,gCAAAA,SAAS,WAAW;AAC5D,OAAI,CAAC,OACH,OAAM,IAAIC,gCAAAA,0BACR,YAAYD,gCAAAA,SAAS,WAAW,uEACjC;AAEH,UAAO;IACL,kBAAkB,UAAyB;;;MACzC,MAAM,QAAA,YAAA,EAAQ,IAAIE,gCAAAA,eAChB,OAAO,gBAAgB,GAAG,oBAAoB,MAAM,CAAgC,CACrF,CAAA;AACD,WAAK,aAAa,MAAM,MAAM,MAAM;MACpC,MAAM,QAAQ,MAAM,MAAM;MAE1B,MAAM,MAAM,GAAG,GAAG,WAAW;AAC7B,SAAG,GAAG,UAAU,MAAM,IAAI,CAAC,SAAS,MAAM,GAAG,GAAG,QAAQ,KAAK,OAAO,EAAE,CAAC;AACvE,SAAG,GACA,YAAY,eAAe;AAC1B,aAAM,OAAO;QACb,CACD,SAAS,OAAO,GAAG,GAAG,QAAQ,KAAK,SAAS,GAAG,CAAC;AACnD,aAAO;;;;;;;IAGT,cAAc,WACZ,GAAG,oBAAoB,OAAO,YAAY,GAAG,GAAG,UAAU,OAAO,CAAoB,CAAC;IAExF,kBAAkB,QAAuB,YACvC,GAAG,oBACD,OAAO,gBACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,UACX,GAAG,oBACD,OAAO,WAAW,GAAG,oBAAoB,MAAM,CAAoB,CACpE;IAEH,eAAe,QAAuB,YACpC,GAAG,oBACD,OAAO,aACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,gBAAgB,QAAuB,YACrC,GAAG,oBACD,OAAO,cACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,SACX,GAAG,oBAAoB,OAAO,WAAW,GAAG,oBAAoB,KAAK,CAAa,CAAC;IAErF,eAAe,QACb,GAAG,oBAAoB,OAAO,aAAa,GAAG,oBAAoB,IAAI,CAAW,CAAC;IAEpF,WAAW,MAAqB,aAC9B,GAAG,kBACD,OAAO,SACL,GAAG,oBAAoB,KAAK,EAC5B,GAAG,oBAAoB,SAAS,CACjC,CACF;IAEH,kBAAkB,WAA0B,aAC1C,GAAG,kBACD,OAAO,gBACL,GAAG,oBAAoB,UAAU,EACjC,GAAG,oBAAoB,SAAS,CACjC,CACF;IACJ;;EAKH,eAAe,EAAE;EAEjB,SAAS,EAAE,MAAM,UAAkC;GACjD,eAAe,QACb,GAAG,kBACD,KAAK,aACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GAEH,eAAe,QACb,GAAG,kBACD,KAAK,aACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GAEH,iBAAiB,QACf,GAAG,kBACD,KAAK,eACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GACJ;EACF"}
|
|
@@ -36,6 +36,7 @@ function getServiceInjectors() {
|
|
|
36
36
|
findTableColumn: (tableSpec, selector) => vm.exportSingleValue(driver.findTableColumn(vm.importObjectViaJson(tableSpec), vm.importObjectViaJson(selector)))
|
|
37
37
|
};
|
|
38
38
|
},
|
|
39
|
+
Dialog: () => ({}),
|
|
39
40
|
PFrame: ({ host, vm }) => ({
|
|
40
41
|
createPFrame: (def) => vm.exportSingleValue(host.createPFrame(vm.importObjectViaJson(def))),
|
|
41
42
|
createPTable: (def) => vm.exportSingleValue(host.createPTable(vm.importObjectViaJson(def))),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service_injectors.js","names":[],"sources":["../../src/js_render/service_injectors.ts"],"sourcesContent":["import type { QuickJSHandle, VmFunctionImplementation } from \"quickjs-emscripten\";\nimport type { InferServiceModel, ServiceBrand } from \"@milaboratories/pl-model-common\";\nimport { Services, ServiceNotRegisteredError } from \"@milaboratories/pl-model-common\";\nimport type {\n AxesId,\n AxesSpec,\n DataInfo,\n PColumn,\n PColumnSpec,\n PColumnValues,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n BuildQueryInput,\n DeleteColumnRequest,\n DiscoverColumnsRequest,\n PFrameDef,\n SpecQuery,\n PTableDef,\n PTableDefV2,\n SpecFrameHandle,\n} from \"@milaboratories/pl-model-common\";\nimport { PoolEntryGuard } from \"@milaboratories/pl-model-common\";\nimport type { JsExecutionContext } from \"./context\";\nimport type { ComputableContextHelper } from \"./computable_context\";\n\ntype VmMethod = VmFunctionImplementation<QuickJSHandle>;\n\nexport type ServiceInjectorContext = {\n host: ComputableContextHelper;\n vm: JsExecutionContext;\n};\n\n// Each injector returns a record of method name -> VM function implementation.\n// The framework automatically registers them with serviceFnKey(serviceId, methodName).\nexport type ServiceInjector<Methods extends string = string> = (\n ctx: ServiceInjectorContext,\n) => Record<Methods, VmMethod>;\n\n// Type-safe injector for a specific service — must return all methods from the model interface.\ntype ServiceInjectorFor<S extends keyof typeof Services> = ServiceInjector<\n string & keyof InferServiceModel<ServiceBrand<(typeof Services)[S]>>\n>;\n\n// Complete, type-checked injector map.\n// Adding a service to Services without an entry here is a compile-time error.\n// Missing a method from the interface is also a compile-time error.\ntype ServiceInjectorMap = { [K in keyof typeof Services]: ServiceInjectorFor<K> };\n\nexport function getServiceInjectors(): ServiceInjectorMap {\n return {\n PFrameSpec: ({ host, vm }: ServiceInjectorContext) => {\n const driver = host.serviceRegistry.get(Services.PFrameSpec);\n if (!driver)\n throw new ServiceNotRegisteredError(\n `Service \"${Services.PFrameSpec}\" has no factory in ModelServiceRegistry. Provide a non-null factory.`,\n );\n\n return {\n createSpecFrame: (specs: QuickJSHandle) => {\n using guard = new PoolEntryGuard(\n driver.createSpecFrame(vm.importObjectViaJson(specs) as Record<string, PColumnSpec>),\n );\n host.addOnDestroy(guard.entry.unref);\n const entry = guard.keep();\n // TODO: add [Symbol.dispose] once QuickJS supports ES2024 explicit resource management\n const obj = vm.vm.newObject();\n vm.vm.newString(entry.key).consume((k) => vm.vm.setProp(obj, \"key\", k));\n vm.vm\n .newFunction(\"unref\", () => {\n entry.unref();\n })\n .consume((fn) => vm.vm.setProp(obj, \"unref\", fn));\n return obj;\n },\n\n listColumns: (handle: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.listColumns(vm.vm.getString(handle) as SpecFrameHandle)),\n\n discoverColumns: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.discoverColumns(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DiscoverColumnsRequest,\n ),\n ),\n\n buildQuery: (input: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.buildQuery(vm.importObjectViaJson(input) as BuildQueryInput),\n ),\n\n deleteColumn: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.deleteColumn(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DeleteColumnRequest,\n ),\n ),\n\n evaluateQuery: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.evaluateQuery(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as SpecQuery,\n ),\n ),\n\n expandAxes: (spec: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.expandAxes(vm.importObjectViaJson(spec) as AxesSpec)),\n\n collapseAxes: (ids: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.collapseAxes(vm.importObjectViaJson(ids) as AxesId)),\n\n findAxis: (spec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findAxis(\n vm.importObjectViaJson(spec) as AxesSpec,\n vm.importObjectViaJson(selector) as SingleAxisSelector,\n ),\n ),\n\n findTableColumn: (tableSpec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findTableColumn(\n vm.importObjectViaJson(tableSpec) as PTableColumnSpec[],\n vm.importObjectViaJson(selector) as PTableColumnId,\n ),\n ),\n };\n },\n\n PFrame: ({ host, vm }: ServiceInjectorContext) => ({\n createPFrame: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPFrame(\n vm.importObjectViaJson(def) as PFrameDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTable: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTable(\n vm.importObjectViaJson(def) as PTableDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTableV2: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTableV2(\n vm.importObjectViaJson(def) as PTableDefV2<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n }),\n };\n}\n"],"mappings":";;;AAiDA,SAAgB,sBAA0C;AACxD,QAAO;EACL,aAAa,EAAE,MAAM,SAAiC;GACpD,MAAM,SAAS,KAAK,gBAAgB,IAAI,SAAS,WAAW;AAC5D,OAAI,CAAC,OACH,OAAM,IAAI,0BACR,YAAY,SAAS,WAAW,uEACjC;AAEH,UAAO;IACL,kBAAkB,UAAyB;;;MACzC,MAAM,QAAA,YAAA,EAAQ,IAAI,eAChB,OAAO,gBAAgB,GAAG,oBAAoB,MAAM,CAAgC,CACrF,CAAA;AACD,WAAK,aAAa,MAAM,MAAM,MAAM;MACpC,MAAM,QAAQ,MAAM,MAAM;MAE1B,MAAM,MAAM,GAAG,GAAG,WAAW;AAC7B,SAAG,GAAG,UAAU,MAAM,IAAI,CAAC,SAAS,MAAM,GAAG,GAAG,QAAQ,KAAK,OAAO,EAAE,CAAC;AACvE,SAAG,GACA,YAAY,eAAe;AAC1B,aAAM,OAAO;QACb,CACD,SAAS,OAAO,GAAG,GAAG,QAAQ,KAAK,SAAS,GAAG,CAAC;AACnD,aAAO;;;;;;;IAGT,cAAc,WACZ,GAAG,oBAAoB,OAAO,YAAY,GAAG,GAAG,UAAU,OAAO,CAAoB,CAAC;IAExF,kBAAkB,QAAuB,YACvC,GAAG,oBACD,OAAO,gBACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,UACX,GAAG,oBACD,OAAO,WAAW,GAAG,oBAAoB,MAAM,CAAoB,CACpE;IAEH,eAAe,QAAuB,YACpC,GAAG,oBACD,OAAO,aACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,gBAAgB,QAAuB,YACrC,GAAG,oBACD,OAAO,cACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,SACX,GAAG,oBAAoB,OAAO,WAAW,GAAG,oBAAoB,KAAK,CAAa,CAAC;IAErF,eAAe,QACb,GAAG,oBAAoB,OAAO,aAAa,GAAG,oBAAoB,IAAI,CAAW,CAAC;IAEpF,WAAW,MAAqB,aAC9B,GAAG,kBACD,OAAO,SACL,GAAG,oBAAoB,KAAK,EAC5B,GAAG,oBAAoB,SAAS,CACjC,CACF;IAEH,kBAAkB,WAA0B,aAC1C,GAAG,kBACD,OAAO,gBACL,GAAG,oBAAoB,UAAU,EACjC,GAAG,oBAAoB,SAAS,CACjC,CACF;IACJ;;
|
|
1
|
+
{"version":3,"file":"service_injectors.js","names":[],"sources":["../../src/js_render/service_injectors.ts"],"sourcesContent":["import type { QuickJSHandle, VmFunctionImplementation } from \"quickjs-emscripten\";\nimport type { InferServiceModel, ServiceBrand } from \"@milaboratories/pl-model-common\";\nimport { Services, ServiceNotRegisteredError } from \"@milaboratories/pl-model-common\";\nimport type {\n AxesId,\n AxesSpec,\n DataInfo,\n PColumn,\n PColumnSpec,\n PColumnValues,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n BuildQueryInput,\n DeleteColumnRequest,\n DiscoverColumnsRequest,\n PFrameDef,\n SpecQuery,\n PTableDef,\n PTableDefV2,\n SpecFrameHandle,\n} from \"@milaboratories/pl-model-common\";\nimport { PoolEntryGuard } from \"@milaboratories/pl-model-common\";\nimport type { JsExecutionContext } from \"./context\";\nimport type { ComputableContextHelper } from \"./computable_context\";\n\ntype VmMethod = VmFunctionImplementation<QuickJSHandle>;\n\nexport type ServiceInjectorContext = {\n host: ComputableContextHelper;\n vm: JsExecutionContext;\n};\n\n// Each injector returns a record of method name -> VM function implementation.\n// The framework automatically registers them with serviceFnKey(serviceId, methodName).\nexport type ServiceInjector<Methods extends string = string> = (\n ctx: ServiceInjectorContext,\n) => Record<Methods, VmMethod>;\n\n// Type-safe injector for a specific service — must return all methods from the model interface.\ntype ServiceInjectorFor<S extends keyof typeof Services> = ServiceInjector<\n string & keyof InferServiceModel<ServiceBrand<(typeof Services)[S]>>\n>;\n\n// Complete, type-checked injector map.\n// Adding a service to Services without an entry here is a compile-time error.\n// Missing a method from the interface is also a compile-time error.\ntype ServiceInjectorMap = { [K in keyof typeof Services]: ServiceInjectorFor<K> };\n\nexport function getServiceInjectors(): ServiceInjectorMap {\n return {\n PFrameSpec: ({ host, vm }: ServiceInjectorContext) => {\n const driver = host.serviceRegistry.get(Services.PFrameSpec);\n if (!driver)\n throw new ServiceNotRegisteredError(\n `Service \"${Services.PFrameSpec}\" has no factory in ModelServiceRegistry. Provide a non-null factory.`,\n );\n\n return {\n createSpecFrame: (specs: QuickJSHandle) => {\n using guard = new PoolEntryGuard(\n driver.createSpecFrame(vm.importObjectViaJson(specs) as Record<string, PColumnSpec>),\n );\n host.addOnDestroy(guard.entry.unref);\n const entry = guard.keep();\n // TODO: add [Symbol.dispose] once QuickJS supports ES2024 explicit resource management\n const obj = vm.vm.newObject();\n vm.vm.newString(entry.key).consume((k) => vm.vm.setProp(obj, \"key\", k));\n vm.vm\n .newFunction(\"unref\", () => {\n entry.unref();\n })\n .consume((fn) => vm.vm.setProp(obj, \"unref\", fn));\n return obj;\n },\n\n listColumns: (handle: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.listColumns(vm.vm.getString(handle) as SpecFrameHandle)),\n\n discoverColumns: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.discoverColumns(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DiscoverColumnsRequest,\n ),\n ),\n\n buildQuery: (input: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.buildQuery(vm.importObjectViaJson(input) as BuildQueryInput),\n ),\n\n deleteColumn: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.deleteColumn(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as DeleteColumnRequest,\n ),\n ),\n\n evaluateQuery: (handle: QuickJSHandle, request: QuickJSHandle) =>\n vm.exportObjectViaJson(\n driver.evaluateQuery(\n vm.vm.getString(handle) as SpecFrameHandle,\n vm.importObjectViaJson(request) as SpecQuery,\n ),\n ),\n\n expandAxes: (spec: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.expandAxes(vm.importObjectViaJson(spec) as AxesSpec)),\n\n collapseAxes: (ids: QuickJSHandle) =>\n vm.exportObjectViaJson(driver.collapseAxes(vm.importObjectViaJson(ids) as AxesId)),\n\n findAxis: (spec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findAxis(\n vm.importObjectViaJson(spec) as AxesSpec,\n vm.importObjectViaJson(selector) as SingleAxisSelector,\n ),\n ),\n\n findTableColumn: (tableSpec: QuickJSHandle, selector: QuickJSHandle) =>\n vm.exportSingleValue(\n driver.findTableColumn(\n vm.importObjectViaJson(tableSpec) as PTableColumnSpec[],\n vm.importObjectViaJson(selector) as PTableColumnId,\n ),\n ),\n };\n },\n\n // Dialog has no model-side surface — workflow scripts cannot open save dialogs.\n // Declared as an empty injector to satisfy the exhaustive ServiceInjectorMap.\n Dialog: () => ({}) as Record<never, VmMethod>,\n\n PFrame: ({ host, vm }: ServiceInjectorContext) => ({\n createPFrame: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPFrame(\n vm.importObjectViaJson(def) as PFrameDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTable: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTable(\n vm.importObjectViaJson(def) as PTableDef<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n\n createPTableV2: (def: QuickJSHandle) =>\n vm.exportSingleValue(\n host.createPTableV2(\n vm.importObjectViaJson(def) as PTableDefV2<\n PColumn<string | PColumnValues | DataInfo<string>>\n >,\n ),\n ),\n }),\n };\n}\n"],"mappings":";;;AAiDA,SAAgB,sBAA0C;AACxD,QAAO;EACL,aAAa,EAAE,MAAM,SAAiC;GACpD,MAAM,SAAS,KAAK,gBAAgB,IAAI,SAAS,WAAW;AAC5D,OAAI,CAAC,OACH,OAAM,IAAI,0BACR,YAAY,SAAS,WAAW,uEACjC;AAEH,UAAO;IACL,kBAAkB,UAAyB;;;MACzC,MAAM,QAAA,YAAA,EAAQ,IAAI,eAChB,OAAO,gBAAgB,GAAG,oBAAoB,MAAM,CAAgC,CACrF,CAAA;AACD,WAAK,aAAa,MAAM,MAAM,MAAM;MACpC,MAAM,QAAQ,MAAM,MAAM;MAE1B,MAAM,MAAM,GAAG,GAAG,WAAW;AAC7B,SAAG,GAAG,UAAU,MAAM,IAAI,CAAC,SAAS,MAAM,GAAG,GAAG,QAAQ,KAAK,OAAO,EAAE,CAAC;AACvE,SAAG,GACA,YAAY,eAAe;AAC1B,aAAM,OAAO;QACb,CACD,SAAS,OAAO,GAAG,GAAG,QAAQ,KAAK,SAAS,GAAG,CAAC;AACnD,aAAO;;;;;;;IAGT,cAAc,WACZ,GAAG,oBAAoB,OAAO,YAAY,GAAG,GAAG,UAAU,OAAO,CAAoB,CAAC;IAExF,kBAAkB,QAAuB,YACvC,GAAG,oBACD,OAAO,gBACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,UACX,GAAG,oBACD,OAAO,WAAW,GAAG,oBAAoB,MAAM,CAAoB,CACpE;IAEH,eAAe,QAAuB,YACpC,GAAG,oBACD,OAAO,aACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,gBAAgB,QAAuB,YACrC,GAAG,oBACD,OAAO,cACL,GAAG,GAAG,UAAU,OAAO,EACvB,GAAG,oBAAoB,QAAQ,CAChC,CACF;IAEH,aAAa,SACX,GAAG,oBAAoB,OAAO,WAAW,GAAG,oBAAoB,KAAK,CAAa,CAAC;IAErF,eAAe,QACb,GAAG,oBAAoB,OAAO,aAAa,GAAG,oBAAoB,IAAI,CAAW,CAAC;IAEpF,WAAW,MAAqB,aAC9B,GAAG,kBACD,OAAO,SACL,GAAG,oBAAoB,KAAK,EAC5B,GAAG,oBAAoB,SAAS,CACjC,CACF;IAEH,kBAAkB,WAA0B,aAC1C,GAAG,kBACD,OAAO,gBACL,GAAG,oBAAoB,UAAU,EACjC,GAAG,oBAAoB,SAAS,CACjC,CACF;IACJ;;EAKH,eAAe,EAAE;EAEjB,SAAS,EAAE,MAAM,UAAkC;GACjD,eAAe,QACb,GAAG,kBACD,KAAK,aACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GAEH,eAAe,QACb,GAAG,kBACD,KAAK,aACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GAEH,iBAAiB,QACf,GAAG,kBACD,KAAK,eACH,GAAG,oBAAoB,IAAI,CAG5B,CACF;GACJ;EACF"}
|
package/dist/model/args.cjs
CHANGED
|
@@ -17,37 +17,67 @@ function outputRef(blockId, name, requireEnrichments) {
|
|
|
17
17
|
function isBlockOutputReference(obj) {
|
|
18
18
|
return typeof obj === "object" && obj !== null && "__isRef" in obj && obj.__isRef === true && "blockId" in obj && "name" in obj;
|
|
19
19
|
}
|
|
20
|
+
/** Extracts all resource ids referenced by args object. */
|
|
21
|
+
function inferAllReferencedBlocks(args, allowed) {
|
|
22
|
+
const result = {
|
|
23
|
+
upstreams: /* @__PURE__ */ new Set(),
|
|
24
|
+
upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
|
|
25
|
+
missingReferences: false
|
|
26
|
+
};
|
|
27
|
+
addAllReferencedBlocks(result, args, allowed);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
20
30
|
function addAllReferencedBlocks(result, node, allowed) {
|
|
21
31
|
const type = typeof node;
|
|
22
32
|
switch (type) {
|
|
23
33
|
case "function":
|
|
24
34
|
case "bigint":
|
|
25
35
|
case "number":
|
|
26
|
-
case "string":
|
|
27
36
|
case "boolean":
|
|
28
37
|
case "symbol":
|
|
29
38
|
case "undefined": return;
|
|
39
|
+
case "string":
|
|
40
|
+
unwrapEmbeddedRef(node, (parsed) => addAllReferencedBlocks(result, parsed, allowed));
|
|
41
|
+
return;
|
|
30
42
|
case "object":
|
|
31
43
|
if (node === null) return;
|
|
32
|
-
if (isBlockOutputReference(node))
|
|
33
|
-
result.upstreams.add(node.blockId);
|
|
34
|
-
if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
|
|
35
|
-
} else result.missingReferences = true;
|
|
44
|
+
if (isBlockOutputReference(node)) recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
|
|
36
45
|
else if (Array.isArray(node)) for (const child of node) addAllReferencedBlocks(result, child, allowed);
|
|
37
46
|
else for (const [, child] of Object.entries(node)) addAllReferencedBlocks(result, child, allowed);
|
|
38
47
|
return;
|
|
39
48
|
default: (0, _milaboratories_ts_helpers.assertNever)(type);
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
|
|
53
|
+
*
|
|
54
|
+
* A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
|
|
55
|
+
* passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
|
|
56
|
+
* chars (the escape padding) of equal length. The regex below is the strict
|
|
57
|
+
* shape gate — non-ref strings fail at the very first character, so we never
|
|
58
|
+
* scan their body. One pass is peeled per call; deeper nesting is unwrapped
|
|
59
|
+
* via recursion in the caller.
|
|
60
|
+
*/
|
|
61
|
+
const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
|
|
62
|
+
function unwrapEmbeddedRef(s, onParsed) {
|
|
63
|
+
const c0 = s.charCodeAt(0);
|
|
64
|
+
if (c0 !== 123 && c0 !== 34) return;
|
|
65
|
+
const m = EMBEDDED_REF_RE.exec(s);
|
|
66
|
+
if (m === null) return;
|
|
67
|
+
if (m.groups.pre.length !== m.groups.suf.length) return;
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(s);
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (parsed !== s) onParsed(parsed);
|
|
75
|
+
}
|
|
76
|
+
function recordRef(result, blockId, requireEnrichments, allowed) {
|
|
77
|
+
if (allowed === void 0 || allowed.has(blockId)) {
|
|
78
|
+
result.upstreams.add(blockId);
|
|
79
|
+
if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
|
|
80
|
+
} else result.missingReferences = true;
|
|
51
81
|
}
|
|
52
82
|
//#endregion
|
|
53
83
|
exports.inferAllReferencedBlocks = inferAllReferencedBlocks;
|
package/dist/model/args.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.cjs","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"
|
|
1
|
+
{"version":3,"file":"args.cjs","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n case \"string\": {\n unwrapEmbeddedRef(node as string, (parsed) =>\n addAllReferencedBlocks(result, parsed, allowed),\n );\n return;\n }\n case \"object\": {\n if (node === null) return;\n if (isBlockOutputReference(node)) {\n recordRef(result, node.blockId, node.requireEnrichments === true, allowed);\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n }\n default:\n assertNever(type);\n }\n}\n\n/**\n * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.\n *\n * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`\n * passes. Each pass adds a symmetric prefix/suffix made only of `\"` and `\\`\n * chars (the escape padding) of equal length. The regex below is the strict\n * shape gate — non-ref strings fail at the very first character, so we never\n * scan their body. One pass is peeled per call; deeper nesting is unwrapped\n * via recursion in the caller.\n */\nconst EMBEDDED_REF_RE = /^(?<pre>[\\\\\"]*)\\{[\\s\\S]*?__isRef[\\s\\S]*\\}(?<suf>[\\\\\"]*)$/;\n\nfunction unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {\n const c0 = s.charCodeAt(0);\n if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* \" */) return;\n const m = EMBEDDED_REF_RE.exec(s);\n if (m === null) return;\n if (m.groups!.pre.length !== m.groups!.suf.length) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n return;\n }\n if (parsed !== s) onParsed(parsed);\n}\n\nfunction recordRef(\n result: BlockUpstreams,\n blockId: string,\n requireEnrichments: boolean,\n allowed?: Set<string>,\n) {\n if (allowed === undefined || allowed.has(blockId)) {\n result.upstreams.add(blockId);\n if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);\n } else result.missingReferences = true;\n}\n"],"mappings":";;;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;;AAcd,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO;;AAGT,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EACF,KAAK;AACH,qBAAkB,OAAiB,WACjC,uBAAuB,QAAQ,QAAQ,QAAQ,CAChD;AACD;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AACnB,OAAI,uBAAuB,KAAK,CAC9B,WAAU,QAAQ,KAAK,SAAS,KAAK,uBAAuB,MAAM,QAAQ;YACjE,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,EAAA,GAAA,2BAAA,aAAY,KAAK;;;;;;;;;;;;;AAcvB,MAAM,kBAAkB;AAExB,SAAS,kBAAkB,GAAW,UAAoC;CACxE,MAAM,KAAK,EAAE,WAAW,EAAE;AAC1B,KAAI,OAAO,OAAgB,OAAO,GAAc;CAChD,MAAM,IAAI,gBAAgB,KAAK,EAAE;AACjC,KAAI,MAAM,KAAM;AAChB,KAAI,EAAE,OAAQ,IAAI,WAAW,EAAE,OAAQ,IAAI,OAAQ;CACnD,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,EAAE;SAChB;AACN;;AAEF,KAAI,WAAW,EAAG,UAAS,OAAO;;AAGpC,SAAS,UACP,QACA,SACA,oBACA,SACA;AACA,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,QAAQ,EAAE;AACjD,SAAO,UAAU,IAAI,QAAQ;AAC7B,MAAI,mBAAoB,QAAO,8BAA8B,IAAI,QAAQ;OACpE,QAAO,oBAAoB"}
|
package/dist/model/args.js
CHANGED
|
@@ -16,37 +16,67 @@ function outputRef(blockId, name, requireEnrichments) {
|
|
|
16
16
|
function isBlockOutputReference(obj) {
|
|
17
17
|
return typeof obj === "object" && obj !== null && "__isRef" in obj && obj.__isRef === true && "blockId" in obj && "name" in obj;
|
|
18
18
|
}
|
|
19
|
+
/** Extracts all resource ids referenced by args object. */
|
|
20
|
+
function inferAllReferencedBlocks(args, allowed) {
|
|
21
|
+
const result = {
|
|
22
|
+
upstreams: /* @__PURE__ */ new Set(),
|
|
23
|
+
upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
|
|
24
|
+
missingReferences: false
|
|
25
|
+
};
|
|
26
|
+
addAllReferencedBlocks(result, args, allowed);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
19
29
|
function addAllReferencedBlocks(result, node, allowed) {
|
|
20
30
|
const type = typeof node;
|
|
21
31
|
switch (type) {
|
|
22
32
|
case "function":
|
|
23
33
|
case "bigint":
|
|
24
34
|
case "number":
|
|
25
|
-
case "string":
|
|
26
35
|
case "boolean":
|
|
27
36
|
case "symbol":
|
|
28
37
|
case "undefined": return;
|
|
38
|
+
case "string":
|
|
39
|
+
unwrapEmbeddedRef(node, (parsed) => addAllReferencedBlocks(result, parsed, allowed));
|
|
40
|
+
return;
|
|
29
41
|
case "object":
|
|
30
42
|
if (node === null) return;
|
|
31
|
-
if (isBlockOutputReference(node))
|
|
32
|
-
result.upstreams.add(node.blockId);
|
|
33
|
-
if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
|
|
34
|
-
} else result.missingReferences = true;
|
|
43
|
+
if (isBlockOutputReference(node)) recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
|
|
35
44
|
else if (Array.isArray(node)) for (const child of node) addAllReferencedBlocks(result, child, allowed);
|
|
36
45
|
else for (const [, child] of Object.entries(node)) addAllReferencedBlocks(result, child, allowed);
|
|
37
46
|
return;
|
|
38
47
|
default: assertNever(type);
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
|
|
52
|
+
*
|
|
53
|
+
* A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
|
|
54
|
+
* passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
|
|
55
|
+
* chars (the escape padding) of equal length. The regex below is the strict
|
|
56
|
+
* shape gate — non-ref strings fail at the very first character, so we never
|
|
57
|
+
* scan their body. One pass is peeled per call; deeper nesting is unwrapped
|
|
58
|
+
* via recursion in the caller.
|
|
59
|
+
*/
|
|
60
|
+
const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
|
|
61
|
+
function unwrapEmbeddedRef(s, onParsed) {
|
|
62
|
+
const c0 = s.charCodeAt(0);
|
|
63
|
+
if (c0 !== 123 && c0 !== 34) return;
|
|
64
|
+
const m = EMBEDDED_REF_RE.exec(s);
|
|
65
|
+
if (m === null) return;
|
|
66
|
+
if (m.groups.pre.length !== m.groups.suf.length) return;
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(s);
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (parsed !== s) onParsed(parsed);
|
|
74
|
+
}
|
|
75
|
+
function recordRef(result, blockId, requireEnrichments, allowed) {
|
|
76
|
+
if (allowed === void 0 || allowed.has(blockId)) {
|
|
77
|
+
result.upstreams.add(blockId);
|
|
78
|
+
if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
|
|
79
|
+
} else result.missingReferences = true;
|
|
50
80
|
}
|
|
51
81
|
//#endregion
|
|
52
82
|
export { inferAllReferencedBlocks, outputRef };
|
package/dist/model/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"
|
|
1
|
+
{"version":3,"file":"args.js","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n case \"string\": {\n unwrapEmbeddedRef(node as string, (parsed) =>\n addAllReferencedBlocks(result, parsed, allowed),\n );\n return;\n }\n case \"object\": {\n if (node === null) return;\n if (isBlockOutputReference(node)) {\n recordRef(result, node.blockId, node.requireEnrichments === true, allowed);\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n }\n default:\n assertNever(type);\n }\n}\n\n/**\n * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.\n *\n * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`\n * passes. Each pass adds a symmetric prefix/suffix made only of `\"` and `\\`\n * chars (the escape padding) of equal length. The regex below is the strict\n * shape gate — non-ref strings fail at the very first character, so we never\n * scan their body. One pass is peeled per call; deeper nesting is unwrapped\n * via recursion in the caller.\n */\nconst EMBEDDED_REF_RE = /^(?<pre>[\\\\\"]*)\\{[\\s\\S]*?__isRef[\\s\\S]*\\}(?<suf>[\\\\\"]*)$/;\n\nfunction unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {\n const c0 = s.charCodeAt(0);\n if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* \" */) return;\n const m = EMBEDDED_REF_RE.exec(s);\n if (m === null) return;\n if (m.groups!.pre.length !== m.groups!.suf.length) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n return;\n }\n if (parsed !== s) onParsed(parsed);\n}\n\nfunction recordRef(\n result: BlockUpstreams,\n blockId: string,\n requireEnrichments: boolean,\n allowed?: Set<string>,\n) {\n if (allowed === undefined || allowed.has(blockId)) {\n result.upstreams.add(blockId);\n if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);\n } else result.missingReferences = true;\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;;AAcd,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO;;AAGT,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EACF,KAAK;AACH,qBAAkB,OAAiB,WACjC,uBAAuB,QAAQ,QAAQ,QAAQ,CAChD;AACD;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AACnB,OAAI,uBAAuB,KAAK,CAC9B,WAAU,QAAQ,KAAK,SAAS,KAAK,uBAAuB,MAAM,QAAQ;YACjE,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,aAAY,KAAK;;;;;;;;;;;;;AAcvB,MAAM,kBAAkB;AAExB,SAAS,kBAAkB,GAAW,UAAoC;CACxE,MAAM,KAAK,EAAE,WAAW,EAAE;AAC1B,KAAI,OAAO,OAAgB,OAAO,GAAc;CAChD,MAAM,IAAI,gBAAgB,KAAK,EAAE;AACjC,KAAI,MAAM,KAAM;AAChB,KAAI,EAAE,OAAQ,IAAI,WAAW,EAAE,OAAQ,IAAI,OAAQ;CACnD,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,EAAE;SAChB;AACN;;AAEF,KAAI,WAAW,EAAG,UAAS,OAAO;;AAGpC,SAAS,UACP,QACA,SACA,oBACA,SACA;AACA,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,QAAQ,EAAE;AACjD,SAAO,UAAU,IAAI,QAAQ;AAC7B,MAAI,mBAAoB,QAAO,8BAA8B,IAAI,QAAQ;OACpE,QAAO,oBAAoB"}
|
|
@@ -12,7 +12,8 @@ let _milaboratories_pf_spec_driver = require("@milaboratories/pf-spec-driver");
|
|
|
12
12
|
function createModelServiceRegistry(options) {
|
|
13
13
|
return new _milaboratories_pl_model_common.ModelServiceRegistry(_milaboratories_pl_model_common.Services, {
|
|
14
14
|
PFrameSpec: () => new _milaboratories_pf_spec_driver.SpecDriver({ logger: options.logger }),
|
|
15
|
-
PFrame: null
|
|
15
|
+
PFrame: null,
|
|
16
|
+
Dialog: null
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
19
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service_factories.cjs","names":["ModelServiceRegistry","Services","SpecDriver"],"sources":["../src/service_factories.ts"],"sourcesContent":["/**\n * Model service factories — add a factory for each new service here.\n *\n * Each entry maps a Services key to a factory function that creates the\n * model-side driver instance, or null if the service has no model-side driver\n * (e.g. node services whose driver is provided by the desktop app).\n */\n\nimport { ModelServiceRegistry, Services } from \"@milaboratories/pl-model-common\";\nimport { SpecDriver } from \"@milaboratories/pf-spec-driver\";\nimport { type MiLogger } from \"@milaboratories/ts-helpers\";\n\nexport type ModelServiceOptions = {\n logger: MiLogger;\n};\n\nexport function createModelServiceRegistry(options: ModelServiceOptions) {\n return new ModelServiceRegistry(Services, {\n PFrameSpec: () => new SpecDriver({ logger: options.logger }),\n PFrame: null,\n });\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAgB,2BAA2B,SAA8B;AACvE,QAAO,IAAIA,gCAAAA,qBAAqBC,gCAAAA,UAAU;EACxC,kBAAkB,IAAIC,+BAAAA,WAAW,EAAE,QAAQ,QAAQ,QAAQ,CAAC;EAC5D,QAAQ;EACT,CAAC"}
|
|
1
|
+
{"version":3,"file":"service_factories.cjs","names":["ModelServiceRegistry","Services","SpecDriver"],"sources":["../src/service_factories.ts"],"sourcesContent":["/**\n * Model service factories — add a factory for each new service here.\n *\n * Each entry maps a Services key to a factory function that creates the\n * model-side driver instance, or null if the service has no model-side driver\n * (e.g. node services whose driver is provided by the desktop app).\n */\n\nimport { ModelServiceRegistry, Services } from \"@milaboratories/pl-model-common\";\nimport { SpecDriver } from \"@milaboratories/pf-spec-driver\";\nimport { type MiLogger } from \"@milaboratories/ts-helpers\";\n\nexport type ModelServiceOptions = {\n logger: MiLogger;\n};\n\nexport function createModelServiceRegistry(options: ModelServiceOptions) {\n return new ModelServiceRegistry(Services, {\n PFrameSpec: () => new SpecDriver({ logger: options.logger }),\n PFrame: null,\n Dialog: null,\n });\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAgB,2BAA2B,SAA8B;AACvE,QAAO,IAAIA,gCAAAA,qBAAqBC,gCAAAA,UAAU;EACxC,kBAAkB,IAAIC,+BAAAA,WAAW,EAAE,QAAQ,QAAQ,QAAQ,CAAC;EAC5D,QAAQ;EACR,QAAQ;EACT,CAAC"}
|
|
@@ -11,7 +11,8 @@ import { SpecDriver } from "@milaboratories/pf-spec-driver";
|
|
|
11
11
|
function createModelServiceRegistry(options) {
|
|
12
12
|
return new ModelServiceRegistry(Services, {
|
|
13
13
|
PFrameSpec: () => new SpecDriver({ logger: options.logger }),
|
|
14
|
-
PFrame: null
|
|
14
|
+
PFrame: null,
|
|
15
|
+
Dialog: null
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service_factories.js","names":[],"sources":["../src/service_factories.ts"],"sourcesContent":["/**\n * Model service factories — add a factory for each new service here.\n *\n * Each entry maps a Services key to a factory function that creates the\n * model-side driver instance, or null if the service has no model-side driver\n * (e.g. node services whose driver is provided by the desktop app).\n */\n\nimport { ModelServiceRegistry, Services } from \"@milaboratories/pl-model-common\";\nimport { SpecDriver } from \"@milaboratories/pf-spec-driver\";\nimport { type MiLogger } from \"@milaboratories/ts-helpers\";\n\nexport type ModelServiceOptions = {\n logger: MiLogger;\n};\n\nexport function createModelServiceRegistry(options: ModelServiceOptions) {\n return new ModelServiceRegistry(Services, {\n PFrameSpec: () => new SpecDriver({ logger: options.logger }),\n PFrame: null,\n });\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,2BAA2B,SAA8B;AACvE,QAAO,IAAI,qBAAqB,UAAU;EACxC,kBAAkB,IAAI,WAAW,EAAE,QAAQ,QAAQ,QAAQ,CAAC;EAC5D,QAAQ;EACT,CAAC"}
|
|
1
|
+
{"version":3,"file":"service_factories.js","names":[],"sources":["../src/service_factories.ts"],"sourcesContent":["/**\n * Model service factories — add a factory for each new service here.\n *\n * Each entry maps a Services key to a factory function that creates the\n * model-side driver instance, or null if the service has no model-side driver\n * (e.g. node services whose driver is provided by the desktop app).\n */\n\nimport { ModelServiceRegistry, Services } from \"@milaboratories/pl-model-common\";\nimport { SpecDriver } from \"@milaboratories/pf-spec-driver\";\nimport { type MiLogger } from \"@milaboratories/ts-helpers\";\n\nexport type ModelServiceOptions = {\n logger: MiLogger;\n};\n\nexport function createModelServiceRegistry(options: ModelServiceOptions) {\n return new ModelServiceRegistry(Services, {\n PFrameSpec: () => new SpecDriver({ logger: options.logger }),\n PFrame: null,\n Dialog: null,\n });\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,2BAA2B,SAA8B;AACvE,QAAO,IAAI,qBAAqB,UAAU;EACxC,kBAAkB,IAAI,WAAW,EAAE,QAAQ,QAAQ,QAAQ,CAAC;EAC5D,QAAQ;EACR,QAAQ;EACT,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-middle-layer",
|
|
3
|
-
"version": "1.55.
|
|
3
|
+
"version": "1.55.23",
|
|
4
4
|
"description": "Pl Middle Layer",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -30,24 +30,24 @@
|
|
|
30
30
|
"utility-types": "^3.11.0",
|
|
31
31
|
"yaml": "^2.8.0",
|
|
32
32
|
"zod": "~3.25.76",
|
|
33
|
-
"@milaboratories/pf-driver": "1.3.11",
|
|
34
33
|
"@milaboratories/computable": "2.9.2",
|
|
35
|
-
"@milaboratories/pf-
|
|
36
|
-
"@milaboratories/pl-client": "3.1.7",
|
|
37
|
-
"@milaboratories/pl-errors": "1.3.6",
|
|
34
|
+
"@milaboratories/pf-driver": "1.4.0",
|
|
38
35
|
"@milaboratories/helpers": "1.14.1",
|
|
39
|
-
"@milaboratories/
|
|
36
|
+
"@milaboratories/pf-spec-driver": "1.3.4",
|
|
37
|
+
"@milaboratories/pl-client": "3.1.8",
|
|
38
|
+
"@milaboratories/pl-drivers": "1.12.19",
|
|
39
|
+
"@milaboratories/pl-errors": "1.3.7",
|
|
40
|
+
"@milaboratories/pl-deployments": "2.17.7",
|
|
40
41
|
"@milaboratories/pl-http": "1.2.4",
|
|
41
|
-
"@milaboratories/pl-
|
|
42
|
-
"@milaboratories/pl-model-backend": "1.2.
|
|
43
|
-
"@milaboratories/pl-
|
|
44
|
-
"@milaboratories/pl-
|
|
45
|
-
"@milaboratories/pl-model-middle-layer": "1.18.4",
|
|
46
|
-
"@milaboratories/resolve-helper": "1.1.3",
|
|
42
|
+
"@milaboratories/pl-model-common": "1.36.0",
|
|
43
|
+
"@milaboratories/pl-model-backend": "1.2.15",
|
|
44
|
+
"@milaboratories/pl-model-middle-layer": "1.18.5",
|
|
45
|
+
"@milaboratories/pl-tree": "1.9.17",
|
|
47
46
|
"@milaboratories/ts-helpers": "1.8.1",
|
|
48
|
-
"@
|
|
49
|
-
"@platforma-sdk/block-tools": "2.7.
|
|
50
|
-
"@platforma-sdk/
|
|
47
|
+
"@milaboratories/resolve-helper": "1.1.3",
|
|
48
|
+
"@platforma-sdk/block-tools": "2.7.14",
|
|
49
|
+
"@platforma-sdk/model": "1.68.0",
|
|
50
|
+
"@platforma-sdk/workflow-tengo": "5.15.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "~24.5.2",
|
|
@@ -130,6 +130,10 @@ export function getServiceInjectors(): ServiceInjectorMap {
|
|
|
130
130
|
};
|
|
131
131
|
},
|
|
132
132
|
|
|
133
|
+
// Dialog has no model-side surface — workflow scripts cannot open save dialogs.
|
|
134
|
+
// Declared as an empty injector to satisfy the exhaustive ServiceInjectorMap.
|
|
135
|
+
Dialog: () => ({}) as Record<never, VmMethod>,
|
|
136
|
+
|
|
133
137
|
PFrame: ({ host, vm }: ServiceInjectorContext) => ({
|
|
134
138
|
createPFrame: (def: QuickJSHandle) =>
|
|
135
139
|
vm.exportSingleValue(
|
package/src/model/args.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
2
3
|
import { inferAllReferencedBlocks, outputRef } from "./args";
|
|
3
4
|
|
|
4
5
|
describe("inferAllReferencedBlocks", () => {
|
|
@@ -29,4 +30,106 @@ describe("inferAllReferencedBlocks", () => {
|
|
|
29
30
|
expect(result.upstreams).toContain("block1");
|
|
30
31
|
expect(result.upstreams.size).toBe(1);
|
|
31
32
|
});
|
|
33
|
+
|
|
34
|
+
it("extracts PlRef embedded as canonicalized JSON string (global-form PObjectId)", () => {
|
|
35
|
+
const id = canonicalize(outputRef("blockA", "exportName"))!;
|
|
36
|
+
|
|
37
|
+
const result = inferAllReferencedBlocks({ columnId: id });
|
|
38
|
+
|
|
39
|
+
expect(result.upstreams).toContain("blockA");
|
|
40
|
+
expect(result.upstreams.size).toBe(1);
|
|
41
|
+
expect(result.upstreamsRequiringEnrichments.size).toBe(0);
|
|
42
|
+
expect(result.missingReferences).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("propagates requireEnrichments from canonicalized PlRef string", () => {
|
|
46
|
+
const id = canonicalize(outputRef("blockA", "exportName", true))!;
|
|
47
|
+
|
|
48
|
+
const result = inferAllReferencedBlocks(id);
|
|
49
|
+
|
|
50
|
+
expect(result.upstreams).toContain("blockA");
|
|
51
|
+
expect(result.upstreamsRequiringEnrichments).toContain("blockA");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("finds multiple PlRef strings inside an args object", () => {
|
|
55
|
+
const a = canonicalize(outputRef("blockA", "n1"))!;
|
|
56
|
+
const b = canonicalize(outputRef("blockB", "n2", true))!;
|
|
57
|
+
|
|
58
|
+
const result = inferAllReferencedBlocks({ first: a, second: b });
|
|
59
|
+
|
|
60
|
+
expect(result.upstreams).toEqual(new Set(["blockA", "blockB"]));
|
|
61
|
+
expect(result.upstreamsRequiringEnrichments).toEqual(new Set(["blockB"]));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("respects allowed set for embedded refs", () => {
|
|
65
|
+
const id = canonicalize(outputRef("blockX", "n"))!;
|
|
66
|
+
|
|
67
|
+
const result = inferAllReferencedBlocks({ id }, new Set(["blockY"]));
|
|
68
|
+
|
|
69
|
+
expect(result.upstreams.size).toBe(0);
|
|
70
|
+
expect(result.missingReferences).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("decodes JSON escapes inside blockId", () => {
|
|
74
|
+
const id = canonicalize(outputRef('weird"id\\with\nescapes', "n"))!;
|
|
75
|
+
|
|
76
|
+
const result = inferAllReferencedBlocks({ id });
|
|
77
|
+
|
|
78
|
+
expect(result.upstreams).toContain('weird"id\\with\nescapes');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("ignores strings without __isRef marker", () => {
|
|
82
|
+
const result = inferAllReferencedBlocks({
|
|
83
|
+
a: "plain text",
|
|
84
|
+
b: '{"some":"json","without":"ref"}',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(result.upstreams.size).toBe(0);
|
|
88
|
+
expect(result.missingReferences).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("detects PlRef string regardless of JSON.stringify nesting depth (depth 0..10)", () => {
|
|
92
|
+
const id = canonicalize(outputRef("blockA", "n", true))!;
|
|
93
|
+
|
|
94
|
+
for (let depth = 0; depth <= 10; depth++) {
|
|
95
|
+
let value: string = id;
|
|
96
|
+
for (let i = 0; i < depth; i++) value = JSON.stringify(value);
|
|
97
|
+
|
|
98
|
+
const result = inferAllReferencedBlocks({ raw: value });
|
|
99
|
+
|
|
100
|
+
expect(result.upstreams, `depth=${depth}`).toContain("blockA");
|
|
101
|
+
expect(result.upstreamsRequiringEnrichments, `depth=${depth}`).toContain("blockA");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("detects PlRef when the entire args container is stringified multiple times", () => {
|
|
106
|
+
const ref = outputRef("blockZ", "n");
|
|
107
|
+
const container: { col: unknown } = { col: ref };
|
|
108
|
+
|
|
109
|
+
let value: unknown = container;
|
|
110
|
+
for (let i = 0; i < 5; i++) value = JSON.stringify(value);
|
|
111
|
+
|
|
112
|
+
const result = inferAllReferencedBlocks({ wrapper: value });
|
|
113
|
+
|
|
114
|
+
expect(result.upstreams).toContain("blockZ");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("detects PlRef when args itself is the multi-stringified ref string", () => {
|
|
118
|
+
let value: string = canonicalize(outputRef("blockTop", "n"))!;
|
|
119
|
+
for (let i = 0; i < 4; i++) value = JSON.stringify(value);
|
|
120
|
+
|
|
121
|
+
const result = inferAllReferencedBlocks(value);
|
|
122
|
+
|
|
123
|
+
expect(result.upstreams).toContain("blockTop");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("ignores malformed __isRef occurrences", () => {
|
|
127
|
+
const result = inferAllReferencedBlocks({
|
|
128
|
+
a: '{"__isRef":true,"blockId"', // truncated
|
|
129
|
+
b: '"__isRef":false', // wrong value
|
|
130
|
+
c: "__isRef without quotes",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(result.upstreams.size).toBe(0);
|
|
134
|
+
});
|
|
32
135
|
});
|
package/src/model/args.ts
CHANGED
|
@@ -17,26 +17,46 @@ export function isBlockOutputReference(obj: unknown): obj is PlRef {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface BlockUpstreams {
|
|
21
|
+
/** All direct block dependencies */
|
|
22
|
+
upstreams: Set<string>;
|
|
23
|
+
/** Direct block dependencies which enrichments are also required by current block */
|
|
24
|
+
upstreamsRequiringEnrichments: Set<string>;
|
|
25
|
+
/** True if not-allowed references was encountered */
|
|
26
|
+
missingReferences: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Extracts all resource ids referenced by args object. */
|
|
30
|
+
export function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {
|
|
31
|
+
const result = {
|
|
32
|
+
upstreams: new Set<string>(),
|
|
33
|
+
upstreamsRequiringEnrichments: new Set<string>(),
|
|
34
|
+
missingReferences: false,
|
|
35
|
+
};
|
|
36
|
+
addAllReferencedBlocks(result, args, allowed);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {
|
|
21
41
|
const type = typeof node;
|
|
22
42
|
switch (type) {
|
|
23
43
|
case "function":
|
|
24
44
|
case "bigint":
|
|
25
45
|
case "number":
|
|
26
|
-
case "string":
|
|
27
46
|
case "boolean":
|
|
28
47
|
case "symbol":
|
|
29
48
|
case "undefined":
|
|
30
49
|
return;
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
case "string": {
|
|
51
|
+
unwrapEmbeddedRef(node as string, (parsed) =>
|
|
52
|
+
addAllReferencedBlocks(result, parsed, allowed),
|
|
53
|
+
);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
case "object": {
|
|
33
57
|
if (node === null) return;
|
|
34
|
-
|
|
35
58
|
if (isBlockOutputReference(node)) {
|
|
36
|
-
|
|
37
|
-
result.upstreams.add(node.blockId);
|
|
38
|
-
if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
|
|
39
|
-
} else result.missingReferences = true;
|
|
59
|
+
recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
|
|
40
60
|
} else if (Array.isArray(node)) {
|
|
41
61
|
for (const child of node) addAllReferencedBlocks(result, child, allowed);
|
|
42
62
|
} else {
|
|
@@ -45,28 +65,47 @@ function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?:
|
|
|
45
65
|
}
|
|
46
66
|
|
|
47
67
|
return;
|
|
48
|
-
|
|
68
|
+
}
|
|
49
69
|
default:
|
|
50
70
|
assertNever(type);
|
|
51
71
|
}
|
|
52
72
|
}
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
|
|
76
|
+
*
|
|
77
|
+
* A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
|
|
78
|
+
* passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
|
|
79
|
+
* chars (the escape padding) of equal length. The regex below is the strict
|
|
80
|
+
* shape gate — non-ref strings fail at the very first character, so we never
|
|
81
|
+
* scan their body. One pass is peeled per call; deeper nesting is unwrapped
|
|
82
|
+
* via recursion in the caller.
|
|
83
|
+
*/
|
|
84
|
+
const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
|
|
85
|
+
|
|
86
|
+
function unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {
|
|
87
|
+
const c0 = s.charCodeAt(0);
|
|
88
|
+
if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* " */) return;
|
|
89
|
+
const m = EMBEDDED_REF_RE.exec(s);
|
|
90
|
+
if (m === null) return;
|
|
91
|
+
if (m.groups!.pre.length !== m.groups!.suf.length) return;
|
|
92
|
+
let parsed: unknown;
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(s);
|
|
95
|
+
} catch {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (parsed !== s) onParsed(parsed);
|
|
61
99
|
}
|
|
62
100
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
101
|
+
function recordRef(
|
|
102
|
+
result: BlockUpstreams,
|
|
103
|
+
blockId: string,
|
|
104
|
+
requireEnrichments: boolean,
|
|
105
|
+
allowed?: Set<string>,
|
|
106
|
+
) {
|
|
107
|
+
if (allowed === undefined || allowed.has(blockId)) {
|
|
108
|
+
result.upstreams.add(blockId);
|
|
109
|
+
if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
|
|
110
|
+
} else result.missingReferences = true;
|
|
72
111
|
}
|
package/src/service_factories.ts
CHANGED