@platforma-sdk/model 1.60.2 → 1.61.1

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.
Files changed (39) hide show
  1. package/dist/block_model.cjs +3 -1
  2. package/dist/block_model.cjs.map +1 -1
  3. package/dist/block_model.js +3 -1
  4. package/dist/block_model.js.map +1 -1
  5. package/dist/columns/column_collection_builder.cjs +15 -12
  6. package/dist/columns/column_collection_builder.cjs.map +1 -1
  7. package/dist/columns/column_collection_builder.d.ts +4 -2
  8. package/dist/columns/column_collection_builder.js +15 -12
  9. package/dist/columns/column_collection_builder.js.map +1 -1
  10. package/dist/columns/column_selector.cjs.map +1 -1
  11. package/dist/columns/column_selector.d.ts +3 -3
  12. package/dist/columns/column_selector.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/package.cjs +1 -1
  15. package/dist/package.js +1 -1
  16. package/dist/platforma.d.ts +3 -1
  17. package/dist/plugin_model.cjs +48 -2
  18. package/dist/plugin_model.cjs.map +1 -1
  19. package/dist/plugin_model.d.ts +43 -4
  20. package/dist/plugin_model.js +48 -2
  21. package/dist/plugin_model.js.map +1 -1
  22. package/dist/render/api.cjs +19 -10
  23. package/dist/render/api.cjs.map +1 -1
  24. package/dist/render/api.d.ts +8 -7
  25. package/dist/render/api.js +19 -10
  26. package/dist/render/api.js.map +1 -1
  27. package/dist/render/internal.cjs.map +1 -1
  28. package/dist/render/internal.d.ts +10 -2
  29. package/dist/render/internal.js.map +1 -1
  30. package/package.json +8 -7
  31. package/src/block_model.ts +2 -0
  32. package/src/columns/column_collection_builder.test.ts +24 -40
  33. package/src/columns/column_collection_builder.ts +17 -15
  34. package/src/columns/column_selector.ts +6 -5
  35. package/src/index.ts +1 -0
  36. package/src/platforma.ts +1 -1
  37. package/src/plugin_model.ts +74 -3
  38. package/src/render/api.ts +33 -13
  39. package/src/render/internal.ts +18 -1
