@milaboratories/pf-spec-driver 1.1.0 → 1.2.0

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.
@@ -0,0 +1,10 @@
1
+
2
+ //#region src/logging.ts
3
+ const LogPFrames = Boolean(process.env.MI_LOG_PFRAMES);
4
+ function logPFrames() {
5
+ return LogPFrames;
6
+ }
7
+
8
+ //#endregion
9
+ exports.logPFrames = logPFrames;
10
+ //# sourceMappingURL=logging.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.cjs","names":[],"sources":["../src/logging.ts"],"sourcesContent":["const LogPFrames = Boolean(process.env.MI_LOG_PFRAMES);\n\nexport function logPFrames(): boolean {\n return LogPFrames;\n}\n"],"mappings":";;AAAA,MAAM,aAAa,QAAQ,QAAQ,IAAI,eAAe;AAEtD,SAAgB,aAAsB;AACpC,QAAO"}
@@ -0,0 +1,9 @@
1
+ //#region src/logging.ts
2
+ const LogPFrames = Boolean(process.env.MI_LOG_PFRAMES);
3
+ function logPFrames() {
4
+ return LogPFrames;
5
+ }
6
+
7
+ //#endregion
8
+ export { logPFrames };
9
+ //# sourceMappingURL=logging.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.js","names":[],"sources":["../src/logging.ts"],"sourcesContent":["const LogPFrames = Boolean(process.env.MI_LOG_PFRAMES);\n\nexport function logPFrames(): boolean {\n return LogPFrames;\n}\n"],"mappings":";AAAA,MAAM,aAAa,QAAQ,QAAQ,IAAI,eAAe;AAEtD,SAAgB,aAAsB;AACpC,QAAO"}
@@ -0,0 +1,34 @@
1
+ const require_logging = require('./logging.cjs');
2
+ let _milaboratories_pframes_rs_wasm = require("@milaboratories/pframes-rs-wasm");
3
+ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
4
+ let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
5
+ let _noble_hashes_blake3_js = require("@noble/hashes/blake3.js");
6
+ let _noble_hashes_utils_js = require("@noble/hashes/utils.js");
7
+
8
+ //#region src/pframe_pool.ts
9
+ var PFramePool = class extends _milaboratories_ts_helpers.RefCountPoolBase {
10
+ constructor(logger) {
11
+ super();
12
+ this.logger = logger;
13
+ }
14
+ calculateParamsKey(params) {
15
+ return (0, _noble_hashes_utils_js.bytesToHex)((0, _noble_hashes_blake3_js.blake3)(new TextEncoder().encode((0, _milaboratories_pl_model_common.canonicalizeJson)(params))));
16
+ }
17
+ createNewResource(params, key) {
18
+ if (require_logging.logPFrames()) this.logger.info(`Creating SpecFrame for handle = ${key}, columns: ` + (0, _milaboratories_pl_model_common.stringifyJson)(params));
19
+ return (0, _milaboratories_pframes_rs_wasm.createPFrame)(params);
20
+ }
21
+ getByKey(key) {
22
+ const resource = super.tryGetByKey(key);
23
+ if (!resource) {
24
+ const error = new _milaboratories_pl_model_common.PFrameDriverError(`Invalid SpecFrame handle`);
25
+ error.cause = /* @__PURE__ */ new Error(`SpecFrame with handle ${key} not found`);
26
+ throw error;
27
+ }
28
+ return resource;
29
+ }
30
+ };
31
+
32
+ //#endregion
33
+ exports.PFramePool = PFramePool;
34
+ //# sourceMappingURL=pframe_pool.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pframe_pool.cjs","names":["RefCountPoolBase","logPFrames","PFrameDriverError"],"sources":["../src/pframe_pool.ts"],"sourcesContent":["import {\n PFrameDriverError,\n PColumnSpec,\n SpecFrameHandle,\n stringifyJson,\n canonicalizeJson,\n} from \"@milaboratories/pl-model-common\";\nimport { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport { MiLogger, RefCountPoolBase } from \"@milaboratories/ts-helpers\";\nimport { logPFrames } from \"./logging\";\nimport { createPFrame } from \"@milaboratories/pframes-rs-wasm\";\nimport { blake3 } from \"@noble/hashes/blake3.js\";\nimport { bytesToHex } from \"@noble/hashes/utils.js\";\n\nexport class PFramePool extends RefCountPoolBase<\n Record<string, PColumnSpec>,\n SpecFrameHandle,\n PFrameInternal.PFrameWasmV2\n> {\n constructor(private readonly logger: MiLogger) {\n super();\n }\n\n protected calculateParamsKey(params: Record<string, PColumnSpec>): SpecFrameHandle {\n return bytesToHex(\n blake3(new TextEncoder().encode(canonicalizeJson(params))),\n ) as SpecFrameHandle;\n }\n\n protected createNewResource(\n params: Record<string, PColumnSpec>,\n key: SpecFrameHandle,\n ): PFrameInternal.PFrameWasmV2 {\n if (logPFrames()) {\n this.logger.info(`Creating SpecFrame for handle = ${key}, columns: ` + stringifyJson(params));\n }\n return createPFrame(params);\n }\n\n public getByKey(key: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {\n const resource = super.tryGetByKey(key);\n if (!resource) {\n const error = new PFrameDriverError(`Invalid SpecFrame handle`);\n error.cause = new Error(`SpecFrame with handle ${key} not found`);\n throw error;\n }\n return resource;\n }\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,aAAb,cAAgCA,4CAI9B;CACA,YAAY,AAAiB,QAAkB;AAC7C,SAAO;EADoB;;CAI7B,AAAU,mBAAmB,QAAsD;AACjF,oFACS,IAAI,aAAa,CAAC,6DAAwB,OAAO,CAAC,CAAC,CAC3D;;CAGH,AAAU,kBACR,QACA,KAC6B;AAC7B,MAAIC,4BAAY,CACd,MAAK,OAAO,KAAK,mCAAmC,IAAI,kEAA6B,OAAO,CAAC;AAE/F,2DAAoB,OAAO;;CAG7B,AAAO,SAAS,KAAmD;EACjE,MAAM,WAAW,MAAM,YAAY,IAAI;AACvC,MAAI,CAAC,UAAU;GACb,MAAM,QAAQ,IAAIC,kDAAkB,2BAA2B;AAC/D,SAAM,wBAAQ,IAAI,MAAM,yBAAyB,IAAI,YAAY;AACjE,SAAM;;AAER,SAAO"}
@@ -0,0 +1,34 @@
1
+ import { logPFrames } from "./logging.js";
2
+ import { createPFrame } from "@milaboratories/pframes-rs-wasm";
3
+ import { PFrameDriverError, canonicalizeJson, stringifyJson } from "@milaboratories/pl-model-common";
4
+ import { RefCountPoolBase } from "@milaboratories/ts-helpers";
5
+ import { blake3 } from "@noble/hashes/blake3.js";
6
+ import { bytesToHex } from "@noble/hashes/utils.js";
7
+
8
+ //#region src/pframe_pool.ts
9
+ var PFramePool = class extends RefCountPoolBase {
10
+ constructor(logger) {
11
+ super();
12
+ this.logger = logger;
13
+ }
14
+ calculateParamsKey(params) {
15
+ return bytesToHex(blake3(new TextEncoder().encode(canonicalizeJson(params))));
16
+ }
17
+ createNewResource(params, key) {
18
+ if (logPFrames()) this.logger.info(`Creating SpecFrame for handle = ${key}, columns: ` + stringifyJson(params));
19
+ return createPFrame(params);
20
+ }
21
+ getByKey(key) {
22
+ const resource = super.tryGetByKey(key);
23
+ if (!resource) {
24
+ const error = new PFrameDriverError(`Invalid SpecFrame handle`);
25
+ error.cause = /* @__PURE__ */ new Error(`SpecFrame with handle ${key} not found`);
26
+ throw error;
27
+ }
28
+ return resource;
29
+ }
30
+ };
31
+
32
+ //#endregion
33
+ export { PFramePool };
34
+ //# sourceMappingURL=pframe_pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pframe_pool.js","names":[],"sources":["../src/pframe_pool.ts"],"sourcesContent":["import {\n PFrameDriverError,\n PColumnSpec,\n SpecFrameHandle,\n stringifyJson,\n canonicalizeJson,\n} from \"@milaboratories/pl-model-common\";\nimport { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport { MiLogger, RefCountPoolBase } from \"@milaboratories/ts-helpers\";\nimport { logPFrames } from \"./logging\";\nimport { createPFrame } from \"@milaboratories/pframes-rs-wasm\";\nimport { blake3 } from \"@noble/hashes/blake3.js\";\nimport { bytesToHex } from \"@noble/hashes/utils.js\";\n\nexport class PFramePool extends RefCountPoolBase<\n Record<string, PColumnSpec>,\n SpecFrameHandle,\n PFrameInternal.PFrameWasmV2\n> {\n constructor(private readonly logger: MiLogger) {\n super();\n }\n\n protected calculateParamsKey(params: Record<string, PColumnSpec>): SpecFrameHandle {\n return bytesToHex(\n blake3(new TextEncoder().encode(canonicalizeJson(params))),\n ) as SpecFrameHandle;\n }\n\n protected createNewResource(\n params: Record<string, PColumnSpec>,\n key: SpecFrameHandle,\n ): PFrameInternal.PFrameWasmV2 {\n if (logPFrames()) {\n this.logger.info(`Creating SpecFrame for handle = ${key}, columns: ` + stringifyJson(params));\n }\n return createPFrame(params);\n }\n\n public getByKey(key: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {\n const resource = super.tryGetByKey(key);\n if (!resource) {\n const error = new PFrameDriverError(`Invalid SpecFrame handle`);\n error.cause = new Error(`SpecFrame with handle ${key} not found`);\n throw error;\n }\n return resource;\n }\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,aAAb,cAAgC,iBAI9B;CACA,YAAY,AAAiB,QAAkB;AAC7C,SAAO;EADoB;;CAI7B,AAAU,mBAAmB,QAAsD;AACjF,SAAO,WACL,OAAO,IAAI,aAAa,CAAC,OAAO,iBAAiB,OAAO,CAAC,CAAC,CAC3D;;CAGH,AAAU,kBACR,QACA,KAC6B;AAC7B,MAAI,YAAY,CACd,MAAK,OAAO,KAAK,mCAAmC,IAAI,eAAe,cAAc,OAAO,CAAC;AAE/F,SAAO,aAAa,OAAO;;CAG7B,AAAO,SAAS,KAAmD;EACjE,MAAM,WAAW,MAAM,YAAY,IAAI;AACvC,MAAI,CAAC,UAAU;GACb,MAAM,QAAQ,IAAI,kBAAkB,2BAA2B;AAC/D,SAAM,wBAAQ,IAAI,MAAM,yBAAyB,IAAI,YAAY;AACjE,SAAM;;AAER,SAAO"}
@@ -1,5 +1,8 @@
1
+ const require_logging = require('./logging.cjs');
2
+ const require_pframe_pool = require('./pframe_pool.cjs');
1
3
  let _milaboratories_pframes_rs_wasm = require("@milaboratories/pframes-rs-wasm");
