@milaboratories/pl-middle-layer 1.65.9 → 1.66.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.
- package/dist/js_render/column_registry.cjs +56 -0
- package/dist/js_render/column_registry.cjs.map +1 -0
- package/dist/js_render/column_registry.js +55 -0
- package/dist/js_render/column_registry.js.map +1 -0
- package/dist/js_render/computable_context.cjs +80 -12
- package/dist/js_render/computable_context.cjs.map +1 -1
- package/dist/js_render/computable_context.js +81 -13
- package/dist/js_render/computable_context.js.map +1 -1
- package/dist/js_render/service_injectors.cjs +43 -2
- package/dist/js_render/service_injectors.cjs.map +1 -1
- package/dist/js_render/service_injectors.js +43 -2
- package/dist/js_render/service_injectors.js.map +1 -1
- package/dist/js_render/spec_override_collapse.cjs +108 -0
- package/dist/js_render/spec_override_collapse.cjs.map +1 -0
- package/dist/js_render/spec_override_collapse.js +108 -0
- package/dist/js_render/spec_override_collapse.js.map +1 -0
- package/dist/middle_layer/block_ctx.cjs +2 -1
- package/dist/middle_layer/block_ctx.cjs.map +1 -1
- package/dist/middle_layer/block_ctx.js +2 -1
- package/dist/middle_layer/block_ctx.js.map +1 -1
- package/dist/mutator/template/template_cache.cjs.map +1 -1
- package/dist/mutator/template/template_cache.d.ts.map +1 -1
- package/dist/mutator/template/template_cache.js.map +1 -1
- package/dist/pool/data.cjs +2 -9
- package/dist/pool/data.cjs.map +1 -1
- package/dist/pool/data.d.ts.map +1 -1
- package/dist/pool/data.js +3 -10
- package/dist/pool/data.js.map +1 -1
- package/dist/pool/result_pool.cjs +0 -20
- package/dist/pool/result_pool.cjs.map +1 -1
- package/dist/pool/result_pool.js +1 -21
- package/dist/pool/result_pool.js.map +1 -1
- package/dist/pool/upstream_block_ctx.cjs +64 -0
- package/dist/pool/upstream_block_ctx.cjs.map +1 -0
- package/dist/pool/upstream_block_ctx.js +64 -0
- package/dist/pool/upstream_block_ctx.js.map +1 -0
- package/dist/service_factories.cjs +3 -1
- package/dist/service_factories.cjs.map +1 -1
- package/dist/service_factories.js +3 -1
- package/dist/service_factories.js.map +1 -1
- package/package.json +18 -17
- package/src/js_render/column_registry.ts +95 -0
- package/src/js_render/computable_context.ts +109 -26
- package/src/js_render/service_injectors.ts +106 -8
- package/src/js_render/spec_override_collapse.ts +211 -0
- package/src/middle_layer/block_ctx.ts +6 -1
- package/src/mutator/template/template_cache.test.ts +0 -14
- package/src/mutator/template/template_cache.ts +0 -14
- package/src/pool/data.ts +4 -3
- package/src/pool/result_pool.ts +1 -24
- package/src/pool/upstream_block_ctx.ts +85 -0
- package/src/service_factories.ts +2 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { PlTreeNodeAccessor } from "@milaboratories/pl-tree";
|
|
2
|
+
import type { DataInfo, PColumn, PColumnValues } from "@platforma-sdk/model";
|
|
3
|
+
import { applySpecOverrides, matchAxis } from "@milaboratories/pl-model-common";
|
|
4
|
+
import type {
|
|
5
|
+
AxesSpec,
|
|
6
|
+
AxisPatches,
|
|
7
|
+
SpecOverrides,
|
|
8
|
+
SpecQuery,
|
|
9
|
+
} from "@milaboratories/pl-model-common";
|
|
10
|
+
|
|
11
|
+
type Leaf = PColumn<PlTreeNodeAccessor | PColumnValues | DataInfo<PlTreeNodeAccessor>>;
|
|
12
|
+
type Node = SpecQuery<Leaf>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Collapse the client-side `specOverride` query node at the host boundary —
|
|
16
|
+
* pframe-engine never sees it. The override is pushed down through the
|
|
17
|
+
* subtree until it lands on `column`-leaf nodes, where it is folded into
|
|
18
|
+
* the leaf's spec via {@link applySpecOverrides}. Intermediate join nodes
|
|
19
|
+
* are left untouched — pframe-engine will recompute their result spec
|
|
20
|
+
* from the patched leaves.
|
|
21
|
+
*
|
|
22
|
+
* Designed to run as the `node` visitor in `mapPTableDefV2` (post-order),
|
|
23
|
+
* so by the time we see a `specOverride` its inner subtree is already
|
|
24
|
+
* mapped to {@link PColumn} leaves.
|
|
25
|
+
*
|
|
26
|
+
* Currently only `column` and `linkerJoin` are walked. Other node shapes
|
|
27
|
+
* under `specOverride` throw — pframe-engine support for them is still
|
|
28
|
+
* in progress. Extend the switch in {@link pushSpecOverrideDown} as more
|
|
29
|
+
* shapes become needed.
|
|
30
|
+
*/
|
|
31
|
+
export function collapseSpecOverrideNode(node: Node): Node {
|
|
32
|
+
if (node.type !== "specOverride") return node;
|
|
33
|
+
return pushSpecOverrideDown(node.input, node.override);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pushSpecOverrideDown(node: Node, overrides: SpecOverrides): Node {
|
|
37
|
+
switch (node.type) {
|
|
38
|
+
case "column":
|
|
39
|
+
return {
|
|
40
|
+
type: "column",
|
|
41
|
+
column: { ...node.column, spec: applySpecOverrides(node.column.spec, overrides) },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
case "linkerJoin": {
|
|
45
|
+
// `axesSpec` patches are positional against the linkerJoin's OUTPUT axes,
|
|
46
|
+
// which equal the secondary side's axes (ColumnDiscoveredRecipe emits the
|
|
47
|
+
// hit on the many-side of the linker). Route axesSpec only into secondary;
|
|
48
|
+
// applying the same positional indices to the linker would silently
|
|
49
|
+
// mis-target the linker's own axes.
|
|
50
|
+
//
|
|
51
|
+
// Non-axesSpec patches (domain / contextDomain / annotations) are
|
|
52
|
+
// column-level and not positional — keep the existing both-sides walk.
|
|
53
|
+
const { axesSpec: _axesSpec, ...nonAxesOverrides } = overrides;
|
|
54
|
+
const hasNonAxes =
|
|
55
|
+
nonAxesOverrides.domain !== undefined ||
|
|
56
|
+
nonAxesOverrides.contextDomain !== undefined ||
|
|
57
|
+
nonAxesOverrides.annotations !== undefined;
|
|
58
|
+
return {
|
|
59
|
+
...node,
|
|
60
|
+
linker: hasNonAxes ? pushSpecOverrideDown(node.linker, nonAxesOverrides) : node.linker,
|
|
61
|
+
secondary: node.secondary.map((e) => ({
|
|
62
|
+
...e,
|
|
63
|
+
entry: pushSpecOverrideDown(e.entry, overrides),
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// case "outerJoin":
|
|
69
|
+
// return {
|
|
70
|
+
// ...node,
|
|
71
|
+
// primary: {
|
|
72
|
+
// ...node.primary,
|
|
73
|
+
// entry: pushSpecOverrideDown(node.primary.entry, overrides),
|
|
74
|
+
// },
|
|
75
|
+
// secondary: node.secondary.map((e) => ({
|
|
76
|
+
// ...e,
|
|
77
|
+
// entry: pushSpecOverrideDown(e.entry, overrides),
|
|
78
|
+
// })),
|
|
79
|
+
// };
|
|
80
|
+
|
|
81
|
+
// case "innerJoin":
|
|
82
|
+
// case "fullJoin":
|
|
83
|
+
// case "symmetricJoin":
|
|
84
|
+
// return {
|
|
85
|
+
// ...node,
|
|
86
|
+
// entries: node.entries.map((e) => ({
|
|
87
|
+
// ...e,
|
|
88
|
+
// entry: pushSpecOverrideDown(e.entry, overrides),
|
|
89
|
+
// })),
|
|
90
|
+
// };
|
|
91
|
+
|
|
92
|
+
case "sliceAxes": {
|
|
93
|
+
// axesSpec patches are positional against the slice OUTPUT axes (the
|
|
94
|
+
// recipe-layer spec the consumer sees), but the inner input still
|
|
95
|
+
// carries the full pre-slice axes list. Translate outer indices to
|
|
96
|
+
// inner indices before pushing the override down.
|
|
97
|
+
//
|
|
98
|
+
// Non-axesSpec overrides (domain / contextDomain / annotations) are
|
|
99
|
+
// column-level and pass through transparently.
|
|
100
|
+
if (overrides.axesSpec === undefined || Object.keys(overrides.axesSpec).length === 0) {
|
|
101
|
+
return { ...node, input: pushSpecOverrideDown(node.input, overrides) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const innerAxes = effectiveAxesSpec(node.input);
|
|
105
|
+
if (innerAxes === undefined) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`specOverride over sliceAxes: cannot translate axesSpec — ` +
|
|
108
|
+
`inner shape unknown for input type "${node.input.type}"`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const filteredInnerIdxs = new Set<number>();
|
|
113
|
+
for (const filter of node.axisFilters) {
|
|
114
|
+
const idx = innerAxes.findIndex((a) => matchAxis(filter.axisSelector, a));
|
|
115
|
+
if (idx < 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`specOverride over sliceAxes: filter selector ` +
|
|
118
|
+
`${JSON.stringify(filter.axisSelector)} not found in inner axes`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
filteredInnerIdxs.add(idx);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// outer index j (post-slice) ↔ inner index = j-th surviving position.
|
|
125
|
+
const outerToInner: number[] = [];
|
|
126
|
+
for (let i = 0; i < innerAxes.length; i++) {
|
|
127
|
+
if (!filteredInnerIdxs.has(i)) outerToInner.push(i);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const translated: AxisPatches = {};
|
|
131
|
+
for (const [outerKey, patch] of Object.entries(overrides.axesSpec)) {
|
|
132
|
+
const outerIdx = Number(outerKey);
|
|
133
|
+
const innerIdx = outerToInner[outerIdx];
|
|
134
|
+
if (innerIdx === undefined) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`specOverride over sliceAxes: outer axesSpec index ${outerIdx} ` +
|
|
137
|
+
`out of range (slice output has ${outerToInner.length} axes)`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
translated[innerIdx] = patch;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const translatedOverrides: SpecOverrides = { ...overrides, axesSpec: translated };
|
|
144
|
+
return { ...node, input: pushSpecOverrideDown(node.input, translatedOverrides) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// // Transparent transforms — passthrough.
|
|
148
|
+
// case "sort":
|
|
149
|
+
// case "filter":
|
|
150
|
+
// case "transformColumns":
|
|
151
|
+
// return { ...node, input: pushSpecOverrideDown(node.input, overrides) };
|
|
152
|
+
|
|
153
|
+
// // Synthetic leaves — patch the spec carrier they expose, or
|
|
154
|
+
// // throw if the node has no natural place to absorb overrides.
|
|
155
|
+
// case "inlineColumn":
|
|
156
|
+
// return {
|
|
157
|
+
// ...node,
|
|
158
|
+
// column: { ...node.column, spec: applySpecOverrides(node.column.spec, overrides) },
|
|
159
|
+
// };
|
|
160
|
+
|
|
161
|
+
// case "sparseToDenseColumn":
|
|
162
|
+
// // Same idea: patch the new dense column's spec carrier.
|
|
163
|
+
// return {
|
|
164
|
+
// ...node,
|
|
165
|
+
// newSpec: applySpecOverrides(node.newSpec, overrides),
|
|
166
|
+
// };
|
|
167
|
+
|
|
168
|
+
default:
|
|
169
|
+
throw new Error(
|
|
170
|
+
`specOverride: inner node "${node.type}" is not supported yet — ` +
|
|
171
|
+
"only column / linkerJoin can carry an override " +
|
|
172
|
+
"(pframe-engine support for the rest is in progress)",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Effective post-traversal {@link AxesSpec} of a {@link Node} — used to
|
|
179
|
+
* translate positional axesSpec patches across structural nodes whose
|
|
180
|
+
* indexing differs from their input (currently only `sliceAxes`).
|
|
181
|
+
*
|
|
182
|
+
* Returns `undefined` when the node shape is not introspectable here.
|
|
183
|
+
* Callers must then refuse axesSpec translation and surface a clear error,
|
|
184
|
+
* rather than silently mis-targeting indices.
|
|
185
|
+
*/
|
|
186
|
+
function effectiveAxesSpec(node: Node): AxesSpec | undefined {
|
|
187
|
+
switch (node.type) {
|
|
188
|
+
case "column":
|
|
189
|
+
return node.column.spec.axesSpec;
|
|
190
|
+
|
|
191
|
+
case "linkerJoin":
|
|
192
|
+
// Output axes equal the secondary side's axes (linker side drops out).
|
|
193
|
+
// All secondary entries share the same axis identity post-join — read
|
|
194
|
+
// the first one.
|
|
195
|
+
return node.secondary.length === 0 ? undefined : effectiveAxesSpec(node.secondary[0].entry);
|
|
196
|
+
|
|
197
|
+
case "sliceAxes": {
|
|
198
|
+
const inner = effectiveAxesSpec(node.input);
|
|
199
|
+
if (inner === undefined) return undefined;
|
|
200
|
+
const filtered = new Set<number>();
|
|
201
|
+
for (const f of node.axisFilters) {
|
|
202
|
+
const i = inner.findIndex((a) => matchAxis(f.axisSelector, a));
|
|
203
|
+
if (i >= 0) filtered.add(i);
|
|
204
|
+
}
|
|
205
|
+
return inner.filter((_, i) => !filtered.has(i));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
default:
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -22,9 +22,13 @@ export type BlockContextFull = BlockContextArgsOnly & {
|
|
|
22
22
|
readonly prod: (cCtx: ComputableCtx) => PlTreeEntry | undefined;
|
|
23
23
|
readonly staging: (cCtx: ComputableCtx) => PlTreeEntry | undefined;
|
|
24
24
|
readonly getResultsPool: (cCtx: ComputableCtx) => ResultPool;
|
|
25
|
+
readonly projectEntry: PlTreeEntry;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
export type BlockContextAny = Optional<
|
|
28
|
+
export type BlockContextAny = Optional<
|
|
29
|
+
BlockContextFull,
|
|
30
|
+
"prod" | "staging" | "getResultsPool" | "projectEntry"
|
|
31
|
+
>;
|
|
28
32
|
|
|
29
33
|
export function constructBlockContextArgsOnly(
|
|
30
34
|
projectEntry: PlTreeEntry,
|
|
@@ -159,5 +163,6 @@ export function constructBlockContext(
|
|
|
159
163
|
return result;
|
|
160
164
|
},
|
|
161
165
|
getResultsPool: (cCtx: ComputableCtx) => ResultPool.create(cCtx, projectEntry, blockId),
|
|
166
|
+
projectEntry,
|
|
162
167
|
};
|
|
163
168
|
}
|
|
@@ -39,8 +39,6 @@ function createTestCacheInTx(pl: Parameters<Parameters<typeof TestHelpers.withTe
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// ─── flattenTemplateTree ─────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
42
|
describe("flattenTemplateTree", () => {
|
|
45
43
|
test("produces nodes in topological order for V2 template", () => {
|
|
46
44
|
const data = parseTemplate(ExplicitTemplateEnterNumbers);
|
|
@@ -77,8 +75,6 @@ describe("flattenTemplateTree", () => {
|
|
|
77
75
|
});
|
|
78
76
|
});
|
|
79
77
|
|
|
80
|
-
// ─── getOrCreateTemplateCache / dropTemplateCache ────────────────────────────
|
|
81
|
-
|
|
82
78
|
describe("getOrCreateTemplateCache", () => {
|
|
83
79
|
test("creates cache on first call and reuses on second", async () => {
|
|
84
80
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -100,8 +96,6 @@ describe("dropTemplateCache", () => {
|
|
|
100
96
|
});
|
|
101
97
|
});
|
|
102
98
|
|
|
103
|
-
// ─── loadTemplateCached ──────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
99
|
describe("loadTemplateCached", () => {
|
|
106
100
|
test("cache miss then cache hit returns same SignedResourceId", async () => {
|
|
107
101
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -184,8 +178,6 @@ describe("loadTemplateCached", () => {
|
|
|
184
178
|
}, 15000);
|
|
185
179
|
});
|
|
186
180
|
|
|
187
|
-
// ─── cacheBlockPackTemplate ──────────────────────────────────────────────────
|
|
188
|
-
|
|
189
181
|
describe("cacheBlockPackTemplate", () => {
|
|
190
182
|
test("replaces prepared template with cached reference", async () => {
|
|
191
183
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -235,8 +227,6 @@ describe("cacheBlockPackTemplate", () => {
|
|
|
235
227
|
}, 15000);
|
|
236
228
|
});
|
|
237
229
|
|
|
238
|
-
// ─── Equivalence: cached vs legacy produce identical resources ───────────────
|
|
239
|
-
|
|
240
230
|
describe("template cache produces equivalent resources", () => {
|
|
241
231
|
test("cached and legacy templates deduplicate to same original (V2)", async () => {
|
|
242
232
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -272,8 +262,6 @@ describe("template cache produces equivalent resources", () => {
|
|
|
272
262
|
}, 30000);
|
|
273
263
|
});
|
|
274
264
|
|
|
275
|
-
// ─── Shared library dedup ────────────────────────────────────────────────────
|
|
276
|
-
|
|
277
265
|
describe("shared library dedup", () => {
|
|
278
266
|
test("two different templates sharing a library reuse the same cache entry", async () => {
|
|
279
267
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -303,8 +291,6 @@ describe("shared library dedup", () => {
|
|
|
303
291
|
}, 15000);
|
|
304
292
|
});
|
|
305
293
|
|
|
306
|
-
// ─── GC ──────────────────────────────────────────────────────────────────────
|
|
307
|
-
|
|
308
294
|
describe("GC", () => {
|
|
309
295
|
test("evicts entries when cache exceeds max entries", async () => {
|
|
310
296
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
@@ -48,8 +48,6 @@ export const ACCESS_COUNT_KEY = "_accessCount";
|
|
|
48
48
|
/** @internal exported for testing */
|
|
49
49
|
export const ACCESS_KEY_PREFIX = "access_";
|
|
50
50
|
|
|
51
|
-
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
51
|
export type TemplateCacheStat = {
|
|
54
52
|
totalMs: number;
|
|
55
53
|
flattenMs: number;
|
|
@@ -82,8 +80,6 @@ function initialStat(): TemplateCacheStat {
|
|
|
82
80
|
};
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
// ─── Tree node abstraction ───────────────────────────────────────────────────
|
|
86
|
-
|
|
87
83
|
interface CacheableNode {
|
|
88
84
|
/** SHA-256 content hash (includes all descendant content) */
|
|
89
85
|
hash: string;
|
|
@@ -94,8 +90,6 @@ interface CacheableNode {
|
|
|
94
90
|
childHashes: string[];
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
// ─── Hash computation helpers ────────────────────────────────────────────────
|
|
98
|
-
|
|
99
93
|
function getSourceCode(name: string, sources: Record<string, string>, sourceHash: string): string {
|
|
100
94
|
return notEmpty(
|
|
101
95
|
sources[sourceHash],
|
|
@@ -448,8 +442,6 @@ export function flattenTemplateTree(data: TemplateData | CompiledTemplateV3): Ca
|
|
|
448
442
|
}
|
|
449
443
|
}
|
|
450
444
|
|
|
451
|
-
// ─── Cache operations ────────────────────────────────────────────────────────
|
|
452
|
-
|
|
453
445
|
/** In-memory cache for the TemplateCache SignedResourceId per PlClient instance. */
|
|
454
446
|
const cacheRidMap = new WeakMap<PlClient, SignedResourceId>();
|
|
455
447
|
|
|
@@ -502,8 +494,6 @@ export async function dropTemplateCache(pl: PlClient): Promise<void> {
|
|
|
502
494
|
invalidateTemplateCacheId(pl);
|
|
503
495
|
}
|
|
504
496
|
|
|
505
|
-
// ─── GC ──────────────────────────────────────────────────────────────────────
|
|
506
|
-
|
|
507
497
|
/**
|
|
508
498
|
* Run count-based garbage collection on the template cache.
|
|
509
499
|
* Evicts least-recently-used entries when the cache exceeds maxEntries.
|
|
@@ -549,8 +539,6 @@ export async function runGc(
|
|
|
549
539
|
});
|
|
550
540
|
}
|
|
551
541
|
|
|
552
|
-
// ─── Batched materialization ─────────────────────────────────────────────────
|
|
553
|
-
|
|
554
542
|
/** Create a batch of cache nodes in the current transaction. */
|
|
555
543
|
function createBatchNodes(
|
|
556
544
|
tx: PlTransaction,
|
|
@@ -788,8 +776,6 @@ export async function loadTemplateCached(
|
|
|
788
776
|
}
|
|
789
777
|
}
|
|
790
778
|
|
|
791
|
-
// ─── Caller helper ───────────────────────────────────────────────────────────
|
|
792
|
-
|
|
793
779
|
/**
|
|
794
780
|
* Pre-materialize a block pack's template via cache.
|
|
795
781
|
* Returns a copy of the spec with the template replaced by a cached reference.
|
package/src/pool/data.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createGlobalPObjectId,
|
|
3
|
+
createLocalPObjectId,
|
|
2
4
|
PFrameDriverError,
|
|
3
5
|
type BinaryChunk,
|
|
4
6
|
type ParquetChunk,
|
|
5
7
|
type ParquetChunkMapping,
|
|
6
8
|
type ParquetChunkMetadata,
|
|
7
9
|
type PColumnValue,
|
|
8
|
-
type PlRef,
|
|
9
10
|
type PObjectId,
|
|
10
11
|
type PObjectSpec,
|
|
11
12
|
} from "@platforma-sdk/model";
|
|
@@ -331,9 +332,9 @@ export function deriveLegacyPObjectId(spec: PObjectSpec, data: PlTreeNodeAccesso
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
export function deriveGlobalPObjectId(blockId: string, exportName: string): PObjectId {
|
|
334
|
-
return
|
|
335
|
+
return createGlobalPObjectId(blockId, exportName);
|
|
335
336
|
}
|
|
336
337
|
|
|
337
338
|
export function deriveLocalPObjectId(resolvePath: string[], outputName: string): PObjectId {
|
|
338
|
-
return
|
|
339
|
+
return createLocalPObjectId(resolvePath, outputName);
|
|
339
340
|
}
|
package/src/pool/result_pool.ts
CHANGED
|
@@ -4,13 +4,12 @@ import type {
|
|
|
4
4
|
Option,
|
|
5
5
|
PObject,
|
|
6
6
|
PObjectSpec,
|
|
7
|
-
PSpecPredicate,
|
|
8
7
|
PlRef,
|
|
9
8
|
ResultCollection,
|
|
10
9
|
ResultPoolEntry,
|
|
11
10
|
ValueOrError,
|
|
12
11
|
} from "@platforma-sdk/model";
|
|
13
|
-
import {
|
|
12
|
+
import { mapValueInVOE } from "@platforma-sdk/model";
|
|
14
13
|
import { notEmpty } from "@milaboratories/ts-helpers";
|
|
15
14
|
import { outputRef } from "../model/args";
|
|
16
15
|
import type { Block, ProjectStructure } from "../model/project_model";
|
|
@@ -239,28 +238,6 @@ export class ResultPool {
|
|
|
239
238
|
return { entries, isComplete, instabilityMarker };
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
public calculateOptions(predicate: PSpecPredicate): ExtendedOption[] {
|
|
243
|
-
const found: ExtendedOption[] = [];
|
|
244
|
-
for (const block of this.blocks.values()) {
|
|
245
|
-
const exportsChecked = new Set<string>();
|
|
246
|
-
const addToFound = (ctx: RawPObjectCollection) => {
|
|
247
|
-
for (const [exportName, result] of ctx.results) {
|
|
248
|
-
if (exportsChecked.has(exportName) || result.spec === undefined) continue;
|
|
249
|
-
exportsChecked.add(exportName);
|
|
250
|
-
if (executePSpecPredicate(predicate, result.spec))
|
|
251
|
-
found.push({
|
|
252
|
-
label: block.info.label + " / " + exportName,
|
|
253
|
-
ref: outputRef(block.info.id, exportName),
|
|
254
|
-
spec: result.spec,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
if (block.staging !== undefined) addToFound(block.staging);
|
|
259
|
-
if (block.prod !== undefined) addToFound(block.prod);
|
|
260
|
-
}
|
|
261
|
-
return found;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
241
|
public static create(ctx: ComputableCtx, prjEntry: PlTreeEntry, rootBlockId: string): ResultPool {
|
|
265
242
|
const prj = ctx.accessor(prjEntry).node();
|
|
266
243
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComputableCtx } from "@milaboratories/computable";
|
|
2
|
+
import type { PlTreeEntry, PlTreeNodeAccessor } from "@milaboratories/pl-tree";
|
|
3
|
+
import { notEmpty } from "@milaboratories/ts-helpers";
|
|
4
|
+
import type { UpstreamBlockCtx } from "@milaboratories/pl-model-common";
|
|
5
|
+
import type { ProjectStructure } from "../model/project_model";
|
|
6
|
+
import { ProjectStructureKey, projectFieldName } from "../model/project_model";
|
|
7
|
+
import { allBlocks, stagingGraph } from "../model/project_model_util";
|
|
8
|
+
|
|
9
|
+
export type { UpstreamBlockCtx } from "@milaboratories/pl-model-common";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Collect upstream-block ctx accessors for the given root block.
|
|
13
|
+
*
|
|
14
|
+
* For each upstream block (per the staging graph) we try to resolve its
|
|
15
|
+
* `prodUiCtx` and `stagingUiCtx` resources. Only the `.ok` outcomes are
|
|
16
|
+
* surfaced; failures and missing fields collapse to `undefined`.
|
|
17
|
+
*
|
|
18
|
+
* SDK-side providers compose enumerate/status/data operations on top of these
|
|
19
|
+
* accessors using the helpers in `column_providers/accessor_traversal`.
|
|
20
|
+
*
|
|
21
|
+
* Note: this function is the only place that reads the project tree to build
|
|
22
|
+
* the upstream-pool view — the legacy `ResultPool` class is not used.
|
|
23
|
+
*/
|
|
24
|
+
export function collectUpstreamBlockCtx(
|
|
25
|
+
ctx: ComputableCtx,
|
|
26
|
+
prjEntry: PlTreeEntry,
|
|
27
|
+
rootBlockId: string,
|
|
28
|
+
): UpstreamBlockCtx<PlTreeNodeAccessor>[] {
|
|
29
|
+
const prj = ctx.accessor(prjEntry).node();
|
|
30
|
+
const structure = notEmpty(prj.getKeyValueAsJson<ProjectStructure>(ProjectStructureKey));
|
|
31
|
+
const graph = stagingGraph(structure);
|
|
32
|
+
const targetBlocks = graph.traverseIds("upstream", rootBlockId);
|
|
33
|
+
|
|
34
|
+
const out: UpstreamBlockCtx<PlTreeNodeAccessor>[] = [];
|
|
35
|
+
for (const blockInfo of allBlocks(structure)) {
|
|
36
|
+
if (!targetBlocks.has(blockInfo.id)) continue;
|
|
37
|
+
|
|
38
|
+
const prod = resolveCtxAccessor(prj, blockInfo.id, "prod");
|
|
39
|
+
const staging = resolveCtxAccessor(prj, blockInfo.id, "staging");
|
|
40
|
+
|
|
41
|
+
const prodIncomplete = prod.calculated && prod.accessor === undefined;
|
|
42
|
+
const stagingIncomplete = staging.calculated && staging.accessor === undefined;
|
|
43
|
+
|
|
44
|
+
out.push({
|
|
45
|
+
blockId: blockInfo.id,
|
|
46
|
+
prodCtx: prod.accessor,
|
|
47
|
+
stagingCtx: staging.accessor,
|
|
48
|
+
prodIncomplete: prodIncomplete || undefined,
|
|
49
|
+
stagingIncomplete: stagingIncomplete || undefined,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface CtxProbe {
|
|
56
|
+
/** True if the ctx-holder field exists in the project tree (block has started rendering). */
|
|
57
|
+
readonly calculated: boolean;
|
|
58
|
+
/** Resolved ui-ctx accessor (only when `.ok`). */
|
|
59
|
+
readonly accessor: PlTreeNodeAccessor | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveCtxAccessor(
|
|
63
|
+
prj: PlTreeNodeAccessor,
|
|
64
|
+
blockId: string,
|
|
65
|
+
kind: "prod" | "staging",
|
|
66
|
+
): CtxProbe {
|
|
67
|
+
const ctxField = kind === "prod" ? "prodCtx" : "stagingCtx";
|
|
68
|
+
const uiCtxField = kind === "prod" ? "prodUiCtx" : "stagingUiCtx";
|
|
69
|
+
|
|
70
|
+
const calculated =
|
|
71
|
+
prj.traverse({
|
|
72
|
+
field: projectFieldName(blockId, ctxField),
|
|
73
|
+
ignoreError: true,
|
|
74
|
+
pureFieldErrorToUndefined: true,
|
|
75
|
+
stableIfNotFound: true,
|
|
76
|
+
}) !== undefined;
|
|
77
|
+
|
|
78
|
+
const uiCtx = prj.traverseOrError({
|
|
79
|
+
field: projectFieldName(blockId, uiCtxField),
|
|
80
|
+
stableIfNotFound: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (uiCtx === undefined || !uiCtx.ok) return { calculated, accessor: undefined };
|
|
84
|
+
return { calculated, accessor: uiCtx.value };
|
|
85
|
+
}
|
package/src/service_factories.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { ModelServiceRegistry, Services } from "@milaboratories/pl-model-common";
|
|
10
10
|
import { SpecDriver } from "@milaboratories/pf-spec-driver";
|
|
11
|
+
import { ColumnsCollectionDriverImpl } from "@milaboratories/columns-collection-driver";
|
|
11
12
|
import { type MiLogger } from "@milaboratories/ts-helpers";
|
|
12
13
|
|
|
13
14
|
export type ModelServiceOptions = {
|
|
@@ -19,5 +20,6 @@ export function createModelServiceRegistry(options: ModelServiceOptions) {
|
|
|
19
20
|
PFrameSpec: () => new SpecDriver({ logger: options.logger }),
|
|
20
21
|
PFrame: null,
|
|
21
22
|
Dialog: null,
|
|
23
|
+
ColumnsCollection: () => new ColumnsCollectionDriverImpl(),
|
|
22
24
|
});
|
|
23
25
|
}
|