@@ -1 +1 @@
1
- {"version":3,"file":"internal.js","names":[],"sources":["../../src/render/internal.ts"],"sourcesContent":["import type { Optional } from \"utility-types\";\nimport type {\n Branded,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n StringifiedJson,\n} from \"@milaboratories/pl-model-common\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\nimport type {\n ArchiveFormat,\n AnyFunction,\n Option,\n PColumn,\n PColumnValues,\n PFrameDef,\n PFrameHandle,\n PObject,\n PObjectSpec,\n PSpecPredicate,\n PTableDef,\n PTableDefV2,\n PTableHandle,\n ResultCollection,\n ValueOrError,\n DataInfo,\n RangeBytes,\n PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport type { TreeNodeAccessor } from \"./accessor\";\n\nexport const StagingAccessorName = \"staging\";\nexport const MainAccessorName = \"main\";\n\nexport type AccessorHandle = Branded<string, \"AccessorHandle\">;\nexport type FutureHandle = Branded<string, \"FutureHandle\">;\n\nexport type PColumnDataUniversal<TreeEntry = TreeNodeAccessor> =\n | TreeEntry\n | DataInfo<TreeEntry>\n | PColumnValues;\n\nexport interface GlobalCfgRenderCtxMethods<AHandle = AccessorHandle, FHandle = FutureHandle> {\n //\n // Root accessor creation\n //\n\n getAccessorHandleByName(name: string): AHandle | undefined;\n\n //\n // Basic resource accessor actions\n //\n\n resolveWithCommon(\n handle: AHandle,\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): AHandle | undefined;\n\n getResourceType(handle: AHandle): ResourceType;\n\n getInputsLocked(handle: AHandle): boolean;\n\n getOutputsLocked(handle: AHandle): boolean;\n\n getIsReadyOrError(handle: AHandle): boolean;\n\n getIsFinal(handle: AHandle): boolean;\n\n getError(handle: AHandle): AHandle | undefined;\n\n listInputFields(handle: AHandle): string[];\n\n listOutputFields(handle: AHandle): string[];\n\n listDynamicFields(handle: AHandle): string[];\n\n getKeyValueBase64(handle: AHandle, key: string): string | undefined;\n\n getKeyValueAsString(handle: AHandle, key: string): string | undefined;\n\n getDataBase64(handle: AHandle): string | undefined;\n\n getDataAsString(handle: AHandle): string | undefined;\n\n /** If not final returns undefined */\n parsePObjectCollection(\n handle: AHandle,\n errorOnUnknownField: boolean,\n prefix: string,\n ...resolvePath: string[]\n ): Record<string, PObject<AHandle>> | undefined;\n\n //\n // Blob\n //\n\n getBlobContentAsBase64(handle: AHandle, range?: RangeBytes): FHandle; // string | undefined\n\n getBlobContentAsString(handle: AHandle, range?: RangeBytes): FHandle; // string | undefined\n\n getDownloadedBlobContentHandle(handle: AHandle): FHandle; // LocalBlobHandleAndSize | undefined;\n\n getOnDemandBlobContentHandle(handle: AHandle): FHandle; // RemoteBlobHandleAndSize | undefined;\n\n //\n // Blobs to URLs\n //\n\n extractArchiveAndGetURL(handle: AHandle, format: ArchiveFormat): FHandle;\n\n //\n // Import progress\n //\n\n getImportProgress(handle: AHandle): FHandle; // ImportProgress;\n\n //\n // Logs\n //\n\n getLastLogs(handle: AHandle, nLines: number): FHandle; // string | undefined;\n\n getProgressLog(handle: AHandle, patternToSearch: string): FHandle; // string | undefined;\n\n getProgressLogWithInfo(handle: AHandle, patternToSearch: string): FHandle; // ProgressLogWithInfo | undefined;\n\n getLogHandle(handle: AHandle): FHandle; // AnyLogHandle | undefined;\n\n //\n // Blocks\n //\n\n /** @deprecated at some point will stop working and will return dummy values */\n getBlockLabel(blockId: string): string;\n\n //\n // Result Pool\n //\n\n getDataFromResultPool(): ResultCollection<PObject<AHandle>>;\n\n getDataWithErrorsFromResultPool(): ResultCollection<\n Optional<PObject<ValueOrError<AHandle, Error>>, \"id\">\n >;\n\n getSpecsFromResultPool(): ResultCollection<PObjectSpec>;\n\n getSpecFromResultPoolByRef(blockId: string, exportName: string): PObjectSpec | undefined;\n\n getDataFromResultPoolByRef(blockId: string, exportName: string): PObject<AHandle> | undefined;\n\n calculateOptions(predicate: PSpecPredicate): Option[];\n\n //\n // PFrame / PTable\n //\n\n createPFrame(def: PFrameDef<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>): PFrameHandle;\n\n createPTable(def: PTableDef<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>): PTableHandle;\n\n createPTableV2(\n def: PTableDefV2<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>,\n ): PTableHandle;\n\n //\n // Spec Frames (synchronous WASM-based PFrame for spec-level operations)\n //\n\n createSpecFrame(specs: Record<string, PColumnSpec>): string;\n\n specFrameDiscoverColumns(\n handle: string,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse;\n\n specFrameDispose(handle: string): void;\n\n //\n // Computable\n //\n\n getCurrentUnstableMarker(): string | undefined;\n\n //\n // Logging\n //\n\n logInfo(message: string): void;\n\n logWarn(message: string): void;\n\n logError(message: string): void;\n}\n\nexport const GlobalCfgRenderCtxFeatureFlags = {\n explicitColumnsSupport: true as const,\n inlineColumnsSupport: true as const,\n activeArgs: true as const,\n pTablePartitionFiltersSupport: true as const,\n pFrameInSetFilterSupport: true as const,\n};\n\nexport interface GlobalCfgRenderCtx extends GlobalCfgRenderCtxMethods {\n //\n // State: Args, UI State, Active Args\n //\n // Old runtime injects these values as strings, new runtime injects them as functions\n // that return strings, if block declares supportsLazyState flag.\n // If function not called in lazy state API, then resulting output will not depend on these values,\n // and thus will not be recalculated on corresponding state change.\n //\n\n readonly args: string | (() => string);\n /** @deprecated Use `data` instead. Optional for backward compatibility - falls back to `data` if not injected. */\n readonly uiState?: string | (() => string);\n readonly data: string | (() => string);\n readonly activeArgs: undefined | string | (() => string | undefined);\n\n readonly blockStorage: () => StringifiedJson;\n\n // Note: strings below are used because, anyway, using strings is the only way\n // to get data inside the QuickJS context, as it is implemented now. With this\n // approach deserialization can be lazily postponed until it is actually needed.\n readonly callbackRegistry: Record<string, AnyFunction>;\n readonly featureFlags?: typeof GlobalCfgRenderCtxFeatureFlags;\n}\n\nexport type FutureAwait = {\n __awaited_futures__: FutureHandle[];\n};\n\nexport function isFutureAwait(obj: unknown): obj is FutureAwait {\n return typeof obj === \"object\" && obj !== null && \"__awaited_futures__\" in obj;\n}\n\nfunction addAllFutureAwaits(set: Set<string>, visited: Set<unknown>, node: unknown) {\n if (visited.has(node)) return;\n visited.add(node);\n\n const type = typeof node;\n if (type === \"object\") {\n if (isFutureAwait(node)) node.__awaited_futures__.forEach((a) => set.add(a));\n else if (Array.isArray(node))\n for (const nested of node) addAllFutureAwaits(set, visited, nested);\n else\n for (const [, nested] of Object.entries(node as object))\n if (nested !== node) addAllFutureAwaits(set, visited, nested);\n }\n}\n\nexport function getAllFutureAwaits(obj: unknown): Set<string> {\n const set = new Set<string>();\n addAllFutureAwaits(set, new Set(), obj);\n return set;\n}\n"],"mappings":";;;;;;;;;;AA8BA,MAAa,sBAAsB;AACnC,MAAa,mBAAmB;AAoKhC,MAAa,iCAAiC;CAC5C,wBAAwB;CACxB,sBAAsB;CACtB,YAAY;CACZ,+BAA+B;CAC/B,0BAA0B;CAC3B;AA+BD,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,yBAAyB;;AAG7E,SAAS,mBAAmB,KAAkB,SAAuB,MAAe;AAClF,KAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,SAAQ,IAAI,KAAK;AAGjB,KADa,OAAO,SACP,UACX;MAAI,cAAc,KAAK,CAAE,MAAK,oBAAoB,SAAS,MAAM,IAAI,IAAI,EAAE,CAAC;WACnE,MAAM,QAAQ,KAAK,CAC1B,MAAK,MAAM,UAAU,KAAM,oBAAmB,KAAK,SAAS,OAAO;MAEnE,MAAK,MAAM,GAAG,WAAW,OAAO,QAAQ,KAAe,CACrD,KAAI,WAAW,KAAM,oBAAmB,KAAK,SAAS,OAAO;;;AAIrE,SAAgB,mBAAmB,KAA2B;CAC5D,MAAM,sBAAM,IAAI,KAAa;AAC7B,oBAAmB,qBAAK,IAAI,KAAK,EAAE,IAAI;AACvC,QAAO"}
1
+ {"version":3,"file":"internal.js","names":[],"sources":["../../src/render/internal.ts"],"sourcesContent":["import type { Optional } from \"utility-types\";\nimport type {\n AxesId,\n AxesSpec,\n Branded,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n StringifiedJson,\n} from \"@milaboratories/pl-model-common\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\nimport type {\n ArchiveFormat,\n AnyFunction,\n Option,\n PColumn,\n PColumnValues,\n PFrameDef,\n PFrameHandle,\n PObject,\n PObjectSpec,\n PSpecPredicate,\n PTableDef,\n PTableDefV2,\n PTableHandle,\n ResultCollection,\n ValueOrError,\n DataInfo,\n RangeBytes,\n PColumnSpec,\n} from \"@milaboratories/pl-model-common\";\nimport type { TreeNodeAccessor } from \"./accessor\";\n\nexport const StagingAccessorName = \"staging\";\nexport const MainAccessorName = \"main\";\n\nexport type AccessorHandle = Branded<string, \"AccessorHandle\">;\nexport type FutureHandle = Branded<string, \"FutureHandle\">;\n\nexport type PColumnDataUniversal<TreeEntry = TreeNodeAccessor> =\n | TreeEntry\n | DataInfo<TreeEntry>\n | PColumnValues;\n\nexport interface GlobalCfgRenderCtxMethods<AHandle = AccessorHandle, FHandle = FutureHandle> {\n //\n // Root accessor creation\n //\n\n getAccessorHandleByName(name: string): AHandle | undefined;\n\n //\n // Basic resource accessor actions\n //\n\n resolveWithCommon(\n handle: AHandle,\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): AHandle | undefined;\n\n getResourceType(handle: AHandle): ResourceType;\n\n getInputsLocked(handle: AHandle): boolean;\n\n getOutputsLocked(handle: AHandle): boolean;\n\n getIsReadyOrError(handle: AHandle): boolean;\n\n getIsFinal(handle: AHandle): boolean;\n\n getError(handle: AHandle): AHandle | undefined;\n\n listInputFields(handle: AHandle): string[];\n\n listOutputFields(handle: AHandle): string[];\n\n listDynamicFields(handle: AHandle): string[];\n\n getKeyValueBase64(handle: AHandle, key: string): string | undefined;\n\n getKeyValueAsString(handle: AHandle, key: string): string | undefined;\n\n getDataBase64(handle: AHandle): string | undefined;\n\n getDataAsString(handle: AHandle): string | undefined;\n\n /** If not final returns undefined */\n parsePObjectCollection(\n handle: AHandle,\n errorOnUnknownField: boolean,\n prefix: string,\n ...resolvePath: string[]\n ): Record<string, PObject<AHandle>> | undefined;\n\n //\n // Blob\n //\n\n getBlobContentAsBase64(handle: AHandle, range?: RangeBytes): FHandle; // string | undefined\n\n getBlobContentAsString(handle: AHandle, range?: RangeBytes): FHandle; // string | undefined\n\n getDownloadedBlobContentHandle(handle: AHandle): FHandle; // LocalBlobHandleAndSize | undefined;\n\n getOnDemandBlobContentHandle(handle: AHandle): FHandle; // RemoteBlobHandleAndSize | undefined;\n\n //\n // Blobs to URLs\n //\n\n extractArchiveAndGetURL(handle: AHandle, format: ArchiveFormat): FHandle;\n\n //\n // Import progress\n //\n\n getImportProgress(handle: AHandle): FHandle; // ImportProgress;\n\n //\n // Logs\n //\n\n getLastLogs(handle: AHandle, nLines: number): FHandle; // string | undefined;\n\n getProgressLog(handle: AHandle, patternToSearch: string): FHandle; // string | undefined;\n\n getProgressLogWithInfo(handle: AHandle, patternToSearch: string): FHandle; // ProgressLogWithInfo | undefined;\n\n getLogHandle(handle: AHandle): FHandle; // AnyLogHandle | undefined;\n\n //\n // Blocks\n //\n\n /** @deprecated at some point will stop working and will return dummy values */\n getBlockLabel(blockId: string): string;\n\n //\n // Result Pool\n //\n\n getDataFromResultPool(): ResultCollection<PObject<AHandle>>;\n\n getDataWithErrorsFromResultPool(): ResultCollection<\n Optional<PObject<ValueOrError<AHandle, Error>>, \"id\">\n >;\n\n getSpecsFromResultPool(): ResultCollection<PObjectSpec>;\n\n getSpecFromResultPoolByRef(blockId: string, exportName: string): PObjectSpec | undefined;\n\n getDataFromResultPoolByRef(blockId: string, exportName: string): PObject<AHandle> | undefined;\n\n calculateOptions(predicate: PSpecPredicate): Option[];\n\n //\n // PFrame / PTable\n //\n\n createPFrame(def: PFrameDef<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>): PFrameHandle;\n\n createPTable(def: PTableDef<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>): PTableHandle;\n\n createPTableV2(\n def: PTableDefV2<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>,\n ): PTableHandle;\n\n //\n // Spec Frames (synchronous WASM-based PFrame for spec-level operations)\n //\n\n createSpecFrame(specs: Record<string, PColumnSpec>): string;\n\n specFrameDiscoverColumns(\n handle: string,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse;\n\n disposeSpecFrame(handle: string): void;\n\n /** Expand index-based parentAxes in AxesSpec to resolved AxisId parents in AxesId. */\n expandAxes(spec: AxesSpec): AxesId;\n\n /** Collapse resolved AxisId parents back to index-based parentAxes in AxesSpec. */\n collapseAxes(ids: AxesId): AxesSpec;\n\n /** Find the index of an axis matching the given selector. Returns -1 if not found. */\n findAxis(spec: AxesSpec, selector: SingleAxisSelector): number;\n\n /** Find the flat index of a table column matching the given selector. Returns -1 if not found. */\n findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number;\n\n //\n // Computable\n //\n\n getCurrentUnstableMarker(): string | undefined;\n\n //\n // Logging\n //\n\n logInfo(message: string): void;\n\n logWarn(message: string): void;\n\n logError(message: string): void;\n}\n\nexport const GlobalCfgRenderCtxFeatureFlags = {\n explicitColumnsSupport: true as const,\n inlineColumnsSupport: true as const,\n activeArgs: true as const,\n pTablePartitionFiltersSupport: true as const,\n pFrameInSetFilterSupport: true as const,\n};\n\nexport interface GlobalCfgRenderCtx extends GlobalCfgRenderCtxMethods {\n //\n // State: Args, UI State, Active Args\n //\n // Old runtime injects these values as strings, new runtime injects them as functions\n // that return strings, if block declares supportsLazyState flag.\n // If function not called in lazy state API, then resulting output will not depend on these values,\n // and thus will not be recalculated on corresponding state change.\n //\n\n readonly args: string | (() => string);\n /** @deprecated Use `data` instead. Optional for backward compatibility - falls back to `data` if not injected. */\n readonly uiState?: string | (() => string);\n readonly data: string | (() => string);\n readonly activeArgs: undefined | string | (() => string | undefined);\n\n readonly blockStorage: () => StringifiedJson;\n\n // Note: strings below are used because, anyway, using strings is the only way\n // to get data inside the QuickJS context, as it is implemented now. With this\n // approach deserialization can be lazily postponed until it is actually needed.\n readonly callbackRegistry: Record<string, AnyFunction>;\n readonly featureFlags?: typeof GlobalCfgRenderCtxFeatureFlags;\n}\n\nexport type FutureAwait = {\n __awaited_futures__: FutureHandle[];\n};\n\nexport function isFutureAwait(obj: unknown): obj is FutureAwait {\n return typeof obj === \"object\" && obj !== null && \"__awaited_futures__\" in obj;\n}\n\nfunction addAllFutureAwaits(set: Set<string>, visited: Set<unknown>, node: unknown) {\n if (visited.has(node)) return;\n visited.add(node);\n\n const type = typeof node;\n if (type === \"object\") {\n if (isFutureAwait(node)) node.__awaited_futures__.forEach((a) => set.add(a));\n else if (Array.isArray(node))\n for (const nested of node) addAllFutureAwaits(set, visited, nested);\n else\n for (const [, nested] of Object.entries(node as object))\n if (nested !== node) addAllFutureAwaits(set, visited, nested);\n }\n}\n\nexport function getAllFutureAwaits(obj: unknown): Set<string> {\n const set = new Set<string>();\n addAllFutureAwaits(set, new Set(), obj);\n return set;\n}\n"],"mappings":";;;;;;;;;;AAmCA,MAAa,sBAAsB;AACnC,MAAa,mBAAmB;AAgLhC,MAAa,iCAAiC;CAC5C,wBAAwB;CACxB,sBAAsB;CACtB,YAAY;CACZ,+BAA+B;CAC/B,0BAA0B;CAC3B;AA+BD,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,yBAAyB;;AAG7E,SAAS,mBAAmB,KAAkB,SAAuB,MAAe;AAClF,KAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,SAAQ,IAAI,KAAK;AAGjB,KADa,OAAO,SACP,UACX;MAAI,cAAc,KAAK,CAAE,MAAK,oBAAoB,SAAS,MAAM,IAAI,IAAI,EAAE,CAAC;WACnE,MAAM,QAAQ,KAAK,CAC1B,MAAK,MAAM,UAAU,KAAM,oBAAmB,KAAK,SAAS,OAAO;MAEnE,MAAK,MAAM,GAAG,WAAW,OAAO,QAAQ,KAAe,CACrD,KAAI,WAAW,KAAM,oBAAmB,KAAK,SAAS,OAAO;;;AAIrE,SAAgB,mBAAmB,KAA2B;CAC5D,MAAM,sBAAM,IAAI,KAAa;AAC7B,oBAAmB,qBAAK,IAAI,KAAK,EAAE,IAAI;AACvC,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.60.2",
3
+ "version": "1.61.1",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -30,11 +30,11 @@
30
30
  "fast-json-patch": "^3.1.1",
31
31
  "utility-types": "^3.11.0",
32
32
  "zod": "~3.23.8",
33
- "@milaboratories/pl-model-common": "1.28.0",
34
- "@milaboratories/pl-error-like": "1.12.9",
35
- "@milaboratories/ptabler-expression-js": "1.2.1",
36
33
  "@milaboratories/helpers": "1.14.0",
37
- "@milaboratories/pl-model-middle-layer": "1.15.0"
34
+ "@milaboratories/pl-model-common": "1.29.0",
35
+ "@milaboratories/pl-model-middle-layer": "1.16.0",
36
+ "@milaboratories/ptabler-expression-js": "1.2.2",
37
+ "@milaboratories/pl-error-like": "1.12.9"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@vitest/coverage-istanbul": "^4.0.18",
@@ -42,9 +42,10 @@
42
42
  "typescript": "~5.9.3",
43
43
  "vitest": "^4.0.18",
44
44
  "@milaboratories/build-configs": "1.5.2",
45
+ "@milaboratories/pf-spec-driver": "1.1.0",
46
+ "@milaboratories/pf-driver": "1.2.0",
45
47
  "@milaboratories/ts-configs": "1.2.2",
46
- "@milaboratories/ts-builder": "1.3.0",
47
- "@milaboratories/pf-driver": "1.1.1"
48
+ "@milaboratories/ts-builder": "1.3.0"
48
49
  },
