@milaboratories/pl-client 3.17.5 → 3.17.7
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/core/capabilities.cjs.map +1 -1
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/final.cjs +4 -2
- package/dist/core/final.cjs.map +1 -1
- package/dist/core/final.js +5 -3
- package/dist/core/final.js.map +1 -1
- package/package.json +5 -5
- package/src/core/capabilities.ts +3 -4
- package/src/core/final.test.ts +73 -0
- package/src/core/final.ts +13 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.cjs","names":[],"sources":["../../src/core/capabilities.ts"],"sourcesContent":["/**\n * Backend capability tokens advertised by pl in\n * `MaintenanceAPI.Ping.Response.capabilities`.\n * Capabilities affect behaviour of inner ML mechanisms and allow\n * to detect incompatibilities between blocks and backend via\n * via `TemplateDataV3.requiredCapabilities`.\n *\n * Keep tokens in sync with the backend's advertised list. Format is\n * `<feature>:<version>`; bump the version when wire semantics change.\n *\n * Shared across:\n * - `@milaboratories/pl-client` — client-side capability checks\n * - `@platforma-sdk/tengo-builder` — populates `requiredCapabilities` at compile time\n * - `@platforma-sdk/block-tools` — copies it onto the published manifest\n * - `@milaboratories/pl-middle-layer` — checks it at install time\n */\nexport type BackendCapability =\n | \"auth:v2\"\n | \"treeFilter:v2\"\n // Delta tree reads: Tree, ResourceAPI.Get and ResourceKVAPI.List accept a\n // changed_since_token and answer with only what changed since it. Narrower in name\n // than in reach, since it gates all three.\n | \"treeChangedSince:v1\"\n // Same token, different wire contract: an unchanged resource is omitted and ends the walk
|
|
1
|
+
{"version":3,"file":"capabilities.cjs","names":[],"sources":["../../src/core/capabilities.ts"],"sourcesContent":["/**\n * Backend capability tokens advertised by pl in\n * `MaintenanceAPI.Ping.Response.capabilities`.\n * Capabilities affect behaviour of inner ML mechanisms and allow\n * to detect incompatibilities between blocks and backend via\n * via `TemplateDataV3.requiredCapabilities`.\n *\n * Keep tokens in sync with the backend's advertised list. Format is\n * `<feature>:<version>`; bump the version when wire semantics change.\n *\n * Shared across:\n * - `@milaboratories/pl-client` — client-side capability checks\n * - `@platforma-sdk/tengo-builder` — populates `requiredCapabilities` at compile time\n * - `@platforma-sdk/block-tools` — copies it onto the published manifest\n * - `@milaboratories/pl-middle-layer` — checks it at install time\n */\nexport type BackendCapability =\n | \"auth:v2\"\n | \"treeFilter:v2\"\n // Delta tree reads: Tree, ResourceAPI.Get and ResourceKVAPI.List accept a\n // changed_since_token and answer with only what changed since it. Narrower in name\n // than in reach, since it gates all three.\n | \"treeChangedSince:v1\"\n // Same token, different wire contract: an unchanged resource is omitted and ends the walk.\n // v1 backends instead emit a body-less frame for it, which this client reads as a stop\n // marker, so delta tree sync is gated on v2.\n | \"treeChangedSince:v2\"\n | \"wasm:v1\"\n // Advertised only where the deployment actually has scratch storage, not merely where the\n // build understands a 'scratchFreeSpace' request: a client that sees this stops arranging\n // its own temporary storage. Off unless pl was started with --has-scratch-space.\n | \"scratchSpace:v1\"\n // Project-sharing capability tokens.\n | \"crossTreeRefs:v1\" // cross-color field attach (accept a foreign-colored shared envelope)\n | \"userListing:v1\" // list users for the recipient picker\n | \"publicGrants:v1\" // public (everyone) grants allowed for any role\n | \"txListGrants:v1\"; // list grants inside a transaction (batched recipient reads)\n\n/** True iff `capabilities` advertises the requested token. */\nexport function hasCapability(\n capabilities: readonly string[] | undefined,\n capability: BackendCapability,\n): boolean {\n return capabilities?.includes(capability) ?? false;\n}\n"],"mappings":";;AAuCA,SAAgB,cACd,cACA,YACS;CACT,OAAO,cAAc,SAAS,UAAU,KAAK;AAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","names":[],"sources":["../../src/core/capabilities.ts"],"mappings":";;;;;;;;;;;;;;;;;YAgBY;;
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","names":[],"sources":["../../src/core/capabilities.ts"],"mappings":";;;;;;;;;;;;;;;;;YAgBY;;wBAuBI,cACd,6CACA,YAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.js","names":[],"sources":["../../src/core/capabilities.ts"],"sourcesContent":["/**\n * Backend capability tokens advertised by pl in\n * `MaintenanceAPI.Ping.Response.capabilities`.\n * Capabilities affect behaviour of inner ML mechanisms and allow\n * to detect incompatibilities between blocks and backend via\n * via `TemplateDataV3.requiredCapabilities`.\n *\n * Keep tokens in sync with the backend's advertised list. Format is\n * `<feature>:<version>`; bump the version when wire semantics change.\n *\n * Shared across:\n * - `@milaboratories/pl-client` — client-side capability checks\n * - `@platforma-sdk/tengo-builder` — populates `requiredCapabilities` at compile time\n * - `@platforma-sdk/block-tools` — copies it onto the published manifest\n * - `@milaboratories/pl-middle-layer` — checks it at install time\n */\nexport type BackendCapability =\n | \"auth:v2\"\n | \"treeFilter:v2\"\n // Delta tree reads: Tree, ResourceAPI.Get and ResourceKVAPI.List accept a\n // changed_since_token and answer with only what changed since it. Narrower in name\n // than in reach, since it gates all three.\n | \"treeChangedSince:v1\"\n // Same token, different wire contract: an unchanged resource is omitted and ends the walk
|
|
1
|
+
{"version":3,"file":"capabilities.js","names":[],"sources":["../../src/core/capabilities.ts"],"sourcesContent":["/**\n * Backend capability tokens advertised by pl in\n * `MaintenanceAPI.Ping.Response.capabilities`.\n * Capabilities affect behaviour of inner ML mechanisms and allow\n * to detect incompatibilities between blocks and backend via\n * via `TemplateDataV3.requiredCapabilities`.\n *\n * Keep tokens in sync with the backend's advertised list. Format is\n * `<feature>:<version>`; bump the version when wire semantics change.\n *\n * Shared across:\n * - `@milaboratories/pl-client` — client-side capability checks\n * - `@platforma-sdk/tengo-builder` — populates `requiredCapabilities` at compile time\n * - `@platforma-sdk/block-tools` — copies it onto the published manifest\n * - `@milaboratories/pl-middle-layer` — checks it at install time\n */\nexport type BackendCapability =\n | \"auth:v2\"\n | \"treeFilter:v2\"\n // Delta tree reads: Tree, ResourceAPI.Get and ResourceKVAPI.List accept a\n // changed_since_token and answer with only what changed since it. Narrower in name\n // than in reach, since it gates all three.\n | \"treeChangedSince:v1\"\n // Same token, different wire contract: an unchanged resource is omitted and ends the walk.\n // v1 backends instead emit a body-less frame for it, which this client reads as a stop\n // marker, so delta tree sync is gated on v2.\n | \"treeChangedSince:v2\"\n | \"wasm:v1\"\n // Advertised only where the deployment actually has scratch storage, not merely where the\n // build understands a 'scratchFreeSpace' request: a client that sees this stops arranging\n // its own temporary storage. Off unless pl was started with --has-scratch-space.\n | \"scratchSpace:v1\"\n // Project-sharing capability tokens.\n | \"crossTreeRefs:v1\" // cross-color field attach (accept a foreign-colored shared envelope)\n | \"userListing:v1\" // list users for the recipient picker\n | \"publicGrants:v1\" // public (everyone) grants allowed for any role\n | \"txListGrants:v1\"; // list grants inside a transaction (batched recipient reads)\n\n/** True iff `capabilities` advertises the requested token. */\nexport function hasCapability(\n capabilities: readonly string[] | undefined,\n capability: BackendCapability,\n): boolean {\n return capabilities?.includes(capability) ?? false;\n}\n"],"mappings":";;AAuCA,SAAgB,cACd,cACA,YACS;CACT,OAAO,cAAc,SAAS,UAAU,KAAK;AAC/C"}
|
package/dist/core/final.cjs
CHANGED
|
@@ -19,8 +19,10 @@ const DefaultFinalResourceDataPredicate = (r) => {
|
|
|
19
19
|
if (!readyOrDuplicateOrError(r)) return false;
|
|
20
20
|
if (r.fields === void 0) return true;
|
|
21
21
|
if (require_types.isNotNullSignedResourceId(r.error)) return true;
|
|
22
|
-
const downloadable =
|
|
23
|
-
|
|
22
|
+
const downloadable = r.fields.find((f) => f.name === "downloadable");
|
|
23
|
+
const stream = r.fields.find((f) => f.name === "stream");
|
|
24
|
+
if (downloadable === void 0 || stream === void 0 || require_types.isNullSignedResourceId(stream.value)) return false;
|
|
25
|
+
return stream.value === downloadable.value;
|
|
24
26
|
}
|
|
25
27
|
case _milaboratories_pl_model_common.ResourceTypeName.StdMap:
|
|
26
28
|
case _milaboratories_pl_model_common.ResourceTypeName.StdMapSlash:
|
package/dist/core/final.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"final.cjs","names":["isNotNullSignedResourceId","isNullSignedResourceId","ResourceTypeName","
|
|
1
|
+
{"version":3,"file":"final.cjs","names":["isNotNullSignedResourceId","isNullSignedResourceId","ResourceTypeName","ResourceTypePrefix"],"sources":["../../src/core/final.ts"],"sourcesContent":["import type { Optional } from \"utility-types\";\nimport type { BasicResourceData, ResourceData } from \"./types\";\nimport { isNotNullSignedResourceId, isNullSignedResourceId } from \"./types\";\nimport { ResourceTypeName, ResourceTypePrefix } from \"@milaboratories/pl-model-common\";\nexport { ResourceTypeName, ResourceTypePrefix };\n\n/**\n * Function is used to guide multiple layers of caching in pl-client and derived pl-tree.\n *\n * This function defines expected resource-specific state mutation behaviour,\n * if it returns true, system will expect that this data will never change as long as resource exist.\n *\n * If resource data contain information about fields, if should be taken into account, fields are undefined,\n * \"final\" state should be calculated for \"basic\" part of resource data only.\n */\nexport type FinalResourceDataPredicate = (\n resourceData: Optional<ResourceData, \"fields\">,\n) => boolean;\n\nexport function readyOrDuplicateOrError(r: ResourceData | BasicResourceData): boolean {\n return (\n r.resourceReady ||\n isNotNullSignedResourceId(r.originalResourceId) ||\n isNotNullSignedResourceId(r.error)\n );\n}\n\nfunction readyAndHasAllOutputsFilled(r: Optional<ResourceData, \"fields\">): boolean {\n if (!readyOrDuplicateOrError(r)) return false;\n if (!r.outputsLocked) return false;\n if (r.fields === undefined) return true; // if fields are not provided basic resource state is not expected to change in the future\n for (const f of r.fields)\n if (isNullSignedResourceId(f.error) && (isNullSignedResourceId(f.value) || !f.valueIsFinal))\n return false;\n return true;\n}\n\n// solely for logging\nconst unknownResourceTypeNames = new Set<string>();\n\n/** Default implementation, defining behaviour for built-in resource types. */\nexport const DefaultFinalResourceDataPredicate: FinalResourceDataPredicate = (r): boolean => {\n switch (r.type.name) {\n case ResourceTypeName.StreamManager: {\n if (!readyOrDuplicateOrError(r)) return false;\n if (r.fields === undefined) return true; // if fields are not provided basic resource state is not expected to change in the future\n if (isNotNullSignedResourceId(r.error)) return true;\n // Fields can be pruned away by the reader: without them nothing proves the switch, so\n // the resource is not final. The predicate must not throw, since a throw inside the\n // tree's update invalidates and rebuilds the whole tree.\n const downloadable = r.fields.find((f) => f.name === \"downloadable\");\n const stream = r.fields.find((f) => f.name === \"stream\");\n // Both still empty is not a switch either.\n if (\n downloadable === undefined ||\n stream === undefined ||\n isNullSignedResourceId(stream.value)\n )\n return false;\n return stream.value === downloadable.value; // it's equal to the resource is marked as final on backend side\n }\n case ResourceTypeName.StdMap:\n case ResourceTypeName.StdMapSlash:\n case ResourceTypeName.EphStdMap:\n case ResourceTypeName.PFrame:\n case ResourceTypeName.ParquetChunk:\n case ResourceTypeName.BContext:\n case ResourceTypeName.BlockPackCustom:\n case ResourceTypeName.BinaryMap:\n case ResourceTypeName.BinaryValue:\n case ResourceTypeName.BlobMap:\n case ResourceTypeName.BResolveSingle:\n case ResourceTypeName.BResolveSingleNoResult:\n case ResourceTypeName.BQueryResult:\n case ResourceTypeName.TengoTemplate:\n case ResourceTypeName.TengoLib:\n case ResourceTypeName.SoftwareInfo:\n case ResourceTypeName.Dummy:\n return readyOrDuplicateOrError(r);\n case ResourceTypeName.JsonResourceError:\n return r.type.version === \"1\";\n case ResourceTypeName.JsonObject:\n case ResourceTypeName.JsonGzObject:\n case ResourceTypeName.JsonString:\n case ResourceTypeName.JsonArray:\n case ResourceTypeName.JsonNumber:\n case ResourceTypeName.BContextEnd:\n case ResourceTypeName.FrontendFromUrl:\n case ResourceTypeName.FrontendFromFolder:\n case ResourceTypeName.BObjectSpec:\n case ResourceTypeName.Blob:\n case ResourceTypeName.Null:\n case ResourceTypeName.Binary:\n case ResourceTypeName.LSProvider:\n case ResourceTypeName.WorkingDirectory:\n return true;\n case ResourceTypeName.UserProject:\n case ResourceTypeName.Projects:\n case ResourceTypeName.ClientRoot:\n // Never final — these sharing resources gain and lose dynamic child fields over their lifetime.\n case ResourceTypeName.SharingOutbox:\n case ResourceTypeName.SharingState:\n case ResourceTypeName.SharedEnvelope:\n return false;\n default:\n if (\n r.type.name.startsWith(ResourceTypePrefix.Blob) ||\n r.type.name.startsWith(ResourceTypePrefix.LS) ||\n r.type.name.startsWith(ResourceTypePrefix.WorkingDirectory) ||\n r.type.name.startsWith(ResourceTypePrefix.StorageSpaceAllocation)\n ) {\n return true;\n } else if (\n r.type.name.startsWith(ResourceTypePrefix.BlobUpload) ||\n r.type.name.startsWith(ResourceTypePrefix.BlobIndex)\n ) {\n return readyAndHasAllOutputsFilled(r);\n } else if (r.type.name.startsWith(ResourceTypePrefix.PColumnData)) {\n return readyOrDuplicateOrError(r);\n } else if (r.type.name.startsWith(ResourceTypePrefix.StreamWorkdir)) {\n return readyOrDuplicateOrError(r);\n } else {\n // Unknown resource type detected\n // Set used to log this message only once\n if (!unknownResourceTypeNames.has(r.type.name)) {\n console.log(\"UNKNOWN RESOURCE TYPE: \" + r.type.name);\n unknownResourceTypeNames.add(r.type.name);\n }\n }\n }\n return false;\n};\n"],"mappings":"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"}
|
package/dist/core/final.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNotNullSignedResourceId, isNullSignedResourceId } from "./types.js";
|
|
2
2
|
import { ResourceTypeName, ResourceTypePrefix } from "@milaboratories/pl-model-common";
|
|
3
3
|
//#region src/core/final.ts
|
|
4
4
|
function readyOrDuplicateOrError(r) {
|
|
@@ -19,8 +19,10 @@ const DefaultFinalResourceDataPredicate = (r) => {
|
|
|
19
19
|
if (!readyOrDuplicateOrError(r)) return false;
|
|
20
20
|
if (r.fields === void 0) return true;
|
|
21
21
|
if (isNotNullSignedResourceId(r.error)) return true;
|
|
22
|
-
const downloadable =
|
|
23
|
-
|
|
22
|
+
const downloadable = r.fields.find((f) => f.name === "downloadable");
|
|
23
|
+
const stream = r.fields.find((f) => f.name === "stream");
|
|
24
|
+
if (downloadable === void 0 || stream === void 0 || isNullSignedResourceId(stream.value)) return false;
|
|
25
|
+
return stream.value === downloadable.value;
|
|
24
26
|
}
|
|
25
27
|
case ResourceTypeName.StdMap:
|
|
26
28
|
case ResourceTypeName.StdMapSlash:
|
package/dist/core/final.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"final.js","names":[],"sources":["../../src/core/final.ts"],"sourcesContent":["import type { Optional } from \"utility-types\";\nimport type { BasicResourceData, ResourceData } from \"./types\";\nimport {
|
|
1
|
+
{"version":3,"file":"final.js","names":[],"sources":["../../src/core/final.ts"],"sourcesContent":["import type { Optional } from \"utility-types\";\nimport type { BasicResourceData, ResourceData } from \"./types\";\nimport { isNotNullSignedResourceId, isNullSignedResourceId } from \"./types\";\nimport { ResourceTypeName, ResourceTypePrefix } from \"@milaboratories/pl-model-common\";\nexport { ResourceTypeName, ResourceTypePrefix };\n\n/**\n * Function is used to guide multiple layers of caching in pl-client and derived pl-tree.\n *\n * This function defines expected resource-specific state mutation behaviour,\n * if it returns true, system will expect that this data will never change as long as resource exist.\n *\n * If resource data contain information about fields, if should be taken into account, fields are undefined,\n * \"final\" state should be calculated for \"basic\" part of resource data only.\n */\nexport type FinalResourceDataPredicate = (\n resourceData: Optional<ResourceData, \"fields\">,\n) => boolean;\n\nexport function readyOrDuplicateOrError(r: ResourceData | BasicResourceData): boolean {\n return (\n r.resourceReady ||\n isNotNullSignedResourceId(r.originalResourceId) ||\n isNotNullSignedResourceId(r.error)\n );\n}\n\nfunction readyAndHasAllOutputsFilled(r: Optional<ResourceData, \"fields\">): boolean {\n if (!readyOrDuplicateOrError(r)) return false;\n if (!r.outputsLocked) return false;\n if (r.fields === undefined) return true; // if fields are not provided basic resource state is not expected to change in the future\n for (const f of r.fields)\n if (isNullSignedResourceId(f.error) && (isNullSignedResourceId(f.value) || !f.valueIsFinal))\n return false;\n return true;\n}\n\n// solely for logging\nconst unknownResourceTypeNames = new Set<string>();\n\n/** Default implementation, defining behaviour for built-in resource types. */\nexport const DefaultFinalResourceDataPredicate: FinalResourceDataPredicate = (r): boolean => {\n switch (r.type.name) {\n case ResourceTypeName.StreamManager: {\n if (!readyOrDuplicateOrError(r)) return false;\n if (r.fields === undefined) return true; // if fields are not provided basic resource state is not expected to change in the future\n if (isNotNullSignedResourceId(r.error)) return true;\n // Fields can be pruned away by the reader: without them nothing proves the switch, so\n // the resource is not final. The predicate must not throw, since a throw inside the\n // tree's update invalidates and rebuilds the whole tree.\n const downloadable = r.fields.find((f) => f.name === \"downloadable\");\n const stream = r.fields.find((f) => f.name === \"stream\");\n // Both still empty is not a switch either.\n if (\n downloadable === undefined ||\n stream === undefined ||\n isNullSignedResourceId(stream.value)\n )\n return false;\n return stream.value === downloadable.value; // it's equal to the resource is marked as final on backend side\n }\n case ResourceTypeName.StdMap:\n case ResourceTypeName.StdMapSlash:\n case ResourceTypeName.EphStdMap:\n case ResourceTypeName.PFrame:\n case ResourceTypeName.ParquetChunk:\n case ResourceTypeName.BContext:\n case ResourceTypeName.BlockPackCustom:\n case ResourceTypeName.BinaryMap:\n case ResourceTypeName.BinaryValue:\n case ResourceTypeName.BlobMap:\n case ResourceTypeName.BResolveSingle:\n case ResourceTypeName.BResolveSingleNoResult:\n case ResourceTypeName.BQueryResult:\n case ResourceTypeName.TengoTemplate:\n case ResourceTypeName.TengoLib:\n case ResourceTypeName.SoftwareInfo:\n case ResourceTypeName.Dummy:\n return readyOrDuplicateOrError(r);\n case ResourceTypeName.JsonResourceError:\n return r.type.version === \"1\";\n case ResourceTypeName.JsonObject:\n case ResourceTypeName.JsonGzObject:\n case ResourceTypeName.JsonString:\n case ResourceTypeName.JsonArray:\n case ResourceTypeName.JsonNumber:\n case ResourceTypeName.BContextEnd:\n case ResourceTypeName.FrontendFromUrl:\n case ResourceTypeName.FrontendFromFolder:\n case ResourceTypeName.BObjectSpec:\n case ResourceTypeName.Blob:\n case ResourceTypeName.Null:\n case ResourceTypeName.Binary:\n case ResourceTypeName.LSProvider:\n case ResourceTypeName.WorkingDirectory:\n return true;\n case ResourceTypeName.UserProject:\n case ResourceTypeName.Projects:\n case ResourceTypeName.ClientRoot:\n // Never final — these sharing resources gain and lose dynamic child fields over their lifetime.\n case ResourceTypeName.SharingOutbox:\n case ResourceTypeName.SharingState:\n case ResourceTypeName.SharedEnvelope:\n return false;\n default:\n if (\n r.type.name.startsWith(ResourceTypePrefix.Blob) ||\n r.type.name.startsWith(ResourceTypePrefix.LS) ||\n r.type.name.startsWith(ResourceTypePrefix.WorkingDirectory) ||\n r.type.name.startsWith(ResourceTypePrefix.StorageSpaceAllocation)\n ) {\n return true;\n } else if (\n r.type.name.startsWith(ResourceTypePrefix.BlobUpload) ||\n r.type.name.startsWith(ResourceTypePrefix.BlobIndex)\n ) {\n return readyAndHasAllOutputsFilled(r);\n } else if (r.type.name.startsWith(ResourceTypePrefix.PColumnData)) {\n return readyOrDuplicateOrError(r);\n } else if (r.type.name.startsWith(ResourceTypePrefix.StreamWorkdir)) {\n return readyOrDuplicateOrError(r);\n } else {\n // Unknown resource type detected\n // Set used to log this message only once\n if (!unknownResourceTypeNames.has(r.type.name)) {\n console.log(\"UNKNOWN RESOURCE TYPE: \" + r.type.name);\n unknownResourceTypeNames.add(r.type.name);\n }\n }\n }\n return false;\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-client",
|
|
3
|
-
"version": "3.17.
|
|
3
|
+
"version": "3.17.7",
|
|
4
4
|
"description": "New TS/JS client for Platform API",
|
|
5
5
|
"files": [
|
|
6
6
|
"./dist/**/*",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"utility-types": "^3.11.0",
|
|
32
32
|
"yaml": "^2.8.0",
|
|
33
33
|
"@milaboratories/pl-http": "1.2.4",
|
|
34
|
-
"@milaboratories/
|
|
35
|
-
"@milaboratories/
|
|
34
|
+
"@milaboratories/pl-model-common": "1.51.0",
|
|
35
|
+
"@milaboratories/ts-helpers": "1.8.6"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@protobuf-ts/plugin": "2.11.1",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript": "7.0.2",
|
|
43
43
|
"vitest": "^4.1.3",
|
|
44
44
|
"@milaboratories/build-configs": "2.0.2",
|
|
45
|
-
"@milaboratories/ts-
|
|
46
|
-
"@milaboratories/ts-
|
|
45
|
+
"@milaboratories/ts-builder": "1.7.2",
|
|
46
|
+
"@milaboratories/ts-configs": "1.4.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=22.19.0"
|
package/src/core/capabilities.ts
CHANGED
|
@@ -21,10 +21,9 @@ export type BackendCapability =
|
|
|
21
21
|
// changed_since_token and answer with only what changed since it. Narrower in name
|
|
22
22
|
// than in reach, since it gates all three.
|
|
23
23
|
| "treeChangedSince:v1"
|
|
24
|
-
// Same token, different wire contract: an unchanged resource is omitted and ends the walk
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// advertises it yet, so delta stays off until pl does.
|
|
24
|
+
// Same token, different wire contract: an unchanged resource is omitted and ends the walk.
|
|
25
|
+
// v1 backends instead emit a body-less frame for it, which this client reads as a stop
|
|
26
|
+
// marker, so delta tree sync is gated on v2.
|
|
28
27
|
| "treeChangedSince:v2"
|
|
29
28
|
| "wasm:v1"
|
|
30
29
|
// Advertised only where the deployment actually has scratch storage, not merely where the
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { DefaultFinalResourceDataPredicate } from "./final";
|
|
3
|
+
import type { FieldData, ResourceData } from "./types";
|
|
4
|
+
import { createSignedResourceId, NullSignedResourceId } from "./types";
|
|
5
|
+
|
|
6
|
+
const A = createSignedResourceId(1n);
|
|
7
|
+
const B = createSignedResourceId(2n);
|
|
8
|
+
|
|
9
|
+
function streamManager(patch: Partial<ResourceData>): ResourceData {
|
|
10
|
+
return {
|
|
11
|
+
id: createSignedResourceId(10n),
|
|
12
|
+
originalResourceId: NullSignedResourceId,
|
|
13
|
+
kind: "Structural",
|
|
14
|
+
type: { name: "StreamManager", version: "1" },
|
|
15
|
+
data: undefined,
|
|
16
|
+
error: NullSignedResourceId,
|
|
17
|
+
inputsLocked: true,
|
|
18
|
+
outputsLocked: true,
|
|
19
|
+
resourceReady: true,
|
|
20
|
+
final: false,
|
|
21
|
+
fields: [],
|
|
22
|
+
...patch,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function field(name: string, value: typeof A): FieldData {
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
type: "Output",
|
|
30
|
+
status: "Resolved",
|
|
31
|
+
value,
|
|
32
|
+
error: NullSignedResourceId,
|
|
33
|
+
valueIsFinal: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test("a ready, non-errored StreamManager with its fields pruned away is not final, and does not throw", () => {
|
|
38
|
+
expect(DefaultFinalResourceDataPredicate(streamManager({ fields: [] }))).toBe(false);
|
|
39
|
+
expect(DefaultFinalResourceDataPredicate(streamManager({ fields: [field("stream", A)] }))).toBe(
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a ready StreamManager is final once stream and downloadable share a non-null value", () => {
|
|
45
|
+
expect(
|
|
46
|
+
DefaultFinalResourceDataPredicate(
|
|
47
|
+
streamManager({ fields: [field("stream", A), field("downloadable", B)] }),
|
|
48
|
+
),
|
|
49
|
+
).toBe(false);
|
|
50
|
+
expect(
|
|
51
|
+
DefaultFinalResourceDataPredicate(
|
|
52
|
+
streamManager({ fields: [field("stream", B), field("downloadable", B)] }),
|
|
53
|
+
),
|
|
54
|
+
).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("an errored StreamManager is final whatever its fields", () => {
|
|
58
|
+
expect(DefaultFinalResourceDataPredicate(streamManager({ error: A, fields: [] }))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a StreamManager with both fields still empty is not final", () => {
|
|
62
|
+
const empty = (name: string): FieldData => ({
|
|
63
|
+
...field(name, A),
|
|
64
|
+
value: NullSignedResourceId,
|
|
65
|
+
status: "Empty",
|
|
66
|
+
valueIsFinal: false,
|
|
67
|
+
});
|
|
68
|
+
expect(
|
|
69
|
+
DefaultFinalResourceDataPredicate(
|
|
70
|
+
streamManager({ fields: [empty("stream"), empty("downloadable")] }),
|
|
71
|
+
),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
});
|
package/src/core/final.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Optional } from "utility-types";
|
|
2
2
|
import type { BasicResourceData, ResourceData } from "./types";
|
|
3
|
-
import {
|
|
3
|
+
import { isNotNullSignedResourceId, isNullSignedResourceId } from "./types";
|
|
4
4
|
import { ResourceTypeName, ResourceTypePrefix } from "@milaboratories/pl-model-common";
|
|
5
5
|
export { ResourceTypeName, ResourceTypePrefix };
|
|
6
6
|
|
|
@@ -45,8 +45,18 @@ export const DefaultFinalResourceDataPredicate: FinalResourceDataPredicate = (r)
|
|
|
45
45
|
if (!readyOrDuplicateOrError(r)) return false;
|
|
46
46
|
if (r.fields === undefined) return true; // if fields are not provided basic resource state is not expected to change in the future
|
|
47
47
|
if (isNotNullSignedResourceId(r.error)) return true;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// Fields can be pruned away by the reader: without them nothing proves the switch, so
|
|
49
|
+
// the resource is not final. The predicate must not throw, since a throw inside the
|
|
50
|
+
// tree's update invalidates and rebuilds the whole tree.
|
|
51
|
+
const downloadable = r.fields.find((f) => f.name === "downloadable");
|
|
52
|
+
const stream = r.fields.find((f) => f.name === "stream");
|
|
53
|
+
// Both still empty is not a switch either.
|
|
54
|
+
if (
|
|
55
|
+
downloadable === undefined ||
|
|
56
|
+
stream === undefined ||
|
|
57
|
+
isNullSignedResourceId(stream.value)
|
|
58
|
+
)
|
|
59
|
+
return false;
|
|
50
60
|
return stream.value === downloadable.value; // it's equal to the resource is marked as final on backend side
|
|
51
61
|
}
|
|
52
62
|
case ResourceTypeName.StdMap:
|