@milaboratories/pl-middle-layer 1.66.4 → 1.66.6
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/middle_layer/middle_layer.cjs +2 -2
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +2 -2
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/mutator/project.cjs +70 -4
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts +9 -1
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +71 -5
- package/dist/mutator/project.js.map +1 -1
- package/package.json +10 -10
- package/src/middle_layer/middle_layer.ts +12 -2
- package/src/mutator/project.ts +148 -7
package/dist/mutator/project.js
CHANGED
|
@@ -9,10 +9,19 @@ import { BLOCK_STORAGE_FACADE_VERSION, UiError, extractConfig } from "@platforma
|
|
|
9
9
|
import { InitialBlockSettings } from "@milaboratories/pl-model-middle-layer";
|
|
10
10
|
import { ConsoleLoggerAdapter, cachedDecode, cachedDeserialize, canonicalJsonBytes, notEmpty } from "@milaboratories/ts-helpers";
|
|
11
11
|
import { gzipSync } from "node:zlib";
|
|
12
|
-
import { Pl, PlClient, ensureSignedResourceIdNotNull, field, isNotNullSignedResourceId, isNullSignedResourceId, isResource, isResourceId, isResourceRef } from "@milaboratories/pl-client";
|
|
12
|
+
import { Pl, PlClient, ensureSignedResourceIdNotNull, field, isNotNullSignedResourceId, isNullSignedResourceId, isResource, isResourceId, isResourceRef, readyOrDuplicateOrError } from "@milaboratories/pl-client";
|
|
13
13
|
import { ModelAPIVersionMismatchError } from "@milaboratories/pl-errors";
|
|
14
14
|
import Denque from "denque";
|
|
15
15
|
//#region src/mutator/project.ts
|
|
16
|
+
/**
|
|
17
|
+
* A production core field is "settled" once its result is immutable — `Ready` or a finished
|
|
18
|
+
* `Error`. `NotReady` (still computing) is the only unsettled status; a missing field is unsettled.
|
|
19
|
+
* Settled production is safe to keep/share; only running production is dropped. The resource-level
|
|
20
|
+
* equivalent is {@link readyOrDuplicateOrError}.
|
|
21
|
+
*/
|
|
22
|
+
function isProductionFieldSettled(status) {
|
|
23
|
+
return status === "Ready" || status === "Error";
|
|
24
|
+
}
|
|
16
25
|
function cached(modIdCb, valueCb) {
|
|
17
26
|
let initialized = false;
|
|
18
27
|
let lastModId = void 0;
|
|
@@ -240,6 +249,32 @@ var ProjectMutator = class ProjectMutator {
|
|
|
240
249
|
if (this.actualProductionGraph === void 0) this.actualProductionGraph = productionGraph(this.struct, (blockId) => this.getProductionGraphBlockInfo(blockId, true));
|
|
241
250
|
return this.actualProductionGraph;
|
|
242
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Blocks whose production must NOT be shared with a copy of the project: those still computing
|
|
254
|
+
* ("NotReady"), plus their downstream closure. Settled production — `Ready` or a finished
|
|
255
|
+
* `Error` — is kept: it is immutable, so sharing by reference is safe, and sharing an errored
|
|
256
|
+
* project (to show it failed) is a valid use-case. Only running production would tie the copy to
|
|
257
|
+
* the source's live computation.
|
|
258
|
+
*
|
|
259
|
+
* The closure keeps the chain consistent: a finished block's `prodCtx` is built from its
|
|
260
|
+
* upstreams' `prodCtx` (see {@link createProdCtx}), so keeping it while dropping a running
|
|
261
|
+
* upstream would leave it referencing an absent context — a break {@link check} misses. Settled
|
|
262
|
+
* blocks are never seeds, so their downstream is retained.
|
|
263
|
+
*
|
|
264
|
+
* Blocks without production are ignored. Returns empty when nothing is running.
|
|
265
|
+
*/
|
|
266
|
+
productionBlocksToDropForCopy() {
|
|
267
|
+
const dropSet = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const [blockId, info] of this.blockInfos) {
|
|
269
|
+
if (!(info.fields.prodOutput !== void 0 || info.fields.prodCtx !== void 0)) continue;
|
|
270
|
+
if (!isProductionFieldSettled(info.fields.prodOutput?.status) || !isProductionFieldSettled(info.fields.prodCtx?.status)) dropSet.add(blockId);
|
|
271
|
+
}
|
|
272
|
+
if (dropSet.size === 0) return dropSet;
|
|
273
|
+
const graph = this.getActualProductionGraph();
|
|
274
|
+
const seeds = [...dropSet].filter((id) => graph.nodes.has(id));
|
|
275
|
+
for (const id of graph.traverseIds("downstream", ...seeds)) dropSet.add(id);
|
|
276
|
+
return dropSet;
|
|
277
|
+
}
|
|
243
278
|
getBlockInfo(blockId) {
|
|
244
279
|
const info = this.blockInfos.get(blockId);
|
|
245
280
|
if (info === void 0) throw new Error(`No such block: ${blockId}`);
|
|
@@ -354,12 +389,12 @@ var ProjectMutator = class ProjectMutator {
|
|
|
354
389
|
}
|
|
355
390
|
this.deleteBlockFields(blockId, "prodOutput", "prodCtx", "prodUiCtx", "prodArgs");
|
|
356
391
|
}
|
|
357
|
-
/** Running blocks are reset,
|
|
392
|
+
/** Running blocks are reset, settled ones (Ready or finished Error) moved to limbo. Returns if
|
|
358
393
|
* either of the actions were actually performed.
|
|
359
394
|
* This method ensures the block is left in a consistent state that passes check() constraints. */
|
|
360
395
|
resetOrLimboProduction(blockId) {
|
|
361
396
|
const fields = this.getBlockInfo(blockId).fields;
|
|
362
|
-
if (fields.prodOutput?.status
|
|
397
|
+
if (isProductionFieldSettled(fields.prodOutput?.status) && isProductionFieldSettled(fields.prodCtx?.status)) {
|
|
363
398
|
if (this.blocksInLimbo.has(blockId)) return false;
|
|
364
399
|
this.blocksInLimbo.add(blockId);
|
|
365
400
|
this.renderingStateChanged = true;
|
|
@@ -592,7 +627,7 @@ var ProjectMutator = class ProjectMutator {
|
|
|
592
627
|
getFieldNamesToDuplicate(blockId) {
|
|
593
628
|
const fields = this.getBlockInfo(blockId).fields;
|
|
594
629
|
const diff = (setA, setB) => new Set([...setA].filter((x) => !setB.has(x)));
|
|
595
|
-
if (fields.prodOutput?.status
|
|
630
|
+
if (isProductionFieldSettled(fields.prodOutput?.status) && isProductionFieldSettled(fields.prodCtx?.status)) {
|
|
596
631
|
if (this.blocksInLimbo.has(blockId)) return FieldsToDuplicate;
|
|
597
632
|
return diff(FieldsToDuplicate, /* @__PURE__ */ new Set([
|
|
598
633
|
"prodOutputPrevious",
|
|
@@ -1007,7 +1042,7 @@ async function createProject(tx, meta = InitialBlockMeta) {
|
|
|
1007
1042
|
* @param sourceRid - resource id of the project to duplicate
|
|
1008
1043
|
* @param options.label - optional label override for the new project; if omitted, copies the source label
|
|
1009
1044
|
*/
|
|
1010
|
-
async function duplicateProject(tx, sourceRid, options) {
|
|
1045
|
+
async function duplicateProject(tx, sourceRid, options, projectHelper) {
|
|
1011
1046
|
const sourceDataP = tx.getResourceData(sourceRid, true);
|
|
1012
1047
|
const sourceKVsP = tx.listKeyValuesString(sourceRid);
|
|
1013
1048
|
const sourceData = await sourceDataP;
|
|
@@ -1017,6 +1052,36 @@ async function duplicateProject(tx, sourceRid, options) {
|
|
|
1017
1052
|
if (schema !== "4" && schema !== "3") throw new UiError(`Cannot duplicate project with schema version ${schema ?? "unknown"}. Only schema versions 3 and 4 are supported. Try opening the project first to trigger migration.`);
|
|
1018
1053
|
const newPrj = tx.createEphemeral(ProjectResourceType);
|
|
1019
1054
|
tx.lock(newPrj);
|
|
1055
|
+
const prodFields = /* @__PURE__ */ new Set([
|
|
1056
|
+
"prodOutput",
|
|
1057
|
+
"prodCtx",
|
|
1058
|
+
"prodUiCtx",
|
|
1059
|
+
"prodArgs"
|
|
1060
|
+
]);
|
|
1061
|
+
const isTargetSettled = async (rid) => {
|
|
1062
|
+
if (rid === void 0 || isNullSignedResourceId(rid)) return false;
|
|
1063
|
+
try {
|
|
1064
|
+
return readyOrDuplicateOrError(await tx.getResourceData(rid, false));
|
|
1065
|
+
} catch {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
const prodCoreByBlock = /* @__PURE__ */ new Map();
|
|
1070
|
+
for (const f of sourceData.fields) {
|
|
1071
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
1072
|
+
const parsed = parseProjectField(f.name);
|
|
1073
|
+
if (parsed === void 0) continue;
|
|
1074
|
+
if (parsed.fieldName === "prodOutput" || parsed.fieldName === "prodCtx") {
|
|
1075
|
+
const core = prodCoreByBlock.get(parsed.blockId) ?? {};
|
|
1076
|
+
core[parsed.fieldName] = f.value;
|
|
1077
|
+
prodCoreByBlock.set(parsed.blockId, core);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const anyRunning = (await Promise.all([...prodCoreByBlock.values()].map(async (core) => !(await isTargetSettled(core.prodOutput) && await isTargetSettled(core.prodCtx))))).some((running) => running);
|
|
1081
|
+
let dropProduction;
|
|
1082
|
+
if (!anyRunning) dropProduction = /* @__PURE__ */ new Set();
|
|
1083
|
+
else if (projectHelper !== void 0 && schema === "4") dropProduction = (await ProjectMutator.load(projectHelper, tx, sourceRid)).productionBlocksToDropForCopy();
|
|
1084
|
+
else dropProduction = new Set(prodCoreByBlock.keys());
|
|
1020
1085
|
const ts = String(Date.now());
|
|
1021
1086
|
const kvSkipPrefixes = [BlockArgsAuthorKeyPrefix];
|
|
1022
1087
|
const kvSkipKeys = /* @__PURE__ */ new Set([
|
|
@@ -1041,6 +1106,7 @@ async function duplicateProject(tx, sourceRid, options) {
|
|
|
1041
1106
|
if (isNullSignedResourceId(f.value)) continue;
|
|
1042
1107
|
const parsed = parseProjectField(f.name);
|
|
1043
1108
|
if (parsed !== void 0 && !FieldsToDuplicate.has(parsed.fieldName)) continue;
|
|
1109
|
+
if (parsed !== void 0 && prodFields.has(parsed.fieldName) && dropProduction.has(parsed.blockId)) continue;
|
|
1044
1110
|
tx.createField(field(newPrj, f.name), "Dynamic", f.value);
|
|
1045
1111
|
}
|
|
1046
1112
|
return newPrj;
|