49
50
  "scripts": {
50
51
  "build": "ts-builder build --target node",
@@ -549,11 +549,13 @@ export class BlockModelV3<
549
549
 
550
550
  // Register plugin outputs (in config pack, evaluated by middle layer)
551
551
  const outputs = model.outputs as Record<string, (ctx: PluginRenderCtx) => unknown>;
552
+ const { outputFlags } = model;
552
553
  for (const [outputKey, outputFn] of Object.entries(outputs)) {
553
554
  const key = pluginOutputKey(handle, outputKey);
554
555
  pluginOutputs[key] = createAndRegisterRenderLambda({
555
556
  handle: key,
556
557
  lambda: () => outputFn(new PluginRenderCtx(handle, wrappedInputs)),
558
+ withStatus: outputFlags[outputKey]?.withStatus,
557
559
  });
558
560
  }
559
561
  }
@@ -5,44 +5,26 @@ import type {
5
5
  SUniversalPColumnId,
6
6
  } from "@milaboratories/pl-model-common";
7
7
  import { AnchoredIdDeriver } from "@milaboratories/pl-model-common";
8
+ import { SpecDriver } from "@milaboratories/pf-spec-driver";
8
9
 
9
- import { describe, expect, test } from "vitest";
10
+ import { afterEach, describe, expect, test } from "vitest";
10
11
  import type { ColumnSnapshotProvider } from "./column_snapshot_provider";
