@milaboratories/pl-model-common 1.46.4 → 1.47.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.
Files changed (178) hide show
  1. package/dist/columns/accessor_traversal.cjs +119 -0
  2. package/dist/columns/accessor_traversal.cjs.map +1 -0
  3. package/dist/columns/accessor_traversal.d.ts +52 -0
  4. package/dist/columns/accessor_traversal.d.ts.map +1 -0
  5. package/dist/columns/accessor_traversal.js +114 -0
  6. package/dist/columns/accessor_traversal.js.map +1 -0
  7. package/dist/columns/column_registry.cjs +40 -0
  8. package/dist/columns/column_registry.cjs.map +1 -0
  9. package/dist/columns/column_registry.d.ts +31 -0
  10. package/dist/columns/column_registry.d.ts.map +1 -0
  11. package/dist/columns/column_registry.js +40 -0
  12. package/dist/columns/column_registry.js.map +1 -0
  13. package/dist/columns/column_selector.cjs +49 -0
  14. package/dist/columns/column_selector.cjs.map +1 -0
  15. package/dist/columns/column_selector.d.ts +33 -0
  16. package/dist/columns/column_selector.d.ts.map +1 -0
  17. package/dist/columns/column_selector.js +47 -0
  18. package/dist/columns/column_selector.js.map +1 -0
  19. package/dist/columns/dedup.cjs +47 -0
  20. package/dist/columns/dedup.cjs.map +1 -0
  21. package/dist/columns/dedup.d.ts +27 -0
  22. package/dist/columns/dedup.d.ts.map +1 -0
  23. package/dist/columns/dedup.js +47 -0
  24. package/dist/columns/dedup.js.map +1 -0
  25. package/dist/columns/index.cjs +18 -0
  26. package/dist/columns/index.d.ts +8 -0
  27. package/dist/columns/index.js +6 -0
  28. package/dist/columns/providers.cjs +62 -0
  29. package/dist/columns/providers.cjs.map +1 -0
  30. package/dist/columns/providers.d.ts +39 -0
  31. package/dist/columns/providers.d.ts.map +1 -0
  32. package/dist/columns/providers.js +61 -0
  33. package/dist/columns/providers.js.map +1 -0
  34. package/dist/columns/types.d.ts +108 -0
  35. package/dist/columns/types.d.ts.map +1 -0
  36. package/dist/driver_kit.d.ts +1 -2
  37. package/dist/driver_kit.d.ts.map +1 -1
  38. package/dist/drivers/columns/columns_collection_driver.d.ts +124 -0
  39. package/dist/drivers/columns/columns_collection_driver.d.ts.map +1 -0
  40. package/dist/drivers/columns/discover_columns_options.cjs +28 -0
  41. package/dist/drivers/columns/discover_columns_options.cjs.map +1 -0
  42. package/dist/drivers/columns/discover_columns_options.d.ts +64 -0
  43. package/dist/drivers/columns/discover_columns_options.d.ts.map +1 -0
  44. package/dist/drivers/columns/discover_columns_options.js +28 -0
  45. package/dist/drivers/columns/discover_columns_options.js.map +1 -0
  46. package/dist/drivers/columns/index.d.ts +3 -0
  47. package/dist/drivers/index.cjs +32 -7
  48. package/dist/drivers/index.d.ts +12 -9
  49. package/dist/drivers/index.js +6 -4
  50. package/dist/drivers/pframe/driver.d.ts +1 -2
  51. package/dist/drivers/pframe/driver.d.ts.map +1 -1
  52. package/dist/drivers/pframe/index.cjs +30 -7
  53. package/dist/drivers/pframe/index.d.ts +9 -8
  54. package/dist/drivers/pframe/index.js +5 -4
  55. package/dist/drivers/pframe/query/index.d.ts +2 -2
  56. package/dist/drivers/pframe/query/query_common.d.ts +24 -1
  57. package/dist/drivers/pframe/query/query_common.d.ts.map +1 -1
  58. package/dist/drivers/pframe/query/query_spec.d.ts +25 -24
  59. package/dist/drivers/pframe/query/query_spec.d.ts.map +1 -1
  60. package/dist/drivers/pframe/query/utils.cjs +18 -8
  61. package/dist/drivers/pframe/query/utils.cjs.map +1 -1
  62. package/dist/drivers/pframe/query/utils.d.ts +5 -1
  63. package/dist/drivers/pframe/query/utils.d.ts.map +1 -1
  64. package/dist/drivers/pframe/query/utils.js +18 -8
  65. package/dist/drivers/pframe/query/utils.js.map +1 -1
  66. package/dist/drivers/pframe/spec/anchored.d.ts +2 -2
  67. package/dist/drivers/pframe/spec/discovered_column.cjs +31 -15
  68. package/dist/drivers/pframe/spec/discovered_column.cjs.map +1 -1
  69. package/dist/drivers/pframe/spec/discovered_column.d.ts +23 -13
  70. package/dist/drivers/pframe/spec/discovered_column.d.ts.map +1 -1
  71. package/dist/drivers/pframe/spec/discovered_column.js +26 -12
  72. package/dist/drivers/pframe/spec/discovered_column.js.map +1 -1
  73. package/dist/drivers/pframe/spec/filtered_column.cjs +46 -0
  74. package/dist/drivers/pframe/spec/filtered_column.cjs.map +1 -1
  75. package/dist/drivers/pframe/spec/filtered_column.d.ts +39 -1
  76. package/dist/drivers/pframe/spec/filtered_column.d.ts.map +1 -1
  77. package/dist/drivers/pframe/spec/filtered_column.js +41 -1
  78. package/dist/drivers/pframe/spec/filtered_column.js.map +1 -1
  79. package/dist/drivers/pframe/spec/ids.cjs +62 -10
  80. package/dist/drivers/pframe/spec/ids.cjs.map +1 -1
  81. package/dist/drivers/pframe/spec/ids.d.ts +48 -15
  82. package/dist/drivers/pframe/spec/ids.d.ts.map +1 -1
  83. package/dist/drivers/pframe/spec/ids.js +60 -9
  84. package/dist/drivers/pframe/spec/ids.js.map +1 -1
  85. package/dist/drivers/pframe/spec/index.cjs +30 -7
  86. package/dist/drivers/pframe/spec/index.d.ts +6 -5
  87. package/dist/drivers/pframe/spec/index.js +5 -4
  88. package/dist/drivers/pframe/spec/native_id.cjs +1 -0
  89. package/dist/drivers/pframe/spec/native_id.cjs.map +1 -1
  90. package/dist/drivers/pframe/spec/native_id.js +1 -0
  91. package/dist/drivers/pframe/spec/native_id.js.map +1 -1
  92. package/dist/drivers/pframe/spec/overridden.cjs +212 -0
  93. package/dist/drivers/pframe/spec/overridden.cjs.map +1 -0
  94. package/dist/drivers/pframe/spec/overridden.d.ts +67 -0
  95. package/dist/drivers/pframe/spec/overridden.d.ts.map +1 -0
  96. package/dist/drivers/pframe/spec/overridden.js +202 -0
  97. package/dist/drivers/pframe/spec/overridden.js.map +1 -0
  98. package/dist/drivers/pframe/spec/selectors.cjs +1 -0
  99. package/dist/drivers/pframe/spec/selectors.cjs.map +1 -1
  100. package/dist/drivers/pframe/spec/selectors.d.ts +3 -0
  101. package/dist/drivers/pframe/spec/selectors.d.ts.map +1 -1
  102. package/dist/drivers/pframe/spec/selectors.js +1 -0
  103. package/dist/drivers/pframe/spec/selectors.js.map +1 -1
  104. package/dist/drivers/pframe/spec/spec.cjs.map +1 -1
  105. package/dist/drivers/pframe/spec/spec.d.ts +2 -3
  106. package/dist/drivers/pframe/spec/spec.d.ts.map +1 -1
  107. package/dist/drivers/pframe/spec/spec.js.map +1 -1
  108. package/dist/drivers/pframe/spec_driver.d.ts +2 -2
  109. package/dist/drivers/pframe/spec_driver.d.ts.map +1 -1
  110. package/dist/drivers/pframe/table_calculate.cjs +2 -2
  111. package/dist/drivers/pframe/table_calculate.cjs.map +1 -1
  112. package/dist/drivers/pframe/table_calculate.d.ts +6 -2
  113. package/dist/drivers/pframe/table_calculate.d.ts.map +1 -1
  114. package/dist/drivers/pframe/table_calculate.js +2 -2
  115. package/dist/drivers/pframe/table_calculate.js.map +1 -1
  116. package/dist/drivers/pframe/table_common.cjs.map +1 -1
  117. package/dist/drivers/pframe/table_common.d.ts +11 -4
  118. package/dist/drivers/pframe/table_common.d.ts.map +1 -1
  119. package/dist/drivers/pframe/table_common.js.map +1 -1
  120. package/dist/index.cjs +63 -10
  121. package/dist/index.d.ts +20 -11
  122. package/dist/index.js +15 -6
  123. package/dist/pool/index.cjs +18 -0
  124. package/dist/pool/index.d.ts +2 -2
  125. package/dist/pool/index.js +3 -0
  126. package/dist/pool/spec.cjs +59 -11
  127. package/dist/pool/spec.cjs.map +1 -1
  128. package/dist/pool/spec.d.ts +24 -9
  129. package/dist/pool/spec.d.ts.map +1 -1
  130. package/dist/pool/spec.js +50 -10
  131. package/dist/pool/spec.js.map +1 -1
  132. package/dist/services/index.d.ts +2 -2
  133. package/dist/services/service_declarations.cjs +22 -0
  134. package/dist/services/service_declarations.cjs.map +1 -1
  135. package/dist/services/service_declarations.d.ts +5 -3
  136. package/dist/services/service_declarations.d.ts.map +1 -1
  137. package/dist/services/service_declarations.js +22 -0
  138. package/dist/services/service_declarations.js.map +1 -1
  139. package/dist/services/service_registry.cjs.map +1 -1
  140. package/dist/services/service_registry.d.ts +3 -3
  141. package/dist/services/service_registry.d.ts.map +1 -1
  142. package/dist/services/service_registry.js.map +1 -1
  143. package/dist/services/service_types.cjs.map +1 -1
  144. package/dist/services/service_types.d.ts +25 -9
  145. package/dist/services/service_types.d.ts.map +1 -1
  146. package/dist/services/service_types.js.map +1 -1
  147. package/package.json +2 -1
  148. package/src/columns/accessor_traversal.ts +139 -0
  149. package/src/columns/column_registry.ts +41 -0
  150. package/src/columns/column_selector.ts +100 -0
  151. package/src/columns/dedup.ts +49 -0
  152. package/src/columns/index.ts +6 -0
  153. package/src/columns/providers.ts +74 -0
  154. package/src/columns/types.ts +116 -0
  155. package/src/drivers/columns/columns_collection_driver.ts +146 -0
  156. package/src/drivers/columns/discover_columns_options.ts +91 -0
  157. package/src/drivers/columns/index.ts +2 -0
  158. package/src/drivers/index.ts +1 -0
  159. package/src/drivers/pframe/index.ts +0 -1
  160. package/src/drivers/pframe/query/query_common.ts +24 -0
  161. package/src/drivers/pframe/query/query_spec.ts +38 -25
  162. package/src/drivers/pframe/query/utils.test.ts +6 -6
  163. package/src/drivers/pframe/query/utils.ts +27 -7
  164. package/src/drivers/pframe/spec/discovered_column.ts +43 -28
  165. package/src/drivers/pframe/spec/filtered_column.ts +66 -0
  166. package/src/drivers/pframe/spec/ids.ts +142 -17
  167. package/src/drivers/pframe/spec/index.ts +1 -0
  168. package/src/drivers/pframe/spec/overridden.ts +285 -0
  169. package/src/drivers/pframe/spec/selectors.ts +3 -0
  170. package/src/drivers/pframe/spec/spec.ts +7 -3
  171. package/src/drivers/pframe/spec_driver.ts +3 -3
  172. package/src/drivers/pframe/table_calculate.ts +13 -3
  173. package/src/drivers/pframe/table_common.ts +10 -3
  174. package/src/index.ts +1 -0
  175. package/src/pool/spec.ts +69 -23
  176. package/src/services/service_declarations.ts +31 -0
  177. package/src/services/service_registry.ts +8 -4
  178. package/src/services/service_types.ts +35 -9
