@milaboratories/pl-tree 1.15.6 → 1.16.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/accessors.cjs +6 -2
- package/dist/accessors.cjs.map +1 -1
- package/dist/accessors.d.ts.map +1 -1
- package/dist/accessors.js +6 -2
- package/dist/accessors.js.map +1 -1
- package/dist/delta_sync.cjs +18 -8
- package/dist/delta_sync.cjs.map +1 -1
- package/dist/delta_sync.js +18 -8
- package/dist/delta_sync.js.map +1 -1
- package/dist/state.cjs +108 -30
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.ts +27 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +108 -30
- package/dist/state.js.map +1 -1
- package/dist/sync.cjs +2 -1
- package/dist/sync.cjs.map +1 -1
- package/dist/sync.d.ts +2 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +2 -1
- package/dist/sync.js.map +1 -1
- package/dist/synchronized_tree.cjs +46 -9
- package/dist/synchronized_tree.cjs.map +1 -1
- package/dist/synchronized_tree.d.ts +6 -0
- package/dist/synchronized_tree.d.ts.map +1 -1
- package/dist/synchronized_tree.js +47 -10
- package/dist/synchronized_tree.js.map +1 -1
- package/package.json +6 -6
- package/src/accessors.test.ts +108 -0
- package/src/accessors.ts +8 -3
- package/src/delta_sync.test.ts +88 -3
- package/src/delta_sync.ts +19 -8
- package/src/delta_sync_live.test.ts +3 -4
- package/src/state.test.ts +397 -2
- package/src/state.ts +137 -46
- package/src/state_notifications.model.test.ts +264 -0
- package/src/sync.ts +6 -3
- package/src/synchronized_tree.test.ts +550 -1
- package/src/synchronized_tree.ts +102 -20
package/dist/accessors.cjs
CHANGED
|
@@ -143,7 +143,7 @@ var PlTreeNodeAccessor = class {
|
|
|
143
143
|
...commonOptions,
|
|
144
144
|
..._step
|
|
145
145
|
};
|
|
146
|
-
const next = current.getField(
|
|
146
|
+
const next = current.getField(step);
|
|
147
147
|
if (next === void 0) return void 0;
|
|
148
148
|
if (step.pureFieldErrorToUndefined && next.value === void 0 && next.error !== void 0) return void 0;
|
|
149
149
|
if ((!step.ignoreError || next.value === void 0) && next.error !== void 0) return {
|
|
@@ -155,7 +155,7 @@ var PlTreeNodeAccessor = class {
|
|
|
155
155
|
ok: false,
|
|
156
156
|
error: /* @__PURE__ */ new Error(`field have no assigned value ${step.field} of ${(0, _milaboratories_pl_client.resourceIdToString)(current.id)}`)
|
|
157
157
|
};
|
|
158
|
-
this.onUnstableLambda("unpopulated_field:" + step.field);
|
|
158
|
+
if (!current.resource.finalState) this.onUnstableLambda("unpopulated_field:" + step.field);
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
current = next.value;
|
|
@@ -208,12 +208,15 @@ var PlTreeNodeAccessor = class {
|
|
|
208
208
|
return this.resource.data !== void 0;
|
|
209
209
|
}
|
|
210
210
|
getData() {
|
|
211
|
+
this.instanceData.guard();
|
|
211
212
|
return this.resource.data;
|
|
212
213
|
}
|
|
213
214
|
getDataAsString() {
|
|
215
|
+
this.instanceData.guard();
|
|
214
216
|
return this.resource.getDataAsString();
|
|
215
217
|
}
|
|
216
218
|
getDataAsJson() {
|
|
219
|
+
this.instanceData.guard();
|
|
217
220
|
return this.resource.getDataAsJson();
|
|
218
221
|
}
|
|
219
222
|
listInputFields() {
|
|
@@ -247,6 +250,7 @@ var PlTreeNodeAccessor = class {
|
|
|
247
250
|
return result;
|
|
248
251
|
}
|
|
249
252
|
getKeyValueAsJson(key, unstableIfNotFound = false) {
|
|
253
|
+
this.instanceData.guard();
|
|
250
254
|
const result = this.resource.getKeyValueAsJson(this.instanceData.ctx.watcher, key);
|
|
251
255
|
if (result === void 0) {
|
|
252
256
|
if (unstableIfNotFound) this.instanceData.ctx.markUnstable("key_not_found_j:" + key);
|
package/dist/accessors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessors.cjs","names":["mapValueAndError"],"sources":["../src/accessors.ts"],"sourcesContent":["import type { PlTreeResource, PlTreeState } from \"./state\";\nimport type {\n AccessorProvider,\n ComputableCtx,\n ComputableHooks,\n UsageGuard,\n} from \"@milaboratories/computable\";\nimport {\n resourceIdToString,\n resourceTypesEqual,\n resourceTypeToString,\n ResourceType,\n OptionalSignedResourceId,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport type { ValueAndError } from \"./value_and_error\";\nimport { mapValueAndError } from \"./value_and_error\";\nimport type {\n CommonFieldTraverseOps,\n FieldTraversalStep,\n GetFieldStep,\n ResourceTraversalOps,\n} from \"./traversal_ops\";\nimport type { ValueOrError } from \"./value_or_error\";\nimport { parsePlError } from \"@milaboratories/pl-errors\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\n/** Error encountered during traversal in field or resource. */\nexport class PlError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport type TreeAccessorData = {\n readonly treeProvider: () => PlTreeState;\n readonly hooks?: ComputableHooks;\n};\n\nexport type TreeAccessorInstanceData = {\n readonly guard: UsageGuard;\n readonly ctx: ComputableCtx;\n};\n\nexport function isPlTreeEntry(obj: unknown): obj is PlTreeEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntry\"\n );\n}\n\nexport function isPlTreeEntryAccessor(obj: unknown): obj is PlTreeEntryAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntryAccessor\"\n );\n}\n\nexport function isPlTreeNodeAccessor(obj: unknown): obj is PlTreeNodeAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeNodeAccessor\"\n );\n}\n\n/** Main entry point for using PlTree in reactive setting */\nexport class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntry\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n public readonly rid: SignedResourceId, // only for runtime. Not exposing rid at all causes too much pain. Never persist this anywhere.\n ) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.accessorData.treeProvider(), this.rid, {\n ctx,\n guard,\n });\n }\n\n /**\n * Tree TreeEntry ID suitable for persistence and use in names (files/objects/whatever), as it does not\n * contain signature which can change from session to session.\n */\n public get id(): string {\n return resourceIdToString(this.rid);\n }\n\n public toJSON(): string {\n return this.toString();\n }\n\n public toString(): string {\n return `[ENTRY:${resourceIdToString(this.rid)}]`;\n }\n}\n\nfunction getResourceFromTree(\n accessorData: TreeAccessorData,\n tree: PlTreeState,\n instanceData: TreeAccessorInstanceData,\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n): PlTreeNodeAccessor {\n const acc = new PlTreeNodeAccessor(\n accessorData,\n tree,\n tree.get(instanceData.ctx.watcher, rid),\n instanceData,\n );\n\n if (!ops.ignoreError) {\n const err = acc.getError();\n if (err !== undefined)\n throw parsePlError(notEmpty(err.getDataAsString()), acc.id, acc.resourceType);\n }\n\n if (\n ops.assertResourceType !== undefined &&\n (Array.isArray(ops.assertResourceType)\n ? ops.assertResourceType.findIndex((rt) => resourceTypesEqual(rt, acc.resourceType)) === -1\n : !resourceTypesEqual(ops.assertResourceType, acc.resourceType))\n )\n throw new Error(\n `wrong resource type ${resourceTypeToString(acc.resourceType)} but expected ${ops.assertResourceType}`,\n );\n\n return acc;\n}\n\nexport class PlTreeEntryAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntryAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly rid: SignedResourceId,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n node(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor {\n this.instanceData.guard();\n\n // this is the only entry point to acquire a PlTreeNodeAccessor,\n // so this is the only point where we should attach the hooks\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, this.rid, ops);\n }\n}\n\n/**\n * Helper type to simplify implementation of APIs requiring type information.\n * */\nexport type ResourceInfo = {\n readonly id: SignedResourceId;\n readonly type: ResourceType;\n};\n\n/**\n * Can be called only when a ctx is provided, because pl tree entry is a computable entity.\n * @deprecated\n * */\nexport function treeEntryToResourceInfo(res: PlTreeEntry | ResourceInfo, ctx: ComputableCtx) {\n if (res instanceof PlTreeEntry) return ctx.accessor(res).node().resourceInfo;\n\n return res;\n}\n\n/**\n * API contracts:\n * - API never return {@link NullSignedResourceId}, absence of link is always modeled as `undefined`\n *\n * Important: never store instances of this class, always get fresh instance from {@link PlTreeState} accessor.\n * */\nexport class PlTreeNodeAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeNodeAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly resource: PlTreeResource,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n public get id(): SignedResourceId {\n this.instanceData.guard();\n return this.resource.id;\n }\n\n public get originalId(): OptionalSignedResourceId {\n this.instanceData.guard();\n return this.resource.originalResourceId;\n }\n\n public get resourceType(): ResourceType {\n this.instanceData.guard();\n return this.resource.type;\n }\n\n public get resourceInfo(): ResourceInfo {\n return { id: this.id, type: this.resourceType };\n }\n\n private getResourceFromTree(\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n ): PlTreeNodeAccessor {\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops);\n }\n\n public traverse(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): PlTreeNodeAccessor;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined {\n return this.traverseWithCommon({}, ...steps);\n }\n\n public traverseOrError(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): ValueOrError<PlTreeNodeAccessor, Error>;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n return this.traverseOrErrorWithCommon({}, ...steps);\n }\n\n public traverseWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): PlTreeNodeAccessor | undefined {\n const result = this.traverseOrErrorWithCommon(commonOptions, ...steps);\n if (result === undefined) return undefined;\n\n if (!result.ok) throw result.error;\n return result.value;\n }\n\n public traverseOrErrorWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: PlTreeNodeAccessor = this;\n\n for (const _step of steps) {\n const step: FieldTraversalStep =\n typeof _step === \"string\"\n ? {\n ...commonOptions,\n field: _step,\n }\n : { ...commonOptions, ..._step };\n\n const next = current.getField(_step);\n\n if (next === undefined) return undefined;\n\n if (step.pureFieldErrorToUndefined && next.value === undefined && next.error !== undefined)\n return undefined;\n\n if ((!step.ignoreError || next.value === undefined) && next.error !== undefined)\n return {\n ok: false,\n\n // FIXME: in next tickets we'll allow Errors to be thrown.\n error: parsePlError(\n notEmpty(next.error.getDataAsString()),\n current.id,\n current.resourceType,\n step.field,\n ),\n };\n\n if (next.value === undefined) {\n if (step.errorIfFieldNotSet)\n return {\n ok: false,\n error: new Error(\n `field have no assigned value ${step.field} of ${resourceIdToString(current.id)}`,\n ),\n };\n // existing but unpopulated field is unstable because it must be resolved at some point\n this.onUnstableLambda(\"unpopulated_field:\" + step.field);\n return undefined;\n }\n\n current = next.value;\n }\n return { ok: true, value: current };\n }\n\n private readonly onUnstableLambda = (marker: string) => {\n this.instanceData.ctx.markUnstable(marker);\n };\n\n public getField(\n _step:\n | (Omit<GetFieldStep, \"errorIfFieldNotFound\"> & { errorIfFieldNotFound: true })\n | (Omit<GetFieldStep, \"errorIfFieldNotSet\"> & { errorIfFieldNotSet: true }),\n ): ValueAndError<PlTreeNodeAccessor>;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined {\n this.instanceData.guard();\n const step: GetFieldStep = typeof _step === \"string\" ? { field: _step } : _step;\n\n const ve = this.resource.getField(this.instanceData.ctx.watcher, step, this.onUnstableLambda);\n\n if (ve === undefined) return undefined;\n\n return mapValueAndError(ve, (rid) => this.getResourceFromTree(rid, { ignoreError: true }));\n }\n\n public getInputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getInputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"inputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getOutputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getOutputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"outputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getIsReadyOrError(): boolean {\n this.instanceData.guard();\n const result = this.resource.getIsReadyOrError(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"not_ready:\" + this.resourceType.name);\n return result;\n }\n\n public getIsFinal() {\n this.instanceData.guard();\n return this.resource.getIsFinal(this.instanceData.ctx.watcher);\n }\n\n public getError(): PlTreeNodeAccessor | undefined {\n this.instanceData.guard();\n const rid = this.resource.getError(this.instanceData.ctx.watcher);\n if (rid === undefined)\n // absence of error always considered as stable\n return undefined;\n return this.getResourceFromTree(rid, {});\n }\n\n public hasData(): boolean {\n this.instanceData.guard();\n return this.resource.data !== undefined;\n }\n\n public getData(): Uint8Array | undefined {\n return this.resource.data;\n }\n\n public getDataAsString(): string | undefined {\n return this.resource.getDataAsString();\n }\n\n public getDataAsJson<T = unknown>(): T | undefined {\n return this.resource.getDataAsJson<T>();\n }\n\n public listInputFields(): string[] {\n this.instanceData.guard();\n this.getInputsLocked(); // will set unstable if not locked\n return this.resource.listInputFields(this.instanceData.ctx.watcher);\n }\n\n public listOutputFields(): string[] {\n this.instanceData.guard();\n this.getOutputsLocked(); // will set unstable if not locked\n return this.resource.listOutputFields(this.instanceData.ctx.watcher);\n }\n\n public listDynamicFields(): string[] {\n this.instanceData.guard();\n return this.resource.listDynamicFields(this.instanceData.ctx.watcher);\n }\n\n public getKeyValue(key: string, unstableIfNotFound: boolean = false): Uint8Array | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValue(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_b:\" + key);\n return result;\n }\n\n /** @deprecated */\n public getKeyValueString(key: string): string | undefined {\n return this.getKeyValueAsString(key);\n }\n\n public getKeyValueAsString(key: string, unstableIfNotFound: boolean = false): string | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueString(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_s:\" + key);\n return result;\n }\n\n public getKeyValueAsJson<T = unknown>(\n key: string,\n unstableIfNotFound: boolean = false,\n ): T | undefined {\n const result = this.resource.getKeyValueAsJson<T>(this.instanceData.ctx.watcher, key);\n if (result === undefined) {\n if (unstableIfNotFound) this.instanceData.ctx.markUnstable(\"key_not_found_j:\" + key);\n return undefined;\n }\n return result;\n }\n\n /**\n * Can be used to pass a higher level accessor that will wrap the resource and throw its\n * errors on node resolution.\n * */\n public toEntryAccessor(): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.tree, this.id, this.instanceData);\n }\n\n /** Can be passed to nested computable. */\n public persist(): PlTreeEntry {\n return new PlTreeEntry(this.accessorData, this.resource.id);\n }\n}\n\n/** Reactive provider for the current root SET of a multi-root tree. Mirrors {@link PlTreeEntry}:\n * reading it inside a Computable tracks the root set as a dependency, so the Computable\n * recomputes when discovered roots appear or disappear. */\nexport class PlTreeRootsEntry implements AccessorProvider<PlTreeRootsAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsEntry\";\n\n constructor(private readonly accessorData: TreeAccessorData) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeRootsAccessor {\n return new PlTreeRootsAccessor(this.accessorData, this.accessorData.treeProvider(), {\n ctx,\n guard,\n });\n }\n\n public toJSON(): string {\n return \"[ROOTS-ENTRY]\";\n }\n\n public toString(): string {\n return \"[ROOTS-ENTRY]\";\n }\n}\n\nexport function isPlTreeRootsEntry(obj: unknown): obj is PlTreeRootsEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsEntry\"\n );\n}\n\n/** Accessor over the current roots of a multi-root tree. Reading any of its methods tracks\n * the root set as a dependency (via {@link PlTreeState.getRoots}). */\nexport class PlTreeRootsAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n /** Current root ids. */\n public rootIds(): SignedResourceId[] {\n this.instanceData.guard();\n return this.tree.getRoots(this.instanceData.ctx.watcher);\n }\n\n /** Current roots as node accessors, ready to traverse. */\n public nodes(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor[] {\n this.instanceData.guard();\n\n // mirror PlTreeEntryAccessor.node(): attach polling hooks on first node acquisition\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops));\n }\n\n /** Current roots as entries, for persisting per-root sub-Computables. */\n public entries(): PlTreeEntry[] {\n this.instanceData.guard();\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => new PlTreeEntry(this.accessorData, rid));\n }\n}\n\nexport function isPlTreeRootsAccessor(obj: unknown): obj is PlTreeRootsAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsAccessor\"\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accessors.cjs","names":["mapValueAndError"],"sources":["../src/accessors.ts"],"sourcesContent":["import type { PlTreeResource, PlTreeState } from \"./state\";\nimport type {\n AccessorProvider,\n ComputableCtx,\n ComputableHooks,\n UsageGuard,\n} from \"@milaboratories/computable\";\nimport {\n resourceIdToString,\n resourceTypesEqual,\n resourceTypeToString,\n ResourceType,\n OptionalSignedResourceId,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport type { ValueAndError } from \"./value_and_error\";\nimport { mapValueAndError } from \"./value_and_error\";\nimport type {\n CommonFieldTraverseOps,\n FieldTraversalStep,\n GetFieldStep,\n ResourceTraversalOps,\n} from \"./traversal_ops\";\nimport type { ValueOrError } from \"./value_or_error\";\nimport { parsePlError } from \"@milaboratories/pl-errors\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\n/** Error encountered during traversal in field or resource. */\nexport class PlError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport type TreeAccessorData = {\n readonly treeProvider: () => PlTreeState;\n readonly hooks?: ComputableHooks;\n};\n\nexport type TreeAccessorInstanceData = {\n readonly guard: UsageGuard;\n readonly ctx: ComputableCtx;\n};\n\nexport function isPlTreeEntry(obj: unknown): obj is PlTreeEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntry\"\n );\n}\n\nexport function isPlTreeEntryAccessor(obj: unknown): obj is PlTreeEntryAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntryAccessor\"\n );\n}\n\nexport function isPlTreeNodeAccessor(obj: unknown): obj is PlTreeNodeAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeNodeAccessor\"\n );\n}\n\n/** Main entry point for using PlTree in reactive setting */\nexport class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntry\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n public readonly rid: SignedResourceId, // only for runtime. Not exposing rid at all causes too much pain. Never persist this anywhere.\n ) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.accessorData.treeProvider(), this.rid, {\n ctx,\n guard,\n });\n }\n\n /**\n * Tree TreeEntry ID suitable for persistence and use in names (files/objects/whatever), as it does not\n * contain signature which can change from session to session.\n */\n public get id(): string {\n return resourceIdToString(this.rid);\n }\n\n public toJSON(): string {\n return this.toString();\n }\n\n public toString(): string {\n return `[ENTRY:${resourceIdToString(this.rid)}]`;\n }\n}\n\nfunction getResourceFromTree(\n accessorData: TreeAccessorData,\n tree: PlTreeState,\n instanceData: TreeAccessorInstanceData,\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n): PlTreeNodeAccessor {\n const acc = new PlTreeNodeAccessor(\n accessorData,\n tree,\n tree.get(instanceData.ctx.watcher, rid),\n instanceData,\n );\n\n if (!ops.ignoreError) {\n const err = acc.getError();\n if (err !== undefined)\n throw parsePlError(notEmpty(err.getDataAsString()), acc.id, acc.resourceType);\n }\n\n if (\n ops.assertResourceType !== undefined &&\n (Array.isArray(ops.assertResourceType)\n ? ops.assertResourceType.findIndex((rt) => resourceTypesEqual(rt, acc.resourceType)) === -1\n : !resourceTypesEqual(ops.assertResourceType, acc.resourceType))\n )\n throw new Error(\n `wrong resource type ${resourceTypeToString(acc.resourceType)} but expected ${ops.assertResourceType}`,\n );\n\n return acc;\n}\n\nexport class PlTreeEntryAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntryAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly rid: SignedResourceId,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n node(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor {\n this.instanceData.guard();\n\n // this is the only entry point to acquire a PlTreeNodeAccessor,\n // so this is the only point where we should attach the hooks\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, this.rid, ops);\n }\n}\n\n/**\n * Helper type to simplify implementation of APIs requiring type information.\n * */\nexport type ResourceInfo = {\n readonly id: SignedResourceId;\n readonly type: ResourceType;\n};\n\n/**\n * Can be called only when a ctx is provided, because pl tree entry is a computable entity.\n * @deprecated\n * */\nexport function treeEntryToResourceInfo(res: PlTreeEntry | ResourceInfo, ctx: ComputableCtx) {\n if (res instanceof PlTreeEntry) return ctx.accessor(res).node().resourceInfo;\n\n return res;\n}\n\n/**\n * API contracts:\n * - API never return {@link NullSignedResourceId}, absence of link is always modeled as `undefined`\n *\n * Important: never store instances of this class, always get fresh instance from {@link PlTreeState} accessor.\n * */\nexport class PlTreeNodeAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeNodeAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly resource: PlTreeResource,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n public get id(): SignedResourceId {\n this.instanceData.guard();\n return this.resource.id;\n }\n\n public get originalId(): OptionalSignedResourceId {\n this.instanceData.guard();\n return this.resource.originalResourceId;\n }\n\n public get resourceType(): ResourceType {\n this.instanceData.guard();\n return this.resource.type;\n }\n\n public get resourceInfo(): ResourceInfo {\n return { id: this.id, type: this.resourceType };\n }\n\n private getResourceFromTree(\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n ): PlTreeNodeAccessor {\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops);\n }\n\n public traverse(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): PlTreeNodeAccessor;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined {\n return this.traverseWithCommon({}, ...steps);\n }\n\n public traverseOrError(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): ValueOrError<PlTreeNodeAccessor, Error>;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n return this.traverseOrErrorWithCommon({}, ...steps);\n }\n\n public traverseWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): PlTreeNodeAccessor | undefined {\n const result = this.traverseOrErrorWithCommon(commonOptions, ...steps);\n if (result === undefined) return undefined;\n\n if (!result.ok) throw result.error;\n return result.value;\n }\n\n public traverseOrErrorWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: PlTreeNodeAccessor = this;\n\n for (const _step of steps) {\n const step: FieldTraversalStep =\n typeof _step === \"string\"\n ? {\n ...commonOptions,\n field: _step,\n }\n : { ...commonOptions, ..._step };\n\n const next = current.getField(step);\n\n if (next === undefined) return undefined;\n\n if (step.pureFieldErrorToUndefined && next.value === undefined && next.error !== undefined)\n return undefined;\n\n if ((!step.ignoreError || next.value === undefined) && next.error !== undefined)\n return {\n ok: false,\n\n // FIXME: in next tickets we'll allow Errors to be thrown.\n error: parsePlError(\n notEmpty(next.error.getDataAsString()),\n current.id,\n current.resourceType,\n step.field,\n ),\n };\n\n if (next.value === undefined) {\n if (step.errorIfFieldNotSet)\n return {\n ok: false,\n error: new Error(\n `field have no assigned value ${step.field} of ${resourceIdToString(current.id)}`,\n ),\n };\n // existing but unpopulated field is unstable because it must be resolved at some point,\n // unless its resource is final and so never will be\n if (!current.resource.finalState) this.onUnstableLambda(\"unpopulated_field:\" + step.field);\n return undefined;\n }\n\n current = next.value;\n }\n return { ok: true, value: current };\n }\n\n private readonly onUnstableLambda = (marker: string) => {\n this.instanceData.ctx.markUnstable(marker);\n };\n\n public getField(\n _step:\n | (Omit<GetFieldStep, \"errorIfFieldNotFound\"> & { errorIfFieldNotFound: true })\n | (Omit<GetFieldStep, \"errorIfFieldNotSet\"> & { errorIfFieldNotSet: true }),\n ): ValueAndError<PlTreeNodeAccessor>;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined {\n this.instanceData.guard();\n const step: GetFieldStep = typeof _step === \"string\" ? { field: _step } : _step;\n\n const ve = this.resource.getField(this.instanceData.ctx.watcher, step, this.onUnstableLambda);\n\n if (ve === undefined) return undefined;\n\n return mapValueAndError(ve, (rid) => this.getResourceFromTree(rid, { ignoreError: true }));\n }\n\n public getInputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getInputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"inputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getOutputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getOutputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"outputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getIsReadyOrError(): boolean {\n this.instanceData.guard();\n const result = this.resource.getIsReadyOrError(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"not_ready:\" + this.resourceType.name);\n return result;\n }\n\n public getIsFinal() {\n this.instanceData.guard();\n return this.resource.getIsFinal(this.instanceData.ctx.watcher);\n }\n\n public getError(): PlTreeNodeAccessor | undefined {\n this.instanceData.guard();\n const rid = this.resource.getError(this.instanceData.ctx.watcher);\n if (rid === undefined)\n // absence of error always considered as stable\n return undefined;\n return this.getResourceFromTree(rid, {});\n }\n\n public hasData(): boolean {\n this.instanceData.guard();\n return this.resource.data !== undefined;\n }\n\n public getData(): Uint8Array | undefined {\n this.instanceData.guard();\n return this.resource.data;\n }\n\n public getDataAsString(): string | undefined {\n this.instanceData.guard();\n return this.resource.getDataAsString();\n }\n\n public getDataAsJson<T = unknown>(): T | undefined {\n this.instanceData.guard();\n return this.resource.getDataAsJson<T>();\n }\n\n public listInputFields(): string[] {\n this.instanceData.guard();\n this.getInputsLocked(); // will set unstable if not locked\n return this.resource.listInputFields(this.instanceData.ctx.watcher);\n }\n\n public listOutputFields(): string[] {\n this.instanceData.guard();\n this.getOutputsLocked(); // will set unstable if not locked\n return this.resource.listOutputFields(this.instanceData.ctx.watcher);\n }\n\n public listDynamicFields(): string[] {\n this.instanceData.guard();\n return this.resource.listDynamicFields(this.instanceData.ctx.watcher);\n }\n\n public getKeyValue(key: string, unstableIfNotFound: boolean = false): Uint8Array | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValue(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_b:\" + key);\n return result;\n }\n\n /** @deprecated */\n public getKeyValueString(key: string): string | undefined {\n return this.getKeyValueAsString(key);\n }\n\n public getKeyValueAsString(key: string, unstableIfNotFound: boolean = false): string | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueString(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_s:\" + key);\n return result;\n }\n\n public getKeyValueAsJson<T = unknown>(\n key: string,\n unstableIfNotFound: boolean = false,\n ): T | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueAsJson<T>(this.instanceData.ctx.watcher, key);\n if (result === undefined) {\n if (unstableIfNotFound) this.instanceData.ctx.markUnstable(\"key_not_found_j:\" + key);\n return undefined;\n }\n return result;\n }\n\n /**\n * Can be used to pass a higher level accessor that will wrap the resource and throw its\n * errors on node resolution.\n * */\n public toEntryAccessor(): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.tree, this.id, this.instanceData);\n }\n\n /** Can be passed to nested computable. */\n public persist(): PlTreeEntry {\n return new PlTreeEntry(this.accessorData, this.resource.id);\n }\n}\n\n/** Reactive provider for the current root SET of a multi-root tree. Mirrors {@link PlTreeEntry}:\n * reading it inside a Computable tracks the root set as a dependency, so the Computable\n * recomputes when discovered roots appear or disappear. */\nexport class PlTreeRootsEntry implements AccessorProvider<PlTreeRootsAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsEntry\";\n\n constructor(private readonly accessorData: TreeAccessorData) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeRootsAccessor {\n return new PlTreeRootsAccessor(this.accessorData, this.accessorData.treeProvider(), {\n ctx,\n guard,\n });\n }\n\n public toJSON(): string {\n return \"[ROOTS-ENTRY]\";\n }\n\n public toString(): string {\n return \"[ROOTS-ENTRY]\";\n }\n}\n\nexport function isPlTreeRootsEntry(obj: unknown): obj is PlTreeRootsEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsEntry\"\n );\n}\n\n/** Accessor over the current roots of a multi-root tree. Reading any of its methods tracks\n * the root set as a dependency (via {@link PlTreeState.getRoots}). */\nexport class PlTreeRootsAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n /** Current root ids. */\n public rootIds(): SignedResourceId[] {\n this.instanceData.guard();\n return this.tree.getRoots(this.instanceData.ctx.watcher);\n }\n\n /** Current roots as node accessors, ready to traverse. */\n public nodes(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor[] {\n this.instanceData.guard();\n\n // mirror PlTreeEntryAccessor.node(): attach polling hooks on first node acquisition\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops));\n }\n\n /** Current roots as entries, for persisting per-root sub-Computables. */\n public entries(): PlTreeEntry[] {\n this.instanceData.guard();\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => new PlTreeEntry(this.accessorData, rid));\n }\n}\n\nexport function isPlTreeRootsAccessor(obj: unknown): obj is PlTreeRootsAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsAccessor\"\n );\n}\n"],"mappings":"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"}
|
package/dist/accessors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessors.d.ts","names":[],"sources":["../src/accessors.ts"],"mappings":";;;;;;;;qBA2Ba,gBAAgB;EAC3B,YAAY;;YAKF;WACD,oBAAoB;WACpB,QAAQ;;YAGP;WACD,OAAO;WACP,KAAK;;wBAGA,cAAc,eAAe,OAAO;wBAQpC,sBAAsB,eAAe,OAAO;wBAQ5C,qBAAqB,eAAe,OAAO;;qBAS9C,uBAAuB,iBAAiB;mBAIhC;WACD,KAAK;mBAJN;EAEjB,YACmB,cAAc,kBACf,KAAK;EAGhB,eAAe,KAAK,eAAe,OAAO,aAAa;;;;;MAWnD;EAIJ;EAIA;;qBAsCI;mBAIQ;mBACA;mBACA;mBACA;mBANF;EAEjB,YACmB,cAAc,kBACd,MAAM,aACN,KAAK,kBACL,cAAc;EAGjC,KAAK,MAAK,uBAA4B;;;;;YAe5B;WACD,IAAI;WACJ,MAAM;;;;;;wBAOD,wBAAwB,KAAK,cAAc,cAAc,KAAK,gBAAa;;;;;;;qBAY9E;mBAIQ;mBACA;mBACA;mBACA;mBANF;EAEjB,YACmB,cAAc,kBACd,MAAM,aACN,UAAU,gBACV,cAAc;MAGtB,MAAM;MAKN,cAAc;MAKd,gBAAgB;MAKhB,gBAAgB;UAInB;EAOD,YACF,QACD,KAAK;IACH;OAGH;EACI,YAAY,QAAQ,iCAAiC;EAKrD,mBACF,QACD,KAAK;IACH;OAGH,aAAa,oBAAoB;EAC7B,mBACF,QAAQ,iCACV,aAAa,oBAAoB;EAO7B,mBACL,eAAe,2BACZ,QAAQ,iCACV;EAQI,0BACL,eAAe,2BACZ,QAAQ,iCACV,aAAa,oBAAoB;
|
|
1
|
+
{"version":3,"file":"accessors.d.ts","names":[],"sources":["../src/accessors.ts"],"mappings":";;;;;;;;qBA2Ba,gBAAgB;EAC3B,YAAY;;YAKF;WACD,oBAAoB;WACpB,QAAQ;;YAGP;WACD,OAAO;WACP,KAAK;;wBAGA,cAAc,eAAe,OAAO;wBAQpC,sBAAsB,eAAe,OAAO;wBAQ5C,qBAAqB,eAAe,OAAO;;qBAS9C,uBAAuB,iBAAiB;mBAIhC;WACD,KAAK;mBAJN;EAEjB,YACmB,cAAc,kBACf,KAAK;EAGhB,eAAe,KAAK,eAAe,OAAO,aAAa;;;;;MAWnD;EAIJ;EAIA;;qBAsCI;mBAIQ;mBACA;mBACA;mBACA;mBANF;EAEjB,YACmB,cAAc,kBACd,MAAM,aACN,KAAK,kBACL,cAAc;EAGjC,KAAK,MAAK,uBAA4B;;;;;YAe5B;WACD,IAAI;WACJ,MAAM;;;;;;wBAOD,wBAAwB,KAAK,cAAc,cAAc,KAAK,gBAAa;;;;;;;qBAY9E;mBAIQ;mBACA;mBACA;mBACA;mBANF;EAEjB,YACmB,cAAc,kBACd,MAAM,aACN,UAAU,gBACV,cAAc;MAGtB,MAAM;MAKN,cAAc;MAKd,gBAAgB;MAKhB,gBAAgB;UAInB;EAOD,YACF,QACD,KAAK;IACH;OAGH;EACI,YAAY,QAAQ,iCAAiC;EAKrD,mBACF,QACD,KAAK;IACH;OAGH,aAAa,oBAAoB;EAC7B,mBACF,QAAQ,iCACV,aAAa,oBAAoB;EAO7B,mBACL,eAAe,2BACZ,QAAQ,iCACV;EAQI,0BACL,eAAe,2BACZ,QAAQ,iCACV,aAAa,oBAAoB;mBAoDnB;EAIV,SACL,QACK,KAAK;IAA0C;QAC/C,KAAK;IAAwC;OACjD,cAAc;EACV,SAAS,OAAO,wBAAwB,cAAc;EAYtD;EAOA;EAOA;EAOA;EAKA,YAAY;EASZ;EAKA,WAAW;EAKX;EAKA,cAAc,gBAAgB;EAK9B;EAMA;EAMA;EAKA,YAAY,aAAa,+BAAsC;;EAS/D,kBAAkB;EAIlB,oBAAoB,aAAa;EAQjC,kBAAkB,aACvB,aACA,+BACC;;;;;EAcI,mBAAmB;;EAKnB,WAAW;;;;;qBAQP,4BAA4B,iBAAiB;mBAG3B;mBAFZ;EAEjB,YAA6B,cAAc;EAEpC,eAAe,KAAK,eAAe,OAAO,aAAa;EAOvD;EAIA;;wBAKO,mBAAmB,eAAe,OAAO;;;qBAU5C;mBAIQ;mBACA;mBACA;mBALF;EAEjB,YACmB,cAAc,kBACd,MAAM,aACN,cAAc;;EAI1B,WAAW;;EAMX,MAAM,MAAK,uBAA4B;;EAavC,WAAW;;wBAQJ,sBAAsB,eAAe,OAAO"}
|
package/dist/accessors.js
CHANGED
|
@@ -143,7 +143,7 @@ var PlTreeNodeAccessor = class {
|
|
|
143
143
|
...commonOptions,
|
|
144
144
|
..._step
|
|
145
145
|
};
|
|
146
|
-
const next = current.getField(
|
|
146
|
+
const next = current.getField(step);
|
|
147
147
|
if (next === void 0) return void 0;
|
|
148
148
|
if (step.pureFieldErrorToUndefined && next.value === void 0 && next.error !== void 0) return void 0;
|
|
149
149
|
if ((!step.ignoreError || next.value === void 0) && next.error !== void 0) return {
|
|
@@ -155,7 +155,7 @@ var PlTreeNodeAccessor = class {
|
|
|
155
155
|
ok: false,
|
|
156
156
|
error: /* @__PURE__ */ new Error(`field have no assigned value ${step.field} of ${resourceIdToString(current.id)}`)
|
|
157
157
|
};
|
|
158
|
-
this.onUnstableLambda("unpopulated_field:" + step.field);
|
|
158
|
+
if (!current.resource.finalState) this.onUnstableLambda("unpopulated_field:" + step.field);
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
current = next.value;
|
|
@@ -208,12 +208,15 @@ var PlTreeNodeAccessor = class {
|
|
|
208
208
|
return this.resource.data !== void 0;
|
|
209
209
|
}
|
|
210
210
|
getData() {
|
|
211
|
+
this.instanceData.guard();
|
|
211
212
|
return this.resource.data;
|
|
212
213
|
}
|
|
213
214
|
getDataAsString() {
|
|
215
|
+
this.instanceData.guard();
|
|
214
216
|
return this.resource.getDataAsString();
|
|
215
217
|
}
|
|
216
218
|
getDataAsJson() {
|
|
219
|
+
this.instanceData.guard();
|
|
217
220
|
return this.resource.getDataAsJson();
|
|
218
221
|
}
|
|
219
222
|
listInputFields() {
|
|
@@ -247,6 +250,7 @@ var PlTreeNodeAccessor = class {
|
|
|
247
250
|
return result;
|
|
248
251
|
}
|
|
249
252
|
getKeyValueAsJson(key, unstableIfNotFound = false) {
|
|
253
|
+
this.instanceData.guard();
|
|
250
254
|
const result = this.resource.getKeyValueAsJson(this.instanceData.ctx.watcher, key);
|
|
251
255
|
if (result === void 0) {
|
|
252
256
|
if (unstableIfNotFound) this.instanceData.ctx.markUnstable("key_not_found_j:" + key);
|
package/dist/accessors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessors.js","names":[],"sources":["../src/accessors.ts"],"sourcesContent":["import type { PlTreeResource, PlTreeState } from \"./state\";\nimport type {\n AccessorProvider,\n ComputableCtx,\n ComputableHooks,\n UsageGuard,\n} from \"@milaboratories/computable\";\nimport {\n resourceIdToString,\n resourceTypesEqual,\n resourceTypeToString,\n ResourceType,\n OptionalSignedResourceId,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport type { ValueAndError } from \"./value_and_error\";\nimport { mapValueAndError } from \"./value_and_error\";\nimport type {\n CommonFieldTraverseOps,\n FieldTraversalStep,\n GetFieldStep,\n ResourceTraversalOps,\n} from \"./traversal_ops\";\nimport type { ValueOrError } from \"./value_or_error\";\nimport { parsePlError } from \"@milaboratories/pl-errors\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\n/** Error encountered during traversal in field or resource. */\nexport class PlError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport type TreeAccessorData = {\n readonly treeProvider: () => PlTreeState;\n readonly hooks?: ComputableHooks;\n};\n\nexport type TreeAccessorInstanceData = {\n readonly guard: UsageGuard;\n readonly ctx: ComputableCtx;\n};\n\nexport function isPlTreeEntry(obj: unknown): obj is PlTreeEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntry\"\n );\n}\n\nexport function isPlTreeEntryAccessor(obj: unknown): obj is PlTreeEntryAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntryAccessor\"\n );\n}\n\nexport function isPlTreeNodeAccessor(obj: unknown): obj is PlTreeNodeAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeNodeAccessor\"\n );\n}\n\n/** Main entry point for using PlTree in reactive setting */\nexport class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntry\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n public readonly rid: SignedResourceId, // only for runtime. Not exposing rid at all causes too much pain. Never persist this anywhere.\n ) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.accessorData.treeProvider(), this.rid, {\n ctx,\n guard,\n });\n }\n\n /**\n * Tree TreeEntry ID suitable for persistence and use in names (files/objects/whatever), as it does not\n * contain signature which can change from session to session.\n */\n public get id(): string {\n return resourceIdToString(this.rid);\n }\n\n public toJSON(): string {\n return this.toString();\n }\n\n public toString(): string {\n return `[ENTRY:${resourceIdToString(this.rid)}]`;\n }\n}\n\nfunction getResourceFromTree(\n accessorData: TreeAccessorData,\n tree: PlTreeState,\n instanceData: TreeAccessorInstanceData,\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n): PlTreeNodeAccessor {\n const acc = new PlTreeNodeAccessor(\n accessorData,\n tree,\n tree.get(instanceData.ctx.watcher, rid),\n instanceData,\n );\n\n if (!ops.ignoreError) {\n const err = acc.getError();\n if (err !== undefined)\n throw parsePlError(notEmpty(err.getDataAsString()), acc.id, acc.resourceType);\n }\n\n if (\n ops.assertResourceType !== undefined &&\n (Array.isArray(ops.assertResourceType)\n ? ops.assertResourceType.findIndex((rt) => resourceTypesEqual(rt, acc.resourceType)) === -1\n : !resourceTypesEqual(ops.assertResourceType, acc.resourceType))\n )\n throw new Error(\n `wrong resource type ${resourceTypeToString(acc.resourceType)} but expected ${ops.assertResourceType}`,\n );\n\n return acc;\n}\n\nexport class PlTreeEntryAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntryAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly rid: SignedResourceId,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n node(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor {\n this.instanceData.guard();\n\n // this is the only entry point to acquire a PlTreeNodeAccessor,\n // so this is the only point where we should attach the hooks\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, this.rid, ops);\n }\n}\n\n/**\n * Helper type to simplify implementation of APIs requiring type information.\n * */\nexport type ResourceInfo = {\n readonly id: SignedResourceId;\n readonly type: ResourceType;\n};\n\n/**\n * Can be called only when a ctx is provided, because pl tree entry is a computable entity.\n * @deprecated\n * */\nexport function treeEntryToResourceInfo(res: PlTreeEntry | ResourceInfo, ctx: ComputableCtx) {\n if (res instanceof PlTreeEntry) return ctx.accessor(res).node().resourceInfo;\n\n return res;\n}\n\n/**\n * API contracts:\n * - API never return {@link NullSignedResourceId}, absence of link is always modeled as `undefined`\n *\n * Important: never store instances of this class, always get fresh instance from {@link PlTreeState} accessor.\n * */\nexport class PlTreeNodeAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeNodeAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly resource: PlTreeResource,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n public get id(): SignedResourceId {\n this.instanceData.guard();\n return this.resource.id;\n }\n\n public get originalId(): OptionalSignedResourceId {\n this.instanceData.guard();\n return this.resource.originalResourceId;\n }\n\n public get resourceType(): ResourceType {\n this.instanceData.guard();\n return this.resource.type;\n }\n\n public get resourceInfo(): ResourceInfo {\n return { id: this.id, type: this.resourceType };\n }\n\n private getResourceFromTree(\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n ): PlTreeNodeAccessor {\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops);\n }\n\n public traverse(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): PlTreeNodeAccessor;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined {\n return this.traverseWithCommon({}, ...steps);\n }\n\n public traverseOrError(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): ValueOrError<PlTreeNodeAccessor, Error>;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n return this.traverseOrErrorWithCommon({}, ...steps);\n }\n\n public traverseWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): PlTreeNodeAccessor | undefined {\n const result = this.traverseOrErrorWithCommon(commonOptions, ...steps);\n if (result === undefined) return undefined;\n\n if (!result.ok) throw result.error;\n return result.value;\n }\n\n public traverseOrErrorWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: PlTreeNodeAccessor = this;\n\n for (const _step of steps) {\n const step: FieldTraversalStep =\n typeof _step === \"string\"\n ? {\n ...commonOptions,\n field: _step,\n }\n : { ...commonOptions, ..._step };\n\n const next = current.getField(_step);\n\n if (next === undefined) return undefined;\n\n if (step.pureFieldErrorToUndefined && next.value === undefined && next.error !== undefined)\n return undefined;\n\n if ((!step.ignoreError || next.value === undefined) && next.error !== undefined)\n return {\n ok: false,\n\n // FIXME: in next tickets we'll allow Errors to be thrown.\n error: parsePlError(\n notEmpty(next.error.getDataAsString()),\n current.id,\n current.resourceType,\n step.field,\n ),\n };\n\n if (next.value === undefined) {\n if (step.errorIfFieldNotSet)\n return {\n ok: false,\n error: new Error(\n `field have no assigned value ${step.field} of ${resourceIdToString(current.id)}`,\n ),\n };\n // existing but unpopulated field is unstable because it must be resolved at some point\n this.onUnstableLambda(\"unpopulated_field:\" + step.field);\n return undefined;\n }\n\n current = next.value;\n }\n return { ok: true, value: current };\n }\n\n private readonly onUnstableLambda = (marker: string) => {\n this.instanceData.ctx.markUnstable(marker);\n };\n\n public getField(\n _step:\n | (Omit<GetFieldStep, \"errorIfFieldNotFound\"> & { errorIfFieldNotFound: true })\n | (Omit<GetFieldStep, \"errorIfFieldNotSet\"> & { errorIfFieldNotSet: true }),\n ): ValueAndError<PlTreeNodeAccessor>;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined {\n this.instanceData.guard();\n const step: GetFieldStep = typeof _step === \"string\" ? { field: _step } : _step;\n\n const ve = this.resource.getField(this.instanceData.ctx.watcher, step, this.onUnstableLambda);\n\n if (ve === undefined) return undefined;\n\n return mapValueAndError(ve, (rid) => this.getResourceFromTree(rid, { ignoreError: true }));\n }\n\n public getInputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getInputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"inputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getOutputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getOutputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"outputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getIsReadyOrError(): boolean {\n this.instanceData.guard();\n const result = this.resource.getIsReadyOrError(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"not_ready:\" + this.resourceType.name);\n return result;\n }\n\n public getIsFinal() {\n this.instanceData.guard();\n return this.resource.getIsFinal(this.instanceData.ctx.watcher);\n }\n\n public getError(): PlTreeNodeAccessor | undefined {\n this.instanceData.guard();\n const rid = this.resource.getError(this.instanceData.ctx.watcher);\n if (rid === undefined)\n // absence of error always considered as stable\n return undefined;\n return this.getResourceFromTree(rid, {});\n }\n\n public hasData(): boolean {\n this.instanceData.guard();\n return this.resource.data !== undefined;\n }\n\n public getData(): Uint8Array | undefined {\n return this.resource.data;\n }\n\n public getDataAsString(): string | undefined {\n return this.resource.getDataAsString();\n }\n\n public getDataAsJson<T = unknown>(): T | undefined {\n return this.resource.getDataAsJson<T>();\n }\n\n public listInputFields(): string[] {\n this.instanceData.guard();\n this.getInputsLocked(); // will set unstable if not locked\n return this.resource.listInputFields(this.instanceData.ctx.watcher);\n }\n\n public listOutputFields(): string[] {\n this.instanceData.guard();\n this.getOutputsLocked(); // will set unstable if not locked\n return this.resource.listOutputFields(this.instanceData.ctx.watcher);\n }\n\n public listDynamicFields(): string[] {\n this.instanceData.guard();\n return this.resource.listDynamicFields(this.instanceData.ctx.watcher);\n }\n\n public getKeyValue(key: string, unstableIfNotFound: boolean = false): Uint8Array | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValue(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_b:\" + key);\n return result;\n }\n\n /** @deprecated */\n public getKeyValueString(key: string): string | undefined {\n return this.getKeyValueAsString(key);\n }\n\n public getKeyValueAsString(key: string, unstableIfNotFound: boolean = false): string | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueString(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_s:\" + key);\n return result;\n }\n\n public getKeyValueAsJson<T = unknown>(\n key: string,\n unstableIfNotFound: boolean = false,\n ): T | undefined {\n const result = this.resource.getKeyValueAsJson<T>(this.instanceData.ctx.watcher, key);\n if (result === undefined) {\n if (unstableIfNotFound) this.instanceData.ctx.markUnstable(\"key_not_found_j:\" + key);\n return undefined;\n }\n return result;\n }\n\n /**\n * Can be used to pass a higher level accessor that will wrap the resource and throw its\n * errors on node resolution.\n * */\n public toEntryAccessor(): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.tree, this.id, this.instanceData);\n }\n\n /** Can be passed to nested computable. */\n public persist(): PlTreeEntry {\n return new PlTreeEntry(this.accessorData, this.resource.id);\n }\n}\n\n/** Reactive provider for the current root SET of a multi-root tree. Mirrors {@link PlTreeEntry}:\n * reading it inside a Computable tracks the root set as a dependency, so the Computable\n * recomputes when discovered roots appear or disappear. */\nexport class PlTreeRootsEntry implements AccessorProvider<PlTreeRootsAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsEntry\";\n\n constructor(private readonly accessorData: TreeAccessorData) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeRootsAccessor {\n return new PlTreeRootsAccessor(this.accessorData, this.accessorData.treeProvider(), {\n ctx,\n guard,\n });\n }\n\n public toJSON(): string {\n return \"[ROOTS-ENTRY]\";\n }\n\n public toString(): string {\n return \"[ROOTS-ENTRY]\";\n }\n}\n\nexport function isPlTreeRootsEntry(obj: unknown): obj is PlTreeRootsEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsEntry\"\n );\n}\n\n/** Accessor over the current roots of a multi-root tree. Reading any of its methods tracks\n * the root set as a dependency (via {@link PlTreeState.getRoots}). */\nexport class PlTreeRootsAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n /** Current root ids. */\n public rootIds(): SignedResourceId[] {\n this.instanceData.guard();\n return this.tree.getRoots(this.instanceData.ctx.watcher);\n }\n\n /** Current roots as node accessors, ready to traverse. */\n public nodes(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor[] {\n this.instanceData.guard();\n\n // mirror PlTreeEntryAccessor.node(): attach polling hooks on first node acquisition\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops));\n }\n\n /** Current roots as entries, for persisting per-root sub-Computables. */\n public entries(): PlTreeEntry[] {\n this.instanceData.guard();\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => new PlTreeEntry(this.accessorData, rid));\n }\n}\n\nexport function isPlTreeRootsAccessor(obj: unknown): obj is PlTreeRootsAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsAccessor\"\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accessors.js","names":[],"sources":["../src/accessors.ts"],"sourcesContent":["import type { PlTreeResource, PlTreeState } from \"./state\";\nimport type {\n AccessorProvider,\n ComputableCtx,\n ComputableHooks,\n UsageGuard,\n} from \"@milaboratories/computable\";\nimport {\n resourceIdToString,\n resourceTypesEqual,\n resourceTypeToString,\n ResourceType,\n OptionalSignedResourceId,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport type { ValueAndError } from \"./value_and_error\";\nimport { mapValueAndError } from \"./value_and_error\";\nimport type {\n CommonFieldTraverseOps,\n FieldTraversalStep,\n GetFieldStep,\n ResourceTraversalOps,\n} from \"./traversal_ops\";\nimport type { ValueOrError } from \"./value_or_error\";\nimport { parsePlError } from \"@milaboratories/pl-errors\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\n/** Error encountered during traversal in field or resource. */\nexport class PlError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport type TreeAccessorData = {\n readonly treeProvider: () => PlTreeState;\n readonly hooks?: ComputableHooks;\n};\n\nexport type TreeAccessorInstanceData = {\n readonly guard: UsageGuard;\n readonly ctx: ComputableCtx;\n};\n\nexport function isPlTreeEntry(obj: unknown): obj is PlTreeEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntry\"\n );\n}\n\nexport function isPlTreeEntryAccessor(obj: unknown): obj is PlTreeEntryAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeEntryAccessor\"\n );\n}\n\nexport function isPlTreeNodeAccessor(obj: unknown): obj is PlTreeNodeAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeNodeAccessor\"\n );\n}\n\n/** Main entry point for using PlTree in reactive setting */\nexport class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntry\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n public readonly rid: SignedResourceId, // only for runtime. Not exposing rid at all causes too much pain. Never persist this anywhere.\n ) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.accessorData.treeProvider(), this.rid, {\n ctx,\n guard,\n });\n }\n\n /**\n * Tree TreeEntry ID suitable for persistence and use in names (files/objects/whatever), as it does not\n * contain signature which can change from session to session.\n */\n public get id(): string {\n return resourceIdToString(this.rid);\n }\n\n public toJSON(): string {\n return this.toString();\n }\n\n public toString(): string {\n return `[ENTRY:${resourceIdToString(this.rid)}]`;\n }\n}\n\nfunction getResourceFromTree(\n accessorData: TreeAccessorData,\n tree: PlTreeState,\n instanceData: TreeAccessorInstanceData,\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n): PlTreeNodeAccessor {\n const acc = new PlTreeNodeAccessor(\n accessorData,\n tree,\n tree.get(instanceData.ctx.watcher, rid),\n instanceData,\n );\n\n if (!ops.ignoreError) {\n const err = acc.getError();\n if (err !== undefined)\n throw parsePlError(notEmpty(err.getDataAsString()), acc.id, acc.resourceType);\n }\n\n if (\n ops.assertResourceType !== undefined &&\n (Array.isArray(ops.assertResourceType)\n ? ops.assertResourceType.findIndex((rt) => resourceTypesEqual(rt, acc.resourceType)) === -1\n : !resourceTypesEqual(ops.assertResourceType, acc.resourceType))\n )\n throw new Error(\n `wrong resource type ${resourceTypeToString(acc.resourceType)} but expected ${ops.assertResourceType}`,\n );\n\n return acc;\n}\n\nexport class PlTreeEntryAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeEntryAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly rid: SignedResourceId,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n node(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor {\n this.instanceData.guard();\n\n // this is the only entry point to acquire a PlTreeNodeAccessor,\n // so this is the only point where we should attach the hooks\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, this.rid, ops);\n }\n}\n\n/**\n * Helper type to simplify implementation of APIs requiring type information.\n * */\nexport type ResourceInfo = {\n readonly id: SignedResourceId;\n readonly type: ResourceType;\n};\n\n/**\n * Can be called only when a ctx is provided, because pl tree entry is a computable entity.\n * @deprecated\n * */\nexport function treeEntryToResourceInfo(res: PlTreeEntry | ResourceInfo, ctx: ComputableCtx) {\n if (res instanceof PlTreeEntry) return ctx.accessor(res).node().resourceInfo;\n\n return res;\n}\n\n/**\n * API contracts:\n * - API never return {@link NullSignedResourceId}, absence of link is always modeled as `undefined`\n *\n * Important: never store instances of this class, always get fresh instance from {@link PlTreeState} accessor.\n * */\nexport class PlTreeNodeAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeNodeAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly resource: PlTreeResource,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n public get id(): SignedResourceId {\n this.instanceData.guard();\n return this.resource.id;\n }\n\n public get originalId(): OptionalSignedResourceId {\n this.instanceData.guard();\n return this.resource.originalResourceId;\n }\n\n public get resourceType(): ResourceType {\n this.instanceData.guard();\n return this.resource.type;\n }\n\n public get resourceInfo(): ResourceInfo {\n return { id: this.id, type: this.resourceType };\n }\n\n private getResourceFromTree(\n rid: SignedResourceId,\n ops: ResourceTraversalOps,\n ): PlTreeNodeAccessor {\n return getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops);\n }\n\n public traverse(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): PlTreeNodeAccessor;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined;\n public traverse(...steps: (FieldTraversalStep | string)[]): PlTreeNodeAccessor | undefined {\n return this.traverseWithCommon({}, ...steps);\n }\n\n public traverseOrError(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotSet: true;\n },\n ]\n ): ValueOrError<PlTreeNodeAccessor, Error>;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined;\n public traverseOrError(\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n return this.traverseOrErrorWithCommon({}, ...steps);\n }\n\n public traverseWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): PlTreeNodeAccessor | undefined {\n const result = this.traverseOrErrorWithCommon(commonOptions, ...steps);\n if (result === undefined) return undefined;\n\n if (!result.ok) throw result.error;\n return result.value;\n }\n\n public traverseOrErrorWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): ValueOrError<PlTreeNodeAccessor, Error> | undefined {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let current: PlTreeNodeAccessor = this;\n\n for (const _step of steps) {\n const step: FieldTraversalStep =\n typeof _step === \"string\"\n ? {\n ...commonOptions,\n field: _step,\n }\n : { ...commonOptions, ..._step };\n\n const next = current.getField(step);\n\n if (next === undefined) return undefined;\n\n if (step.pureFieldErrorToUndefined && next.value === undefined && next.error !== undefined)\n return undefined;\n\n if ((!step.ignoreError || next.value === undefined) && next.error !== undefined)\n return {\n ok: false,\n\n // FIXME: in next tickets we'll allow Errors to be thrown.\n error: parsePlError(\n notEmpty(next.error.getDataAsString()),\n current.id,\n current.resourceType,\n step.field,\n ),\n };\n\n if (next.value === undefined) {\n if (step.errorIfFieldNotSet)\n return {\n ok: false,\n error: new Error(\n `field have no assigned value ${step.field} of ${resourceIdToString(current.id)}`,\n ),\n };\n // existing but unpopulated field is unstable because it must be resolved at some point,\n // unless its resource is final and so never will be\n if (!current.resource.finalState) this.onUnstableLambda(\"unpopulated_field:\" + step.field);\n return undefined;\n }\n\n current = next.value;\n }\n return { ok: true, value: current };\n }\n\n private readonly onUnstableLambda = (marker: string) => {\n this.instanceData.ctx.markUnstable(marker);\n };\n\n public getField(\n _step:\n | (Omit<GetFieldStep, \"errorIfFieldNotFound\"> & { errorIfFieldNotFound: true })\n | (Omit<GetFieldStep, \"errorIfFieldNotSet\"> & { errorIfFieldNotSet: true }),\n ): ValueAndError<PlTreeNodeAccessor>;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined;\n public getField(_step: GetFieldStep | string): ValueAndError<PlTreeNodeAccessor> | undefined {\n this.instanceData.guard();\n const step: GetFieldStep = typeof _step === \"string\" ? { field: _step } : _step;\n\n const ve = this.resource.getField(this.instanceData.ctx.watcher, step, this.onUnstableLambda);\n\n if (ve === undefined) return undefined;\n\n return mapValueAndError(ve, (rid) => this.getResourceFromTree(rid, { ignoreError: true }));\n }\n\n public getInputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getInputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"inputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getOutputsLocked(): boolean {\n this.instanceData.guard();\n const result = this.resource.getOutputsLocked(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"outputs_unlocked:\" + this.resourceType.name);\n return result;\n }\n\n public getIsReadyOrError(): boolean {\n this.instanceData.guard();\n const result = this.resource.getIsReadyOrError(this.instanceData.ctx.watcher);\n if (!result) this.instanceData.ctx.markUnstable(\"not_ready:\" + this.resourceType.name);\n return result;\n }\n\n public getIsFinal() {\n this.instanceData.guard();\n return this.resource.getIsFinal(this.instanceData.ctx.watcher);\n }\n\n public getError(): PlTreeNodeAccessor | undefined {\n this.instanceData.guard();\n const rid = this.resource.getError(this.instanceData.ctx.watcher);\n if (rid === undefined)\n // absence of error always considered as stable\n return undefined;\n return this.getResourceFromTree(rid, {});\n }\n\n public hasData(): boolean {\n this.instanceData.guard();\n return this.resource.data !== undefined;\n }\n\n public getData(): Uint8Array | undefined {\n this.instanceData.guard();\n return this.resource.data;\n }\n\n public getDataAsString(): string | undefined {\n this.instanceData.guard();\n return this.resource.getDataAsString();\n }\n\n public getDataAsJson<T = unknown>(): T | undefined {\n this.instanceData.guard();\n return this.resource.getDataAsJson<T>();\n }\n\n public listInputFields(): string[] {\n this.instanceData.guard();\n this.getInputsLocked(); // will set unstable if not locked\n return this.resource.listInputFields(this.instanceData.ctx.watcher);\n }\n\n public listOutputFields(): string[] {\n this.instanceData.guard();\n this.getOutputsLocked(); // will set unstable if not locked\n return this.resource.listOutputFields(this.instanceData.ctx.watcher);\n }\n\n public listDynamicFields(): string[] {\n this.instanceData.guard();\n return this.resource.listDynamicFields(this.instanceData.ctx.watcher);\n }\n\n public getKeyValue(key: string, unstableIfNotFound: boolean = false): Uint8Array | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValue(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_b:\" + key);\n return result;\n }\n\n /** @deprecated */\n public getKeyValueString(key: string): string | undefined {\n return this.getKeyValueAsString(key);\n }\n\n public getKeyValueAsString(key: string, unstableIfNotFound: boolean = false): string | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueString(this.instanceData.ctx.watcher, key);\n if (result === undefined && unstableIfNotFound)\n this.instanceData.ctx.markUnstable(\"key_not_found_s:\" + key);\n return result;\n }\n\n public getKeyValueAsJson<T = unknown>(\n key: string,\n unstableIfNotFound: boolean = false,\n ): T | undefined {\n this.instanceData.guard();\n const result = this.resource.getKeyValueAsJson<T>(this.instanceData.ctx.watcher, key);\n if (result === undefined) {\n if (unstableIfNotFound) this.instanceData.ctx.markUnstable(\"key_not_found_j:\" + key);\n return undefined;\n }\n return result;\n }\n\n /**\n * Can be used to pass a higher level accessor that will wrap the resource and throw its\n * errors on node resolution.\n * */\n public toEntryAccessor(): PlTreeEntryAccessor {\n return new PlTreeEntryAccessor(this.accessorData, this.tree, this.id, this.instanceData);\n }\n\n /** Can be passed to nested computable. */\n public persist(): PlTreeEntry {\n return new PlTreeEntry(this.accessorData, this.resource.id);\n }\n}\n\n/** Reactive provider for the current root SET of a multi-root tree. Mirrors {@link PlTreeEntry}:\n * reading it inside a Computable tracks the root set as a dependency, so the Computable\n * recomputes when discovered roots appear or disappear. */\nexport class PlTreeRootsEntry implements AccessorProvider<PlTreeRootsAccessor> {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsEntry\";\n\n constructor(private readonly accessorData: TreeAccessorData) {}\n\n public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeRootsAccessor {\n return new PlTreeRootsAccessor(this.accessorData, this.accessorData.treeProvider(), {\n ctx,\n guard,\n });\n }\n\n public toJSON(): string {\n return \"[ROOTS-ENTRY]\";\n }\n\n public toString(): string {\n return \"[ROOTS-ENTRY]\";\n }\n}\n\nexport function isPlTreeRootsEntry(obj: unknown): obj is PlTreeRootsEntry {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsEntry\"\n );\n}\n\n/** Accessor over the current roots of a multi-root tree. Reading any of its methods tracks\n * the root set as a dependency (via {@link PlTreeState.getRoots}). */\nexport class PlTreeRootsAccessor {\n private readonly __pl_tree_type_marker__ = \"PlTreeRootsAccessor\";\n\n constructor(\n private readonly accessorData: TreeAccessorData,\n private readonly tree: PlTreeState,\n private readonly instanceData: TreeAccessorInstanceData,\n ) {}\n\n /** Current root ids. */\n public rootIds(): SignedResourceId[] {\n this.instanceData.guard();\n return this.tree.getRoots(this.instanceData.ctx.watcher);\n }\n\n /** Current roots as node accessors, ready to traverse. */\n public nodes(ops: ResourceTraversalOps = {}): PlTreeNodeAccessor[] {\n this.instanceData.guard();\n\n // mirror PlTreeEntryAccessor.node(): attach polling hooks on first node acquisition\n if (this.accessorData.hooks !== undefined)\n this.instanceData.ctx.attacheHooks(this.accessorData.hooks);\n\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops));\n }\n\n /** Current roots as entries, for persisting per-root sub-Computables. */\n public entries(): PlTreeEntry[] {\n this.instanceData.guard();\n return this.tree\n .getRoots(this.instanceData.ctx.watcher)\n .map((rid) => new PlTreeEntry(this.accessorData, rid));\n }\n}\n\nexport function isPlTreeRootsAccessor(obj: unknown): obj is PlTreeRootsAccessor {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n (obj as any)[\"__pl_tree_type_marker__\"] === \"PlTreeRootsAccessor\"\n );\n}\n"],"mappings":"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"}
|
package/dist/delta_sync.cjs
CHANGED
|
@@ -2,15 +2,23 @@ const require_state = require("./state.cjs");
|
|
|
2
2
|
const require_sync = require("./sync.cjs");
|
|
3
3
|
let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
4
4
|
//#region src/delta_sync.ts
|
|
5
|
-
/**
|
|
5
|
+
/** The `unconditionalDepth` of a resolution round: emit everything at or below this depth from
|
|
6
|
+
* a resolution seed, whatever its change token
|
|
6
7
|
* says. It bounds descent as well as emission: under a token the walk otherwise ends at the
|
|
7
|
-
* first unchanged resource (api.proto, changed_since_token), so
|
|
8
|
-
*
|
|
8
|
+
* first unchanged resource (api.proto, changed_since_token), so at depth 0 a newly attached
|
|
9
|
+
* subtree costs one sequential round trip per level.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
11
|
+
* Round n of a poll (from 1) asks with `min(RESOLUTION_INITIAL_DEPTH * 2^(n-1),
|
|
12
|
+
* RESOLUTION_MAX_DEPTH)`: 1, 2, 4, 8, 16, 32, 32, … Most references a delta body brings are
|
|
13
|
+
* shallow, so the first round asks for little and re-sends little of what the mirror already
|
|
14
|
+
* holds under deduplicated outputs; a subtree that keeps needing rounds gets a walk twice as
|
|
15
|
+
* deep each time: an unheld chain of L levels costs about log2(L) rounds while it is within the
|
|
16
|
+
* cap's reach (63 levels), then one round per 33 more. */
|
|
17
|
+
const RESOLUTION_INITIAL_DEPTH = 1;
|
|
18
|
+
const RESOLUTION_MAX_DEPTH = 32;
|
|
19
|
+
function resolutionDepth(round) {
|
|
20
|
+
return Math.min(RESOLUTION_INITIAL_DEPTH * 2 ** (round - 1), RESOLUTION_MAX_DEPTH);
|
|
21
|
+
}
|
|
14
22
|
/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws
|
|
15
23
|
* `orphan resource` for any that resolves to nothing. */
|
|
16
24
|
function referencesOf(resource) {
|
|
@@ -95,12 +103,14 @@ async function loadDeltaTreeState(tx, loadingRequest, stats, logger) {
|
|
|
95
103
|
await consume(seeds);
|
|
96
104
|
const collectedFromPoll = collected.size;
|
|
97
105
|
const fetched = /* @__PURE__ */ new Set();
|
|
106
|
+
let resolutionRound = 0;
|
|
98
107
|
while (missing.size > 0) {
|
|
99
108
|
const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));
|
|
100
109
|
if (round.length === 0) throw new require_state.TreeStateUpdateError(`delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`);
|
|
101
110
|
for (const id of round) fetched.add(id);
|
|
111
|
+
resolutionRound++;
|
|
102
112
|
if (stats) stats.deltaResolutionRounds++;
|
|
103
|
-
await consume(round,
|
|
113
|
+
await consume(round, resolutionDepth(resolutionRound));
|
|
104
114
|
}
|
|
105
115
|
if (changedSinceToken !== void 0 && knownResources.size > 0 && collectedFromPoll >= knownResources.size) {
|
|
106
116
|
if (stats) stats.deltaSuspectedFullAnswers++;
|
package/dist/delta_sync.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta_sync.cjs","names":["TreeStateUpdateError"],"sources":["../src/delta_sync.ts"],"sourcesContent":["import type { PlTransaction, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { isNotNullSignedResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData } from \"./state\";\nimport { TreeStateUpdateError } from \"./state\";\nimport type { TreeLoadingRequest, TreeLoadingStat } from \"./sync\";\nimport { collectStatsForResource } from \"./sync\";\n\n/** Emit everything at or below this depth from a resolution seed, whatever its change token\n * says. It bounds descent as well as emission: under a token the walk otherwise ends at the\n * first unchanged resource (api.proto, changed_since_token), so without this a round returns\n * the seeds alone and a newly attached subtree costs one sequential round trip per level.\n *\n * Deep enough to clear a subtree in one round, which is the trade: real server-side walk and\n * downlink for those levels, against a round trip each at ~1.4s on a slow link. Not free -\n * lower it if resolution rounds ever dominate a poll. */\nconst RESOLUTION_DEPTH = 32;\n\n/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws\n * `orphan resource` for any that resolves to nothing. */\nfunction referencesOf(resource: ExtendedResourceData): SignedResourceId[] {\n const refs: SignedResourceId[] = [];\n if (isNotNullSignedResourceId(resource.error)) refs.push(resource.error);\n for (const field of resource.fields) {\n if (isNotNullSignedResourceId(field.value)) refs.push(field.value);\n if (isNotNullSignedResourceId(field.error)) refs.push(field.error);\n }\n return refs;\n}\n\n/**\n * One delta poll: hand the backend this transaction's change token, take only what is newer.\n *\n * The caller commits the token, and only after the whole batch applies - advancing it past a\n * partial apply loses the dropped resources for good.\n *\n * Removals arrive as a rewrite of the referring resource, never as absence, and the refcount\n * GC does the rest. There is deliberately no periodic full reconcile; the blind spot is a\n * removal under a resource the mirror marked final, which streaming and BFS share.\n */\nexport async function loadDeltaTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n logger?: { warn: (msg: string) => void },\n): Promise<ExtendedResourceData[]> {\n const {\n seedResources,\n finalResources,\n roots,\n knownResources,\n pruningFunction,\n fieldFilter,\n traverseStopRules,\n changedSinceToken,\n } = loadingRequest;\n\n // Stop rules are never sent. The backend ignores them under a usable token anyway, but a\n // token-less poll then walks unpruned where streaming would have pruned. Sending them only\n // when the token is absent would change the request shape between polls, which a token\n // holder must not do - so the cost is warned about instead.\n if (traverseStopRules !== undefined && changedSinceToken === undefined) {\n logger?.warn(\n \"delta poll: traverseStopRules supplied but not sent, and this poll carries no token,\" +\n \" so the backend walk is unpruned where the streaming path would have pruned it\",\n );\n }\n\n // Seed at every non-final resource, not the roots. Required, not an optimisation: the\n // backend ends a walk at the first unchanged resource, so a root-seeded poll never reaches a\n // change under a quiet parent - measured at 0 of 3 changes delivered. algorithm_equivalence\n // fails on its KV step if this is ever narrowed back to the roots.\n //\n // The seed set changes every poll and the token is deliberately NOT discarded for it, though\n // api.proto counts it as a shape input. Safe because every seed is a resource the mirror\n // already holds, so a change can only widen the result, and widening hides nothing.\n // Discarding per poll would make every poll a full read and defeat the mechanism.\n //\n // The roots stand in when the frontier is empty: resourceTree needs at least one seed.\n const seeds = seedResources.length > 0 ? seedResources : [...roots];\n if (seeds.length === 0) return [];\n\n const collected = new Map<SignedResourceId, ExtendedResourceData>();\n // References neither the mirror nor this batch can satisfy; applying with one outstanding\n // invalidates the whole tree. Maintained incrementally as frames arrive: rescanning\n // everything collected once per round would be quadratic in a poll that needs several.\n const missing = new Set<SignedResourceId>();\n // Same ids, in arrival order, so a round consumes only what is new rather than walking the\n // whole set. An id enters both exactly once: `collected` is checked before the add and never\n // cleared, so nothing re-enters after it resolves.\n const pending: SignedResourceId[] = [];\n const collect = (resource: ExtendedResourceData) => {\n collected.set(resource.id, resource);\n missing.delete(resource.id);\n for (const ref of referencesOf(resource))\n if (!collected.has(ref) && !knownResources.has(ref) && !missing.has(ref)) {\n missing.add(ref);\n pending.push(ref);\n }\n };\n\n let warnedStopMarker = false;\n const consume = async (walkSeeds: SignedResourceId[], unconditionalDepth?: number) => {\n if (stats) {\n stats.roundTrips++;\n stats.streamRounds++;\n stats.deltaSeedsSent += walkSeeds.length;\n }\n\n for await (const frame of tx.resourceTree(walkSeeds, {\n includeKv: true,\n fieldFilter,\n changedSinceToken,\n unconditionalDepth,\n })) {\n // A body-less frame. The client reads that as a stop marker, and a stop marker can only\n // follow traverse_stop_rules, which this path never sends. Nothing here needs it, so it\n // is skipped - but not silently: reaching this means the backend put a meaning on a\n // body-less frame that this client does not know, and a quiet skip would leave no trace\n // of it. Warn once per poll rather than per frame, since a contract change would emit\n // one of these for every resource visited.\n if (frame.frameKind === \"stopMarker\") {\n if (!warnedStopMarker) {\n warnedStopMarker = true;\n logger?.warn(\n `delta poll: ignoring a body-less frame for ${frame.id}; this poll sent no ` +\n `traverse stop rules, so the backend is using body-less frames for something ` +\n `this client does not interpret`,\n );\n }\n continue;\n }\n\n if (stats) stats.resourceFrames++;\n\n // updateFromResourceData throws on any body for a held final resource and invalidates\n // the tree. A token-less poll walks unpruned and emits exactly those, which is what the\n // first poll after a restored snapshot does. Both sibling algorithms skip the same way.\n // Safe for references: finalResources is a subset of knownResources.\n if (finalResources.has(frame.id)) {\n if (stats) stats.finalResourcesSkipped++;\n continue;\n }\n\n // An explicit literal rather than two rest-spreads of the frame. Measured ~15% off the\n // per-frame cost: a spread copies every property twice and gives the object a hidden\n // class of its own, where this is the shape the streaming path already produces.\n const resource: ExtendedResourceData = {\n id: frame.id,\n type: frame.type,\n kind: frame.kind,\n data: frame.data,\n resourceReady: frame.resourceReady,\n error: frame.error,\n originalResourceId: frame.originalResourceId,\n final: frame.final,\n inputsLocked: frame.inputsLocked,\n outputsLocked: frame.outputsLocked,\n fields:\n pruningFunction !== undefined\n ? pruningFunction(frame as unknown as ExtendedResourceData)\n : frame.fields,\n kv: frame.kv,\n };\n if (stats) stats.prunedFields += frame.fields.length - resource.fields.length;\n\n collect(resource);\n collectStatsForResource(resource, stats);\n }\n };\n\n await consume(seeds);\n\n // Captured before the resolution rounds. They fetch unconditionally by design, so counting\n // afterwards lets a modest unheld subtree inflate the total past a small mirror and report a\n // refused token that was never refused.\n const collectedFromPoll = collected.size;\n\n // A body may reference a resource the response did not carry - a field repointed at one we\n // never held. Ids already asked for are never re-requested, so the loop terminates: the tree\n // is finite and `fetched` only grows.\n //\n // Deliberately unbounded on rounds. A round-count cap would turn a legal tree shape into a\n // desktop error, since how deep the references chain is a property of what the backend\n // allowed rather than of anything wrong here, and the streaming path's stop-marker follow-up\n // loop has always run on the same assumption.\n const fetched = new Set<SignedResourceId>();\n while (missing.size > 0) {\n const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));\n if (round.length === 0) {\n // Asked for all of these and none arrived; a soft-deleted referent gets here, since\n // those frames are dropped client-side. TreeStateUpdateError specifically: the poll loop\n // rebuilds and discards the token for that class, where a plain Error is only logged and\n // the identical request fails forever.\n throw new TreeStateUpdateError(\n `delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`,\n );\n }\n for (const id of round) fetched.add(id);\n\n if (stats) stats.deltaResolutionRounds++;\n await consume(round, RESOLUTION_DEPTH);\n }\n\n // A refused token is answered with the full tree and no error, so this is the only tell.\n // Reported, not acted on: the response is a correct superset, and the token stored after\n // this poll comes from the current instance, so it self-heals next poll.\n if (\n changedSinceToken !== undefined &&\n knownResources.size > 0 &&\n collectedFromPoll >= knownResources.size\n ) {\n if (stats) stats.deltaSuspectedFullAnswers++;\n logger?.warn(\n `delta poll: sent a token but received ${collectedFromPoll} resources against a mirror of ` +\n `${knownResources.size}; the backend may have refused the token (instance change or ` +\n `rewound numbering), in which case this poll cost a full tree read`,\n );\n }\n\n return [...collected.values()];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delta_sync.cjs","names":["TreeStateUpdateError"],"sources":["../src/delta_sync.ts"],"sourcesContent":["import type { PlTransaction, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { isNotNullSignedResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData } from \"./state\";\nimport { TreeStateUpdateError } from \"./state\";\nimport type { TreeLoadingRequest, TreeLoadingStat } from \"./sync\";\nimport { collectStatsForResource } from \"./sync\";\n\n/** The `unconditionalDepth` of a resolution round: emit everything at or below this depth from\n * a resolution seed, whatever its change token\n * says. It bounds descent as well as emission: under a token the walk otherwise ends at the\n * first unchanged resource (api.proto, changed_since_token), so at depth 0 a newly attached\n * subtree costs one sequential round trip per level.\n *\n * Round n of a poll (from 1) asks with `min(RESOLUTION_INITIAL_DEPTH * 2^(n-1),\n * RESOLUTION_MAX_DEPTH)`: 1, 2, 4, 8, 16, 32, 32, … Most references a delta body brings are\n * shallow, so the first round asks for little and re-sends little of what the mirror already\n * holds under deduplicated outputs; a subtree that keeps needing rounds gets a walk twice as\n * deep each time: an unheld chain of L levels costs about log2(L) rounds while it is within the\n * cap's reach (63 levels), then one round per 33 more. */\nconst RESOLUTION_INITIAL_DEPTH = 1;\nconst RESOLUTION_MAX_DEPTH = 32;\n\nfunction resolutionDepth(round: number): number {\n return Math.min(RESOLUTION_INITIAL_DEPTH * 2 ** (round - 1), RESOLUTION_MAX_DEPTH);\n}\n\n/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws\n * `orphan resource` for any that resolves to nothing. */\nfunction referencesOf(resource: ExtendedResourceData): SignedResourceId[] {\n const refs: SignedResourceId[] = [];\n if (isNotNullSignedResourceId(resource.error)) refs.push(resource.error);\n for (const field of resource.fields) {\n if (isNotNullSignedResourceId(field.value)) refs.push(field.value);\n if (isNotNullSignedResourceId(field.error)) refs.push(field.error);\n }\n return refs;\n}\n\n/**\n * One delta poll: hand the backend this transaction's change token, take only what is newer.\n *\n * The caller commits the token, and only after the whole batch applies - advancing it past a\n * partial apply loses the dropped resources for good.\n *\n * Removals arrive as a rewrite of the referring resource, never as absence, and the refcount\n * GC does the rest. There is deliberately no periodic full reconcile; the blind spot is a\n * removal under a resource the mirror marked final, which streaming and BFS share.\n */\nexport async function loadDeltaTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n logger?: { warn: (msg: string) => void },\n): Promise<ExtendedResourceData[]> {\n const {\n seedResources,\n finalResources,\n roots,\n knownResources,\n pruningFunction,\n fieldFilter,\n traverseStopRules,\n changedSinceToken,\n } = loadingRequest;\n\n // Stop rules are never sent. The backend ignores them under a usable token anyway, but a\n // token-less poll then walks unpruned where streaming would have pruned. Sending them only\n // when the token is absent would change the request shape between polls, which a token\n // holder must not do - so the cost is warned about instead.\n if (traverseStopRules !== undefined && changedSinceToken === undefined) {\n logger?.warn(\n \"delta poll: traverseStopRules supplied but not sent, and this poll carries no token,\" +\n \" so the backend walk is unpruned where the streaming path would have pruned it\",\n );\n }\n\n // Seed at every non-final resource, not the roots. Required, not an optimisation: the\n // backend ends a walk at the first unchanged resource, so a root-seeded poll never reaches a\n // change under a quiet parent - measured at 0 of 3 changes delivered. algorithm_equivalence\n // fails on its KV step if this is ever narrowed back to the roots.\n //\n // The seed set changes every poll and the token is deliberately NOT discarded for it, though\n // api.proto counts it as a shape input. Safe because every seed is a resource the mirror\n // already holds, so a change can only widen the result, and widening hides nothing.\n // Discarding per poll would make every poll a full read and defeat the mechanism.\n //\n // The roots stand in when the frontier is empty: resourceTree needs at least one seed.\n const seeds = seedResources.length > 0 ? seedResources : [...roots];\n if (seeds.length === 0) return [];\n\n const collected = new Map<SignedResourceId, ExtendedResourceData>();\n // References neither the mirror nor this batch can satisfy; applying with one outstanding\n // invalidates the whole tree. Maintained incrementally as frames arrive: rescanning\n // everything collected once per round would be quadratic in a poll that needs several.\n const missing = new Set<SignedResourceId>();\n // Same ids, in arrival order, so a round consumes only what is new rather than walking the\n // whole set. An id enters both exactly once: `collected` is checked before the add and never\n // cleared, so nothing re-enters after it resolves.\n const pending: SignedResourceId[] = [];\n const collect = (resource: ExtendedResourceData) => {\n collected.set(resource.id, resource);\n missing.delete(resource.id);\n for (const ref of referencesOf(resource))\n if (!collected.has(ref) && !knownResources.has(ref) && !missing.has(ref)) {\n missing.add(ref);\n pending.push(ref);\n }\n };\n\n let warnedStopMarker = false;\n const consume = async (walkSeeds: SignedResourceId[], unconditionalDepth?: number) => {\n if (stats) {\n stats.roundTrips++;\n stats.streamRounds++;\n stats.deltaSeedsSent += walkSeeds.length;\n }\n\n for await (const frame of tx.resourceTree(walkSeeds, {\n includeKv: true,\n fieldFilter,\n changedSinceToken,\n unconditionalDepth,\n })) {\n // A body-less frame. The client reads that as a stop marker, and a stop marker can only\n // follow traverse_stop_rules, which this path never sends. Nothing here needs it, so it\n // is skipped - but not silently: reaching this means the backend put a meaning on a\n // body-less frame that this client does not know, and a quiet skip would leave no trace\n // of it. Warn once per poll rather than per frame, since a contract change would emit\n // one of these for every resource visited.\n if (frame.frameKind === \"stopMarker\") {\n if (!warnedStopMarker) {\n warnedStopMarker = true;\n logger?.warn(\n `delta poll: ignoring a body-less frame for ${frame.id}; this poll sent no ` +\n `traverse stop rules, so the backend is using body-less frames for something ` +\n `this client does not interpret`,\n );\n }\n continue;\n }\n\n if (stats) stats.resourceFrames++;\n\n // updateFromResourceData throws on any body for a held final resource and invalidates\n // the tree. A token-less poll walks unpruned and emits exactly those, which is what the\n // first poll after a restored snapshot does. Both sibling algorithms skip the same way.\n // Safe for references: finalResources is a subset of knownResources.\n if (finalResources.has(frame.id)) {\n if (stats) stats.finalResourcesSkipped++;\n continue;\n }\n\n // An explicit literal rather than two rest-spreads of the frame. Measured ~15% off the\n // per-frame cost: a spread copies every property twice and gives the object a hidden\n // class of its own, where this is the shape the streaming path already produces.\n const resource: ExtendedResourceData = {\n id: frame.id,\n type: frame.type,\n kind: frame.kind,\n data: frame.data,\n resourceReady: frame.resourceReady,\n error: frame.error,\n originalResourceId: frame.originalResourceId,\n final: frame.final,\n inputsLocked: frame.inputsLocked,\n outputsLocked: frame.outputsLocked,\n fields:\n pruningFunction !== undefined\n ? pruningFunction(frame as unknown as ExtendedResourceData)\n : frame.fields,\n kv: frame.kv,\n };\n if (stats) stats.prunedFields += frame.fields.length - resource.fields.length;\n\n collect(resource);\n collectStatsForResource(resource, stats);\n }\n };\n\n await consume(seeds);\n\n // Captured before the resolution rounds. They fetch unconditionally by design, so counting\n // afterwards lets a modest unheld subtree inflate the total past a small mirror and report a\n // refused token that was never refused.\n const collectedFromPoll = collected.size;\n\n // A body may reference a resource the response did not carry - a field repointed at one we\n // never held. Ids already asked for are never re-requested, so the loop terminates: the tree\n // is finite and `fetched` only grows.\n //\n // Deliberately unbounded on rounds. A round-count cap would turn a legal tree shape into a\n // desktop error, since how deep the references chain is a property of what the backend\n // allowed rather than of anything wrong here, and the streaming path's stop-marker follow-up\n // loop has always run on the same assumption.\n const fetched = new Set<SignedResourceId>();\n let resolutionRound = 0;\n while (missing.size > 0) {\n const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));\n if (round.length === 0) {\n // Asked for all of these and none arrived; a soft-deleted referent gets here, since\n // those frames are dropped client-side. TreeStateUpdateError specifically: the poll loop\n // rebuilds and discards the token for that class, where a plain Error is only logged and\n // the identical request fails forever.\n throw new TreeStateUpdateError(\n `delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`,\n );\n }\n for (const id of round) fetched.add(id);\n\n resolutionRound++;\n if (stats) stats.deltaResolutionRounds++;\n await consume(round, resolutionDepth(resolutionRound));\n }\n\n // A refused token is answered with the full tree and no error, so this is the only tell.\n // Reported, not acted on: the response is a correct superset, and the token stored after\n // this poll comes from the current instance, so it self-heals next poll.\n if (\n changedSinceToken !== undefined &&\n knownResources.size > 0 &&\n collectedFromPoll >= knownResources.size\n ) {\n if (stats) stats.deltaSuspectedFullAnswers++;\n logger?.warn(\n `delta poll: sent a token but received ${collectedFromPoll} resources against a mirror of ` +\n `${knownResources.size}; the backend may have refused the token (instance change or ` +\n `rewound numbering), in which case this poll cost a full tree read`,\n );\n }\n\n return [...collected.values()];\n}\n"],"mappings":"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"}
|
package/dist/delta_sync.js
CHANGED
|
@@ -2,15 +2,23 @@ import { TreeStateUpdateError } from "./state.js";
|
|
|
2
2
|
import { collectStatsForResource } from "./sync.js";
|
|
3
3
|
import { isNotNullSignedResourceId } from "@milaboratories/pl-client";
|
|
4
4
|
//#region src/delta_sync.ts
|
|
5
|
-
/**
|
|
5
|
+
/** The `unconditionalDepth` of a resolution round: emit everything at or below this depth from
|
|
6
|
+
* a resolution seed, whatever its change token
|
|
6
7
|
* says. It bounds descent as well as emission: under a token the walk otherwise ends at the
|
|
7
|
-
* first unchanged resource (api.proto, changed_since_token), so
|
|
8
|
-
*
|
|
8
|
+
* first unchanged resource (api.proto, changed_since_token), so at depth 0 a newly attached
|
|
9
|
+
* subtree costs one sequential round trip per level.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
11
|
+
* Round n of a poll (from 1) asks with `min(RESOLUTION_INITIAL_DEPTH * 2^(n-1),
|
|
12
|
+
* RESOLUTION_MAX_DEPTH)`: 1, 2, 4, 8, 16, 32, 32, … Most references a delta body brings are
|
|
13
|
+
* shallow, so the first round asks for little and re-sends little of what the mirror already
|
|
14
|
+
* holds under deduplicated outputs; a subtree that keeps needing rounds gets a walk twice as
|
|
15
|
+
* deep each time: an unheld chain of L levels costs about log2(L) rounds while it is within the
|
|
16
|
+
* cap's reach (63 levels), then one round per 33 more. */
|
|
17
|
+
const RESOLUTION_INITIAL_DEPTH = 1;
|
|
18
|
+
const RESOLUTION_MAX_DEPTH = 32;
|
|
19
|
+
function resolutionDepth(round) {
|
|
20
|
+
return Math.min(RESOLUTION_INITIAL_DEPTH * 2 ** (round - 1), RESOLUTION_MAX_DEPTH);
|
|
21
|
+
}
|
|
14
22
|
/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws
|
|
15
23
|
* `orphan resource` for any that resolves to nothing. */
|
|
16
24
|
function referencesOf(resource) {
|
|
@@ -95,12 +103,14 @@ async function loadDeltaTreeState(tx, loadingRequest, stats, logger) {
|
|
|
95
103
|
await consume(seeds);
|
|
96
104
|
const collectedFromPoll = collected.size;
|
|
97
105
|
const fetched = /* @__PURE__ */ new Set();
|
|
106
|
+
let resolutionRound = 0;
|
|
98
107
|
while (missing.size > 0) {
|
|
99
108
|
const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));
|
|
100
109
|
if (round.length === 0) throw new TreeStateUpdateError(`delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`);
|
|
101
110
|
for (const id of round) fetched.add(id);
|
|
111
|
+
resolutionRound++;
|
|
102
112
|
if (stats) stats.deltaResolutionRounds++;
|
|
103
|
-
await consume(round,
|
|
113
|
+
await consume(round, resolutionDepth(resolutionRound));
|
|
104
114
|
}
|
|
105
115
|
if (changedSinceToken !== void 0 && knownResources.size > 0 && collectedFromPoll >= knownResources.size) {
|
|
106
116
|
if (stats) stats.deltaSuspectedFullAnswers++;
|
package/dist/delta_sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta_sync.js","names":[],"sources":["../src/delta_sync.ts"],"sourcesContent":["import type { PlTransaction, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { isNotNullSignedResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData } from \"./state\";\nimport { TreeStateUpdateError } from \"./state\";\nimport type { TreeLoadingRequest, TreeLoadingStat } from \"./sync\";\nimport { collectStatsForResource } from \"./sync\";\n\n/** Emit everything at or below this depth from a resolution seed, whatever its change token\n * says. It bounds descent as well as emission: under a token the walk otherwise ends at the\n * first unchanged resource (api.proto, changed_since_token), so without this a round returns\n * the seeds alone and a newly attached subtree costs one sequential round trip per level.\n *\n * Deep enough to clear a subtree in one round, which is the trade: real server-side walk and\n * downlink for those levels, against a round trip each at ~1.4s on a slow link. Not free -\n * lower it if resolution rounds ever dominate a poll. */\nconst RESOLUTION_DEPTH = 32;\n\n/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws\n * `orphan resource` for any that resolves to nothing. */\nfunction referencesOf(resource: ExtendedResourceData): SignedResourceId[] {\n const refs: SignedResourceId[] = [];\n if (isNotNullSignedResourceId(resource.error)) refs.push(resource.error);\n for (const field of resource.fields) {\n if (isNotNullSignedResourceId(field.value)) refs.push(field.value);\n if (isNotNullSignedResourceId(field.error)) refs.push(field.error);\n }\n return refs;\n}\n\n/**\n * One delta poll: hand the backend this transaction's change token, take only what is newer.\n *\n * The caller commits the token, and only after the whole batch applies - advancing it past a\n * partial apply loses the dropped resources for good.\n *\n * Removals arrive as a rewrite of the referring resource, never as absence, and the refcount\n * GC does the rest. There is deliberately no periodic full reconcile; the blind spot is a\n * removal under a resource the mirror marked final, which streaming and BFS share.\n */\nexport async function loadDeltaTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n logger?: { warn: (msg: string) => void },\n): Promise<ExtendedResourceData[]> {\n const {\n seedResources,\n finalResources,\n roots,\n knownResources,\n pruningFunction,\n fieldFilter,\n traverseStopRules,\n changedSinceToken,\n } = loadingRequest;\n\n // Stop rules are never sent. The backend ignores them under a usable token anyway, but a\n // token-less poll then walks unpruned where streaming would have pruned. Sending them only\n // when the token is absent would change the request shape between polls, which a token\n // holder must not do - so the cost is warned about instead.\n if (traverseStopRules !== undefined && changedSinceToken === undefined) {\n logger?.warn(\n \"delta poll: traverseStopRules supplied but not sent, and this poll carries no token,\" +\n \" so the backend walk is unpruned where the streaming path would have pruned it\",\n );\n }\n\n // Seed at every non-final resource, not the roots. Required, not an optimisation: the\n // backend ends a walk at the first unchanged resource, so a root-seeded poll never reaches a\n // change under a quiet parent - measured at 0 of 3 changes delivered. algorithm_equivalence\n // fails on its KV step if this is ever narrowed back to the roots.\n //\n // The seed set changes every poll and the token is deliberately NOT discarded for it, though\n // api.proto counts it as a shape input. Safe because every seed is a resource the mirror\n // already holds, so a change can only widen the result, and widening hides nothing.\n // Discarding per poll would make every poll a full read and defeat the mechanism.\n //\n // The roots stand in when the frontier is empty: resourceTree needs at least one seed.\n const seeds = seedResources.length > 0 ? seedResources : [...roots];\n if (seeds.length === 0) return [];\n\n const collected = new Map<SignedResourceId, ExtendedResourceData>();\n // References neither the mirror nor this batch can satisfy; applying with one outstanding\n // invalidates the whole tree. Maintained incrementally as frames arrive: rescanning\n // everything collected once per round would be quadratic in a poll that needs several.\n const missing = new Set<SignedResourceId>();\n // Same ids, in arrival order, so a round consumes only what is new rather than walking the\n // whole set. An id enters both exactly once: `collected` is checked before the add and never\n // cleared, so nothing re-enters after it resolves.\n const pending: SignedResourceId[] = [];\n const collect = (resource: ExtendedResourceData) => {\n collected.set(resource.id, resource);\n missing.delete(resource.id);\n for (const ref of referencesOf(resource))\n if (!collected.has(ref) && !knownResources.has(ref) && !missing.has(ref)) {\n missing.add(ref);\n pending.push(ref);\n }\n };\n\n let warnedStopMarker = false;\n const consume = async (walkSeeds: SignedResourceId[], unconditionalDepth?: number) => {\n if (stats) {\n stats.roundTrips++;\n stats.streamRounds++;\n stats.deltaSeedsSent += walkSeeds.length;\n }\n\n for await (const frame of tx.resourceTree(walkSeeds, {\n includeKv: true,\n fieldFilter,\n changedSinceToken,\n unconditionalDepth,\n })) {\n // A body-less frame. The client reads that as a stop marker, and a stop marker can only\n // follow traverse_stop_rules, which this path never sends. Nothing here needs it, so it\n // is skipped - but not silently: reaching this means the backend put a meaning on a\n // body-less frame that this client does not know, and a quiet skip would leave no trace\n // of it. Warn once per poll rather than per frame, since a contract change would emit\n // one of these for every resource visited.\n if (frame.frameKind === \"stopMarker\") {\n if (!warnedStopMarker) {\n warnedStopMarker = true;\n logger?.warn(\n `delta poll: ignoring a body-less frame for ${frame.id}; this poll sent no ` +\n `traverse stop rules, so the backend is using body-less frames for something ` +\n `this client does not interpret`,\n );\n }\n continue;\n }\n\n if (stats) stats.resourceFrames++;\n\n // updateFromResourceData throws on any body for a held final resource and invalidates\n // the tree. A token-less poll walks unpruned and emits exactly those, which is what the\n // first poll after a restored snapshot does. Both sibling algorithms skip the same way.\n // Safe for references: finalResources is a subset of knownResources.\n if (finalResources.has(frame.id)) {\n if (stats) stats.finalResourcesSkipped++;\n continue;\n }\n\n // An explicit literal rather than two rest-spreads of the frame. Measured ~15% off the\n // per-frame cost: a spread copies every property twice and gives the object a hidden\n // class of its own, where this is the shape the streaming path already produces.\n const resource: ExtendedResourceData = {\n id: frame.id,\n type: frame.type,\n kind: frame.kind,\n data: frame.data,\n resourceReady: frame.resourceReady,\n error: frame.error,\n originalResourceId: frame.originalResourceId,\n final: frame.final,\n inputsLocked: frame.inputsLocked,\n outputsLocked: frame.outputsLocked,\n fields:\n pruningFunction !== undefined\n ? pruningFunction(frame as unknown as ExtendedResourceData)\n : frame.fields,\n kv: frame.kv,\n };\n if (stats) stats.prunedFields += frame.fields.length - resource.fields.length;\n\n collect(resource);\n collectStatsForResource(resource, stats);\n }\n };\n\n await consume(seeds);\n\n // Captured before the resolution rounds. They fetch unconditionally by design, so counting\n // afterwards lets a modest unheld subtree inflate the total past a small mirror and report a\n // refused token that was never refused.\n const collectedFromPoll = collected.size;\n\n // A body may reference a resource the response did not carry - a field repointed at one we\n // never held. Ids already asked for are never re-requested, so the loop terminates: the tree\n // is finite and `fetched` only grows.\n //\n // Deliberately unbounded on rounds. A round-count cap would turn a legal tree shape into a\n // desktop error, since how deep the references chain is a property of what the backend\n // allowed rather than of anything wrong here, and the streaming path's stop-marker follow-up\n // loop has always run on the same assumption.\n const fetched = new Set<SignedResourceId>();\n while (missing.size > 0) {\n const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));\n if (round.length === 0) {\n // Asked for all of these and none arrived; a soft-deleted referent gets here, since\n // those frames are dropped client-side. TreeStateUpdateError specifically: the poll loop\n // rebuilds and discards the token for that class, where a plain Error is only logged and\n // the identical request fails forever.\n throw new TreeStateUpdateError(\n `delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`,\n );\n }\n for (const id of round) fetched.add(id);\n\n if (stats) stats.deltaResolutionRounds++;\n await consume(round, RESOLUTION_DEPTH);\n }\n\n // A refused token is answered with the full tree and no error, so this is the only tell.\n // Reported, not acted on: the response is a correct superset, and the token stored after\n // this poll comes from the current instance, so it self-heals next poll.\n if (\n changedSinceToken !== undefined &&\n knownResources.size > 0 &&\n collectedFromPoll >= knownResources.size\n ) {\n if (stats) stats.deltaSuspectedFullAnswers++;\n logger?.warn(\n `delta poll: sent a token but received ${collectedFromPoll} resources against a mirror of ` +\n `${knownResources.size}; the backend may have refused the token (instance change or ` +\n `rewound numbering), in which case this poll cost a full tree read`,\n );\n }\n\n return [...collected.values()];\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAM,mBAAmB;;;AAIzB,SAAS,aAAa,UAAoD;CACxE,MAAM,OAA2B,CAAC;CAClC,IAAI,0BAA0B,SAAS,KAAK,GAAG,KAAK,KAAK,SAAS,KAAK;CACvE,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,IAAI,0BAA0B,MAAM,KAAK,GAAG,KAAK,KAAK,MAAM,KAAK;EACjE,IAAI,0BAA0B,MAAM,KAAK,GAAG,KAAK,KAAK,MAAM,KAAK;CACnE;CACA,OAAO;AACT;;;;;;;;;;;AAYA,eAAsB,mBACpB,IACA,gBACA,OACA,QACiC;CACjC,MAAM,EACJ,eACA,gBACA,OACA,gBACA,iBACA,aACA,mBACA,sBACE;CAMJ,IAAI,sBAAsB,KAAA,KAAa,sBAAsB,KAAA,GAC3D,QAAQ,KACN,oKAEF;CAcF,MAAM,QAAQ,cAAc,SAAS,IAAI,gBAAgB,CAAC,GAAG,KAAK;CAClE,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;CAEhC,MAAM,4BAAY,IAAI,IAA4C;CAIlE,MAAM,0BAAU,IAAI,IAAsB;CAI1C,MAAM,UAA8B,CAAC;CACrC,MAAM,WAAW,aAAmC;EAClD,UAAU,IAAI,SAAS,IAAI,QAAQ;EACnC,QAAQ,OAAO,SAAS,EAAE;EAC1B,KAAK,MAAM,OAAO,aAAa,QAAQ,GACrC,IAAI,CAAC,UAAU,IAAI,GAAG,KAAK,CAAC,eAAe,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,GAAG,GAAG;GACxE,QAAQ,IAAI,GAAG;GACf,QAAQ,KAAK,GAAG;EAClB;CACJ;CAEA,IAAI,mBAAmB;CACvB,MAAM,UAAU,OAAO,WAA+B,uBAAgC;EACpF,IAAI,OAAO;GACT,MAAM;GACN,MAAM;GACN,MAAM,kBAAkB,UAAU;EACpC;EAEA,WAAW,MAAM,SAAS,GAAG,aAAa,WAAW;GACnD,WAAW;GACX;GACA;GACA;EACF,CAAC,GAAG;GAOF,IAAI,MAAM,cAAc,cAAc;IACpC,IAAI,CAAC,kBAAkB;KACrB,mBAAmB;KACnB,QAAQ,KACN,8CAA8C,MAAM,GAAG,+HAGzD;IACF;IACA;GACF;GAEA,IAAI,OAAO,MAAM;GAMjB,IAAI,eAAe,IAAI,MAAM,EAAE,GAAG;IAChC,IAAI,OAAO,MAAM;IACjB;GACF;GAKA,MAAM,WAAiC;IACrC,IAAI,MAAM;IACV,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,eAAe,MAAM;IACrB,OAAO,MAAM;IACb,oBAAoB,MAAM;IAC1B,OAAO,MAAM;IACb,cAAc,MAAM;IACpB,eAAe,MAAM;IACrB,QACE,oBAAoB,KAAA,IAChB,gBAAgB,KAAwC,IACxD,MAAM;IACZ,IAAI,MAAM;GACZ;GACA,IAAI,OAAO,MAAM,gBAAgB,MAAM,OAAO,SAAS,SAAS,OAAO;GAEvE,QAAQ,QAAQ;GAChB,wBAAwB,UAAU,KAAK;EACzC;CACF;CAEA,MAAM,QAAQ,KAAK;CAKnB,MAAM,oBAAoB,UAAU;CAUpC,MAAM,0BAAU,IAAI,IAAsB;CAC1C,OAAO,QAAQ,OAAO,GAAG;EACvB,MAAM,QAAQ,QAAQ,OAAO,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,QAAQ,IAAI,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC;EAClF,IAAI,MAAM,WAAW,GAKnB,MAAM,IAAI,qBACR,eAAe,QAAQ,KAAK,uDAAuD,QAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,OAC7G;EAEF,KAAK,MAAM,MAAM,OAAO,QAAQ,IAAI,EAAE;EAEtC,IAAI,OAAO,MAAM;EACjB,MAAM,QAAQ,OAAO,gBAAgB;CACvC;CAKA,IACE,sBAAsB,KAAA,KACtB,eAAe,OAAO,KACtB,qBAAqB,eAAe,MACpC;EACA,IAAI,OAAO,MAAM;EACjB,QAAQ,KACN,yCAAyC,kBAAkB,iCACtD,eAAe,KAAK,+HAE3B;CACF;CAEA,OAAO,CAAC,GAAG,UAAU,OAAO,CAAC;AAC/B"}
|
|
1
|
+
{"version":3,"file":"delta_sync.js","names":[],"sources":["../src/delta_sync.ts"],"sourcesContent":["import type { PlTransaction, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { isNotNullSignedResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData } from \"./state\";\nimport { TreeStateUpdateError } from \"./state\";\nimport type { TreeLoadingRequest, TreeLoadingStat } from \"./sync\";\nimport { collectStatsForResource } from \"./sync\";\n\n/** The `unconditionalDepth` of a resolution round: emit everything at or below this depth from\n * a resolution seed, whatever its change token\n * says. It bounds descent as well as emission: under a token the walk otherwise ends at the\n * first unchanged resource (api.proto, changed_since_token), so at depth 0 a newly attached\n * subtree costs one sequential round trip per level.\n *\n * Round n of a poll (from 1) asks with `min(RESOLUTION_INITIAL_DEPTH * 2^(n-1),\n * RESOLUTION_MAX_DEPTH)`: 1, 2, 4, 8, 16, 32, 32, … Most references a delta body brings are\n * shallow, so the first round asks for little and re-sends little of what the mirror already\n * holds under deduplicated outputs; a subtree that keeps needing rounds gets a walk twice as\n * deep each time: an unheld chain of L levels costs about log2(L) rounds while it is within the\n * cap's reach (63 levels), then one round per 33 more. */\nconst RESOLUTION_INITIAL_DEPTH = 1;\nconst RESOLUTION_MAX_DEPTH = 32;\n\nfunction resolutionDepth(round: number): number {\n return Math.min(RESOLUTION_INITIAL_DEPTH * 2 ** (round - 1), RESOLUTION_MAX_DEPTH);\n}\n\n/** Every id a body points at. Exactly what `updateFromResourceData` refcounts, and it throws\n * `orphan resource` for any that resolves to nothing. */\nfunction referencesOf(resource: ExtendedResourceData): SignedResourceId[] {\n const refs: SignedResourceId[] = [];\n if (isNotNullSignedResourceId(resource.error)) refs.push(resource.error);\n for (const field of resource.fields) {\n if (isNotNullSignedResourceId(field.value)) refs.push(field.value);\n if (isNotNullSignedResourceId(field.error)) refs.push(field.error);\n }\n return refs;\n}\n\n/**\n * One delta poll: hand the backend this transaction's change token, take only what is newer.\n *\n * The caller commits the token, and only after the whole batch applies - advancing it past a\n * partial apply loses the dropped resources for good.\n *\n * Removals arrive as a rewrite of the referring resource, never as absence, and the refcount\n * GC does the rest. There is deliberately no periodic full reconcile; the blind spot is a\n * removal under a resource the mirror marked final, which streaming and BFS share.\n */\nexport async function loadDeltaTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n logger?: { warn: (msg: string) => void },\n): Promise<ExtendedResourceData[]> {\n const {\n seedResources,\n finalResources,\n roots,\n knownResources,\n pruningFunction,\n fieldFilter,\n traverseStopRules,\n changedSinceToken,\n } = loadingRequest;\n\n // Stop rules are never sent. The backend ignores them under a usable token anyway, but a\n // token-less poll then walks unpruned where streaming would have pruned. Sending them only\n // when the token is absent would change the request shape between polls, which a token\n // holder must not do - so the cost is warned about instead.\n if (traverseStopRules !== undefined && changedSinceToken === undefined) {\n logger?.warn(\n \"delta poll: traverseStopRules supplied but not sent, and this poll carries no token,\" +\n \" so the backend walk is unpruned where the streaming path would have pruned it\",\n );\n }\n\n // Seed at every non-final resource, not the roots. Required, not an optimisation: the\n // backend ends a walk at the first unchanged resource, so a root-seeded poll never reaches a\n // change under a quiet parent - measured at 0 of 3 changes delivered. algorithm_equivalence\n // fails on its KV step if this is ever narrowed back to the roots.\n //\n // The seed set changes every poll and the token is deliberately NOT discarded for it, though\n // api.proto counts it as a shape input. Safe because every seed is a resource the mirror\n // already holds, so a change can only widen the result, and widening hides nothing.\n // Discarding per poll would make every poll a full read and defeat the mechanism.\n //\n // The roots stand in when the frontier is empty: resourceTree needs at least one seed.\n const seeds = seedResources.length > 0 ? seedResources : [...roots];\n if (seeds.length === 0) return [];\n\n const collected = new Map<SignedResourceId, ExtendedResourceData>();\n // References neither the mirror nor this batch can satisfy; applying with one outstanding\n // invalidates the whole tree. Maintained incrementally as frames arrive: rescanning\n // everything collected once per round would be quadratic in a poll that needs several.\n const missing = new Set<SignedResourceId>();\n // Same ids, in arrival order, so a round consumes only what is new rather than walking the\n // whole set. An id enters both exactly once: `collected` is checked before the add and never\n // cleared, so nothing re-enters after it resolves.\n const pending: SignedResourceId[] = [];\n const collect = (resource: ExtendedResourceData) => {\n collected.set(resource.id, resource);\n missing.delete(resource.id);\n for (const ref of referencesOf(resource))\n if (!collected.has(ref) && !knownResources.has(ref) && !missing.has(ref)) {\n missing.add(ref);\n pending.push(ref);\n }\n };\n\n let warnedStopMarker = false;\n const consume = async (walkSeeds: SignedResourceId[], unconditionalDepth?: number) => {\n if (stats) {\n stats.roundTrips++;\n stats.streamRounds++;\n stats.deltaSeedsSent += walkSeeds.length;\n }\n\n for await (const frame of tx.resourceTree(walkSeeds, {\n includeKv: true,\n fieldFilter,\n changedSinceToken,\n unconditionalDepth,\n })) {\n // A body-less frame. The client reads that as a stop marker, and a stop marker can only\n // follow traverse_stop_rules, which this path never sends. Nothing here needs it, so it\n // is skipped - but not silently: reaching this means the backend put a meaning on a\n // body-less frame that this client does not know, and a quiet skip would leave no trace\n // of it. Warn once per poll rather than per frame, since a contract change would emit\n // one of these for every resource visited.\n if (frame.frameKind === \"stopMarker\") {\n if (!warnedStopMarker) {\n warnedStopMarker = true;\n logger?.warn(\n `delta poll: ignoring a body-less frame for ${frame.id}; this poll sent no ` +\n `traverse stop rules, so the backend is using body-less frames for something ` +\n `this client does not interpret`,\n );\n }\n continue;\n }\n\n if (stats) stats.resourceFrames++;\n\n // updateFromResourceData throws on any body for a held final resource and invalidates\n // the tree. A token-less poll walks unpruned and emits exactly those, which is what the\n // first poll after a restored snapshot does. Both sibling algorithms skip the same way.\n // Safe for references: finalResources is a subset of knownResources.\n if (finalResources.has(frame.id)) {\n if (stats) stats.finalResourcesSkipped++;\n continue;\n }\n\n // An explicit literal rather than two rest-spreads of the frame. Measured ~15% off the\n // per-frame cost: a spread copies every property twice and gives the object a hidden\n // class of its own, where this is the shape the streaming path already produces.\n const resource: ExtendedResourceData = {\n id: frame.id,\n type: frame.type,\n kind: frame.kind,\n data: frame.data,\n resourceReady: frame.resourceReady,\n error: frame.error,\n originalResourceId: frame.originalResourceId,\n final: frame.final,\n inputsLocked: frame.inputsLocked,\n outputsLocked: frame.outputsLocked,\n fields:\n pruningFunction !== undefined\n ? pruningFunction(frame as unknown as ExtendedResourceData)\n : frame.fields,\n kv: frame.kv,\n };\n if (stats) stats.prunedFields += frame.fields.length - resource.fields.length;\n\n collect(resource);\n collectStatsForResource(resource, stats);\n }\n };\n\n await consume(seeds);\n\n // Captured before the resolution rounds. They fetch unconditionally by design, so counting\n // afterwards lets a modest unheld subtree inflate the total past a small mirror and report a\n // refused token that was never refused.\n const collectedFromPoll = collected.size;\n\n // A body may reference a resource the response did not carry - a field repointed at one we\n // never held. Ids already asked for are never re-requested, so the loop terminates: the tree\n // is finite and `fetched` only grows.\n //\n // Deliberately unbounded on rounds. A round-count cap would turn a legal tree shape into a\n // desktop error, since how deep the references chain is a property of what the backend\n // allowed rather than of anything wrong here, and the streaming path's stop-marker follow-up\n // loop has always run on the same assumption.\n const fetched = new Set<SignedResourceId>();\n let resolutionRound = 0;\n while (missing.size > 0) {\n const round = pending.splice(0).filter((id) => !fetched.has(id) && missing.has(id));\n if (round.length === 0) {\n // Asked for all of these and none arrived; a soft-deleted referent gets here, since\n // those frames are dropped client-side. TreeStateUpdateError specifically: the poll loop\n // rebuilds and discards the token for that class, where a plain Error is only logged and\n // the identical request fails forever.\n throw new TreeStateUpdateError(\n `delta poll: ${missing.size} referenced resource(s) could not be resolved, first ${missing.values().next().value}`,\n );\n }\n for (const id of round) fetched.add(id);\n\n resolutionRound++;\n if (stats) stats.deltaResolutionRounds++;\n await consume(round, resolutionDepth(resolutionRound));\n }\n\n // A refused token is answered with the full tree and no error, so this is the only tell.\n // Reported, not acted on: the response is a correct superset, and the token stored after\n // this poll comes from the current instance, so it self-heals next poll.\n if (\n changedSinceToken !== undefined &&\n knownResources.size > 0 &&\n collectedFromPoll >= knownResources.size\n ) {\n if (stats) stats.deltaSuspectedFullAnswers++;\n logger?.warn(\n `delta poll: sent a token but received ${collectedFromPoll} resources against a mirror of ` +\n `${knownResources.size}; the backend may have refused the token (instance change or ` +\n `rewound numbering), in which case this poll cost a full tree read`,\n );\n }\n\n return [...collected.values()];\n}\n"],"mappings":"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"}
|