11
12
  import type { ColumnSnapshot } from "./column_snapshot";
12
13
  import { ColumnCollectionBuilder } from "./column_collection_builder";
13
14
 
14
- // --- Mock SpecFrameCtx ---
15
+ const drivers: SpecDriver[] = [];
15
16
 
16
17
  function createSpecFrameCtx() {
17
- const frames = new Map<string, Record<string, PColumnSpec>>();
18
- let nextId = 0;
19
-
20
- return {
21
- createSpecFrame: (specs: Record<string, PColumnSpec>) => {
22
- const handle = `frame-${nextId++}`;
23
- frames.set(handle, specs);
24
- return handle;
25
- },
26
- specFrameDiscoverColumns: (handle: string, _request: any) => {
27
- const specs = frames.get(handle)!;
28
- return {
29
- hits: Object.entries(specs).map(([columnId, spec]) => ({
30
- hit: { columnId: columnId as PObjectId, spec },
31
- mappingVariants: [
32
- {
33
- qualifications: { forQueries: [], forHit: [] },
34
- distinctiveQualifications: { forQueries: [], forHit: [] },
35
- },
36
- ],
37
- })),
38
- };
39
- },
40
- specFrameDispose: (handle: string) => {
41
- frames.delete(handle);
42
- },
43
- };
18
+ const driver = new SpecDriver();
19
+ drivers.push(driver);
20
+ return driver;
44
21
  }