2
4
  let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
5
+ let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
3
6
 
4
7
  //#region src/spec_driver.ts
5
8
  /**
@@ -8,50 +11,101 @@ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common")
8
11
  * All operations are synchronous — WASM computes results immediately.
9
12
  */
10
13
  var SpecDriver = class {
11
- frames = /* @__PURE__ */ new Map();
14
+ logger;
15
+ frames;
16
+ constructor(options) {
17
+ this.logger = options?.logger ?? new _milaboratories_ts_helpers.ConsoleLoggerAdapter();
18
+ this.frames = new require_pframe_pool.PFramePool(this.logger);
19
+ }
12
20
  createSpecFrame(specs) {
13
- const frame = (0, _milaboratories_pframes_rs_wasm.createPFrame)(specs);
14
- const handle = crypto.randomUUID();
15
- this.frames.set(handle, frame);
16
- return handle;
21
+ const ValueTypes = new Set(Object.values(_milaboratories_pl_model_common.ValueType));
22
+ const filtered = Object.fromEntries(Object.entries(specs).filter(([, spec]) => ValueTypes.has(spec.valueType)).map(([id, spec]) => [id, (0, _milaboratories_pl_model_common.resolveAnnotationParents)(spec)]));
23
+ try {
24
+ if (require_logging.logPFrames()) this.logger.info(`createSpecFrame: ${Object.keys(filtered).length} columns`);
25
+ return this.frames.acquire(filtered);
26
+ } catch (err) {
27
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`createSpecFrame failed`);
28
+ error.cause = (0, _milaboratories_pl_model_common.ensureError)(err);
29
+ throw error;
30
+ }
31
+ }
32
+ discoverColumns(handle, request) {
33
+ const pframe = this.frames.getByKey(handle);
34
+ try {
35
+ if (require_logging.logPFrames()) this.logger.info(`discoverColumns: handle = ${handle}, request: ${JSON.stringify(request)}`);
36
+ return pframe.discoverColumns(request);
37
+ } catch (err) {
38
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`discoverColumns failed`);
39
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
40
+ throw error;
41
+ }
17
42
  }
18
- specFrameDiscoverColumns(handle, request) {
19
- return this.getFrame(handle).discoverColumns(request);
43
+ deleteColumn(handle, request) {
44
+ const pframe = this.frames.getByKey(handle);
45
+ try {
46
+ if (require_logging.logPFrames()) this.logger.info(`deleteColumn: handle = ${handle}, request: ${JSON.stringify(request)}`);
47
+ return { axes: pframe.deleteColumns({
48
+ columns: request.axes,
49
+ delete: request.delete
50
+ }).columns };
51
+ } catch (err) {
52
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`deleteColumn failed`);
53
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
54
+ throw error;
55
+ }
20
56
  }
