@milaboratories/pl-middle-layer 1.58.4 → 1.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/middle_layer/middle_layer.cjs +71 -47
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +18 -18
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +72 -48
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/project.cjs +8 -9
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts +6 -5
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +9 -10
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_list.cjs +3 -3
- package/dist/middle_layer/project_list.cjs.map +1 -1
- package/dist/middle_layer/project_list.d.ts +1 -1
- package/dist/middle_layer/project_list.js +4 -4
- package/dist/middle_layer/project_list.js.map +1 -1
- package/dist/middle_layer/project_overview.cjs.map +1 -1
- package/dist/middle_layer/project_overview.js.map +1 -1
- package/dist/middle_layer/util.cjs.map +1 -1
- package/dist/middle_layer/util.js.map +1 -1
- package/dist/model/index.d.ts +2 -2
- package/dist/model/project_helper.cjs.map +1 -1
- package/dist/model/project_helper.d.ts +3 -3
- package/dist/model/project_helper.d.ts.map +1 -1
- package/dist/model/project_helper.js.map +1 -1
- package/dist/model/project_model.cjs.map +1 -1
- package/dist/model/project_model.d.ts +6 -5
- package/dist/model/project_model.d.ts.map +1 -1
- package/dist/model/project_model.js.map +1 -1
- package/dist/model/template_spec.d.ts +2 -2
- package/dist/model/template_spec.d.ts.map +1 -1
- package/dist/mutator/migration.cjs +1 -1
- package/dist/mutator/migration.cjs.map +1 -1
- package/dist/mutator/migration.js +2 -2
- package/dist/mutator/migration.js.map +1 -1
- package/dist/mutator/project.cjs +6 -6
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts +3 -3
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +7 -7
- package/dist/mutator/project.js.map +1 -1
- package/dist/mutator/template/template_cache.cjs +7 -7
- package/dist/mutator/template/template_cache.cjs.map +1 -1
- package/dist/mutator/template/template_cache.d.ts +8 -8
- package/dist/mutator/template/template_cache.d.ts.map +1 -1
- package/dist/mutator/template/template_cache.js +8 -8
- package/dist/mutator/template/template_cache.js.map +1 -1
- package/dist/network_check/template.cjs +5 -5
- package/dist/network_check/template.cjs.map +1 -1
- package/dist/network_check/template.js +6 -6
- package/dist/network_check/template.js.map +1 -1
- package/dist/pool/data.cjs +2 -1
- package/dist/pool/data.cjs.map +1 -1
- package/dist/pool/data.d.ts +1 -1
- package/dist/pool/data.d.ts.map +1 -1
- package/dist/pool/data.js +3 -2
- package/dist/pool/data.js.map +1 -1
- package/dist/pool/driver.cjs +3 -1
- package/dist/pool/driver.cjs.map +1 -1
- package/dist/pool/driver.d.ts.map +1 -1
- package/dist/pool/driver.js +3 -1
- package/dist/pool/driver.js.map +1 -1
- package/package.json +15 -15
- package/src/index.ts +1 -1
- package/src/middle_layer/middle_layer.ts +99 -61
- package/src/middle_layer/project.ts +14 -13
- package/src/middle_layer/project_list.ts +10 -8
- package/src/middle_layer/project_overview.ts +2 -2
- package/src/middle_layer/render.test.ts +9 -9
- package/src/middle_layer/util.ts +2 -2
- package/src/model/index.ts +1 -1
- package/src/model/project_helper.ts +2 -2
- package/src/model/project_model.ts +7 -4
- package/src/model/template_spec.ts +2 -2
- package/src/mutator/block-pack/block_pack.test.ts +7 -2
- package/src/mutator/migration.ts +7 -7
- package/src/mutator/project.ts +20 -19
- package/src/mutator/template/template_cache.test.ts +6 -6
- package/src/mutator/template/template_cache.ts +24 -21
- package/src/mutator/template/template_render.test.ts +7 -7
- package/src/network_check/template.ts +8 -8
- package/src/pool/data.ts +6 -4
- package/src/pool/driver.ts +3 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { PruningFunction } from "@milaboratories/pl-tree";
|
|
2
2
|
import { SynchronizedTreeState } from "@milaboratories/pl-tree";
|
|
3
|
-
import type { PlClient,
|
|
4
|
-
import { resourceTypesEqual } from "@milaboratories/pl-client";
|
|
3
|
+
import type { PlClient, SignedResourceId, ResourceType } from "@milaboratories/pl-client";
|
|
4
|
+
import { resourceIdToString, resourceTypesEqual } from "@milaboratories/pl-client";
|
|
5
5
|
import type { TreeAndComputableU } from "./types";
|
|
6
6
|
import type { WatchableValue } from "@milaboratories/computable";
|
|
7
7
|
import { Computable } from "@milaboratories/computable";
|
|
8
|
-
import type { ProjectListEntry } from "../model/project_model";
|
|
8
|
+
import type { ProjectId, ProjectListEntry } from "../model/project_model";
|
|
9
9
|
import {
|
|
10
10
|
ProjectCreatedTimestamp,
|
|
11
11
|
ProjectLastModifiedTimestamp,
|
|
@@ -25,8 +25,8 @@ export const ProjectsListTreePruningFunction: PruningFunction = (resource) => {
|
|
|
25
25
|
|
|
26
26
|
export async function createProjectList(
|
|
27
27
|
pl: PlClient,
|
|
28
|
-
rid:
|
|
29
|
-
openedProjects: WatchableValue<
|
|
28
|
+
rid: SignedResourceId,
|
|
29
|
+
openedProjects: WatchableValue<ProjectId[]>,
|
|
30
30
|
env: MiddleLayerEnvironment,
|
|
31
31
|
): Promise<TreeAndComputableU<ProjectListEntry[]>> {
|
|
32
32
|
const tree = await SynchronizedTreeState.init(
|
|
@@ -44,18 +44,20 @@ export async function createProjectList(
|
|
|
44
44
|
const oProjects = openedProjects.getValue(ctx);
|
|
45
45
|
if (node === undefined) return undefined;
|
|
46
46
|
const result: ProjectListEntry[] = [];
|
|
47
|
+
|
|
48
|
+
// Projects list resource keeps projects assigned to fields. Each field name is project's UUID
|
|
47
49
|
for (const field of node.listDynamicFields()) {
|
|
48
50
|
const prj = node.traverse(field);
|
|
49
51
|
if (prj === undefined) continue;
|
|
50
52
|
const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));
|
|
51
53
|
const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));
|
|
52
54
|
const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));
|
|
55
|
+
const projectId = resourceIdToString(prj.id) as ProjectId;
|
|
53
56
|
result.push({
|
|
54
|
-
id:
|
|
55
|
-
rid: prj.id,
|
|
57
|
+
id: projectId,
|
|
56
58
|
created: new Date(created),
|
|
57
59
|
lastModified: new Date(lastModified),
|
|
58
|
-
opened: oProjects.indexOf(
|
|
60
|
+
opened: oProjects.indexOf(projectId) >= 0,
|
|
59
61
|
meta,
|
|
60
62
|
});
|
|
61
63
|
}
|
|
@@ -28,12 +28,12 @@ import { extractCodeWithInfo, wrapCallback } from "@platforma-sdk/model";
|
|
|
28
28
|
import { computableFromCfgOrRF } from "./render";
|
|
29
29
|
import type { NavigationStates } from "./navigation_states";
|
|
30
30
|
import { getBlockPackInfo } from "./util";
|
|
31
|
-
import { resourceIdToString, type
|
|
31
|
+
import { resourceIdToString, type SignedResourceId } from "@milaboratories/pl-client";
|
|
32
32
|
import { omitBy, isEqual } from "es-toolkit";
|
|
33
33
|
import { getDebugFlags } from "../debug";
|
|
34
34
|
|
|
35
35
|
type BlockInfo = {
|
|
36
|
-
argsRid?:
|
|
36
|
+
argsRid?: SignedResourceId;
|
|
37
37
|
currentArguments: unknown;
|
|
38
38
|
prod?: ProdState;
|
|
39
39
|
};
|
|
@@ -60,9 +60,9 @@ export async function awaitBlockDone(prj: Project, blockId: string, timeout: num
|
|
|
60
60
|
|
|
61
61
|
test("test JS render enter numbers", async () => {
|
|
62
62
|
await withMl(async (ml) => {
|
|
63
|
-
const
|
|
64
|
-
await ml.openProject(
|
|
65
|
-
const prj = ml.getOpenedProject(
|
|
63
|
+
const prj1Id = await ml.createProject({ label: "Project 1" });
|
|
64
|
+
await ml.openProject(prj1Id);
|
|
65
|
+
const prj = ml.getOpenedProject(prj1Id);
|
|
66
66
|
|
|
67
67
|
const block1Id = await prj.addBlock("Block 1", {
|
|
68
68
|
type: "from-registry-v1",
|
|
@@ -82,9 +82,9 @@ test("test JS render enter numbers", async () => {
|
|
|
82
82
|
|
|
83
83
|
test.skip("test JS render options", async () => {
|
|
84
84
|
await withMl(async (ml) => {
|
|
85
|
-
const
|
|
86
|
-
await ml.openProject(
|
|
87
|
-
const prj = ml.getOpenedProject(
|
|
85
|
+
const prj1Id = await ml.createProject({ label: "Project 1" });
|
|
86
|
+
await ml.openProject(prj1Id);
|
|
87
|
+
const prj = ml.getOpenedProject(prj1Id);
|
|
88
88
|
|
|
89
89
|
const block1Id = await prj.addBlock("Block 1", {
|
|
90
90
|
type: "from-registry-v1",
|
|
@@ -118,9 +118,9 @@ test.skip("test JS render options", async () => {
|
|
|
118
118
|
|
|
119
119
|
test.skip("test JS render download", async () => {
|
|
120
120
|
await withMl(async (ml) => {
|
|
121
|
-
const
|
|
122
|
-
await ml.openProject(
|
|
123
|
-
const prj = ml.getOpenedProject(
|
|
121
|
+
const prj1Id = await ml.createProject({ label: "Project 1" });
|
|
122
|
+
await ml.openProject(prj1Id);
|
|
123
|
+
const prj = ml.getOpenedProject(prj1Id);
|
|
124
124
|
|
|
125
125
|
const block1Id = await prj.addBlock("Block 1", {
|
|
126
126
|
type: "from-registry-v1",
|
package/src/middle_layer/util.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PlTreeNodeAccessor } from "@milaboratories/pl-tree";
|
|
2
2
|
import { projectFieldName } from "../model/project_model";
|
|
3
|
-
import type {
|
|
3
|
+
import type { SignedResourceId } from "@milaboratories/pl-client";
|
|
4
4
|
import { Pl } from "@milaboratories/pl-client";
|
|
5
5
|
import { ifNotUndef } from "../cfg_render/util";
|
|
6
6
|
import type { BlockPackInfo } from "../model/block_pack";
|
|
@@ -8,7 +8,7 @@ import type { BlockConfig } from "@platforma-sdk/model";
|
|
|
8
8
|
import { extractConfig } from "@platforma-sdk/model";
|
|
9
9
|
|
|
10
10
|
export type BlockPackInfoAndId = {
|
|
11
|
-
readonly bpResourceId:
|
|
11
|
+
readonly bpResourceId: SignedResourceId;
|
|
12
12
|
/** To be added to computable keys, to force reload on config change */
|
|
13
13
|
readonly bpId: string;
|
|
14
14
|
/** Full block-pack info */
|
package/src/model/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { LRUCache } from "lru-cache";
|
|
10
10
|
import type { QuickJSWASMModule } from "quickjs-emscripten";
|
|
11
11
|
import { executeSingleLambda } from "../js_render";
|
|
12
|
-
import type {
|
|
12
|
+
import type { SignedResourceId } from "@milaboratories/pl-client";
|
|
13
13
|
import { ConsoleLoggerAdapter, type MiLogger } from "@milaboratories/ts-helpers";
|
|
14
14
|
import type { StorageDebugView } from "@milaboratories/pl-model-middle-layer";
|
|
15
15
|
|
|
@@ -146,7 +146,7 @@ export class ProjectHelper {
|
|
|
146
146
|
public getEnrichmentTargets(
|
|
147
147
|
blockConfig: () => BlockConfig,
|
|
148
148
|
args: () => unknown,
|
|
149
|
-
key?: { argsRid:
|
|
149
|
+
key?: { argsRid: SignedResourceId; blockPackRid: SignedResourceId },
|
|
150
150
|
): PlRef[] | undefined {
|
|
151
151
|
const req = { blockConfig, args };
|
|
152
152
|
if (key === undefined) return this.calculateEnrichmentTargets(req);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResourceType } from "@milaboratories/pl-client";
|
|
2
|
+
import type { ProjectId } from "@milaboratories/pl-model-common";
|
|
2
3
|
import type {
|
|
3
4
|
ProjectListEntry as ProjectListEntryFromModel,
|
|
4
5
|
ProjectMeta,
|
|
5
6
|
} from "@milaboratories/pl-model-middle-layer";
|
|
6
7
|
import type { BlockRenderingMode } from "@platforma-sdk/model";
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export type { ProjectId };
|
|
10
|
+
|
|
11
|
+
export interface ProjectListEntry extends Omit<ProjectListEntryFromModel, "id"> {
|
|
12
|
+
/** Unique project identifier in middle layer. Use to operate with given project. */
|
|
13
|
+
id: ProjectId;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/** Entry representing a single block in block structure */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SignedResourceId } from "@milaboratories/pl-client";
|
|
2
2
|
import type { CompiledTemplateV3, TemplateData } from "@milaboratories/pl-model-backend";
|
|
3
3
|
|
|
4
4
|
export interface TemplateFromRegistry {
|
|
@@ -19,7 +19,7 @@ export interface PreparedTemplate {
|
|
|
19
19
|
|
|
20
20
|
export interface CachedTemplate {
|
|
21
21
|
readonly type: "cached";
|
|
22
|
-
readonly resourceId:
|
|
22
|
+
readonly resourceId: SignedResourceId;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface TemplateFromFile {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isNullSignedResourceId,
|
|
3
|
+
poll,
|
|
4
|
+
TestHelpers,
|
|
5
|
+
toGlobalResourceId,
|
|
6
|
+
} from "@milaboratories/pl-client";
|
|
2
7
|
import { defaultHttpDispatcher } from "@milaboratories/pl-http";
|
|
3
8
|
import { HmacSha256Signer } from "@milaboratories/ts-helpers";
|
|
4
9
|
import path from "node:path";
|
|
@@ -50,7 +55,7 @@ test.each([
|
|
|
50
55
|
|
|
51
56
|
await poll(pl, async (a) => {
|
|
52
57
|
const r = await a.get(bp).then((r) => r.final()); // this will await final state
|
|
53
|
-
expect(
|
|
58
|
+
expect(isNullSignedResourceId(r.data.error)).toBe(true);
|
|
54
59
|
});
|
|
55
60
|
});
|
|
56
61
|
});
|
package/src/mutator/migration.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UiError } from "@milaboratories/pl-model-common";
|
|
2
|
-
import type { PlClient, PlTransaction,
|
|
2
|
+
import type { PlClient, PlTransaction, SignedResourceId } from "@milaboratories/pl-client";
|
|
3
3
|
import type { ProjectField, ProjectStructure } from "../model/project_model";
|
|
4
4
|
import {
|
|
5
5
|
projectFieldName,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SchemaVersionV3,
|
|
11
11
|
} from "../model/project_model";
|
|
12
12
|
import { BlockFrontendStateKeyPrefixV1, SchemaVersionV1 } from "../model/project_model_v1";
|
|
13
|
-
import { field,
|
|
13
|
+
import { field, isNullSignedResourceId } from "@milaboratories/pl-client";
|
|
14
14
|
import { cachedDeserialize } from "@milaboratories/ts-helpers";
|
|
15
15
|
import { allBlocks } from "../model/project_model_util";
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ import { allBlocks } from "../model/project_model_util";
|
|
|
20
20
|
* @param pl - The client to use.
|
|
21
21
|
* @param rid - The resource id of the project.
|
|
22
22
|
*/
|
|
23
|
-
export async function applyProjectMigrations(pl: PlClient, rid:
|
|
23
|
+
export async function applyProjectMigrations(pl: PlClient, rid: SignedResourceId) {
|
|
24
24
|
await pl.withWriteTx("ProjectMigration", async (tx) => {
|
|
25
25
|
let schemaVersion = await tx.getKValueJson<string>(rid, SchemaVersionKey);
|
|
26
26
|
if (schemaVersion === SchemaVersionCurrent) return;
|
|
@@ -64,7 +64,7 @@ export async function applyProjectMigrations(pl: PlClient, rid: ResourceId) {
|
|
|
64
64
|
* @param tx - The transaction to use.
|
|
65
65
|
* @param rid - The resource id of the project.
|
|
66
66
|
*/
|
|
67
|
-
async function migrateV1ToV2(tx: PlTransaction, rid:
|
|
67
|
+
async function migrateV1ToV2(tx: PlTransaction, rid: SignedResourceId) {
|
|
68
68
|
const [structure, allKV] = await Promise.all([
|
|
69
69
|
tx.getKValueJson<ProjectStructure>(rid, ProjectStructureKey),
|
|
70
70
|
tx.listKeyValues(rid),
|
|
@@ -98,16 +98,16 @@ async function migrateV1ToV2(tx: PlTransaction, rid: ResourceId) {
|
|
|
98
98
|
* @param tx - The transaction to use.
|
|
99
99
|
* @param rid - The resource id of the project.
|
|
100
100
|
*/
|
|
101
|
-
async function migrateV2ToV3(tx: PlTransaction, rid:
|
|
101
|
+
async function migrateV2ToV3(tx: PlTransaction, rid: SignedResourceId) {
|
|
102
102
|
const [structure, fullResourceState] = await Promise.all([
|
|
103
103
|
tx.getKValueJson<ProjectStructure>(rid, ProjectStructureKey),
|
|
104
104
|
tx.getResourceData(rid, true),
|
|
105
105
|
]);
|
|
106
106
|
|
|
107
107
|
// Build a map of field name -> resource id for quick lookup
|
|
108
|
-
const fieldMap = new Map<string,
|
|
108
|
+
const fieldMap = new Map<string, SignedResourceId>();
|
|
109
109
|
for (const f of fullResourceState.fields) {
|
|
110
|
-
if (!
|
|
110
|
+
if (!isNullSignedResourceId(f.value)) {
|
|
111
111
|
fieldMap.set(f.name, f.value);
|
|
112
112
|
}
|
|
113
113
|
}
|
package/src/mutator/project.ts
CHANGED
|
@@ -2,16 +2,17 @@ import type {
|
|
|
2
2
|
AnyRef,
|
|
3
3
|
AnyResourceRef,
|
|
4
4
|
BasicResourceData,
|
|
5
|
+
ResourceRef,
|
|
5
6
|
PlTransaction,
|
|
6
7
|
ResourceData,
|
|
7
|
-
|
|
8
|
+
SignedResourceId,
|
|
8
9
|
TxOps,
|
|
9
10
|
} from "@milaboratories/pl-client";
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
+
ensureSignedResourceIdNotNull,
|
|
12
13
|
field,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
isNotNullSignedResourceId,
|
|
15
|
+
isNullSignedResourceId,
|
|
15
16
|
isResource,
|
|
16
17
|
isResourceId,
|
|
17
18
|
isResourceRef,
|
|
@@ -341,7 +342,7 @@ export class ProjectMutator {
|
|
|
341
342
|
private readonly blocksWithChangedInputs = new Set<string>();
|
|
342
343
|
|
|
343
344
|
constructor(
|
|
344
|
-
public readonly rid:
|
|
345
|
+
public readonly rid: SignedResourceId,
|
|
345
346
|
private readonly tx: PlTransaction,
|
|
346
347
|
private readonly author: AuthorMarker | undefined,
|
|
347
348
|
private readonly schema: string,
|
|
@@ -359,7 +360,7 @@ export class ProjectMutator {
|
|
|
359
360
|
// Fix inconsistent production fields.
|
|
360
361
|
// All four fields (prodArgs, prodOutput, prodCtx, prodUiCtx) must be present together.
|
|
361
362
|
// prodUiCtx can be missing after project duplication: prodCtx uses a holder wrapper
|
|
362
|
-
// (always non-null), but prodUiCtx is a raw FieldRef that may still be
|
|
363
|
+
// (always non-null), but prodUiCtx is a raw FieldRef that may still be NullSignedResourceId
|
|
363
364
|
// at snapshot time and thus not copied.
|
|
364
365
|
this.blockInfos.forEach((blockInfo) => {
|
|
365
366
|
if (
|
|
@@ -1716,7 +1717,7 @@ export class ProjectMutator {
|
|
|
1716
1717
|
public static async load(
|
|
1717
1718
|
projectHelper: ProjectHelper,
|
|
1718
1719
|
tx: PlTransaction,
|
|
1719
|
-
rid:
|
|
1720
|
+
rid: SignedResourceId,
|
|
1720
1721
|
author?: AuthorMarker,
|
|
1721
1722
|
): Promise<ProjectMutator> {
|
|
1722
1723
|
//
|
|
@@ -1749,7 +1750,7 @@ export class ProjectMutator {
|
|
|
1749
1750
|
blockInfoStates.set(projectField.blockId, info);
|
|
1750
1751
|
}
|
|
1751
1752
|
|
|
1752
|
-
info.fields[projectField.fieldName] =
|
|
1753
|
+
info.fields[projectField.fieldName] = isNullSignedResourceId(f.value)
|
|
1753
1754
|
? { modCount: 0 }
|
|
1754
1755
|
: { modCount: 0, ref: f.value };
|
|
1755
1756
|
}
|
|
@@ -1811,8 +1812,8 @@ export class ProjectMutator {
|
|
|
1811
1812
|
for (const [info, fieldName, state, response] of blockFieldRequests) {
|
|
1812
1813
|
const result = await response;
|
|
1813
1814
|
state.value = result.data;
|
|
1814
|
-
if (
|
|
1815
|
-
else if (result.resourceReady ||
|
|
1815
|
+
if (isNotNullSignedResourceId(result.error)) state.status = "Error";
|
|
1816
|
+
else if (result.resourceReady || isNotNullSignedResourceId(result.originalResourceId))
|
|
1816
1817
|
state.status = "Ready";
|
|
1817
1818
|
else state.status = "NotReady";
|
|
1818
1819
|
|
|
@@ -1822,7 +1823,7 @@ export class ProjectMutator {
|
|
|
1822
1823
|
if (refField === undefined) throw new Error("Block pack ref field is missing");
|
|
1823
1824
|
blockPackRequests.push([
|
|
1824
1825
|
info,
|
|
1825
|
-
tx.getResourceData(
|
|
1826
|
+
tx.getResourceData(ensureSignedResourceIdNotNull(refField.value), false),
|
|
1826
1827
|
]);
|
|
1827
1828
|
}
|
|
1828
1829
|
}
|
|
@@ -1852,7 +1853,7 @@ export class ProjectMutator {
|
|
|
1852
1853
|
);
|
|
1853
1854
|
let ctxExportTplHolder: AnyResourceRef;
|
|
1854
1855
|
if (ctxExportTplField !== undefined)
|
|
1855
|
-
ctxExportTplHolder =
|
|
1856
|
+
ctxExportTplHolder = ensureSignedResourceIdNotNull(ctxExportTplField.value);
|
|
1856
1857
|
else {
|
|
1857
1858
|
ctxExportTplHolder = Pl.wrapInHolder(tx, loadTemplate(tx, ctxExportTplEnvelope.spec));
|
|
1858
1859
|
tx.createField(
|
|
@@ -1917,7 +1918,7 @@ export interface ProjectState {
|
|
|
1917
1918
|
export async function createProject(
|
|
1918
1919
|
tx: PlTransaction,
|
|
1919
1920
|
meta: ProjectMeta = InitialBlockMeta,
|
|
1920
|
-
): Promise<
|
|
1921
|
+
): Promise<ResourceRef> {
|
|
1921
1922
|
const prj = tx.createEphemeral(ProjectResourceType);
|
|
1922
1923
|
tx.lock(prj);
|
|
1923
1924
|
const ts = String(Date.now());
|
|
@@ -1950,9 +1951,9 @@ export async function createProject(
|
|
|
1950
1951
|
*/
|
|
1951
1952
|
export async function duplicateProject(
|
|
1952
1953
|
tx: PlTransaction,
|
|
1953
|
-
sourceRid:
|
|
1954
|
+
sourceRid: SignedResourceId,
|
|
1954
1955
|
options?: { label?: string },
|
|
1955
|
-
): Promise<
|
|
1956
|
+
): Promise<ResourceRef> {
|
|
1956
1957
|
// Read source resource data (with fields) and all KV pairs
|
|
1957
1958
|
const sourceDataP = tx.getResourceData(sourceRid, true);
|
|
1958
1959
|
const sourceKVsP = tx.listKeyValuesString(sourceRid);
|
|
@@ -2006,10 +2007,10 @@ export async function duplicateProject(
|
|
|
2006
2007
|
// Copy only persistent block fields (FieldsToDuplicate).
|
|
2007
2008
|
// Transient fields (prodChainCtx, staging*, *Previous) are rebuilt on project open
|
|
2008
2009
|
// by fixProblemsAndMigrate(). Copying them is both unnecessary and dangerous:
|
|
2009
|
-
// some fields use raw FieldRefs (not holder-wrapped) whose values may be
|
|
2010
|
+
// some fields use raw FieldRefs (not holder-wrapped) whose values may be NullSignedResourceId
|
|
2010
2011
|
// at snapshot time, leading to partially copied field groups and broken project state.
|
|
2011
2012
|
for (const f of sourceData.fields) {
|
|
2012
|
-
if (
|
|
2013
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
2013
2014
|
const parsed = parseProjectField(f.name);
|
|
2014
2015
|
if (parsed !== undefined && !FieldsToDuplicate.has(parsed.fieldName)) continue;
|
|
2015
2016
|
tx.createField(field(newPrj, f.name), "Dynamic", f.value);
|
|
@@ -2021,7 +2022,7 @@ export async function duplicateProject(
|
|
|
2021
2022
|
export async function withProject<T>(
|
|
2022
2023
|
projectHelper: ProjectHelper,
|
|
2023
2024
|
txOrPl: PlTransaction | PlClient,
|
|
2024
|
-
rid:
|
|
2025
|
+
rid: SignedResourceId,
|
|
2025
2026
|
cb: (p: ProjectMutator) => T | Promise<T>,
|
|
2026
2027
|
ops?: Partial<TxOps>,
|
|
2027
2028
|
): Promise<T> {
|
|
@@ -2031,7 +2032,7 @@ export async function withProject<T>(
|
|
|
2031
2032
|
export async function withProjectAuthored<T>(
|
|
2032
2033
|
projectHelper: ProjectHelper,
|
|
2033
2034
|
txOrPl: PlTransaction | PlClient,
|
|
2034
|
-
rid:
|
|
2035
|
+
rid: SignedResourceId,
|
|
2035
2036
|
author: AuthorMarker | undefined,
|
|
2036
2037
|
cb: (p: ProjectMutator) => T | Promise<T>,
|
|
2037
2038
|
ops: Partial<TxOps> = {},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ensureSignedResourceIdNotNull,
|
|
3
3
|
field,
|
|
4
|
-
|
|
4
|
+
isNotNullSignedResourceId,
|
|
5
5
|
poll,
|
|
6
6
|
TestHelpers,
|
|
7
7
|
toGlobalResourceId,
|
|
@@ -103,7 +103,7 @@ describe("dropTemplateCache", () => {
|
|
|
103
103
|
// ─── loadTemplateCached ──────────────────────────────────────────────────────
|
|
104
104
|
|
|
105
105
|
describe("loadTemplateCached", () => {
|
|
106
|
-
test("cache miss then cache hit returns same
|
|
106
|
+
test("cache miss then cache hit returns same SignedResourceId", async () => {
|
|
107
107
|
await TestHelpers.withTempRoot(async (pl) => {
|
|
108
108
|
const testCache = await createTestCacheInTx(pl);
|
|
109
109
|
|
|
@@ -151,7 +151,7 @@ describe("loadTemplateCached", () => {
|
|
|
151
151
|
// Verify the field was set correctly
|
|
152
152
|
await pl.withReadTx("verify", async (tx) => {
|
|
153
153
|
const fd = await tx.getField(field(pl.clientRoot, "test_result"));
|
|
154
|
-
expect(
|
|
154
|
+
expect(ensureSignedResourceIdNotNull(fd.value)).toBe(resultId);
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
}, 15000);
|
|
@@ -265,8 +265,8 @@ describe("template cache produces equivalent resources", () => {
|
|
|
265
265
|
|
|
266
266
|
// After dedup, both should resolve to the same canonical resource.
|
|
267
267
|
// Either one points to the other, or both point to a common original.
|
|
268
|
-
const resolvedCached =
|
|
269
|
-
const resolvedLegacy =
|
|
268
|
+
const resolvedCached = isNotNullSignedResourceId(cachedOriginal) ? cachedOriginal : cachedId;
|
|
269
|
+
const resolvedLegacy = isNotNullSignedResourceId(legacyOriginal) ? legacyOriginal : legacyId;
|
|
270
270
|
expect(resolvedCached).toBe(resolvedLegacy);
|
|
271
271
|
});
|
|
272
272
|
}, 30000);
|
|
@@ -3,11 +3,11 @@ import type {
|
|
|
3
3
|
AnyResourceRef,
|
|
4
4
|
PlClient,
|
|
5
5
|
PlTransaction,
|
|
6
|
-
|
|
6
|
+
SignedResourceId,
|
|
7
7
|
ResourceRef,
|
|
8
8
|
} from "@milaboratories/pl-client";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
ensureSignedResourceIdNotNull,
|
|
11
11
|
field,
|
|
12
12
|
resourceType,
|
|
13
13
|
toGlobalResourceId,
|
|
@@ -86,7 +86,7 @@ interface CacheableNode {
|
|
|
86
86
|
/** SHA-256 content hash (includes all descendant content) */
|
|
87
87
|
hash: string;
|
|
88
88
|
/** Creates this node's resource in a transaction.
|
|
89
|
-
* childRefs maps child hash → already-resolved ResourceRef or
|
|
89
|
+
* childRefs maps child hash → already-resolved ResourceRef or SignedResourceId */
|
|
90
90
|
create: (tx: PlTransaction, childRefs: ReadonlyMap<string, AnyResourceRef>) => ResourceRef;
|
|
91
91
|
/** Hashes of direct child nodes this node depends on */
|
|
92
92
|
childHashes: string[];
|
|
@@ -406,8 +406,8 @@ export function flattenTemplateTree(data: TemplateData | CompiledTemplateV3): Ca
|
|
|
406
406
|
|
|
407
407
|
// ─── Cache operations ────────────────────────────────────────────────────────
|
|
408
408
|
|
|
409
|
-
/** In-memory cache for the TemplateCache
|
|
410
|
-
const cacheRidMap = new WeakMap<PlClient,
|
|
409
|
+
/** In-memory cache for the TemplateCache SignedResourceId per PlClient instance. */
|
|
410
|
+
const cacheRidMap = new WeakMap<PlClient, SignedResourceId>();
|
|
411
411
|
|
|
412
412
|
/** Clear the in-memory cacheRid entry (call on errors referencing the cache resource). */
|
|
413
413
|
export function invalidateTemplateCacheId(pl: PlClient): void {
|
|
@@ -415,7 +415,7 @@ export function invalidateTemplateCacheId(pl: PlClient): void {
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
/** Find or create the TemplateCache/1 resource on user root. */
|
|
418
|
-
export async function getOrCreateTemplateCache(pl: PlClient): Promise<
|
|
418
|
+
export async function getOrCreateTemplateCache(pl: PlClient): Promise<SignedResourceId> {
|
|
419
419
|
// Check in-memory cache first (0ms after first call)
|
|
420
420
|
const cached = cacheRidMap.get(pl);
|
|
421
421
|
if (cached !== undefined) return cached;
|
|
@@ -423,7 +423,7 @@ export async function getOrCreateTemplateCache(pl: PlClient): Promise<ResourceId
|
|
|
423
423
|
// Try read-only check
|
|
424
424
|
const existing = await pl.withReadTx("templateCache:check", async (tx) => {
|
|
425
425
|
const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));
|
|
426
|
-
return fd ?
|
|
426
|
+
return fd ? ensureSignedResourceIdNotNull(fd.value) : undefined;
|
|
427
427
|
});
|
|
428
428
|
if (existing) {
|
|
429
429
|
cacheRidMap.set(pl, existing);
|
|
@@ -433,7 +433,7 @@ export async function getOrCreateTemplateCache(pl: PlClient): Promise<ResourceId
|
|
|
433
433
|
const result = await pl.withWriteTx("templateCache:init", async (tx) => {
|
|
434
434
|
// Double-check inside write tx (another instance may have created it)
|
|
435
435
|
const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));
|
|
436
|
-
if (fd) return
|
|
436
|
+
if (fd) return ensureSignedResourceIdNotNull(fd.value);
|
|
437
437
|
|
|
438
438
|
const cache = tx.createStruct(TemplateCacheType);
|
|
439
439
|
tx.createField(field(pl.clientRoot, TemplateCacheFieldName), "Dynamic", cache);
|
|
@@ -470,7 +470,7 @@ export async function dropTemplateCache(pl: PlClient): Promise<void> {
|
|
|
470
470
|
*/
|
|
471
471
|
export async function runGc(
|
|
472
472
|
pl: PlClient,
|
|
473
|
-
cacheRid:
|
|
473
|
+
cacheRid: SignedResourceId,
|
|
474
474
|
maxEntries: number = GC_MAX_ENTRIES,
|
|
475
475
|
): Promise<boolean> {
|
|
476
476
|
return await pl.withWriteTx("templateCache:gc", async (tx) => {
|
|
@@ -510,9 +510,9 @@ export async function runGc(
|
|
|
510
510
|
/** Create a batch of cache nodes in the current transaction. */
|
|
511
511
|
function createBatchNodes(
|
|
512
512
|
tx: PlTransaction,
|
|
513
|
-
cacheRid:
|
|
513
|
+
cacheRid: SignedResourceId,
|
|
514
514
|
batch: CacheableNode[],
|
|
515
|
-
resolvedIds: ReadonlyMap<string,
|
|
515
|
+
resolvedIds: ReadonlyMap<string, SignedResourceId>,
|
|
516
516
|
newRefs: Map<string, ResourceRef>,
|
|
517
517
|
now: string,
|
|
518
518
|
): void {
|
|
@@ -543,17 +543,17 @@ function createBatchNodes(
|
|
|
543
543
|
* Phase 2..N (one write tx per batch):
|
|
544
544
|
* - Create remaining missing nodes in BATCH_SIZE chunks
|
|
545
545
|
*
|
|
546
|
-
* @returns root
|
|
546
|
+
* @returns root SignedResourceId and current access count (for GC decision)
|
|
547
547
|
*/
|
|
548
548
|
async function materialize(
|
|
549
549
|
pl: PlClient,
|
|
550
|
-
cacheRid:
|
|
550
|
+
cacheRid: SignedResourceId,
|
|
551
551
|
rootHash: string,
|
|
552
552
|
nodes: CacheableNode[],
|
|
553
553
|
stat: TemplateCacheStat,
|
|
554
|
-
): Promise<{ rootId:
|
|
554
|
+
): Promise<{ rootId: SignedResourceId; accessCount: number }> {
|
|
555
555
|
const allHashes = nodes.map((n) => n.hash);
|
|
556
|
-
const resolvedIds = new Map<string,
|
|
556
|
+
const resolvedIds = new Map<string, SignedResourceId>();
|
|
557
557
|
|
|
558
558
|
// Phase 1: probe all + first batch
|
|
559
559
|
const phase1 = await pl.withWriteTx("templateCache:materialize", async (tx) => {
|
|
@@ -571,7 +571,7 @@ async function materialize(
|
|
|
571
571
|
// Happy path: root already cached
|
|
572
572
|
if (exists[rootIdx]) {
|
|
573
573
|
const rootFd = await tx.getField(field(cacheRid, rootHash));
|
|
574
|
-
const rootRid =
|
|
574
|
+
const rootRid = ensureSignedResourceIdNotNull(rootFd.value);
|
|
575
575
|
tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + rootHash, now);
|
|
576
576
|
tx.setKValue(cacheRid, ACCESS_COUNT_KEY, newCount.toString());
|
|
577
577
|
await tx.commit();
|
|
@@ -592,7 +592,10 @@ async function materialize(
|
|
|
592
592
|
hitIndices.map((i) => tx.getField(field(cacheRid, allHashes[i]))),
|
|
593
593
|
);
|
|
594
594
|
for (let j = 0; j < hitIndices.length; j++) {
|
|
595
|
-
resolvedIds.set(
|
|
595
|
+
resolvedIds.set(
|
|
596
|
+
allHashes[hitIndices[j]],
|
|
597
|
+
ensureSignedResourceIdNotNull(hitFields[j].value),
|
|
598
|
+
);
|
|
596
599
|
}
|
|
597
600
|
}
|
|
598
601
|
stat.cacheHits = hitIndices.length;
|
|
@@ -659,13 +662,13 @@ async function materialize(
|
|
|
659
662
|
* Materialize a template tree via the cache.
|
|
660
663
|
* Manages its own transactions internally — do NOT call inside an existing tx.
|
|
661
664
|
*
|
|
662
|
-
* @returns concrete
|
|
665
|
+
* @returns concrete SignedResourceId of the root template
|
|
663
666
|
*/
|
|
664
667
|
export async function loadTemplateCached(
|
|
665
668
|
pl: PlClient,
|
|
666
669
|
spec: TemplateSpecPrepared,
|
|
667
|
-
options?: { cacheResourceId?:
|
|
668
|
-
): Promise<
|
|
670
|
+
options?: { cacheResourceId?: SignedResourceId },
|
|
671
|
+
): Promise<SignedResourceId> {
|
|
669
672
|
const stat = initialStat();
|
|
670
673
|
const t0 = performance.now();
|
|
671
674
|
|
|
@@ -751,7 +754,7 @@ export async function loadTemplateCached(
|
|
|
751
754
|
export async function cacheBlockPackTemplate(
|
|
752
755
|
pl: PlClient,
|
|
753
756
|
spec: BlockPackSpecPrepared,
|
|
754
|
-
options?: { cacheResourceId?:
|
|
757
|
+
options?: { cacheResourceId?: SignedResourceId },
|
|
755
758
|
): Promise<BlockPackSpecPrepared> {
|
|
756
759
|
if (spec.template.type === "cached") return spec;
|
|
757
760
|
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
AnyResourceRef,
|
|
4
4
|
field,
|
|
5
5
|
getField,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
isNotNullSignedResourceId,
|
|
7
|
+
isNullSignedResourceId,
|
|
8
8
|
Pl,
|
|
9
9
|
PlClient,
|
|
10
10
|
PlTransaction,
|
|
11
11
|
ResourceData,
|
|
12
|
-
|
|
12
|
+
SignedResourceId,
|
|
13
13
|
TestHelpers,
|
|
14
14
|
valErr,
|
|
15
15
|
} from "@milaboratories/pl-client";
|
|
@@ -235,7 +235,7 @@ async function expectResultAndContext(
|
|
|
235
235
|
ok: boolean;
|
|
236
236
|
}> {
|
|
237
237
|
const fieldData = await tx.getResourceData(root, true);
|
|
238
|
-
expect(
|
|
238
|
+
expect(isNullSignedResourceId(fieldData.error)).toBe(true);
|
|
239
239
|
expectFields(fieldData, ["context", "result"]);
|
|
240
240
|
|
|
241
241
|
const ctxField = await valErr(tx, getField(fieldData, "context"));
|
|
@@ -246,7 +246,7 @@ async function expectResultAndContext(
|
|
|
246
246
|
const ctxId = ctxField.valueId;
|
|
247
247
|
const resultId = resultField.valueId;
|
|
248
248
|
|
|
249
|
-
if (
|
|
249
|
+
if (isNullSignedResourceId(ctxId) || isNullSignedResourceId(resultId)) return { ok: false };
|
|
250
250
|
|
|
251
251
|
const result = await tx.getResourceData(resultId, true);
|
|
252
252
|
const ctx = await tx.getResourceData(ctxId, true);
|
|
@@ -260,8 +260,8 @@ async function expectResource(tx: PlTransaction, res: ResourceData, fieldName: s
|
|
|
260
260
|
const f = getField(res, fieldName);
|
|
261
261
|
const ve = await valErr(tx, f);
|
|
262
262
|
expect(ve.error).toHaveLength(0);
|
|
263
|
-
expect(
|
|
264
|
-
return await tx.getResourceData(ve.valueId as
|
|
263
|
+
expect(isNotNullSignedResourceId(ve.valueId)).toBeTruthy();
|
|
264
|
+
return await tx.getResourceData(ve.valueId as SignedResourceId, true);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
async function expectData(tx: PlTransaction, result: ResourceData, fieldName: string) {
|