45
22
 
23
+ afterEach(() => {
24
+ for (const driver of drivers) driver.dispose();
25
+ drivers.length = 0;
26
+ });
27
+
46
28
  // --- Helpers ---
47
29
 
48
30
  /** Default axis used when none specified — WASM requires at least one axis. */
@@ -173,15 +155,16 @@ describe("ColumnCollection.findColumns", () => {
173
155
  expect(collection.findColumns()).toHaveLength(2);
174
156
  });
175
157
 
176
- test("exclude throws not implemented", () => {
158
+ test("exclude filters out matching columns", () => {
177
159
  const s1 = createSnapshot("id1", createSpec("col1"));
160
+ const s2 = createSnapshot("id2", createSpec("col2"));
178
161
  const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
179
- builder.addSource([s1]);
162
+ builder.addSource([s1, s2]);
180
163
 
181
164
  const collection = builder.build()!;
182
- expect(() => collection.findColumns({ exclude: { name: "col1" } })).toThrow(
183
- /not yet implemented/i,
184
- );
165
+ const results = collection.findColumns({ exclude: { name: "col1" } });
166
+ expect(results).toHaveLength(1);
167
+ expect(results[0].spec.name).toBe("col2");
185
168
  });
186
169
  });
187
170
 
@@ -377,15 +360,16 @@ describe("AnchoredColumnCollection", () => {
377
360
  expect(matches[0].variants).toBeDefined();
378
361
  });
379
362
 
380
- test("findColumns exclude throws not implemented", () => {
381
- const snap = createSnapshot("id1", createSpec("col1", { axesSpec: [sampleAxis("sample")] }));
363
+ test("findColumns exclude filters out matching columns", () => {
364
+ const snap1 = createSnapshot("id1", createSpec("col1", { axesSpec: [sampleAxis("sample")] }));
365
+ const snap2 = createSnapshot("id2", createSpec("col2", { axesSpec: [sampleAxis("sample")] }));
382
366
  const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
383
- builder.addSource([snap]);
367
+ builder.addSource([snap1, snap2]);
384
368
 
385
369
  const collection = builder.build({ anchors: { main: anchorSpec } })!;
386
- expect(() => collection.findColumns({ exclude: { name: "col1" } })).toThrow(
387
- /not yet implemented/i,
388
- );
370
+ const results = collection.findColumns({ exclude: { name: "col1" } });
371
+ expect(results).toHaveLength(1);
372
+ expect(results[0].column.spec.name).toBe("col2");
389
373
  });
390
374
 
391
375
  test("allowPartialColumnList with anchors tracks completeness", () => {
@@ -2,6 +2,7 @@ import type {
2
2
  AxisQualification,
3
3
  ColumnAxesWithQualifications,
4
4
  DiscoverColumnsConstraints,
5
+ DiscoverColumnsStepInfo,
5
6
  MultiColumnSelector,
6
7
  NativePObjectId,
7
8
  PColumnSpec,
@@ -23,7 +24,7 @@ import type { GlobalCfgRenderCtxMethods } from "../render/internal";
23
24
  /** Subset of render context methods needed for spec frame operations. */
24
25
  type SpecFrameCtx = Pick<
25
26
  GlobalCfgRenderCtxMethods,
26
- "createSpecFrame" | "specFrameDiscoverColumns" | "specFrameDispose"
27
+ "createSpecFrame" | "specFrameDiscoverColumns" | "disposeSpecFrame"
27
28
  >;
28
29
 
29
30
  // --- FindColumnsOptions ---
@@ -79,6 +80,8 @@ export interface ColumnMatch {
79
80
  readonly originalId: PObjectId;
80
81
  /** Match variants — different paths/qualifications that reach this column. */
81
82
  readonly variants: MatchVariant[];
83
+ /** Linker steps traversed to reach this hit; empty for direct matches. */
84
+ readonly path: DiscoverColumnsStepInfo[];
82
85
  }
83
86
 
84
87
  /** Qualifications needed for both query (already-integrated) columns and the hit column. */
@@ -253,24 +256,23 @@ class ColumnCollectionImpl implements ColumnCollection {
253
256
  }
254
257
 
255
258
  findColumns(options?: FindColumnsOptions): ColumnSnapshot<PObjectId>[] {
256
- const columnFilter = options?.include ? toMultiColumnSelectors(options.include) : [];
259
+ const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
260
+ const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
257
261
 
258
262
  const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
259
- columnFilter,
263
+ includeColumns,
264
+ excludeColumns,
260
265
  axes: [],
266
+ maxHops: 0,
261
267
  constraints: PLAIN_CONSTRAINTS,
262
268
  });
263
269
 
264
270
  // Map hits back to snapshots
265
- let results = response.hits
271
+ const results = response.hits
266
272
  .map((hit) => this.columns.get(hit.hit.columnId as PObjectId))
267
273
  .filter((col): col is ColumnSnapshot<PObjectId> => col !== undefined)
268
274
  .map((col) => this.toSnapshot(col));
269
275
 
270
- if (options?.exclude) {
271
- throw new Error("Exclude filter is not yet implemented for plain ColumnCollection");
272
- }
273
-
274
276
  return results;
275
277
  }
276
278
 
@@ -336,16 +338,19 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
336
338
  findColumns(options?: AnchoredFindColumnsOptions): ColumnMatch[] {
337
339
  const mode = options?.mode ?? "enrichment";
338
340
  const constraints = matchingModeToConstraints(mode);
339
- const columnFilter = options?.include ? toMultiColumnSelectors(options.include) : [];
341
+ const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
342
+ const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
340
343
 
341
344
  const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
342
- columnFilter,
345
+ includeColumns,
346
+ excludeColumns,
343
347
  constraints,
344
348
  axes: this.anchorAxes,
349
+ maxHops: options?.maxHops ?? 4,
345
350
  });
346
351
 
347
352
  // Map hits back to ColumnMatch entries
348
- let results = response.hits
353
+ const results = response.hits
349
354
  .map((hit) => {
350
355
  const origId = hit.hit.columnId as PObjectId;
351
356
  const col = this.columns.get(origId);
@@ -360,14 +365,11 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
360
365
  distinctiveQualifications: v.distinctiveQualifications,
361
366
  }),
362
367
  ),
368
+ path: hit.path,
363
369
  } satisfies ColumnMatch;