21
- disposeSpecFrame(handle) {
22
- const frame = this.frames.get(handle);
23
- if (frame) {
24
- frame[Symbol.dispose]();
25
- this.frames.delete(handle);
57
+ evaluateQuery(handle, request) {
58
+ const pframe = this.frames.getByKey(handle);
59
+ try {
60
+ if (require_logging.logPFrames()) this.logger.info(`evaluateQuery: handle = ${handle}, request: ${JSON.stringify(request)}`);
61
+ return pframe.evaluateQuery(request);
62
+ } catch (err) {
63
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`evaluateQuery failed`);
64
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
65
+ throw error;
26
66
  }
27
67
  }
28
68
  expandAxes(spec) {
29
- return (0, _milaboratories_pframes_rs_wasm.expandAxes)(spec);
69
+ try {
70
+ return (0, _milaboratories_pframes_rs_wasm.expandAxes)(spec);
71
+ } catch (err) {
72
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`expandAxes failed`);
73
+ error.cause = /* @__PURE__ */ new Error(`spec: ${JSON.stringify(spec)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
74
+ throw error;
75
+ }
30
76
  }
31
77
  collapseAxes(ids) {
32
- return (0, _milaboratories_pframes_rs_wasm.collapseAxes)(ids);
78
+ try {
79
+ return (0, _milaboratories_pframes_rs_wasm.collapseAxes)(ids);
80
+ } catch (err) {
81
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`collapseAxes failed`);
82
+ error.cause = /* @__PURE__ */ new Error(`ids: ${JSON.stringify(ids)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
83
+ throw error;
84
+ }
33
85
  }
34
86
  findAxis(spec, selector) {
35
- return (0, _milaboratories_pframes_rs_wasm.findAxis)(spec, selector);
87
+ try {
88
+ return (0, _milaboratories_pframes_rs_wasm.findAxis)(spec, selector);
89
+ } catch (err) {
90
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`findAxis failed`);
91
+ error.cause = /* @__PURE__ */ new Error(`spec: ${JSON.stringify(spec)}, selector: ${JSON.stringify(selector)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
92
+ throw error;
93
+ }
36
94
  }
37
95
  findTableColumn(tableSpec, selector) {
38
- return (0, _milaboratories_pframes_rs_wasm.findTableColumn)(tableSpec, selector);
39
- }
40
- /** Dispose all managed spec frames. */
41
- dispose() {
42
96
  try {
43
- for (const frame of this.frames.values()) frame[Symbol.dispose]();
44
- } finally {
45
- this.frames.clear();
97
+ return (0, _milaboratories_pframes_rs_wasm.findTableColumn)(tableSpec, selector);
98
+ } catch (err) {
99
+ const error = new _milaboratories_pl_model_common.PFrameSpecDriverError(`findTableColumn failed`);
100
+ error.cause = /* @__PURE__ */ new Error(`selector: ${JSON.stringify(selector)}, error:\n${(0, _milaboratories_pl_model_common.ensureError)(err)}`);
101
+ throw error;
46
102
  }
47
103
  }
48
- [Symbol.dispose]() {
49
- this.dispose();
104
+ async dispose() {
105
+ await this.frames.dispose();
50
106
  }
51
- getFrame(handle) {
52
- const frame = this.frames.get(handle);
53
- if (frame === void 0) throw new _milaboratories_pl_model_common.PFrameSpecDriverError(`No such spec frame: ${handle}`);
54
- return frame;
107
+ async [Symbol.asyncDispose]() {
108
+ await this.dispose();
55
109
  }
56
110
  };
57
111
 
@@ -1 +1 @@
1
- {"version":3,"file":"spec_driver.cjs","names":["PFrameSpecDriverError"],"sources":["../src/spec_driver.ts"],"sourcesContent":["import {\n createPFrame,\n expandAxes,\n collapseAxes,\n findAxis,\n findTableColumn,\n} from \"@milaboratories/pframes-rs-wasm\";\nimport type { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport type {\n AxesId,\n AxesSpec,\n PColumnSpec,\n PFrameSpecDriver,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n SpecFrameHandle,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n} from \"@milaboratories/pl-model-common\";\nimport { PFrameSpecDriverError } from \"@milaboratories/pl-model-common\";\n\n/**\n * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.\n *\n * All operations are synchronous — WASM computes results immediately.\n */\nexport class SpecDriver implements PFrameSpecDriver, Disposable {\n private readonly frames = new Map<SpecFrameHandle, PFrameInternal.PFrameWasmV2>();\n\n createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle {\n // Explicit annotation ensures a WASM version mismatch surfaces at compile time\n // (skipLibCheck won't validate .d.ts in node_modules, but this assignment will)\n const frame: PFrameInternal.PFrameWasmV2 = createPFrame(specs);\n const handle = crypto.randomUUID() as SpecFrameHandle;\n this.frames.set(handle, frame);\n return handle;\n }\n\n specFrameDiscoverColumns(\n handle: SpecFrameHandle,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse {\n return this.getFrame(handle).discoverColumns(request);\n }\n\n disposeSpecFrame(handle: SpecFrameHandle): void {\n const frame = this.frames.get(handle);\n if (frame) {\n frame[Symbol.dispose]();\n this.frames.delete(handle);\n }\n }\n\n expandAxes(spec: AxesSpec): AxesId {\n return expandAxes(spec);\n }\n\n collapseAxes(ids: AxesId): AxesSpec {\n return collapseAxes(ids);\n }\n\n findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {\n return findAxis(spec, selector);\n }\n\n findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {\n return findTableColumn(tableSpec, selector);\n }\n\n /** Dispose all managed spec frames. */\n dispose(): void {\n try {\n for (const frame of this.frames.values()) {\n frame[Symbol.dispose]();\n }\n } finally {\n this.frames.clear();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n private getFrame(handle: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {\n const frame = this.frames.get(handle);\n if (frame === undefined) throw new PFrameSpecDriverError(`No such spec frame: ${handle}`);\n return frame;\n }\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAa,aAAb,MAAgE;CAC9D,AAAiB,yBAAS,IAAI,KAAmD;CAEjF,gBAAgB,OAAqD;EAGnE,MAAM,0DAAkD,MAAM;EAC9D,MAAM,SAAS,OAAO,YAAY;AAClC,OAAK,OAAO,IAAI,QAAQ,MAAM;AAC9B,SAAO;;CAGT,yBACE,QACA,SACyB;AACzB,SAAO,KAAK,SAAS,OAAO,CAAC,gBAAgB,QAAQ;;CAGvD,iBAAiB,QAA+B;EAC9C,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,MAAI,OAAO;AACT,SAAM,OAAO,UAAU;AACvB,QAAK,OAAO,OAAO,OAAO;;;CAI9B,WAAW,MAAwB;AACjC,yDAAkB,KAAK;;CAGzB,aAAa,KAAuB;AAClC,2DAAoB,IAAI;;CAG1B,SAAS,MAAgB,UAAsC;AAC7D,uDAAgB,MAAM,SAAS;;CAGjC,gBAAgB,WAA+B,UAAkC;AAC/E,8DAAuB,WAAW,SAAS;;;CAI7C,UAAgB;AACd,MAAI;AACF,QAAK,MAAM,SAAS,KAAK,OAAO,QAAQ,CACtC,OAAM,OAAO,UAAU;YAEjB;AACR,QAAK,OAAO,OAAO;;;CAIvB,CAAC,OAAO,WAAiB;AACvB,OAAK,SAAS;;CAGhB,AAAQ,SAAS,QAAsD;EACrE,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,MAAI,UAAU,OAAW,OAAM,IAAIA,sDAAsB,uBAAuB,SAAS;AACzF,SAAO"}
1
+ {"version":3,"file":"spec_driver.cjs","names":["ConsoleLoggerAdapter","PFramePool","ValueType","logPFrames","PFrameSpecDriverError"],"sources":["../src/spec_driver.ts"],"sourcesContent":["import {\n expandAxes,\n collapseAxes,\n findAxis,\n findTableColumn,\n} from \"@milaboratories/pframes-rs-wasm\";\nimport type {\n AxesId,\n AxesSpec,\n PColumnSpec,\n PFrameSpecDriver,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n SpecFrameHandle,\n PoolEntry,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n DeleteColumnRequest,\n DeleteColumnResponse,\n EvaluateQueryResponse,\n SpecQuery,\n} from \"@milaboratories/pl-model-common\";\nimport {\n PFrameSpecDriverError,\n ValueType,\n ensureError,\n resolveAnnotationParents,\n} from \"@milaboratories/pl-model-common\";\nimport { type MiLogger, ConsoleLoggerAdapter } from \"@milaboratories/ts-helpers\";\nimport { PFramePool } from \"./pframe_pool\";\nimport { logPFrames } from \"./logging\";\n\n/**\n * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.\n *\n * All operations are synchronous — WASM computes results immediately.\n */\nexport class SpecDriver implements PFrameSpecDriver, AsyncDisposable {\n private readonly logger: MiLogger;\n private readonly frames: PFramePool;\n\n public constructor(options?: { logger?: MiLogger }) {\n this.logger = options?.logger ?? new ConsoleLoggerAdapter();\n this.frames = new PFramePool(this.logger);\n }\n\n createSpecFrame(specs: Record<string, PColumnSpec>): PoolEntry<SpecFrameHandle> {\n const ValueTypes = new Set(Object.values(ValueType));\n const filtered = Object.fromEntries(\n Object.entries(specs)\n .filter(([, spec]) => ValueTypes.has(spec.valueType))\n .map(([id, spec]) => [id, resolveAnnotationParents(spec)]),\n );\n try {\n if (logPFrames()) {\n this.logger.info(`createSpecFrame: ${Object.keys(filtered).length} columns`);\n }\n return this.frames.acquire(filtered);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`createSpecFrame failed`);\n error.cause = ensureError(err);\n throw error;\n }\n }\n\n discoverColumns(\n handle: SpecFrameHandle,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(\n `discoverColumns: handle = ${handle}, request: ${JSON.stringify(request)}`,\n );\n }\n const result = pframe.discoverColumns(request);\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`discoverColumns failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n deleteColumn(handle: SpecFrameHandle, request: DeleteColumnRequest): DeleteColumnResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(`deleteColumn: handle = ${handle}, request: ${JSON.stringify(request)}`);\n }\n const result = {\n axes: pframe.deleteColumns({ columns: request.axes, delete: request.delete }).columns,\n };\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`deleteColumn failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n evaluateQuery(handle: SpecFrameHandle, request: SpecQuery): EvaluateQueryResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(`evaluateQuery: handle = ${handle}, request: ${JSON.stringify(request)}`);\n }\n const result = pframe.evaluateQuery(request);\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`evaluateQuery failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n expandAxes(spec: AxesSpec): AxesId {\n try {\n return expandAxes(spec);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`expandAxes failed`);\n error.cause = new Error(`spec: ${JSON.stringify(spec)}, ` + `error:\\n${ensureError(err)}`);\n throw error;\n }\n }\n\n collapseAxes(ids: AxesId): AxesSpec {\n try {\n return collapseAxes(ids);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`collapseAxes failed`);\n error.cause = new Error(`ids: ${JSON.stringify(ids)}, ` + `error:\\n${ensureError(err)}`);\n throw error;\n }\n }\n\n findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {\n try {\n return findAxis(spec, selector);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`findAxis failed`);\n error.cause = new Error(\n `spec: ${JSON.stringify(spec)}, ` +\n `selector: ${JSON.stringify(selector)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {\n try {\n return findTableColumn(tableSpec, selector);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`findTableColumn failed`);\n error.cause = new Error(\n `selector: ${JSON.stringify(selector)}, ` + `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n async dispose(): Promise<void> {\n await this.frames.dispose();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { AxesId, AxesSpec, DiscoverColumnsRequest, DiscoverColumnsResponse, PColumnSpec, PFrameSpecDriver, PTableColumnId, PTableColumnSpec, SingleAxisSelector, SpecFrameHandle } from "@milaboratories/pl-model-common";
1
+ import { AxesId, AxesSpec, DeleteColumnRequest, DeleteColumnResponse, DiscoverColumnsRequest, DiscoverColumnsResponse, EvaluateQueryResponse, PColumnSpec, PFrameSpecDriver, PTableColumnId, PTableColumnSpec, PoolEntry, SingleAxisSelector, SpecFrameHandle, SpecQuery } from "@milaboratories/pl-model-common";
2
+ import { MiLogger } from "@milaboratories/ts-helpers";
2
3
 
3
4
  //#region src/spec_driver.d.ts
4
5
  /**
@@ -6,19 +7,22 @@ import { AxesId, AxesSpec, DiscoverColumnsRequest, DiscoverColumnsResponse, PCol
6
7
  *
7
8
  * All operations are synchronous — WASM computes results immediately.
8
9
  */
9
- declare class SpecDriver implements PFrameSpecDriver, Disposable {
10
+ declare class SpecDriver implements PFrameSpecDriver, AsyncDisposable {
11
+ private readonly logger;
10
12
  private readonly frames;
11
- createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle;
12
- specFrameDiscoverColumns(handle: SpecFrameHandle, request: DiscoverColumnsRequest): DiscoverColumnsResponse;
13
- disposeSpecFrame(handle: SpecFrameHandle): void;
13
+ constructor(options?: {
14
+ logger?: MiLogger;
15
+ });
16
+ createSpecFrame(specs: Record<string, PColumnSpec>): PoolEntry<SpecFrameHandle>;
17
+ discoverColumns(handle: SpecFrameHandle, request: DiscoverColumnsRequest): DiscoverColumnsResponse;
18
+ deleteColumn(handle: SpecFrameHandle, request: DeleteColumnRequest): DeleteColumnResponse;
19
+ evaluateQuery(handle: SpecFrameHandle, request: SpecQuery): EvaluateQueryResponse;
14
20
  expandAxes(spec: AxesSpec): AxesId;
15
21
  collapseAxes(ids: AxesId): AxesSpec;
16
22
  findAxis(spec: AxesSpec, selector: SingleAxisSelector): number;
17
23
  findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number;
18
- /** Dispose all managed spec frames. */
19
- dispose(): void;
20
- [Symbol.dispose](): void;
21
- private getFrame;
24
+ dispose(): Promise<void>;
25
+ [Symbol.asyncDispose](): Promise<void>;
22
26
  }
23
27
  //#endregion
24
28
  export { SpecDriver };
@@ -1,5 +1,8 @@
1
- import { collapseAxes, createPFrame, expandAxes, findAxis, findTableColumn } from "@milaboratories/pframes-rs-wasm";
2
- import { PFrameSpecDriverError } from "@milaboratories/pl-model-common";
1
+ import { logPFrames } from "./logging.js";
2
+ import { PFramePool } from "./pframe_pool.js";
3
+ import { collapseAxes, expandAxes, findAxis, findTableColumn } from "@milaboratories/pframes-rs-wasm";
4
+ import { PFrameSpecDriverError, ValueType, ensureError, resolveAnnotationParents } from "@milaboratories/pl-model-common";
5
+ import { ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
3
6
 
4
7
  //#region src/spec_driver.ts
5
8
  /**
@@ -8,50 +11,101 @@ import { PFrameSpecDriverError } from "@milaboratories/pl-model-common";
8
11
  * All operations are synchronous — WASM computes results immediately.
9
12
  */
10
13
  var SpecDriver = class {
11
- frames = /* @__PURE__ */ new Map();
14
+ logger;
15
+ frames;
16
+ constructor(options) {
17
+ this.logger = options?.logger ?? new ConsoleLoggerAdapter();
18
+ this.frames = new PFramePool(this.logger);
19
+ }
12
20
  createSpecFrame(specs) {
13
- const frame = createPFrame(specs);
14
- const handle = crypto.randomUUID();
15
- this.frames.set(handle, frame);
16
- return handle;
21
+ const ValueTypes = new Set(Object.values(ValueType));
22
+ const filtered = Object.fromEntries(Object.entries(specs).filter(([, spec]) => ValueTypes.has(spec.valueType)).map(([id, spec]) => [id, resolveAnnotationParents(spec)]));
23
+ try {
24
+ if (logPFrames()) this.logger.info(`createSpecFrame: ${Object.keys(filtered).length} columns`);
25
+ return this.frames.acquire(filtered);
26
+ } catch (err) {
27
+ const error = new PFrameSpecDriverError(`createSpecFrame failed`);
28
+ error.cause = ensureError(err);
29
+ throw error;
30
+ }
31
+ }
32
+ discoverColumns(handle, request) {
33
+ const pframe = this.frames.getByKey(handle);
34
+ try {
35
+ if (logPFrames()) this.logger.info(`discoverColumns: handle = ${handle}, request: ${JSON.stringify(request)}`);
36
+ return pframe.discoverColumns(request);
37
+ } catch (err) {
38
+ const error = new PFrameSpecDriverError(`discoverColumns failed`);
39
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${ensureError(err)}`);
40
+ throw error;
41
+ }
17
42
  }
18
- specFrameDiscoverColumns(handle, request) {
19
- return this.getFrame(handle).discoverColumns(request);
43
+ deleteColumn(handle, request) {
44
+ const pframe = this.frames.getByKey(handle);
45
+ try {
46
+ if (logPFrames()) this.logger.info(`deleteColumn: handle = ${handle}, request: ${JSON.stringify(request)}`);
47
+ return { axes: pframe.deleteColumns({
48
+ columns: request.axes,
49
+ delete: request.delete
50
+ }).columns };
51
+ } catch (err) {
52
+ const error = new PFrameSpecDriverError(`deleteColumn failed`);
53
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${ensureError(err)}`);
54
+ throw error;
55
+ }
20
56
  }
21
- disposeSpecFrame(handle) {
22
- const frame = this.frames.get(handle);
23
- if (frame) {
24
- frame[Symbol.dispose]();
25
- this.frames.delete(handle);
57
+ evaluateQuery(handle, request) {
58
+ const pframe = this.frames.getByKey(handle);
59
+ try {
60
+ if (logPFrames()) this.logger.info(`evaluateQuery: handle = ${handle}, request: ${JSON.stringify(request)}`);
61
+ return pframe.evaluateQuery(request);
62
+ } catch (err) {
63
+ const error = new PFrameSpecDriverError(`evaluateQuery failed`);
64
+ error.cause = /* @__PURE__ */ new Error(`handle: ${handle}, request: ${JSON.stringify(request)}, error:\n${ensureError(err)}`);
65
+ throw error;
26
66
  }
27
67
  }
28
68
  expandAxes(spec) {
29
- return expandAxes(spec);
69
+ try {
70
+ return expandAxes(spec);
71
+ } catch (err) {
72
+ const error = new PFrameSpecDriverError(`expandAxes failed`);
73
+ error.cause = /* @__PURE__ */ new Error(`spec: ${JSON.stringify(spec)}, error:\n${ensureError(err)}`);
74
+ throw error;
75
+ }
30
76
  }
31
77
  collapseAxes(ids) {
32
- return collapseAxes(ids);
78
+ try {
79
+ return collapseAxes(ids);
80
+ } catch (err) {
81
+ const error = new PFrameSpecDriverError(`collapseAxes failed`);
82
+ error.cause = /* @__PURE__ */ new Error(`ids: ${JSON.stringify(ids)}, error:\n${ensureError(err)}`);
83
+ throw error;
84
+ }
33
85
  }
34
86
  findAxis(spec, selector) {
35
- return findAxis(spec, selector);
87
+ try {
88
+ return findAxis(spec, selector);
89
+ } catch (err) {
90
+ const error = new PFrameSpecDriverError(`findAxis failed`);
91
+ error.cause = /* @__PURE__ */ new Error(`spec: ${JSON.stringify(spec)}, selector: ${JSON.stringify(selector)}, error:\n${ensureError(err)}`);
92
+ throw error;
93
+ }
36
94
  }
37
95
  findTableColumn(tableSpec, selector) {
38
- return findTableColumn(tableSpec, selector);
39
- }
40
- /** Dispose all managed spec frames. */
41
- dispose() {
42
96
  try {
43
- for (const frame of this.frames.values()) frame[Symbol.dispose]();
44
- } finally {
45
- this.frames.clear();
97
+ return findTableColumn(tableSpec, selector);
98
+ } catch (err) {
99
+ const error = new PFrameSpecDriverError(`findTableColumn failed`);
100
+ error.cause = /* @__PURE__ */ new Error(`selector: ${JSON.stringify(selector)}, error:\n${ensureError(err)}`);
101
+ throw error;
46
102
  }
47
103
  }
48
- [Symbol.dispose]() {
49
- this.dispose();
104
+ async dispose() {
105
+ await this.frames.dispose();
50
106
  }
51
- getFrame(handle) {
52
- const frame = this.frames.get(handle);
53
- if (frame === void 0) throw new PFrameSpecDriverError(`No such spec frame: ${handle}`);
54
- return frame;
107
+ async [Symbol.asyncDispose]() {
108
+ await this.dispose();
55
109
  }
56
110
  };
57
111
 
@@ -1 +1 @@
1
- {"version":3,"file":"spec_driver.js","names":[],"sources":["../src/spec_driver.ts"],"sourcesContent":["import {\n createPFrame,\n expandAxes,\n collapseAxes,\n findAxis,\n findTableColumn,\n} from \"@milaboratories/pframes-rs-wasm\";\nimport type { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport type {\n AxesId,\n AxesSpec,\n PColumnSpec,\n PFrameSpecDriver,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n SpecFrameHandle,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n} from \"@milaboratories/pl-model-common\";\nimport { PFrameSpecDriverError } from \"@milaboratories/pl-model-common\";\n\n/**\n * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.\n *\n * All operations are synchronous — WASM computes results immediately.\n */\nexport class SpecDriver implements PFrameSpecDriver, Disposable {\n private readonly frames = new Map<SpecFrameHandle, PFrameInternal.PFrameWasmV2>();\n\n createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle {\n // Explicit annotation ensures a WASM version mismatch surfaces at compile time\n // (skipLibCheck won't validate .d.ts in node_modules, but this assignment will)\n const frame: PFrameInternal.PFrameWasmV2 = createPFrame(specs);\n const handle = crypto.randomUUID() as SpecFrameHandle;\n this.frames.set(handle, frame);\n return handle;\n }\n\n specFrameDiscoverColumns(\n handle: SpecFrameHandle,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse {\n return this.getFrame(handle).discoverColumns(request);\n }\n\n disposeSpecFrame(handle: SpecFrameHandle): void {\n const frame = this.frames.get(handle);\n if (frame) {\n frame[Symbol.dispose]();\n this.frames.delete(handle);\n }\n }\n\n expandAxes(spec: AxesSpec): AxesId {\n return expandAxes(spec);\n }\n\n collapseAxes(ids: AxesId): AxesSpec {\n return collapseAxes(ids);\n }\n\n findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {\n return findAxis(spec, selector);\n }\n\n findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {\n return findTableColumn(tableSpec, selector);\n }\n\n /** Dispose all managed spec frames. */\n dispose(): void {\n try {\n for (const frame of this.frames.values()) {\n frame[Symbol.dispose]();\n }\n } finally {\n this.frames.clear();\n }\n }\n\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n private getFrame(handle: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {\n const frame = this.frames.get(handle);\n if (frame === undefined) throw new PFrameSpecDriverError(`No such spec frame: ${handle}`);\n return frame;\n }\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAa,aAAb,MAAgE;CAC9D,AAAiB,yBAAS,IAAI,KAAmD;CAEjF,gBAAgB,OAAqD;EAGnE,MAAM,QAAqC,aAAa,MAAM;EAC9D,MAAM,SAAS,OAAO,YAAY;AAClC,OAAK,OAAO,IAAI,QAAQ,MAAM;AAC9B,SAAO;;CAGT,yBACE,QACA,SACyB;AACzB,SAAO,KAAK,SAAS,OAAO,CAAC,gBAAgB,QAAQ;;CAGvD,iBAAiB,QAA+B;EAC9C,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,MAAI,OAAO;AACT,SAAM,OAAO,UAAU;AACvB,QAAK,OAAO,OAAO,OAAO;;;CAI9B,WAAW,MAAwB;AACjC,SAAO,WAAW,KAAK;;CAGzB,aAAa,KAAuB;AAClC,SAAO,aAAa,IAAI;;CAG1B,SAAS,MAAgB,UAAsC;AAC7D,SAAO,SAAS,MAAM,SAAS;;CAGjC,gBAAgB,WAA+B,UAAkC;AAC/E,SAAO,gBAAgB,WAAW,SAAS;;;CAI7C,UAAgB;AACd,MAAI;AACF,QAAK,MAAM,SAAS,KAAK,OAAO,QAAQ,CACtC,OAAM,OAAO,UAAU;YAEjB;AACR,QAAK,OAAO,OAAO;;;CAIvB,CAAC,OAAO,WAAiB;AACvB,OAAK,SAAS;;CAGhB,AAAQ,SAAS,QAAsD;EACrE,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,MAAI,UAAU,OAAW,OAAM,IAAI,sBAAsB,uBAAuB,SAAS;AACzF,SAAO"}
1
+ {"version":3,"file":"spec_driver.js","names":[],"sources":["../src/spec_driver.ts"],"sourcesContent":["import {\n expandAxes,\n collapseAxes,\n findAxis,\n findTableColumn,\n} from \"@milaboratories/pframes-rs-wasm\";\nimport type {\n AxesId,\n AxesSpec,\n PColumnSpec,\n PFrameSpecDriver,\n PTableColumnId,\n PTableColumnSpec,\n SingleAxisSelector,\n SpecFrameHandle,\n PoolEntry,\n DiscoverColumnsRequest,\n DiscoverColumnsResponse,\n DeleteColumnRequest,\n DeleteColumnResponse,\n EvaluateQueryResponse,\n SpecQuery,\n} from \"@milaboratories/pl-model-common\";\nimport {\n PFrameSpecDriverError,\n ValueType,\n ensureError,\n resolveAnnotationParents,\n} from \"@milaboratories/pl-model-common\";\nimport { type MiLogger, ConsoleLoggerAdapter } from \"@milaboratories/ts-helpers\";\nimport { PFramePool } from \"./pframe_pool\";\nimport { logPFrames } from \"./logging\";\n\n/**\n * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.\n *\n * All operations are synchronous — WASM computes results immediately.\n */\nexport class SpecDriver implements PFrameSpecDriver, AsyncDisposable {\n private readonly logger: MiLogger;\n private readonly frames: PFramePool;\n\n public constructor(options?: { logger?: MiLogger }) {\n this.logger = options?.logger ?? new ConsoleLoggerAdapter();\n this.frames = new PFramePool(this.logger);\n }\n\n createSpecFrame(specs: Record<string, PColumnSpec>): PoolEntry<SpecFrameHandle> {\n const ValueTypes = new Set(Object.values(ValueType));\n const filtered = Object.fromEntries(\n Object.entries(specs)\n .filter(([, spec]) => ValueTypes.has(spec.valueType))\n .map(([id, spec]) => [id, resolveAnnotationParents(spec)]),\n );\n try {\n if (logPFrames()) {\n this.logger.info(`createSpecFrame: ${Object.keys(filtered).length} columns`);\n }\n return this.frames.acquire(filtered);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`createSpecFrame failed`);\n error.cause = ensureError(err);\n throw error;\n }\n }\n\n discoverColumns(\n handle: SpecFrameHandle,\n request: DiscoverColumnsRequest,\n ): DiscoverColumnsResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(\n `discoverColumns: handle = ${handle}, request: ${JSON.stringify(request)}`,\n );\n }\n const result = pframe.discoverColumns(request);\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`discoverColumns failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n deleteColumn(handle: SpecFrameHandle, request: DeleteColumnRequest): DeleteColumnResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(`deleteColumn: handle = ${handle}, request: ${JSON.stringify(request)}`);\n }\n const result = {\n axes: pframe.deleteColumns({ columns: request.axes, delete: request.delete }).columns,\n };\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`deleteColumn failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n evaluateQuery(handle: SpecFrameHandle, request: SpecQuery): EvaluateQueryResponse {\n const pframe = this.frames.getByKey(handle);\n try {\n if (logPFrames()) {\n this.logger.info(`evaluateQuery: handle = ${handle}, request: ${JSON.stringify(request)}`);\n }\n const result = pframe.evaluateQuery(request);\n return result;\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`evaluateQuery failed`);\n error.cause = new Error(\n `handle: ${handle}, ` +\n `request: ${JSON.stringify(request)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n expandAxes(spec: AxesSpec): AxesId {\n try {\n return expandAxes(spec);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`expandAxes failed`);\n error.cause = new Error(`spec: ${JSON.stringify(spec)}, ` + `error:\\n${ensureError(err)}`);\n throw error;\n }\n }\n\n collapseAxes(ids: AxesId): AxesSpec {\n try {\n return collapseAxes(ids);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`collapseAxes failed`);\n error.cause = new Error(`ids: ${JSON.stringify(ids)}, ` + `error:\\n${ensureError(err)}`);\n throw error;\n }\n }\n\n findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {\n try {\n return findAxis(spec, selector);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`findAxis failed`);\n error.cause = new Error(\n `spec: ${JSON.stringify(spec)}, ` +\n `selector: ${JSON.stringify(selector)}, ` +\n `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {\n try {\n return findTableColumn(tableSpec, selector);\n } catch (err: unknown) {\n const error = new PFrameSpecDriverError(`findTableColumn failed`);\n error.cause = new Error(\n `selector: ${JSON.stringify(selector)}, ` + `error:\\n${ensureError(err)}`,\n );\n throw error;\n }\n }\n\n async dispose(): Promise<void> {\n await this.frames.dispose();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pf-spec-driver",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "PSpecDriver implementation: spec-only PFrame operations via WASM",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -20,17 +20,19 @@
20
20
  }
21
21
  },
22
22
  "dependencies": {
23
- "@milaboratories/pframes-rs-wasm": "1.1.15",
24
- "@milaboratories/pl-model-middle-layer": "1.16.0",
25
- "@milaboratories/pl-model-common": "1.29.0"
23
+ "@milaboratories/pframes-rs-wasm": "1.1.16",
24
+ "@noble/hashes": "^2.0.1",
25
+ "@milaboratories/pl-model-middle-layer": "1.16.1",
26
+ "@milaboratories/pl-model-common": "1.30.0",
27
+ "@milaboratories/ts-helpers": "1.8.0"
26
28
  },
27
29
  "devDependencies": {
28
30
  "@vitest/coverage-istanbul": "^4.0.18",
29
31
  "typescript": "~5.9.3",
30
32
  "vitest": "^4.0.18",
31
- "@milaboratories/ts-configs": "1.2.2",
32
33
  "@milaboratories/build-configs": "1.5.2",
33
- "@milaboratories/ts-builder": "1.3.0"
34
+ "@milaboratories/ts-builder": "1.3.0",
35
+ "@milaboratories/ts-configs": "1.2.2"
34
36
  },
35
37
  "scripts": {
36
38
  "build": "ts-builder build --target node",
package/src/logging.ts ADDED
@@ -0,0 +1,5 @@
1
+ const LogPFrames = Boolean(process.env.MI_LOG_PFRAMES);
2
+
3
+ export function logPFrames(): boolean {
4
+ return LogPFrames;
5
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ PFrameDriverError,
3
+ PColumnSpec,
4
+ SpecFrameHandle,
5
+ stringifyJson,
6
+ canonicalizeJson,
7
+ } from "@milaboratories/pl-model-common";
8
+ import { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
9
+ import { MiLogger, RefCountPoolBase } from "@milaboratories/ts-helpers";
10
+ import { logPFrames } from "./logging";
11
+ import { createPFrame } from "@milaboratories/pframes-rs-wasm";
12
+ import { blake3 } from "@noble/hashes/blake3.js";
13
+ import { bytesToHex } from "@noble/hashes/utils.js";
14
+
15
+ export class PFramePool extends RefCountPoolBase<
16
+ Record<string, PColumnSpec>,
17
+ SpecFrameHandle,
18
+ PFrameInternal.PFrameWasmV2
19
+ > {
20
+ constructor(private readonly logger: MiLogger) {
21
+ super();
22
+ }
23
+
24
+ protected calculateParamsKey(params: Record<string, PColumnSpec>): SpecFrameHandle {
25
+ return bytesToHex(
26
+ blake3(new TextEncoder().encode(canonicalizeJson(params))),
27
+ ) as SpecFrameHandle;
28
+ }
29
+
30
+ protected createNewResource(
31
+ params: Record<string, PColumnSpec>,
32
+ key: SpecFrameHandle,
33
+ ): PFrameInternal.PFrameWasmV2 {
34
+ if (logPFrames()) {
35
+ this.logger.info(`Creating SpecFrame for handle = ${key}, columns: ` + stringifyJson(params));
36
+ }
37
+ return createPFrame(params);
38
+ }
39
+
40
+ public getByKey(key: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {
41
+ const resource = super.tryGetByKey(key);
42
+ if (!resource) {
43
+ const error = new PFrameDriverError(`Invalid SpecFrame handle`);
44
+ error.cause = new Error(`SpecFrame with handle ${key} not found`);
45
+ throw error;
46
+ }
47
+ return resource;
48
+ }
49
+ }
@@ -2,25 +2,30 @@ import type { AxisSpec, PColumnSpec } from "@milaboratories/pl-model-common";
2
2
  import { describe, expect, test } from "vitest";
3
3
  import { SpecDriver } from "./spec_driver";
4
4
 
5
- function createSpec(name: string, axesSpec?: AxisSpec[]): PColumnSpec {
5
+ function createSpec(
6
+ name: string,
7
+ axesSpec?: AxisSpec[],
8
+ extra?: Partial<PColumnSpec>,
9
+ ): PColumnSpec {
6
10
  return {
7
11
  kind: "PColumn",
8
12
  name,
9
13
  valueType: "Int",
10
14
  axesSpec: axesSpec ?? [{ name: "id", type: "String" } as AxisSpec],
11
15
  annotations: {},
16
+ ...extra,
12
17
  } as PColumnSpec;
13
18
  }
14
19
 
15
20
  describe("SpecDriver", () => {
16
- test("discoverColumns returns all columns with no filters", () => {
17
- using driver = new SpecDriver();
18
- const handle = driver.createSpecFrame({
21
+ test("discoverColumns returns all columns with no filters", async () => {
22
+ await using driver = new SpecDriver();
23
+ const { key: handle } = driver.createSpecFrame({
19
24
  col1: createSpec("col1"),
20
25
  col2: createSpec("col2"),
21
26
  });
22
27
 
23
- const response = driver.specFrameDiscoverColumns(handle, {
28
+ const response = driver.discoverColumns(handle, {
24
29
  axes: [],
25
30
  maxHops: 0,
26
31
  constraints: {
@@ -35,4 +40,82 @@ describe("SpecDriver", () => {
35
40
  const names = response.hits.map((h) => h.hit.spec.name).sort();
36
41
  expect(names).toEqual(["col1", "col2"]);
37
42
  });
43
+
44
+ test("createSpecFrame converts pl7.app/parents annotation to numeric parentAxes", async () => {
45
+ await using driver = new SpecDriver();
46
+ const linkerAxes: AxisSpec[] = [
47
+ { name: "pl7.app/sampleId", type: "String" },
48
+ {
49
+ name: "pl7.app/sc/cellId",
50
+ type: "String",
51
+ annotations: {
52
+ "pl7.app/parents": '["pl7.app/sampleId"]',
53
+ "pl7.app/label": "Cell ID",
54
+ },
55
+ },
56
+ { name: "pl7.app/vdj/scClonotypeKey", type: "String" },
57
+ ];
58
+ const linkerSpec = createSpec("pl7.app/sc/cellLinker", linkerAxes, {
59
+ annotations: { "pl7.app/isLinkerColumn": "true" },
60
+ });
61
+
62
+ expect(() => driver.createSpecFrame({ linker: linkerSpec })).not.toThrow();
63
+ });
64
+
65
+ test("createSpecFrame works with numeric parentAxes (no annotation)", async () => {
66
+ await using driver = new SpecDriver();
67
+ const linkerAxes: AxisSpec[] = [
68
+ { name: "pl7.app/sampleId", type: "String" },
69
+ {
70
+ name: "pl7.app/sc/cellId",
71
+ type: "String",
72
+ parentAxes: [0],
73
+ annotations: { "pl7.app/label": "Cell ID" },
74
+ },
75
+ { name: "pl7.app/vdj/scClonotypeKey", type: "String" },
76
+ ];
77
+ const linkerSpec = createSpec("pl7.app/sc/cellLinker", linkerAxes, {
78
+ annotations: { "pl7.app/isLinkerColumn": "true" },
79
+ });
80
+
81
+ expect(() => driver.createSpecFrame({ linker: linkerSpec })).not.toThrow();
82
+ });
83
+
84
+ test("createSpecFrame preserves non-parent annotations after conversion", async () => {
85
+ await using driver = new SpecDriver();
86
+ const axes: AxisSpec[] = [
87
+ { name: "sample", type: "String" },
88
+ {
89
+ name: "cell",
90
+ type: "String",
91
+ annotations: {
92
+ "pl7.app/parents": '["sample"]',
93
+ "pl7.app/label": "Cell",
94
+ "pl7.app/table/visibility": "default",
95
+ },
96
+ },
97
+ ];
98
+ const spec = createSpec("data-col", axes);
99
+
100
+ const { key: handle } = driver.createSpecFrame({ col: spec });
101
+ expect(handle).toBeDefined();
102
+
103
+ const response = driver.discoverColumns(handle, {
104
+ axes: [],
105
+ maxHops: 0,
106
+ constraints: {
107
+ allowFloatingSourceAxes: true,
108
+ allowFloatingHitAxes: true,
109
+ allowSourceQualifications: false,
110
+ allowHitQualifications: false,
111
+ },
112
+ });
113
+ expect(response.hits).toHaveLength(1);
114
+ const hitAxes = response.hits[0].hit.spec.axesSpec;
115
+ const cellAxis = hitAxes.find((a: AxisSpec) => a.name === "cell");
116
+ expect(cellAxis).toBeDefined();
117
+ expect(cellAxis!.annotations?.["pl7.app/label"]).toBe("Cell");
118
+ expect(cellAxis!.annotations?.["pl7.app/parents"]).toBe('["sample"]');
119
+ expect(cellAxis!.parentAxes).toEqual([0]);
120
+ });
38
121
  });
@@ -1,11 +1,9 @@
1
1
  import {
2
- createPFrame,
3
2
  expandAxes,
4
3
  collapseAxes,
5
4
  findAxis,
6
5
  findTableColumn,
7
6
  } from "@milaboratories/pframes-rs-wasm";
8
- import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
9
7
  import type {
10
8
  AxesId,
11
9
  AxesSpec,
@@ -15,77 +13,172 @@ import type {
15
13
  PTableColumnSpec,
16
14
  SingleAxisSelector,
17
15
  SpecFrameHandle,
16
+ PoolEntry,
18
17
  DiscoverColumnsRequest,
19
18
  DiscoverColumnsResponse,
19
+ DeleteColumnRequest,
20
+ DeleteColumnResponse,
21
+ EvaluateQueryResponse,
22
+ SpecQuery,
20
23
  } from "@milaboratories/pl-model-common";
21
- import { PFrameSpecDriverError } from "@milaboratories/pl-model-common";
24
+ import {
25
+ PFrameSpecDriverError,
26
+ ValueType,
27
+ ensureError,
28
+ resolveAnnotationParents,
29
+ } from "@milaboratories/pl-model-common";
30
+ import { type MiLogger, ConsoleLoggerAdapter } from "@milaboratories/ts-helpers";
31
+ import { PFramePool } from "./pframe_pool";
32
+ import { logPFrames } from "./logging";
22
33
 
23
34
  /**
24
35
  * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.
25
36
  *
26
37
  * All operations are synchronous — WASM computes results immediately.
27
38
  */
28
- export class SpecDriver implements PFrameSpecDriver, Disposable {
29
- private readonly frames = new Map<SpecFrameHandle, PFrameInternal.PFrameWasmV2>();
39
+ export class SpecDriver implements PFrameSpecDriver, AsyncDisposable {
40
+ private readonly logger: MiLogger;
41
+ private readonly frames: PFramePool;
42
+
43
+ public constructor(options?: { logger?: MiLogger }) {
44
+ this.logger = options?.logger ?? new ConsoleLoggerAdapter();
45
+ this.frames = new PFramePool(this.logger);
46
+ }
30
47
 
31
- createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle {
32
- // Explicit annotation ensures a WASM version mismatch surfaces at compile time
33
- // (skipLibCheck won't validate .d.ts in node_modules, but this assignment will)
34
- const frame: PFrameInternal.PFrameWasmV2 = createPFrame(specs);
35
- const handle = crypto.randomUUID() as SpecFrameHandle;
36
- this.frames.set(handle, frame);
37
- return handle;
48
+ createSpecFrame(specs: Record<string, PColumnSpec>): PoolEntry<SpecFrameHandle> {
49
+ const ValueTypes = new Set(Object.values(ValueType));
50
+ const filtered = Object.fromEntries(
51
+ Object.entries(specs)
52
+ .filter(([, spec]) => ValueTypes.has(spec.valueType))
53
+ .map(([id, spec]) => [id, resolveAnnotationParents(spec)]),
54
+ );
55
+ try {
56
+ if (logPFrames()) {
57
+ this.logger.info(`createSpecFrame: ${Object.keys(filtered).length} columns`);
58
+ }
59
+ return this.frames.acquire(filtered);
60
+ } catch (err: unknown) {
61
+ const error = new PFrameSpecDriverError(`createSpecFrame failed`);
62
+ error.cause = ensureError(err);
63
+ throw error;
64
+ }
38
65
  }
39
66
 
40
- specFrameDiscoverColumns(
67
+ discoverColumns(
41
68
  handle: SpecFrameHandle,
42
69
  request: DiscoverColumnsRequest,
43
70
  ): DiscoverColumnsResponse {
44
- return this.getFrame(handle).discoverColumns(request);
71
+ const pframe = this.frames.getByKey(handle);
72
+ try {
73
+ if (logPFrames()) {
74
+ this.logger.info(
75
+ `discoverColumns: handle = ${handle}, request: ${JSON.stringify(request)}`,
76
+ );
77
+ }
78
+ const result = pframe.discoverColumns(request);
79
+ return result;
80
+ } catch (err: unknown) {
81
+ const error = new PFrameSpecDriverError(`discoverColumns failed`);
82
+ error.cause = new Error(
83
+ `handle: ${handle}, ` +
84
+ `request: ${JSON.stringify(request)}, ` +
85
+ `error:\n${ensureError(err)}`,
86
+ );
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ deleteColumn(handle: SpecFrameHandle, request: DeleteColumnRequest): DeleteColumnResponse {
92
+ const pframe = this.frames.getByKey(handle);
93
+ try {
94
+ if (logPFrames()) {
95
+ this.logger.info(`deleteColumn: handle = ${handle}, request: ${JSON.stringify(request)}`);
96
+ }
97
+ const result = {
98
+ axes: pframe.deleteColumns({ columns: request.axes, delete: request.delete }).columns,
99
+ };
100
+ return result;
101
+ } catch (err: unknown) {
102
+ const error = new PFrameSpecDriverError(`deleteColumn failed`);
103
+ error.cause = new Error(
104
+ `handle: ${handle}, ` +
105
+ `request: ${JSON.stringify(request)}, ` +
106
+ `error:\n${ensureError(err)}`,
107
+ );
108
+ throw error;
109
+ }
45
110
  }
46
111
 
47
- disposeSpecFrame(handle: SpecFrameHandle): void {
48
- const frame = this.frames.get(handle);
49
- if (frame) {
50
- frame[Symbol.dispose]();
51
- this.frames.delete(handle);
112
+ evaluateQuery(handle: SpecFrameHandle, request: SpecQuery): EvaluateQueryResponse {
113
+ const pframe = this.frames.getByKey(handle);
114
+ try {
115
+ if (logPFrames()) {
116
+ this.logger.info(`evaluateQuery: handle = ${handle}, request: ${JSON.stringify(request)}`);
117
+ }
118
+ const result = pframe.evaluateQuery(request);
119
+ return result;
120
+ } catch (err: unknown) {
121
+ const error = new PFrameSpecDriverError(`evaluateQuery failed`);
122
+ error.cause = new Error(
123
+ `handle: ${handle}, ` +
124
+ `request: ${JSON.stringify(request)}, ` +
125
+ `error:\n${ensureError(err)}`,
126
+ );
127
+ throw error;
52
128
  }
53
129
  }
54
130
 
55
131
  expandAxes(spec: AxesSpec): AxesId {
56
- return expandAxes(spec);
132
+ try {
133
+ return expandAxes(spec);
134
+ } catch (err: unknown) {
135
+ const error = new PFrameSpecDriverError(`expandAxes failed`);
136
+ error.cause = new Error(`spec: ${JSON.stringify(spec)}, ` + `error:\n${ensureError(err)}`);
137
+ throw error;
138
+ }
57
139
  }
58
140
 
59
141
  collapseAxes(ids: AxesId): AxesSpec {
60
- return collapseAxes(ids);
142
+ try {
143
+ return collapseAxes(ids);
144
+ } catch (err: unknown) {
145
+ const error = new PFrameSpecDriverError(`collapseAxes failed`);
146
+ error.cause = new Error(`ids: ${JSON.stringify(ids)}, ` + `error:\n${ensureError(err)}`);
147
+ throw error;
148
+ }
61
149
  }
62
150
 
63
151
  findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {
64
- return findAxis(spec, selector);
152
+ try {
153
+ return findAxis(spec, selector);
154
+ } catch (err: unknown) {
155
+ const error = new PFrameSpecDriverError(`findAxis failed`);
156
+ error.cause = new Error(
157
+ `spec: ${JSON.stringify(spec)}, ` +
158
+ `selector: ${JSON.stringify(selector)}, ` +
159
+ `error:\n${ensureError(err)}`,
160
+ );
161
+ throw error;
162
+ }
65
163
  }
66
164
 
67
165
  findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {
68
- return findTableColumn(tableSpec, selector);
69
- }
70
-
71
- /** Dispose all managed spec frames. */
72
- dispose(): void {
73
166
  try {
74
- for (const frame of this.frames.values()) {
75
- frame[Symbol.dispose]();
76
- }
77
- } finally {
78
- this.frames.clear();
167
+ return findTableColumn(tableSpec, selector);
168
+ } catch (err: unknown) {
169
+ const error = new PFrameSpecDriverError(`findTableColumn failed`);
170
+ error.cause = new Error(
171
+ `selector: ${JSON.stringify(selector)}, ` + `error:\n${ensureError(err)}`,
172
+ );
173
+ throw error;
79
174
  }
80
175
  }
81
176
 
82
- [Symbol.dispose](): void {
83
- this.dispose();
177
+ async dispose(): Promise<void> {
178
+ await this.frames.dispose();
84
179
  }
85
180
 
86
- private getFrame(handle: SpecFrameHandle): PFrameInternal.PFrameWasmV2 {
87
- const frame = this.frames.get(handle);
88
- if (frame === undefined) throw new PFrameSpecDriverError(`No such spec frame: ${handle}`);
89
- return frame;
181
+ async [Symbol.asyncDispose](): Promise<void> {
182
+ await this.dispose();
90
183
  }
91
184
  }