@milaboratories/pl-tree 1.9.22 → 1.10.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 +8 -1
- package/dist/accessors.cjs.map +1 -1
- package/dist/accessors.d.ts +13 -8
- package/dist/accessors.d.ts.map +1 -1
- package/dist/accessors.js +8 -1
- package/dist/accessors.js.map +1 -1
- package/dist/snapshot.cjs.map +1 -1
- package/dist/snapshot.d.ts +7 -7
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js.map +1 -1
- package/dist/state.cjs +21 -21
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.ts +15 -15
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +22 -22
- package/dist/state.js.map +1 -1
- package/dist/sync.cjs +1 -1
- package/dist/sync.cjs.map +1 -1
- package/dist/sync.d.ts +3 -3
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +2 -2
- package/dist/sync.js.map +1 -1
- package/dist/synchronized_tree.cjs.map +1 -1
- package/dist/synchronized_tree.d.ts +4 -4
- package/dist/synchronized_tree.d.ts.map +1 -1
- package/dist/synchronized_tree.js.map +1 -1
- package/package.json +5 -5
- package/src/accessors.ts +25 -10
- package/src/snapshot.test.ts +6 -5
- package/src/snapshot.ts +11 -11
- package/src/state.test.ts +12 -14
- package/src/state.ts +46 -46
- package/src/sync.ts +10 -10
- package/src/synchronized_tree.ts +5 -5
- package/src/test_utils.ts +23 -16
package/src/accessors.ts
CHANGED
|
@@ -5,13 +5,17 @@ import type {
|
|
|
5
5
|
ComputableHooks,
|
|
6
6
|
UsageGuard,
|
|
7
7
|
} from "@milaboratories/computable";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
SignedResourceId,
|
|
10
|
+
ResourceType,
|
|
11
|
+
OptionalSignedResourceId,
|
|
12
|
+
} from "@milaboratories/pl-client";
|
|
9
13
|
import {
|
|
10
14
|
resourceIdToString,
|
|
11
15
|
resourceTypesEqual,
|
|
12
16
|
resourceTypeToString,
|
|
13
17
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
14
|
-
|
|
18
|
+
NullSignedResourceId,
|
|
15
19
|
} from "@milaboratories/pl-client";
|
|
16
20
|
import type { ValueAndError } from "./value_and_error";
|
|
17
21
|
import { mapValueAndError } from "./value_and_error";
|
|
@@ -71,7 +75,7 @@ export class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {
|
|
|
71
75
|
|
|
72
76
|
constructor(
|
|
73
77
|
private readonly accessorData: TreeAccessorData,
|
|
74
|
-
public readonly rid:
|
|
78
|
+
public readonly rid: SignedResourceId, // only for runtime. Not exposing rid at all causes too much pain. Never persist this anywhere.
|
|
75
79
|
) {}
|
|
76
80
|
|
|
77
81
|
public createAccessor(ctx: ComputableCtx, guard: UsageGuard): PlTreeEntryAccessor {
|
|
@@ -81,6 +85,14 @@ export class PlTreeEntry implements AccessorProvider<PlTreeEntryAccessor> {
|
|
|
81
85
|
});
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Tree TreeEntry ID suitable for persistence and use in names (files/objects/whatever), as it does not
|
|
90
|
+
* contain signature which can change from session to session.
|
|
91
|
+
*/
|
|
92
|
+
public get id(): string {
|
|
93
|
+
return resourceIdToString(this.rid);
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
public toJSON(): string {
|
|
85
97
|
return this.toString();
|
|
86
98
|
}
|
|
@@ -94,7 +106,7 @@ function getResourceFromTree(
|
|
|
94
106
|
accessorData: TreeAccessorData,
|
|
95
107
|
tree: PlTreeState,
|
|
96
108
|
instanceData: TreeAccessorInstanceData,
|
|
97
|
-
rid:
|
|
109
|
+
rid: SignedResourceId,
|
|
98
110
|
ops: ResourceTraversalOps,
|
|
99
111
|
): PlTreeNodeAccessor {
|
|
100
112
|
const acc = new PlTreeNodeAccessor(
|
|
@@ -130,7 +142,7 @@ export class PlTreeEntryAccessor {
|
|
|
130
142
|
constructor(
|
|
131
143
|
private readonly accessorData: TreeAccessorData,
|
|
132
144
|
private readonly tree: PlTreeState,
|
|
133
|
-
private readonly rid:
|
|
145
|
+
private readonly rid: SignedResourceId,
|
|
134
146
|
private readonly instanceData: TreeAccessorInstanceData,
|
|
135
147
|
) {}
|
|
136
148
|
|
|
@@ -150,7 +162,7 @@ export class PlTreeEntryAccessor {
|
|
|
150
162
|
* Helper type to simplify implementation of APIs requiring type information.
|
|
151
163
|
* */
|
|
152
164
|
export type ResourceInfo = {
|
|
153
|
-
readonly id:
|
|
165
|
+
readonly id: SignedResourceId;
|
|
154
166
|
readonly type: ResourceType;
|
|
155
167
|
};
|
|
156
168
|
|
|
@@ -166,7 +178,7 @@ export function treeEntryToResourceInfo(res: PlTreeEntry | ResourceInfo, ctx: Co
|
|
|
166
178
|
|
|
167
179
|
/**
|
|
168
180
|
* API contracts:
|
|
169
|
-
* - API never return {@link
|
|
181
|
+
* - API never return {@link NullSignedResourceId}, absence of link is always modeled as `undefined`
|
|
170
182
|
*
|
|
171
183
|
* Important: never store instances of this class, always get fresh instance from {@link PlTreeState} accessor.
|
|
172
184
|
* */
|
|
@@ -180,12 +192,12 @@ export class PlTreeNodeAccessor {
|
|
|
180
192
|
private readonly instanceData: TreeAccessorInstanceData,
|
|
181
193
|
) {}
|
|
182
194
|
|
|
183
|
-
public get id():
|
|
195
|
+
public get id(): SignedResourceId {
|
|
184
196
|
this.instanceData.guard();
|
|
185
197
|
return this.resource.id;
|
|
186
198
|
}
|
|
187
199
|
|
|
188
|
-
public get originalId():
|
|
200
|
+
public get originalId(): OptionalSignedResourceId {
|
|
189
201
|
this.instanceData.guard();
|
|
190
202
|
return this.resource.originalResourceId;
|
|
191
203
|
}
|
|
@@ -199,7 +211,10 @@ export class PlTreeNodeAccessor {
|
|
|
199
211
|
return { id: this.id, type: this.resourceType };
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
private getResourceFromTree(
|
|
214
|
+
private getResourceFromTree(
|
|
215
|
+
rid: SignedResourceId,
|
|
216
|
+
ops: ResourceTraversalOps,
|
|
217
|
+
): PlTreeNodeAccessor {
|
|
203
218
|
return getResourceFromTree(this.accessorData, this.tree, this.instanceData, rid, ops);
|
|
204
219
|
}
|
|
205
220
|
|
package/src/snapshot.test.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
2
|
import { Computable } from "@milaboratories/computable";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createSignedResourceId,
|
|
5
|
+
DefaultFinalResourceDataPredicate,
|
|
6
|
+
} from "@milaboratories/pl-client";
|
|
4
7
|
import { z } from "zod";
|
|
5
8
|
import { InferSnapshot, makeResourceSnapshot, rsSchema } from "./snapshot";
|
|
6
9
|
import { PlTreeState } from "./state";
|
|
@@ -14,6 +17,8 @@ import {
|
|
|
14
17
|
iField,
|
|
15
18
|
} from "./test_utils";
|
|
16
19
|
|
|
20
|
+
const rid = createSignedResourceId;
|
|
21
|
+
|
|
17
22
|
// schema definition
|
|
18
23
|
const MyTestResourceState = rsSchema({
|
|
19
24
|
data: z.object({
|
|
@@ -96,7 +101,3 @@ test("simple snapshot test", async () => {
|
|
|
96
101
|
expect((await c1.getValueOrError()).type).toStrictEqual("error");
|
|
97
102
|
expect(c1.isChanged()).toBeFalsy();
|
|
98
103
|
});
|
|
99
|
-
|
|
100
|
-
function rid(id: bigint): ResourceId {
|
|
101
|
-
return id as ResourceId;
|
|
102
|
-
}
|
package/src/snapshot.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SignedResourceId, ResourceType } from "@milaboratories/pl-client";
|
|
2
2
|
import type { Optional, Writable } from "utility-types";
|
|
3
3
|
import type { ZodType, z } from "zod";
|
|
4
4
|
import type { PlTreeNodeAccessor } from "./accessors";
|
|
@@ -13,10 +13,10 @@ import { notEmpty } from "@milaboratories/ts-helpers";
|
|
|
13
13
|
*/
|
|
14
14
|
export type ResourceSnapshot<
|
|
15
15
|
Data = undefined,
|
|
16
|
-
Fields extends Record<string,
|
|
16
|
+
Fields extends Record<string, SignedResourceId | undefined> | undefined = undefined,
|
|
17
17
|
KV extends Record<string, unknown> | undefined = undefined,
|
|
18
18
|
> = {
|
|
19
|
-
readonly id:
|
|
19
|
+
readonly id: SignedResourceId;
|
|
20
20
|
readonly type: ResourceType;
|
|
21
21
|
readonly data: Data;
|
|
22
22
|
readonly fields: Fields;
|
|
@@ -26,7 +26,7 @@ export type ResourceSnapshot<
|
|
|
26
26
|
/** The most generic type of ResourceSnapshot. */
|
|
27
27
|
type ResourceSnapshotGeneric = ResourceSnapshot<
|
|
28
28
|
unknown,
|
|
29
|
-
Record<string,
|
|
29
|
+
Record<string, SignedResourceId | undefined> | undefined,
|
|
30
30
|
Record<string, unknown> | undefined
|
|
31
31
|
>;
|
|
32
32
|
|
|
@@ -80,8 +80,8 @@ type InferFieldsType<Fields extends Record<string, boolean> | undefined> = Field
|
|
|
80
80
|
? undefined
|
|
81
81
|
: {
|
|
82
82
|
[FieldName in keyof Fields]: Fields[FieldName] extends true
|
|
83
|
-
?
|
|
84
|
-
:
|
|
83
|
+
? SignedResourceId
|
|
84
|
+
: SignedResourceId | undefined;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -132,7 +132,7 @@ export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneri
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
if (schema.fields !== undefined) {
|
|
135
|
-
const fields: Record<string,
|
|
135
|
+
const fields: Record<string, SignedResourceId | undefined> = {};
|
|
136
136
|
// even if field is not defined, corresponding object field
|
|
137
137
|
// with "undefined" value will still be added
|
|
138
138
|
for (const [fieldName, required] of Object.entries(schema.fields))
|
|
@@ -165,9 +165,9 @@ export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneri
|
|
|
165
165
|
|
|
166
166
|
/** @deprecated */
|
|
167
167
|
export type ResourceWithData = {
|
|
168
|
-
readonly id:
|
|
168
|
+
readonly id: SignedResourceId;
|
|
169
169
|
readonly type: ResourceType;
|
|
170
|
-
readonly fields: Map<string,
|
|
170
|
+
readonly fields: Map<string, SignedResourceId | undefined>;
|
|
171
171
|
readonly data?: Uint8Array;
|
|
172
172
|
};
|
|
173
173
|
|
|
@@ -181,7 +181,7 @@ export function treeEntryToResourceWithData(
|
|
|
181
181
|
const node = ctx.accessor(res).node();
|
|
182
182
|
const info = node.resourceInfo;
|
|
183
183
|
|
|
184
|
-
const fValues: [string,
|
|
184
|
+
const fValues: [string, SignedResourceId | undefined][] = fields.map((name) => [
|
|
185
185
|
name,
|
|
186
186
|
node.getField(name)?.value?.id,
|
|
187
187
|
]);
|
|
@@ -198,7 +198,7 @@ export function treeEntryToResourceWithData(
|
|
|
198
198
|
|
|
199
199
|
/** @deprecated */
|
|
200
200
|
export type ResourceWithMetadata = {
|
|
201
|
-
readonly id:
|
|
201
|
+
readonly id: SignedResourceId;
|
|
202
202
|
readonly type: ResourceType;
|
|
203
203
|
readonly metadata: Record<string, any>;
|
|
204
204
|
};
|
package/src/state.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
2
|
import { Computable } from "@milaboratories/computable";
|
|
3
3
|
import {
|
|
4
|
+
createSignedResourceId,
|
|
4
5
|
DefaultFinalResourceDataPredicate,
|
|
5
|
-
|
|
6
|
-
ResourceId,
|
|
6
|
+
NullSignedResourceId,
|
|
7
7
|
} from "@milaboratories/pl-client";
|
|
8
8
|
import { isPlTreeEntry, isPlTreeEntryAccessor, isPlTreeNodeAccessor } from "./accessors";
|
|
9
9
|
import { PlTreeState } from "./state";
|
|
@@ -18,9 +18,7 @@ import {
|
|
|
18
18
|
TestValueResourceState1,
|
|
19
19
|
} from "./test_utils";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
return id as ResourceId;
|
|
23
|
-
}
|
|
21
|
+
const rid = createSignedResourceId;
|
|
24
22
|
|
|
25
23
|
test("simple tree test 1", async () => {
|
|
26
24
|
const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
|
|
@@ -54,11 +52,11 @@ test("simple tree test 1", async () => {
|
|
|
54
52
|
expect(c1.isChanged()).toBeFalsy();
|
|
55
53
|
|
|
56
54
|
tree.updateFromResourceData([
|
|
57
|
-
{ ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(
|
|
58
|
-
{ ...TestStructuralResourceState1, id: rid(
|
|
55
|
+
{ ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] },
|
|
56
|
+
{ ...TestStructuralResourceState1, id: rid(1n), fields: [iField("b", rid(2n))] },
|
|
59
57
|
{
|
|
60
58
|
...TestValueResourceState1,
|
|
61
|
-
id: rid(
|
|
59
|
+
id: rid(2n),
|
|
62
60
|
data: new TextEncoder().encode("Test1"),
|
|
63
61
|
},
|
|
64
62
|
]);
|
|
@@ -85,11 +83,11 @@ test("simple tree kv test", async () => {
|
|
|
85
83
|
expect(c1.isChanged()).toBeFalsy();
|
|
86
84
|
|
|
87
85
|
tree.updateFromResourceData([
|
|
88
|
-
{ ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(
|
|
89
|
-
{ ...TestStructuralResourceState1, id: rid(
|
|
86
|
+
{ ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] },
|
|
87
|
+
{ ...TestStructuralResourceState1, id: rid(1n), fields: [iField("b", rid(2n))] },
|
|
90
88
|
{
|
|
91
89
|
...TestValueResourceState1,
|
|
92
|
-
id: rid(
|
|
90
|
+
id: rid(2n),
|
|
93
91
|
data: new TextEncoder().encode("Test1"),
|
|
94
92
|
},
|
|
95
93
|
]);
|
|
@@ -101,7 +99,7 @@ test("simple tree kv test", async () => {
|
|
|
101
99
|
tree.updateFromResourceData([
|
|
102
100
|
{
|
|
103
101
|
...TestValueResourceState1,
|
|
104
|
-
id: rid(
|
|
102
|
+
id: rid(2n),
|
|
105
103
|
data: new TextEncoder().encode("Test1"),
|
|
106
104
|
kv: [{ key: "thekey", value: Buffer.from("thevalue") }],
|
|
107
105
|
},
|
|
@@ -114,7 +112,7 @@ test("simple tree kv test", async () => {
|
|
|
114
112
|
tree.updateFromResourceData([
|
|
115
113
|
{
|
|
116
114
|
...TestValueResourceState1,
|
|
117
|
-
id: rid(
|
|
115
|
+
id: rid(2n),
|
|
118
116
|
data: new TextEncoder().encode("Test1"),
|
|
119
117
|
kv: [],
|
|
120
118
|
},
|
|
@@ -197,7 +195,7 @@ test("field error", async () => {
|
|
|
197
195
|
tree.updateFromResourceData([
|
|
198
196
|
{
|
|
199
197
|
...TestDynamicRootState1,
|
|
200
|
-
fields: [dField("b",
|
|
198
|
+
fields: [dField("b", NullSignedResourceId, rid(7n))],
|
|
201
199
|
},
|
|
202
200
|
{
|
|
203
201
|
...TestErrorResourceState2,
|
package/src/state.ts
CHANGED
|
@@ -4,16 +4,16 @@ import type {
|
|
|
4
4
|
FieldStatus,
|
|
5
5
|
FieldType,
|
|
6
6
|
KeyValue,
|
|
7
|
-
|
|
7
|
+
OptionalSignedResourceId,
|
|
8
8
|
ResourceData,
|
|
9
|
-
|
|
9
|
+
SignedResourceId,
|
|
10
10
|
ResourceKind,
|
|
11
11
|
ResourceType,
|
|
12
12
|
} from "@milaboratories/pl-client";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
isNotNullSignedResourceId,
|
|
15
|
+
isNullSignedResourceId,
|
|
16
|
+
NullSignedResourceId,
|
|
17
17
|
resourceIdToString,
|
|
18
18
|
stringifyWithResourceId,
|
|
19
19
|
} from "@milaboratories/pl-client";
|
|
@@ -42,8 +42,8 @@ class PlTreeField implements FieldData {
|
|
|
42
42
|
constructor(
|
|
43
43
|
public readonly name: string,
|
|
44
44
|
public type: FieldType,
|
|
45
|
-
public value:
|
|
46
|
-
public error:
|
|
45
|
+
public value: OptionalSignedResourceId,
|
|
46
|
+
public error: OptionalSignedResourceId,
|
|
47
47
|
public status: FieldStatus,
|
|
48
48
|
public valueIsFinal: boolean,
|
|
49
49
|
/** Last version of resource this field was observed, used to garbage collect fields in tree patching procedure */
|
|
@@ -98,8 +98,8 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
98
98
|
// kvChangedGlobal? = new ChangeSource();
|
|
99
99
|
kvChangedPerKey? = new KeyedChangeSource();
|
|
100
100
|
|
|
101
|
-
readonly id:
|
|
102
|
-
originalResourceId:
|
|
101
|
+
readonly id: SignedResourceId;
|
|
102
|
+
originalResourceId: OptionalSignedResourceId;
|
|
103
103
|
|
|
104
104
|
readonly kind: ResourceKind;
|
|
105
105
|
readonly type: ResourceType;
|
|
@@ -108,7 +108,7 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
108
108
|
private dataAsString?: string;
|
|
109
109
|
private dataAsJson?: unknown;
|
|
110
110
|
|
|
111
|
-
error:
|
|
111
|
+
error: OptionalSignedResourceId;
|
|
112
112
|
|
|
113
113
|
inputsLocked: boolean;
|
|
114
114
|
outputsLocked: boolean;
|
|
@@ -162,17 +162,17 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
162
162
|
| (Omit<GetFieldStep, "errorIfFieldNotFound"> & { errorIfFieldNotFound: true })
|
|
163
163
|
| (Omit<GetFieldStep, "errorIfFieldNotSet"> & { errorIfFieldNotSet: true }),
|
|
164
164
|
onUnstable: (marker: string) => void,
|
|
165
|
-
): ValueAndError<
|
|
165
|
+
): ValueAndError<SignedResourceId>;
|
|
166
166
|
public getField(
|
|
167
167
|
watcher: Watcher,
|
|
168
168
|
_step: string | GetFieldStep,
|
|
169
169
|
onUnstable: (marker: string) => void,
|
|
170
|
-
): ValueAndError<
|
|
170
|
+
): ValueAndError<SignedResourceId> | undefined;
|
|
171
171
|
public getField(
|
|
172
172
|
watcher: Watcher,
|
|
173
173
|
_step: string | GetFieldStep,
|
|
174
174
|
onUnstable: (marker: string) => void = () => {},
|
|
175
|
-
): ValueAndError<
|
|
175
|
+
): ValueAndError<SignedResourceId> | undefined {
|
|
176
176
|
const step: FieldTraversalStep = typeof _step === "string" ? { field: _step } : _step;
|
|
177
177
|
|
|
178
178
|
const field = this.fieldsMap.get(step.field);
|
|
@@ -208,9 +208,9 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
208
208
|
`Unexpected field type: expected ${step.assertFieldType} but got ${field.type} for the field name ${step.field}`,
|
|
209
209
|
);
|
|
210
210
|
|
|
211
|
-
const ret = {} as ValueAndError<
|
|
212
|
-
if (
|
|
213
|
-
if (
|
|
211
|
+
const ret = {} as ValueAndError<SignedResourceId>;
|
|
212
|
+
if (isNotNullSignedResourceId(field.value)) ret.value = field.value;
|
|
213
|
+
if (isNotNullSignedResourceId(field.error)) ret.error = field.error;
|
|
214
214
|
if (ret.value === undefined && ret.error === undefined)
|
|
215
215
|
// this method returns value and error of the field, thus those values are considered to be accessed;
|
|
216
216
|
// any existing but not resolved field here is considered to be unstable, in the sense it is
|
|
@@ -237,9 +237,9 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
237
237
|
|
|
238
238
|
public get isReadyOrError(): boolean {
|
|
239
239
|
return (
|
|
240
|
-
this.error !==
|
|
240
|
+
this.error !== NullSignedResourceId ||
|
|
241
241
|
this.resourceReady ||
|
|
242
|
-
this.originalResourceId !==
|
|
242
|
+
this.originalResourceId !== NullSignedResourceId
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -255,8 +255,8 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
255
255
|
return this.isReadyOrError;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
public getError(watcher: Watcher):
|
|
259
|
-
if (
|
|
258
|
+
public getError(watcher: Watcher): SignedResourceId | undefined {
|
|
259
|
+
if (isNullSignedResourceId(this.error)) {
|
|
260
260
|
this.resourceStateChange?.attachWatcher(watcher);
|
|
261
261
|
return undefined;
|
|
262
262
|
} else {
|
|
@@ -387,7 +387,7 @@ export class PlTreeResource implements ResourceDataWithFinalState {
|
|
|
387
387
|
|
|
388
388
|
export class PlTreeState {
|
|
389
389
|
/** resource heap */
|
|
390
|
-
private resources: Map<
|
|
390
|
+
private resources: Map<SignedResourceId, PlTreeResource> = new Map();
|
|
391
391
|
private readonly resourcesAdded = new ChangeSource();
|
|
392
392
|
/** Resets to false if any invalid state transitions are registered,
|
|
393
393
|
* after that tree will produce errors for any read or write operations */
|
|
@@ -396,7 +396,7 @@ export class PlTreeState {
|
|
|
396
396
|
|
|
397
397
|
constructor(
|
|
398
398
|
/** This will be the only resource not deleted during GC round */
|
|
399
|
-
public readonly root:
|
|
399
|
+
public readonly root: SignedResourceId,
|
|
400
400
|
public readonly isFinalPredicate: FinalResourceDataPredicate,
|
|
401
401
|
) {}
|
|
402
402
|
|
|
@@ -408,7 +408,7 @@ export class PlTreeState {
|
|
|
408
408
|
if (!this._isValid) throw new Error(this.invalidationMessage ?? "tree is in invalid state");
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
public get(watcher: Watcher, rid:
|
|
411
|
+
public get(watcher: Watcher, rid: SignedResourceId): PlTreeResource {
|
|
412
412
|
this.checkValid();
|
|
413
413
|
const res = this.resources.get(rid);
|
|
414
414
|
if (res === undefined) {
|
|
@@ -425,8 +425,8 @@ export class PlTreeState {
|
|
|
425
425
|
this.checkValid();
|
|
426
426
|
|
|
427
427
|
// All resources for which recount should be incremented, first are aggregated in this list
|
|
428
|
-
const incrementRefs:
|
|
429
|
-
const decrementRefs:
|
|
428
|
+
const incrementRefs: SignedResourceId[] = [];
|
|
429
|
+
const decrementRefs: SignedResourceId[] = [];
|
|
430
430
|
|
|
431
431
|
// patching / creating resources
|
|
432
432
|
for (const rd of resourceData) {
|
|
@@ -456,7 +456,7 @@ export class PlTreeState {
|
|
|
456
456
|
|
|
457
457
|
// duplicate / original
|
|
458
458
|
if (resource.originalResourceId !== rd.originalResourceId) {
|
|
459
|
-
if (resource.originalResourceId !==
|
|
459
|
+
if (resource.originalResourceId !== NullSignedResourceId)
|
|
460
460
|
unexpectedTransitionError("originalResourceId can't change after it is set");
|
|
461
461
|
resource.originalResourceId = rd.originalResourceId;
|
|
462
462
|
// duplicate status of the resource counts as ready for the external observer
|
|
@@ -467,11 +467,11 @@ export class PlTreeState {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
// error
|
|
470
|
-
if (resource.error !== rd.error) {
|
|
471
|
-
if (
|
|
470
|
+
if (isNotNullSignedResourceId(rd.error) && resource.error !== rd.error) {
|
|
471
|
+
if (isNotNullSignedResourceId(resource.error))
|
|
472
472
|
unexpectedTransitionError("resource can't change attached error after it is set");
|
|
473
473
|
resource.error = rd.error;
|
|
474
|
-
incrementRefs.push(resource.error
|
|
474
|
+
incrementRefs.push(resource.error);
|
|
475
475
|
notEmpty(resource.resourceStateChange).markChanged(
|
|
476
476
|
`error changed for ${resourceIdToString(resource.id)}`,
|
|
477
477
|
);
|
|
@@ -494,8 +494,8 @@ export class PlTreeState {
|
|
|
494
494
|
fd.valueIsFinal,
|
|
495
495
|
resource.version,
|
|
496
496
|
);
|
|
497
|
-
if (
|
|
498
|
-
if (
|
|
497
|
+
if (isNotNullSignedResourceId(fd.value)) incrementRefs.push(fd.value);
|
|
498
|
+
if (isNotNullSignedResourceId(fd.error)) incrementRefs.push(fd.error);
|
|
499
499
|
|
|
500
500
|
if (fd.type === "Input" || fd.type === "Service") {
|
|
501
501
|
if (resource.inputsLocked)
|
|
@@ -559,9 +559,9 @@ export class PlTreeState {
|
|
|
559
559
|
|
|
560
560
|
// field value
|
|
561
561
|
if (field.value !== fd.value) {
|
|
562
|
-
if (
|
|
562
|
+
if (isNotNullSignedResourceId(field.value)) decrementRefs.push(field.value);
|
|
563
563
|
field.value = fd.value;
|
|
564
|
-
if (
|
|
564
|
+
if (isNotNullSignedResourceId(fd.value)) incrementRefs.push(fd.value);
|
|
565
565
|
field.change.markChanged(
|
|
566
566
|
`field ${fd.name} value changed in ${resourceIdToString(resource.id)}`,
|
|
567
567
|
);
|
|
@@ -570,9 +570,9 @@ export class PlTreeState {
|
|
|
570
570
|
|
|
571
571
|
// field error
|
|
572
572
|
if (field.error !== fd.error) {
|
|
573
|
-
if (
|
|
573
|
+
if (isNotNullSignedResourceId(field.error)) decrementRefs.push(field.error);
|
|
574
574
|
field.error = fd.error;
|
|
575
|
-
if (
|
|
575
|
+
if (isNotNullSignedResourceId(fd.error)) incrementRefs.push(fd.error);
|
|
576
576
|
field.change.markChanged(
|
|
577
577
|
`field ${fd.name} error changed in ${resourceIdToString(resource.id)}`,
|
|
578
578
|
);
|
|
@@ -611,8 +611,8 @@ export class PlTreeState {
|
|
|
611
611
|
);
|
|
612
612
|
fields.delete(fieldName);
|
|
613
613
|
|
|
614
|
-
if (
|
|
615
|
-
if (
|
|
614
|
+
if (isNotNullSignedResourceId(field.value)) decrementRefs.push(field.value);
|
|
615
|
+
if (isNotNullSignedResourceId(field.error)) decrementRefs.push(field.error);
|
|
616
616
|
|
|
617
617
|
notEmpty(resource!.dynamicFieldListChanged).markChanged(
|
|
618
618
|
`dynamic field ${fieldName} removed from ${resourceIdToString(resource!.id)}`,
|
|
@@ -700,7 +700,7 @@ export class PlTreeState {
|
|
|
700
700
|
|
|
701
701
|
resource = new PlTreeResource(rd);
|
|
702
702
|
resource.verifyReadyState();
|
|
703
|
-
if (
|
|
703
|
+
if (isNotNullSignedResourceId(resource.error)) incrementRefs.push(resource.error);
|
|
704
704
|
for (const fd of rd.fields) {
|
|
705
705
|
const field = new PlTreeField(
|
|
706
706
|
fd.name,
|
|
@@ -711,8 +711,8 @@ export class PlTreeState {
|
|
|
711
711
|
fd.valueIsFinal,
|
|
712
712
|
InitialResourceVersion,
|
|
713
713
|
);
|
|
714
|
-
if (
|
|
715
|
-
if (
|
|
714
|
+
if (isNotNullSignedResourceId(fd.value)) incrementRefs.push(fd.value);
|
|
715
|
+
if (isNotNullSignedResourceId(fd.error)) incrementRefs.push(fd.error);
|
|
716
716
|
resource.fieldsMap.set(fd.name, field);
|
|
717
717
|
}
|
|
718
718
|
|
|
@@ -741,7 +741,7 @@ export class PlTreeState {
|
|
|
741
741
|
// recursively applying refCount decrements / doing garbage collection
|
|
742
742
|
let currentRefs = decrementRefs;
|
|
743
743
|
while (currentRefs.length > 0) {
|
|
744
|
-
const nextRefs:
|
|
744
|
+
const nextRefs: SignedResourceId[] = [];
|
|
745
745
|
for (const rid of currentRefs) {
|
|
746
746
|
const res = this.resources.get(rid);
|
|
747
747
|
if (!res) {
|
|
@@ -754,13 +754,13 @@ export class PlTreeState {
|
|
|
754
754
|
if (res.refCount === 0 && res.id !== this.root) {
|
|
755
755
|
// removing fields
|
|
756
756
|
res.fieldsMap.forEach((field) => {
|
|
757
|
-
if (
|
|
758
|
-
if (
|
|
757
|
+
if (isNotNullSignedResourceId(field.value)) nextRefs.push(field.value);
|
|
758
|
+
if (isNotNullSignedResourceId(field.error)) nextRefs.push(field.error);
|
|
759
759
|
field.change.markChanged(
|
|
760
760
|
`field ${field.name} removed during garbage collection of ${resourceIdToString(res.id)}`,
|
|
761
761
|
);
|
|
762
762
|
});
|
|
763
|
-
if (
|
|
763
|
+
if (isNotNullSignedResourceId(res.error)) nextRefs.push(res.error);
|
|
764
764
|
res.resourceRemoved.markChanged(
|
|
765
765
|
`resource removed during garbage collection: ${resourceIdToString(res.id)}`,
|
|
766
766
|
);
|
|
@@ -782,12 +782,12 @@ export class PlTreeState {
|
|
|
782
782
|
}
|
|
783
783
|
|
|
784
784
|
/** @deprecated use "entry" instead */
|
|
785
|
-
public accessor(rid:
|
|
785
|
+
public accessor(rid: SignedResourceId = this.root): PlTreeEntry {
|
|
786
786
|
this.checkValid();
|
|
787
787
|
return this.entry(rid);
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
-
public entry(rid:
|
|
790
|
+
public entry(rid: SignedResourceId = this.root): PlTreeEntry {
|
|
791
791
|
this.checkValid();
|
|
792
792
|
return new PlTreeEntry({ treeProvider: () => this }, rid);
|
|
793
793
|
}
|
package/src/sync.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FieldData,
|
|
3
|
-
|
|
3
|
+
OptionalSignedResourceId,
|
|
4
4
|
PlTransaction,
|
|
5
|
-
|
|
5
|
+
SignedResourceId,
|
|
6
6
|
} from "@milaboratories/pl-client";
|
|
7
7
|
import Denque from "denque";
|
|
8
|
-
import {
|
|
8
|
+
import { isNullSignedResourceId } from "@milaboratories/pl-client";
|
|
9
9
|
import type { ExtendedResourceData, PlTreeState } from "./state";
|
|
10
10
|
import { ConcurrencyLimitingExecutor, msToHumanReadable } from "@milaboratories/ts-helpers";
|
|
11
11
|
|
|
@@ -16,12 +16,12 @@ export interface TreeLoadingRequest {
|
|
|
16
16
|
/** Resource to prime the traversal algorithm. It is ok, if some of them
|
|
17
17
|
* doesn't exist anymore. Should not contain elements from final resource
|
|
18
18
|
* set. */
|
|
19
|
-
readonly seedResources:
|
|
19
|
+
readonly seedResources: SignedResourceId[];
|
|
20
20
|
|
|
21
21
|
/** Resource ids for which state is already known and not expected to change.
|
|
22
22
|
* Algorithm will not continue traversal over those ids, and states will not
|
|
23
23
|
* be retrieved for them. */
|
|
24
|
-
readonly finalResources: Set<
|
|
24
|
+
readonly finalResources: Set<SignedResourceId>;
|
|
25
25
|
|
|
26
26
|
/** This function is applied to each resource data field list, before
|
|
27
27
|
* using it continue traversal. This modification also is applied to
|
|
@@ -37,8 +37,8 @@ export function constructTreeLoadingRequest(
|
|
|
37
37
|
tree: PlTreeState,
|
|
38
38
|
pruningFunction?: PruningFunction,
|
|
39
39
|
): TreeLoadingRequest {
|
|
40
|
-
const seedResources:
|
|
41
|
-
const finalResources = new Set<
|
|
40
|
+
const seedResources: SignedResourceId[] = [];
|
|
41
|
+
const finalResources = new Set<SignedResourceId>();
|
|
42
42
|
tree.forEachResource((res) => {
|
|
43
43
|
if (res.finalState) finalResources.add(res.id);
|
|
44
44
|
else seedResources.push(res.id);
|
|
@@ -120,11 +120,11 @@ export async function loadTreeState(
|
|
|
120
120
|
let numberOfRoundTrips = 0;
|
|
121
121
|
|
|
122
122
|
// tracking resources we already requested or queued
|
|
123
|
-
const requested = new Set<
|
|
123
|
+
const requested = new Set<SignedResourceId>();
|
|
124
124
|
|
|
125
125
|
/** Mark a resource for fetching. Deduplicates and respects final-resource set. */
|
|
126
|
-
const requestState = (rid:
|
|
127
|
-
if (
|
|
126
|
+
const requestState = (rid: OptionalSignedResourceId) => {
|
|
127
|
+
if (isNullSignedResourceId(rid) || requested.has(rid)) return;
|
|
128
128
|
|
|
129
129
|
if (finalResources.has(rid)) {
|
|
130
130
|
if (stats) stats.finalResourcesSkipped++;
|
package/src/synchronized_tree.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { PlTreeEntry } from "./accessors";
|
|
|
3
3
|
import type {
|
|
4
4
|
FinalResourceDataPredicate,
|
|
5
5
|
PlClient,
|
|
6
|
-
|
|
6
|
+
SignedResourceId,
|
|
7
7
|
TxOps,
|
|
8
8
|
} from "@milaboratories/pl-client";
|
|
9
9
|
import { isTimeoutOrCancelError } from "@milaboratories/pl-client";
|
|
@@ -52,7 +52,7 @@ export class SynchronizedTreeState {
|
|
|
52
52
|
|
|
53
53
|
private constructor(
|
|
54
54
|
private readonly pl: PlClient,
|
|
55
|
-
private readonly root:
|
|
55
|
+
private readonly root: SignedResourceId,
|
|
56
56
|
ops: SynchronizedTreeOps,
|
|
57
57
|
private readonly logger?: MiLogger,
|
|
58
58
|
) {
|
|
@@ -71,12 +71,12 @@ export class SynchronizedTreeState {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/** @deprecated use "entry" instead */
|
|
74
|
-
public accessor(rid:
|
|
74
|
+
public accessor(rid: SignedResourceId = this.root): PlTreeEntry {
|
|
75
75
|
if (this.terminated) throw new Error("tree synchronization is terminated");
|
|
76
76
|
return this.entry(rid);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
public entry(rid:
|
|
79
|
+
public entry(rid: SignedResourceId = this.root): PlTreeEntry {
|
|
80
80
|
if (this.terminated) throw new Error("tree synchronization is terminated");
|
|
81
81
|
return new PlTreeEntry({ treeProvider: () => this.state, hooks: this.hooks }, rid);
|
|
82
82
|
}
|
|
@@ -260,7 +260,7 @@ export class SynchronizedTreeState {
|
|
|
260
260
|
|
|
261
261
|
public static async init(
|
|
262
262
|
pl: PlClient,
|
|
263
|
-
root:
|
|
263
|
+
root: SignedResourceId,
|
|
264
264
|
ops: SynchronizedTreeOps,
|
|
265
265
|
logger?: MiLogger,
|
|
266
266
|
) {
|