364
370
  })
365
371
  .filter((m): m is ColumnMatch => m !== undefined);
366
372
 
367
- if (options?.exclude) {
368
- throw new Error("Exclude filter is not yet implemented for AnchoredColumnCollection");
369
- }
370
-
371
373
  return results;
372
374
  }
373
375
 
@@ -1,9 +1,10 @@
1
1
  import type {
2
+ AxisValueType,
3
+ ColumnValueType,
2
4
  MultiAxisSelector,
3
5
  MultiColumnSelector,
4
6
  PColumnSpec,
5
7
  StringMatcher,
6
- ValueType,
7
8
  } from "@milaboratories/pl-model-common";
8
9
 
9
10
  export type { StringMatcher } from "@milaboratories/pl-model-common";
@@ -19,7 +20,7 @@ export type RelaxedRecord = Record<string, RelaxedStringMatchers>;
19
20
  /** Relaxed axis selector — accepts plain strings where strict requires StringMatcher[]. */
20
21
  export interface RelaxedAxisSelector {
21
22
  name?: RelaxedStringMatchers;
22
- type?: ValueType | ValueType[];
23
+ type?: AxisValueType | AxisValueType[];
23
24
  domain?: RelaxedRecord;
24
25
  contextDomain?: RelaxedRecord;
25
26
  annotations?: RelaxedRecord;
@@ -28,7 +29,7 @@ export interface RelaxedAxisSelector {
28
29
  /** Relaxed column selector — convenient hand-written form. */
29
30
  export interface RelaxedColumnSelector {
30
31
  name?: RelaxedStringMatchers;
31
- type?: ValueType | ValueType[];
32
+ type?: ColumnValueType | ColumnValueType[];
32
33
  domain?: RelaxedRecord;
33
34
  contextDomain?: RelaxedRecord;
34
35
  annotations?: RelaxedRecord;
@@ -57,7 +58,7 @@ function normalizeRecord(input: RelaxedRecord): Record<string, StringMatcher[]>
57
58
  return result;
58
59
  }
59
60
 
60
- function normalizeTypes(input: ValueType | ValueType[]): ValueType[] {
61
+ function normalizeTypes<T>(input: T | T[]): T[] {
61
62
  return Array.isArray(input) ? input : [input];
62
63
  }
63
64
 
@@ -140,7 +141,7 @@ function matchAxisSelector(
140
141
  selector: MultiAxisSelector,
141
142
  ): boolean {
142
143
  if (selector.name !== undefined && !matchStringValue(axis.name, selector.name)) return false;
143
- if (selector.type !== undefined && !selector.type.includes(axis.type as ValueType)) return false;
144
+ if (selector.type !== undefined && !selector.type.includes(axis.type)) return false;
144
145
  if (selector.domain !== undefined && !matchRecordField(axis.domain, selector.domain))
145
146
  return false;
146
147
  if (
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ export {
36
36
  PluginInstance,
37
37
  PluginModel,
38
38
  type PluginFactory,
39
+ type PluginDataModelVersions,
39
40
  } from "./plugin_model";
40
41
  export * from "./bconfig";
41
42
  export * from "./components";
package/src/platforma.ts CHANGED
@@ -149,6 +149,6 @@ export type InferPluginData<Pl, PluginId extends string> =
149
149
  */
150
150
  export type InferPluginHandles<T extends Record<string, unknown>> = {
151
151
  readonly [K in keyof T]: T[K] extends PluginRecord<infer Data, infer Params, infer Outputs>
152
- ? PluginHandle<PluginFactoryLike<Data, Params, Outputs>>
152
+ ? { handle: PluginHandle<PluginFactoryLike<Data, Params, Outputs>> }
153
153
  : never;
154
154
  };
@@ -7,7 +7,7 @@
7
7
  * @module plugin_model
8
8
  */
9
9
 
10
- import type { BlockCodeKnownFeatureFlags } from "@milaboratories/pl-model-common";
10
+ import type { BlockCodeKnownFeatureFlags, OutputWithStatus } from "@milaboratories/pl-model-common";
11
11
  import {
12
12
  type DataModel,
13
13
  DataModelBuilder,
@@ -79,6 +79,11 @@ type PluginChainState = {
79
79
  recoverAtIndex?: number;
80
80
  };
81
81
 
82
+ /** Version → persisted data shape for each migration step (third generic of `PluginModel.define` when `data` is a `PluginDataModel`). */
83
+ export type PluginDataModelVersions<
84
+ M extends PluginDataModel<PluginData, Record<string, unknown>, unknown>,
85
+ > = M extends PluginDataModel<PluginData, infer Versions, unknown> ? Versions : never;
86
+
82
87
  /**
83
88
  * Builder for creating PluginDataModel with type-safe migrations.
84
89
  * Mirrors DataModelBuilder — same .from(), .migrate(), .recover(), .init() chain.
@@ -280,8 +285,8 @@ export class PluginInstance<
280
285
  > implements TransferTarget<Id, TransferData> {
281
286
  readonly id: Id;
282
287
  readonly transferVersion: string;
283
- /** @internal */
284
- readonly __transferBrand?: TransferData;
288
+ /** @internal Phantom for type inference of Data/Params/Outputs; never set at runtime. */
289
+ readonly __instanceTypes?: { data: Data; params: Params; outputs: Outputs };
285
290
  private readonly factory: PluginModelFactory<Data, Params, Outputs, any, any>;
286
291
  private readonly config: any;
287
292
 
@@ -336,6 +341,8 @@ export class PluginModel<
336
341
  readonly outputs: {
337
342
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
338
343
  };
344
+ /** Per-output flags (e.g. withStatus) */
345
+ readonly outputFlags: Record<string, { withStatus: boolean }>;
339
346
  /** Feature flags declared by this plugin */
340
347
  readonly featureFlags?: BlockCodeKnownFeatureFlags;
341
348
  /** Create fresh default data. Config (if any) is captured at creation time. */
@@ -347,12 +354,14 @@ export class PluginModel<
347
354
  outputs: {
348
355
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
349
356
  };
357
+ outputFlags: Record<string, { withStatus: boolean }>;
350
358
  featureFlags?: BlockCodeKnownFeatureFlags;
351
359
  getDefaultData: () => DataVersioned<Data>;
352
360
  }) {
353
361
  this.name = options.name;
354
362
  this.dataModel = options.dataModel;
355
363
  this.outputs = options.outputs;
364
+ this.outputFlags = options.outputFlags;
356
365
  this.featureFlags = options.featureFlags;
357
366
  this.getDefaultData = options.getDefaultData;
358
367
  }
@@ -372,6 +381,7 @@ export class PluginModel<
372
381
  outputs: {
373
382
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
374
383
  };
384
+ outputFlags: Record<string, { withStatus: boolean }>;
375
385
  featureFlags?: BlockCodeKnownFeatureFlags;
376
386
  getDefaultData: () => DataVersioned<Data>;
377
387
  }): PluginModel<Data, Params, Outputs> {
@@ -485,6 +495,7 @@ class PluginModelFactory<
485
495
  readonly outputs: {
486
496
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
487
497
  };
498
+ private readonly outputFlags: Record<string, { withStatus: boolean }>;
488
499
  private readonly featureFlags?: BlockCodeKnownFeatureFlags;
489
500
 
490
501
  private constructor(options: {
@@ -494,12 +505,14 @@ class PluginModelFactory<
494
505
  outputs: {
495
506
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
496
507
  };
508
+ outputFlags: Record<string, { withStatus: boolean }>;
497
509
  featureFlags?: BlockCodeKnownFeatureFlags;
498
510
  }) {
499
511
  this.name = options.name;
500
512
  this.dataFn = options.dataFn;
501
513
  this.getDefaultDataFn = options.getDefaultDataFn;
502
514
  this.outputs = options.outputs;
515
+ this.outputFlags = options.outputFlags;
503
516
  this.featureFlags = options.featureFlags;
504
517
  }
505
518
 
@@ -517,6 +530,7 @@ class PluginModelFactory<
517
530
  outputs: {
518
531
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
519
532
  };
533
+ outputFlags: Record<string, { withStatus: boolean }>;
520
534
  featureFlags?: BlockCodeKnownFeatureFlags;
521
535
  }): PluginModelFactory<Data, Params, Outputs, Config, Versions> {
522
536
  return new PluginModelFactory(options);
@@ -544,6 +558,7 @@ class PluginModelFactory<
544
558
  name: this.name,
545
559
  dataModel,
546
560
  outputs: this.outputs,
561
+ outputFlags: this.outputFlags,
547
562
  featureFlags: this.featureFlags,
548
563
  getDefaultData: getDefaultDataFn
549
564
  ? () => getDefaultDataFn(config)
@@ -584,6 +599,7 @@ class PluginModelBuilder<
584
599
  private readonly outputs: {
585
600
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
586
601
  };
602
+ private readonly outputFlags: Record<string, { withStatus: boolean }>;
587
603
  private readonly featureFlags?: BlockCodeKnownFeatureFlags;
588
604
 
589
605
  private constructor(options: {
@@ -593,6 +609,7 @@ class PluginModelBuilder<
593
609
  outputs?: {
594
610
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
595
611
  };
612
+ outputFlags?: Record<string, { withStatus: boolean }>;
596
613
  featureFlags?: BlockCodeKnownFeatureFlags;
597
614
  }) {
598
615
  this.name = options.name;
@@ -603,6 +620,7 @@ class PluginModelBuilder<
603
620
  ({} as {
604
621
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
605
622
  });
623
+ this.outputFlags = options.outputFlags ?? {};
606
624
  this.featureFlags = options.featureFlags;
607
625
  }
608
626
 
@@ -620,6 +638,7 @@ class PluginModelBuilder<
620
638
  outputs?: {
621
639
  [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
622
640
  };
641
+ outputFlags?: Record<string, { withStatus: boolean }>;
623
642
  featureFlags?: BlockCodeKnownFeatureFlags;
624
643
  }): PluginModelBuilder<Data, Params, Outputs, Config, Versions> {
625
644
  return new PluginModelBuilder(options);
@@ -653,6 +672,57 @@ class PluginModelBuilder<
653
672
  ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>,
654
673
  ) => (Outputs & { [P in Key]: T })[K];
655
674
  },