@@ -0,0 +1,116 @@
1
+ import type { Branded } from "../branding";
2
+ import type { PObjectId } from "../pool";
3
+
4
+ /**
5
+ * Opaque sandbox/host accessor handle.
6
+ *
7
+ * Both the sandbox `TreeNodeAccessor.handle` and the host-issued accessor
8
+ * keys alias this brand — `sdk/model` re-exports the same type so the two
9
+ * sides stay structurally identical.
10
+ */
11
+ export type AccessorHandle = Branded<string, "AccessorHandle">;
12
+
13
+ /**
14
+ * Structural subset of {@link FieldTraversalStep} (from `@platforma-sdk/model`)
15
+ * needed by the host/sandbox column-providers traversal.
16
+ *
17
+ * Defined locally so this module does not depend on the sandbox `render`
18
+ * subtree. Both `TreeNodeAccessor.traverse` (sandbox) and
19
+ * `PlTreeNodeAccessor.traverse` (host) accept a superset of these fields, so
20
+ * structural compatibility is preserved.
21
+ */
22
+ export interface FieldTraversalStepLike {
23
+ /** Field name */
24
+ readonly field: string;
25
+ /** Asserted field type — used by `accessor.traverse` to validate. */
26
+ readonly assertFieldType?: "Input" | "Output" | "Service" | "OTW" | "Dynamic" | "MTW";
27
+ /** Don't terminate chain if current resource or field has an error associated. */
28
+ readonly ignoreError?: true;
29
+ }
30
+
31
+ /**
32
+ * Raw entry returned by {@link GlobalCfgRenderCtxMethods.getUpstreamBlockCtx}
33
+ * on the sandbox side, or by `collectUpstreamBlockCtx` on the host side.
34
+ * Carries handle ids only — providers wrap them into accessor instances as
35
+ * needed.
36
+ *
37
+ * Generic over the handle type so the same shape backs both:
38
+ * - sandbox (`AHandle = AccessorHandle`, a `Branded<string, "AccessorHandle">`)
39
+ * - host (`AHandle = PlTreeNodeAccessor`, the resolved accessor instance)
40
+ *
41
+ * Default `AHandle = string` since `AccessorHandle` is a brand on `string` —
42
+ * the default is safe for the sandbox case.
43
+ */
44
+ export interface UpstreamBlockCtx<AHandle = string> {
45
+ blockId: string;
46
+ prodCtx?: AHandle;
47
+ stagingCtx?: AHandle;
48
+ /** True when the `prodCtx` ctx-holder exists but `prodUiCtx` is still rendering. */
49
+ prodIncomplete?: boolean;
50
+ /** True when the `stagingCtx` ctx-holder exists but `stagingUiCtx` is still rendering. */
51
+ stagingIncomplete?: boolean;
52
+ }
53
+
54
+ /**
55
+ * Minimal accessor surface used by column-providers / column-registry
56
+ * traversal.
57
+ *
58
+ * Both sandbox `TreeNodeAccessor` and host `PlTreeNodeAccessor` satisfy this
59
+ * contract directly — `traverse(step)` is defined on both, and the other
60
+ * members already match by shape. No `resolvePath` member: when canonical
61
+ * `PObjectId`s are required (local-id construction inside the
62
+ * outputs/prerun branch), the traversal helpers thread the path explicitly.
63
+ */
64
+ export interface AccessorLike<Self extends AccessorLike<Self>> {
65
+ /** Resource type carried by the underlying node (only `.name` is used). */
66
+ readonly resourceType: { readonly name: string };
67
+
68
+ /**
69
+ * Single-step field traversal. Returns `undefined` when the field is
70
+ * absent / unresolved (with `ignoreError`). Same shape on sandbox and host —
71
+ * sandbox `TreeNodeAccessor.traverse` is an alias for `resolveAny` and host
72
+ * `PlTreeNodeAccessor.traverse` is the canonical method.
73
+ */
74
+ traverse(step: FieldTraversalStepLike): Self | undefined;
75
+
76
+ /** List input-field names on this node. */
77
+ listInputFields(): string[];
78
+
79
+ /** Whether the input-field collection on this node is finalized. */
80
+ getInputsLocked(): boolean;
81
+
82
+ /** Whether this node has a data payload attached. */
83
+ hasData(): boolean;
84
+
85
+ /** Decode the data payload as JSON. Returns `undefined` if no data. */
86
+ getDataAsJson<T = unknown>(): T | undefined;
87
+ }
88
+
89
+ /**
90
+ * One indexed column — the canonical record produced by every traversal.
91
+ * Carries everything needed to read spec/data/status under a stable id.
92
+ *
93
+ * Generic over the accessor flavour so the same record shape works for both
94
+ * the sandbox `TreeNodeAccessor` and the host `PlTreeNodeAccessor`.
95
+ */
96
+ export type LeafEntry<A extends AccessorLike<A>> = {
97
+ /** PFrame accessor that owns `<name>.spec` / `<name>.data` fields. */
98
+ accessor: A;
99
+ /** Field-name prefix inside the PFrame. */
100
+ name: string;
101
+ /** Canonical id under which this column is reachable. */
102
+ id: PObjectId;
103
+ };
104
+
105
+ /**
106
+ * Base interface for id-indexed column providers — the surface
107
+ * {@link ColumnRegistry} consumes. Generic over the concrete accessor flavour
108
+ * so it can back both sandbox (`TreeNodeAccessor`) and host
109
+ * (`PlTreeNodeAccessor` adapter) registries.
110
+ */
111
+ export interface ColumnEntriesProvider<A extends AccessorLike<A>> {
112
+ /** Map of canonical {@link PObjectId} → {@link LeafEntry} for every column reachable from this source. */
113
+ getPObjectEntries(): ReadonlyMap<PObjectId, LeafEntry<A>>;
114
+ /** Whether enumeration of columns from this source has finalised. */
115
+ isFinal(): boolean;
116
+ }
@@ -0,0 +1,146 @@
1
+ import type { Branded } from "../../branding";
2
+ import type { PoolEntry } from "../../pool_entry";
3
+ import type { AccessorHandle, AccessorLike, UpstreamBlockCtx } from "../../columns/types";
4
+ import type { ColumnUniversalId } from "../pframe/spec/ids";
5
+ import type { ColumnsDiscoverOptions, ColumnsFilterOptions } from "./discover_columns_options";
6
+ import type { PFrameSpecDriver } from "../pframe/spec_driver";
7
+ import type { PColumnSpec } from "../pframe/spec/spec";
8
+ import type { PObjectId } from "../../pool";
9
+
10
+ /**
11
+ * Opaque host-owned handle for a `ColumnsCollection` instance. Issued by
12
+ * {@link ColumnsCollectionDriver.create}, refcounted by the driver, and
13
+ * pinned to the active render ctx via the VM injector — sandbox never
14
+ * sees raw refcounting.
15
+ */
16
+ export type CollectionHandle = Branded<string, "CollectionHandle">;
17
+
18
+ /**
19
+ * JSON descriptor crossing the VM bridge in place of a sandbox
20
+ * `ColumnsSource`. Always plain data — no closures, no class instances.
21
+ *
22
+ * - `"collection"` – reference another driver-managed collection by handle
23
+ * (chaining, splicing).
24
+ * - `"result_pool"` – fan-out into the host's current render ctx upstream
25
+ * block ctxes. Carries no payload — the host always uses its own pool.
26
+ * - `"accessor"` – walk the host tree starting at the given accessor
27
+ * handle from a `path` prefix.
28
+ * - `"ids"` – pre-resolved id list (sandbox-materialised provider).
29
+ */
30
+ export type SerializedColumnsSource =
31
+ | { readonly kind: "collection"; readonly handle: CollectionHandle }
32
+ | { readonly kind: "result_pool" }
33
+ | { readonly kind: "accessor"; readonly accessor: AccessorHandle; readonly path: string[] }
34
+ | { readonly kind: "ids"; readonly ids: ColumnUniversalId[]; readonly isFinal: boolean };
35
+
36
+ /**
37
+ * Per-call host bindings the driver needs to resolve sources whose
38
+ * shape references render-ctx state (`"accessor"`, `"result_pool"`).
39
+ *
40
+ * The VM injector inside `pl-middle-layer` supplies these on every call;
41
+ * UI-side direct callers that only build collections out of `"ids"` /
42
+ * `"collection"` sources may omit the bindings entirely.
43
+ *
44
+ * Parameterised on the concrete accessor flavour so host implementations
45
+ * keep their static types (e.g. `PlTreeNodeAccessor`) without leaking that
46
+ * dependency into `@milaboratories/pl-model-common`.
47
+ */
48
+ export interface ColumnsCollectionDriverHost<A extends AccessorLike<A> = AccessorLike<any>> {
49
+ /** Resolve an {@link AccessorHandle} to the host's concrete accessor. */
50
+ resolveAccessor(handle: AccessorHandle): A;
51
+
52
+ /** Snapshot of upstream-block ctx pairs from the current render ctx. */
53
+ getUpstreamBlockCtxes(): ReadonlyArray<UpstreamBlockCtx<A>>;
54
+
55
+ /**
56
+ * Per-call spec driver. The injector supplies the active render ctx's
57
+ * `PFrameSpec` service; `discover` / `filter` use it to build a spec
58
+ * frame and run a single discovery query.
59
+ */
60
+ getSpecDriver(): PFrameSpecDriver;
61
+
62
+ /**
63
+ * Resolve the canonical {@link PColumnSpec} for a leaf {@link PObjectId}.
64
+ * Returns `undefined` when the id is not present in the active registry
65
+ * (e.g. handed to the driver via a `{kind:"ids"}` source whose underlying
66
+ * column has since left the visible scope). Override-wrapped ids are
67
+ * unwrapped by the caller — this method only resolves the underlying leaf.
68
+ */
69
+ resolveSpec(id: PObjectId): PColumnSpec | undefined;
70
+ }
71
+
72
+ /**
73
+ * Sandbox / UI view of the `ColumnsCollection` driver. Same methods as
74
+ * {@link ColumnsCollectionDriver}, but the `host` parameters are dropped —
75
+ * the VM bridge / UI wrapper supplies them on every call so callers only
76
+ * pass plain data (handles + source descriptors + option objects).
77
+ *
78
+ * `getService("columnsCollection")` returns a value of this shape on both
79
+ * sandbox and UI sides.
80
+ */
81
+ export interface ColumnsCollectionDriverModel {
82
+ /** Build a fresh collection from the supplied source descriptors. */
83
+ create(sources: ReadonlyArray<SerializedColumnsSource>): CollectionHandle;
84
+
85
+ /** Whether the collection currently exposes zero columns. */
86
+ isEmpty(handle: CollectionHandle): boolean;
87
+
88
+ /** Whether enumeration is finalised across every contributing source. */
89
+ isFinal(handle: CollectionHandle): boolean;
90
+
91
+ /** Canonical id list for the columns visible through this collection. */
92
+ getColumns(handle: CollectionHandle): ColumnUniversalId[];
93
+
94
+ /** Append one or more sources and return a fresh collection handle. */
95
+ addSource(
96
+ handle: CollectionHandle,
97
+ sources: ReadonlyArray<SerializedColumnsSource>,
98
+ ): CollectionHandle;
99
+
100
+ /** Anchored/selector-driven discovery. Returns a fresh handle. */
101
+ discover(handle: CollectionHandle, options: ColumnsDiscoverOptions): CollectionHandle;
102
+
103
+ /** Selector-only filter (no anchor traversal). Returns a fresh handle. */
104
+ filter(handle: CollectionHandle, options: ColumnsFilterOptions): CollectionHandle;
105
+ }
106
+
107
+ /**
108
+ * Synchronous host-side driver for `ColumnsCollection` operations. All
109
+ * collection state lives on the host side, addressable through opaque
110
+ * {@link CollectionHandle}s. Every handle-minting method returns a
111
+ * {@link PoolEntry} so callers can wire the refcount into their own
112
+ * lifecycle; the VM bridge pins each entry to the active render ctx and
113
+ * forwards only the handle string to sandbox.
114
+ *
115
+ * Sandbox / UI callers consume {@link ColumnsCollectionDriverModel}
116
+ * instead; the bridge maps that surface onto this one by injecting
117
+ * {@link ColumnsCollectionDriverHost} bindings.
118
+ */
119
+ export interface ColumnsCollectionDriver {
120
+ create(
121
+ sources: ReadonlyArray<SerializedColumnsSource>,
122
+ host: ColumnsCollectionDriverHost,
123
+ ): PoolEntry<CollectionHandle>;
124
+
125
+ isEmpty(handle: CollectionHandle): boolean;
126
+ isFinal(handle: CollectionHandle): boolean;
127
+ getColumns(handle: CollectionHandle, host: ColumnsCollectionDriverHost): ColumnUniversalId[];
128
+
129
+ addSource(
130
+ handle: CollectionHandle,
131
+ sources: ReadonlyArray<SerializedColumnsSource>,
132
+ host: ColumnsCollectionDriverHost,
133
+ ): PoolEntry<CollectionHandle>;
134
+
135
+ discover(
136
+ handle: CollectionHandle,
137
+ options: ColumnsDiscoverOptions,
138
+ host: ColumnsCollectionDriverHost,
139
+ ): PoolEntry<CollectionHandle>;
140
+
141
+ filter(
142
+ handle: CollectionHandle,
143
+ options: ColumnsFilterOptions,
144
+ host: ColumnsCollectionDriverHost,
145
+ ): PoolEntry<CollectionHandle>;
146
+ }
@@ -0,0 +1,91 @@
1
+ import type { ColumnSelector, RelaxedColumnSelector } from "../../columns/column_selector";
2
+ import type { PlRef } from "../../ref";
3
+ import type { PObjectId } from "../../pool";
4
+ import type { PColumnSpec, AxisQualification } from "../pframe/spec";
5
+ import type { DiscoverColumnsConstraints } from "../pframe/spec_driver";
6
+
7
+ /**
8
+ * Axis matching behaviour applied to `discover` requests.
9
+ *
10
+ * - `enrichment` (default) — anchor axes may float over un-mapped hit axes;
11
+ * used by tooling that "extends" a query.
12
+ * - `related` — both source and hit axes may float; widest match.
13
+ * - `exact` — no floating, no qualifications; strict equality.
14
+ */
15
+ export type MatchingMode = "enrichment" | "related" | "exact";
16
+
17
+ /**
18
+ * Single entry accepted by `DiscoverColumnsOptions.anchors`. All variants are
19
+ * trivially JSON-serialisable so the option carrier crosses the
20
+ * sandbox/host VM bridge unchanged.
21
+ */
22
+ export type AnchorEntry = PlRef | PObjectId | PColumnSpec | RelaxedColumnSelector;
23
+
24
+ /** Qualifications needed for both already-integrated anchor columns and the hit column. */
25
+ export interface MatchQualifications {
26
+ /** Qualifications for already-integrated anchor columns */
27
+ readonly forQueries?: Record<PObjectId, AxisQualification[]>;
28
+ /** Qualifications for the hit column. */
29
+ readonly forHit?: AxisQualification[];
30
+ }
31
+
32
+ /**
33
+ * Options object accepted by sandbox `discoverColumns()` and by the host
34
+ * `ColumnsCollectionDriver.discover` / `.filter` methods. Pure JSON shape —
35
+ * no class instances, no closures.
36
+ */
37
+ export interface DiscoverColumnsOptions {
38
+ /** Include columns matching these selectors. If omitted, includes all. */
39
+ include?: ColumnSelector;
40
+ /** Exclude columns matching these selectors. */
41
+ exclude?: ColumnSelector;
42
+ /** Axis matching behavior. Default: 'enrichment'. Ignored if no anchors. */
43
+ mode?: MatchingMode;
44
+ /** Anchors enable axis-aware discovery + linker traversal. */
45
+ // @todo: migrate to array<AnchorEntry>
46
+ anchors?: Record<string, AnchorEntry>;
47
+ /** Maximum linker hops. Default: 4 when anchors present, 0 otherwise. */
48
+ maxHops?: number;
49
+ }
50
+
51
+ /**
52
+ * Options accepted by `ColumnsCollection.discover` / driver `.discover`.
53
+ * Traversal scope (`mode`, `maxHops`) must be specified explicitly — the
54
+ * defaults from {@link DiscoverColumnsOptions} are intentionally surfaced as
55
+ * required choices at the discovery entrypoint.
56
+ */
57
+ export type ColumnsDiscoverOptions = DiscoverColumnsOptions;
58
+
59
+ /**
60
+ * Options accepted by `ColumnsCollection.filter` / driver `.filter`. Traversal
61
+ * scope is fixed by the source collection, so `mode` / `maxHops` are not part
62
+ * of the filter surface — only `include` / `exclude` / `anchors`.
63
+ */
64
+ export type ColumnsFilterOptions = Omit<DiscoverColumnsOptions, "mode" | "maxHops">;
65
+
66
+ /** Translate a {@link MatchingMode} into the boolean-flag form the spec driver consumes. */
67
+ export function matchingModeToConstraints(mode: MatchingMode): DiscoverColumnsConstraints {
68
+ switch (mode) {
69
+ case "enrichment":
70
+ return {
71
+ allowFloatingSourceAxes: true,
72
+ allowFloatingHitAxes: false,
73
+ allowSourceQualifications: true,
74
+ allowHitQualifications: true,
75
+ };
76
+ case "related":
77
+ return {
78
+ allowFloatingSourceAxes: true,
79
+ allowFloatingHitAxes: true,
80
+ allowSourceQualifications: true,
81
+ allowHitQualifications: true,
82
+ };
83
+ case "exact":
84
+ return {
85
+ allowFloatingSourceAxes: false,
86
+ allowFloatingHitAxes: false,
87
+ allowSourceQualifications: false,
88
+ allowHitQualifications: false,
89
+ };
90
+ }
91
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./columns_collection_driver";
2
+ export * from "./discover_columns_options";
@@ -7,4 +7,5 @@ export * from "./log";
7
7
  export * from "./ls";
8
8
 
9
9
  export * from "./pframe";
10
+ export * from "./columns";
10
11
  export * from "./ChunkedStreamReader";
@@ -4,7 +4,6 @@ export * from "./filter_spec";
4
4
  export * from "./data_types";
5
5
  export * from "./find_columns";
6
6
  export * from "./pframe";
7
- export * from "./spec/spec";
8
7
  export * from "./table";
9
8
  export * from "./table_calculate";
10
9
  export * from "./table_common";
@@ -1169,3 +1169,27 @@ export interface QueryTransformColumns<Q, E, SO> {
1169
1169
  /** Derived columns to compute (at least one). */
1170
1170
  columns: [TransformColumnEntry<E, SO>, ...TransformColumnEntry<E, SO>[]];
1171
1171
  }
1172
+
1173
+ /**
1174
+ * Spec-override query operation — client-side-only structural node.
1175
+ *
1176
+ * Overlays a {@link SpecOverrides} patch on top of the inner query's spec.
1177
+ * Carries no topological change — it is collapsed at the host boundary
1178
+ * (`resolvePColumn`) before the query reaches pframe-engine. The engine
1179
+ * never sees this node.
1180
+ *
1181
+ * Emitted by `ColumnOverriddenRecipe.getQuery()`; the only currently
1182
+ * supported shape is `specOverride{ input: <plain column ref>, override }`
1183
+ * (i.e. `Overridden<Lazy>`). More complex projections under Overridden are
1184
+ * a future engine work item.
1185
+ *
1186
+ * @template Q - Input query type
1187
+ * @template SO - Spec override type
1188
+ */
1189
+ export interface QuerySpecOverride<Q, SO> {
1190
+ type: "specOverride";
1191
+ /** Input query whose spec is to be overridden. */
1192
+ input: Q;
1193
+ /** Spec override patch to overlay on the inner spec. */
1194
+ override: SO;
1195
+ }
@@ -1,4 +1,5 @@
1
1
  import type { PObjectId } from "../../../pool";
2
+ import type { ColumnUniversalId } from "../spec/ids";
2
3
  import type {
3
4
  ExprAxisRef,
4
5
  ExprCast,
@@ -28,10 +29,12 @@ import type {
28
29
  QuerySliceAxes,
29
30
  QuerySort,
30
31
  QuerySparseToDenseColumn,
32
+ QuerySpecOverride,
31
33
  QuerySymmetricJoin,
32
34
  QueryTransformColumns,
33
35
  } from "./query_common";
34
36
  import type { Domain, PColumnIdAndSpec, SingleAxisSelector } from "../spec";
37
+ import type { SpecOverrides } from "../spec/overridden";
35
38
 
36
39
  /**
37
40
  * Join entry for spec-layer queries — the base join entry extended with
@@ -43,8 +46,8 @@ import type { Domain, PColumnIdAndSpec, SingleAxisSelector } from "../spec";
43
46
  * qualifications: [{ axis: { name: 'sample' }, contextDomain: { ... } }]
44
47
  * }
45
48
  */
46
- export type SpecQueryJoinEntry<C = PObjectId> = QueryJoinEntry<SpecQuery<C>> & {
47
- qualifications?: {
49
+ export type SpecQueryJoinEntry<C = ColumnUniversalId> = QueryJoinEntry<SpecQuery<C>> & {
50
+ qualifications?: readonly {
48
51
  /** Axis to qualify. */
49
52
  axis: SingleAxisSelector;
50
53
  /** Additional domain constraints for this axis. */
@@ -53,46 +56,54 @@ export type SpecQueryJoinEntry<C = PObjectId> = QueryJoinEntry<SpecQuery<C>> & {
53
56
  };
54
57
 
55
58
  /** @see QueryColumn */
56
- export type SpecQueryColumn<C = PObjectId> = QueryColumn<C>;
59
+ export type SpecQueryColumn<C = ColumnUniversalId> = QueryColumn<C>;
57
60
  /** @see QueryInlineColumn */
58
61
  export type SpecQueryInlineColumn = QueryInlineColumn<PColumnIdAndSpec>;
59
62
  /** @see QuerySparseToDenseColumn */
60
- export type SpecQuerySparseToDenseColumn<C = PObjectId> = QuerySparseToDenseColumn<
63
+ export type SpecQuerySparseToDenseColumn<C = ColumnUniversalId> = QuerySparseToDenseColumn<
61
64
  C,
62
65
  SingleAxisSelector,
63
66
  PColumnIdAndSpec
64
67
  >;
65
68
  /** @see QuerySymmetricJoin */
66
- export type SpecQuerySymmetricJoin<C = PObjectId> = QuerySymmetricJoin<SpecQueryJoinEntry<C>>;
69
+ export type SpecQuerySymmetricJoin<C = ColumnUniversalId> = QuerySymmetricJoin<
70
+ SpecQueryJoinEntry<C>
71
+ >;
67
72
  /** @see QueryOuterJoin */
68
- export type SpecQueryOuterJoin<C = PObjectId> = QueryOuterJoin<SpecQueryJoinEntry<C>>;
69
- /**
70
- * Linker side of a spec-layer linker-join.
71
- *
72
- * At the spec layer the linker is just a column reference — integration artifacts
73
- * (axes mapping, one-side indices) are derived during spec→data conversion.
74
- */
75
- export type SpecQueryLinkerJoinLinker<C = PObjectId> = {
76
- /** Linker column reference. */
77
- column: C;
78
- };
73
+ export type SpecQueryOuterJoin<C = ColumnUniversalId> = QueryOuterJoin<SpecQueryJoinEntry<C>>;
79
74
  /** @see QueryLinkerJoin */
80
- export type SpecQueryLinkerJoin<C = PObjectId> = QueryLinkerJoin<
81
- SpecQueryLinkerJoinLinker<C>,
75
+ export type SpecQueryLinkerJoin<C = ColumnUniversalId> = QueryLinkerJoin<
76
+ SpecQuery<C>,
82
77
  SpecQueryJoinEntry<C>
83
78
  >;
84
79
  /** @see QuerySliceAxes */
85
- export type SpecQuerySliceAxes<C = PObjectId> = QuerySliceAxes<SpecQuery<C>, SingleAxisSelector>;
80
+ export type SpecQuerySliceAxes<C = ColumnUniversalId> = QuerySliceAxes<
81
+ SpecQuery<C>,
82
+ SingleAxisSelector
83
+ >;
86
84
  /** @see QuerySort */
87
- export type SpecQuerySort<C = PObjectId> = QuerySort<SpecQuery<C>, SpecQueryExpression>;
85
+ export type SpecQuerySort<C = ColumnUniversalId> = QuerySort<SpecQuery<C>, SpecQueryExpression>;
88
86
  /** @see QueryFilter */
89
- export type SpecQueryFilter<C = PObjectId> = QueryFilter<SpecQuery<C>, SpecQueryBooleanExpression>;
87
+ export type SpecQueryFilter<C = ColumnUniversalId> = QueryFilter<
88
+ SpecQuery<C>,
89
+ SpecQueryBooleanExpression
90
+ >;
90
91
  /** @see QueryTransformColumns */
91
- export type SpecQueryTransformColumns<C = PObjectId> = QueryTransformColumns<
92
+ export type SpecQueryTransformColumns<C = ColumnUniversalId> = QueryTransformColumns<
92
93
  SpecQuery<C>,
93
94
  SpecQueryExpression,
94
95
  PColumnIdAndSpec
95
96
  >;
97
+ /**
98
+ * Client-side spec-override node — collapsed at the host boundary, never
99
+ * sent to pframe-engine.
100
+ *
101
+ * @see QuerySpecOverride
102
+ */
103
+ export type SpecQuerySpecOverride<C = ColumnUniversalId> = QuerySpecOverride<
104
+ SpecQuery<C>,
105
+ SpecOverrides
106
+ >;
96
107
 
97
108
  /**
98
109
  * Union of all spec layer query types.
@@ -108,8 +119,9 @@ export type SpecQueryTransformColumns<C = PObjectId> = QueryTransformColumns<
108
119
  * - Leaf nodes: column, inlineColumn, sparseToDenseColumn
109
120
  * - Join operations: innerJoin, fullJoin, outerJoin, linkerJoin
110
121
  * - Transformations: sliceAxes, sort, filter, transformColumns
122
+ * - Client-side overlays: specOverride (collapsed before reaching the engine)
111
123
  */
112
- export type SpecQuery<C = PObjectId> =
124
+ export type SpecQuery<C = ColumnUniversalId> =
113
125
  | SpecQueryColumn<C>
114
126
  | SpecQueryInlineColumn
115
127
  | SpecQuerySparseToDenseColumn<C>
@@ -119,12 +131,13 @@ export type SpecQuery<C = PObjectId> =
119
131
  | SpecQuerySliceAxes<C>
120
132
  | SpecQuerySort<C>
121
133
  | SpecQueryFilter<C>
122
- | SpecQueryTransformColumns<C>;
134
+ | SpecQueryTransformColumns<C>
135
+ | SpecQuerySpecOverride<C>;
123
136
 
124
137
  /** @see ExprAxisRef */
125
138
  export type SpecExprAxisRef = ExprAxisRef<SingleAxisSelector>;
126
139
  /** @see ExprColumnRef */
127
- export type SpecExprColumnRef = ExprColumnRef<PObjectId>;
140
+ export type SpecExprColumnRef = ExprColumnRef<ColumnUniversalId>;
128
141
 
129
142
  export type SpecQueryExpression =
130
143
  | SpecExprColumnRef
@@ -85,13 +85,13 @@ describe("traverseQuerySpec", () => {
85
85
  it("transforms columns inside linkerJoin", () => {
86
86
  const q: Q = {
87
87
  type: "linkerJoin",
88
- linker: { column: "l" },
88
+ linker: col("l"),
89
89
  secondary: [entry(col("a")), entry(col("b"))],
90
90
  };
91
91
  const result = traverseQuerySpec(q, { column: (c) => c.toUpperCase() });
92
92
  expect(result).toEqual({
93
93
  type: "linkerJoin",
94
- linker: { column: "L" },
94
+ linker: { type: "column", column: "L" },
95
95
  secondary: [entry({ type: "column", column: "A" }), entry({ type: "column", column: "B" })],
96
96
  });
97
97
  });
@@ -180,7 +180,7 @@ describe("mapSpecQueryColumns", () => {
180
180
  primary: entry(col("a")),
181
181
  secondary: [entry(col("b"))],
182
182
  };
183
- const result = mapSpecQueryColumns(q, (c) => c.toUpperCase());
183
+ const result = mapSpecQueryColumns(q, { column: (c) => c.toUpperCase() });
184
184
  expect(collectSpecQueryColumns(result)).toEqual(["A", "B"]);
185
185
  });
186
186
  });
@@ -229,7 +229,7 @@ describe("collectSpecQueryColumns", () => {
229
229
  it("collects linker and secondary columns from linkerJoin", () => {
230
230
  const q: Q = {
231
231
  type: "linkerJoin",
232
- linker: { column: "l" },
232
+ linker: col("l"),
233
233
  secondary: [entry(col("a")), entry(col("b"))],
234
234
  };
235
235
  expect(collectSpecQueryColumns(q)).toEqual(["l", "a", "b"]);
@@ -263,13 +263,13 @@ describe("sortSpecQuery", () => {
263
263
  it("sorts linkerJoin secondary entries (linker column unchanged)", () => {
264
264
  const q: SpecQuery = {
265
265
  type: "linkerJoin",
266
- linker: { column: pid("l") },
266
+ linker: pcol("l"),
267
267
  secondary: [pentry(pcol("c")), pentry(pcol("a")), pentry(pcol("b"))],
268
268
  };
269
269
  const result = sortSpecQuery(q);
270
270
  expect(result).toEqual({
271
271
  type: "linkerJoin",
272
- linker: { column: "l" },
272
+ linker: pcol("l"),
273
273
  secondary: [pentry(pcol("a")), pentry(pcol("b")), pentry(pcol("c"))],
274
274
  });
275
275
  });
@@ -79,17 +79,25 @@ export function traverseQuerySpec<C1, C2>(
79
79
  secondary: query.secondary.map(traverseEntry),
80
80
  };
81
81
  break;
82
- case "linkerJoin":
82
+ case "linkerJoin": {
83
+ // Back-compat: pre-#c7309fc8 blocks emit `linker` as `{ column }` without
84
+ // a `type` — tag it as a `column` node so recursion handles it normally.
85
+ const linker =
86
+ "type" in query.linker
87
+ ? query.linker
88
+ : ({ type: "column", column: (query.linker as { column: C1 }).column } as SpecQuery<C1>);
83
89
  result = {
84
90
  ...query,
85
- linker: { ...query.linker, column: visitor.column(query.linker.column) },
91
+ linker: traverseQuerySpec(linker, visitor),
86
92
  secondary: query.secondary.map(traverseEntry),
87
93
  };
88
94
  break;
95
+ }
89
96
  case "filter":
90
97
  case "sort":
91
98
  case "sliceAxes":
92
99
  case "transformColumns":
100
+ case "specOverride":
93
101
  result = { ...query, input: traverseQuerySpec(query.input, visitor) };
94
102
  break;
95
103
  default:
@@ -102,9 +110,16 @@ export function traverseQuerySpec<C1, C2>(
102
110
  /** Recursively maps all column references in a SpecQuery tree. */
103
111
  export function mapSpecQueryColumns<C1, C2>(
104
112
  query: SpecQuery<C1>,
105
- cb: (c: C1) => C2,
113
+ visitor: {
114
+ /** Transform column references in leaf nodes (column, sparseToDenseColumn). */
115
+ column: (c: C1) => C2;
116
+ /** Visit a node after its children have been traversed. */
117
+ node?: (node: SpecQuery<C2>) => SpecQuery<C2>;
118
+ /** Visit a join entry after its inner query has been traversed. */
119
+ joinEntry?: (entry: SpecQueryJoinEntry<C2>) => SpecQueryJoinEntry<C2>;
120
+ },
106
121
  ): SpecQuery<C2> {
107
- return traverseQuerySpec(query, { column: cb });
122
+ return traverseQuerySpec(query, visitor);
108
123
  }
109
124
 
110
125
  /** Collects all column references from a SpecQuery tree. */
@@ -227,9 +242,8 @@ function cmpQuerySpec(lhs: SpecQuery, rhs: SpecQuery): number {
227
242
  }
228
243
  case "linkerJoin": {
229
244
  const rhsLinker = rhs as typeof lhs;
230
- if (lhs.linker.column !== rhsLinker.linker.column) {
231
- return lhs.linker.column < rhsLinker.linker.column ? -1 : 1;
232
- }
245
+ const cmp = cmpQuerySpec(lhs.linker, rhsLinker.linker);
246
+ if (cmp !== 0) return cmp;
233
247
  if (lhs.secondary.length !== rhsLinker.secondary.length) {
234
248
  return lhs.secondary.length - rhsLinker.secondary.length;
235
249
  }
@@ -245,6 +259,12 @@ function cmpQuerySpec(lhs: SpecQuery, rhs: SpecQuery): number {
245
259
  return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
246
260
  case "filter":
247
261
  return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
262
+ case "specOverride": {
263
+ const rhsSo = rhs as typeof lhs;
264
+ const cmp = cmpQuerySpec(lhs.input, rhsSo.input);
265
+ if (cmp !== 0) return cmp;
266
+ return canonicalizeJson(lhs.override).localeCompare(canonicalizeJson(rhsSo.override));
267
+ }
248
268
  case "transformColumns": {
249
269
  const rhsTc = rhs as typeof lhs;
250
270
  const cmp = cmpQuerySpec(lhs.input, rhsTc.input);