675
+ outputFlags: { ...this.outputFlags, [key]: { withStatus: false } },
676
+ });
677
+ }
678
+
679
+ /**
680
+ * Adds an output wrapped with status information to the plugin.
681
+ *
682
+ * The UI receives the full {@link OutputWithStatus} object instead of an unwrapped value,
683
+ * allowing it to distinguish between pending, success, and error states.
684
+ *
685
+ * @param key - Output name
686
+ * @param fn - Function that computes the output value from plugin context
687
+ * @returns PluginModel with the new status-wrapped output added
688
+ *
689
+ * @example
690
+ * .outputWithStatus('table', (ctx) => {
691
+ * const pCols = ctx.params.pFrame?.getPColumns();
692
+ * if (pCols === undefined) return undefined;
693
+ * return createPlDataTableV2(ctx, pCols, ctx.data.tableState);
694
+ * })
695
+ */
696
+ outputWithStatus<const Key extends string, T>(
697
+ key: Key,
698
+ fn: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => T,
699
+ ): PluginModelBuilder<
700
+ Data,
701
+ Params,
702
+ Outputs & { [K in Key]: OutputWithStatus<T> },
703
+ Config,
704
+ Versions
705
+ > {
706
+ return new PluginModelBuilder<
707
+ Data,
708
+ Params,
709
+ Outputs & { [K in Key]: OutputWithStatus<T> },
710
+ Config,
711
+ Versions
712
+ >({
713
+ name: this.name,
714
+ dataFn: this.dataFn,
715
+ getDefaultDataFn: this.getDefaultDataFn,
716
+ featureFlags: this.featureFlags,
717
+ outputs: {
718
+ ...this.outputs,
719
+ [key]: fn,
720
+ } as {
721
+ [K in keyof (Outputs & { [P in Key]: OutputWithStatus<T> })]: (
722
+ ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>,
723
+ ) => (Outputs & { [P in Key]: OutputWithStatus<T> })[K];
724
+ },
725
+ outputFlags: { ...this.outputFlags, [key]: { withStatus: true } },
656
726
  });
657
727
  }
658
728
 
@@ -675,6 +745,7 @@ class PluginModelBuilder<
675
745
  dataFn: this.dataFn,
676
746
  getDefaultDataFn: this.getDefaultDataFn,
677
747
  outputs: this.outputs,
748
+ outputFlags: this.outputFlags,
678
749
  featureFlags: this.featureFlags,
679
750
  });
680
751
  }
package/src/render/api.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  AnchoredPColumnSelector,
3
3
  AnyFunction,
4
+ AxesId,
5
+ AxesSpec,
4
6
  AxisId,
5
7
  DataInfo,
6
8
  DiscoverColumnsRequest,
@@ -17,6 +19,8 @@ import type {
17
19
  PObjectId,
18
20
  PObjectSpec,
19
21
  PSpecPredicate,
22
+ PTableColumnId,
23
+ PTableColumnSpec,
20
24
  PTableDef,
21
25
  PTableDefV2,
22
26
  PTableHandle,
@@ -25,6 +29,7 @@ import type {
25
29
  PlRef,
26
30
  ResolveAnchorsOptions,
27
31
  ResultCollection,
32
+ SingleAxisSelector,
28
33
  SUniversalPColumnId,
29
34
  ValueOrError,
30
35
  } from "@milaboratories/pl-model-common";
@@ -727,8 +732,24 @@ export abstract class RenderCtxBase<Args = unknown, Data = unknown> {
727
732
  return this.ctx.specFrameDiscoverColumns(handle, request);
728
733
  }
729
734
 
730
- public specFrameDispose(handle: string): void {
731
- this.ctx.specFrameDispose(handle);
735
+ public disposeSpecFrame(handle: string): void {
736
+ this.ctx.disposeSpecFrame(handle);
737
+ }
738
+
739
+ public expandAxes(spec: AxesSpec): AxesId {
740
+ return this.ctx.expandAxes(spec);
741
+ }
742
+
743
+ public collapseAxes(ids: AxesId): AxesSpec {
744
+ return this.ctx.collapseAxes(ids);
745
+ }
746
+
747
+ public findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {
748
+ return this.ctx.findAxis(spec, selector);
749
+ }
750
+
751
+ public findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {
752
+ return this.ctx.findTableColumn(tableSpec, selector);
732
753
  }
733
754
 
734
755
  public getCurrentUnstableMarker(): string | undefined {
@@ -798,26 +819,28 @@ export class RenderCtxLegacy<Args = unknown, UiState = unknown> extends RenderCt
798
819
  *
799
820
  * @typeParam F - PluginFactoryLike phantom carrying data/params/outputs types
800
821
  */
801
- export class PluginRenderCtx<F extends PluginFactoryLike = PluginFactoryLike> {
802
- private readonly ctx: GlobalCfgRenderCtx;
822
+ export class PluginRenderCtx<F extends PluginFactoryLike = PluginFactoryLike> extends RenderCtxBase<
823
+ unknown,
824
+ InferFactoryData<F>
825
+ > {
803
826
  private readonly handle: PluginHandle<F>;
804
827
  private readonly wrappedInputs: Record<string, () => unknown>;
805
828
 
806
829
  constructor(handle: PluginHandle<F>, wrappedInputs: Record<string, () => unknown>) {
807
- this.ctx = getCfgRenderCtx();
830
+ super();
808
831
  this.handle = handle;
809
832
  this.wrappedInputs = wrappedInputs;
810
833
  }
811
834
 
812
- private dataCache?: { v: InferFactoryData<F> };
835
+ private pluginDataCache?: { v: InferFactoryData<F> };
813
836
 
814
837
  /** Plugin's persistent data from blockStorage.__plugins.{pluginId}.__data */
815
- public get data(): InferFactoryData<F> {
816
- if (this.dataCache === undefined) {
838
+ public override get data(): InferFactoryData<F> {
839
+ if (this.pluginDataCache === undefined) {
817
840
  const raw = this.ctx.blockStorage();
818
- this.dataCache = { v: getPluginData(parseJson(raw), this.handle) };
841
+ this.pluginDataCache = { v: getPluginData(parseJson(raw), this.handle) };
819
842
  }
820
- return this.dataCache.v;
843
+ return this.pluginDataCache.v;
821
844
  }
822
845
 
823
846
  private paramsCache?: { v: InferFactoryParams<F> };
@@ -833,9 +856,6 @@ export class PluginRenderCtx<F extends PluginFactoryLike = PluginFactoryLike> {
833
856
  }
834
857
  return this.paramsCache.v;
835
858
  }
836
-
837
- /** Result pool — same as block, from cfgRenderCtx methods */
838
- public readonly resultPool = new ResultPool();
839
859
  }
840
860
 
841
861
  /** @deprecated Use BlockRenderCtx instead */