@milaboratories/pl-tree 1.4.2 → 1.4.4

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/sync.d.ts CHANGED
@@ -23,13 +23,13 @@ export interface TreeLoadingRequest {
23
23
  export declare function constructTreeLoadingRequest(tree: PlTreeState, pruningFunction?: PruningFunction): TreeLoadingRequest;
24
24
  export type TreeLoadingStat = {
25
25
  requests: number;
26
- roundtrips: number;
26
+ roundTrips: number;
27
27
  retrievedResources: number;
28
28
  retrievedFields: number;
29
29
  retrievedKeyValues: number;
30
30
  retrievedResourceDataBytes: number;
31
31
  retrievedKeyValueBytes: number;
32
- prunnedFields: number;
32
+ prunedFields: number;
33
33
  finalResourcesSkipped: number;
34
34
  millisSpent: number;
35
35
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAIT,aAAa,EACb,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG5D,kDAAkD;AAClD,MAAM,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,SAAS,EAAE,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC;;cAEU;IACV,QAAQ,CAAC,aAAa,EAAE,UAAU,EAAE,CAAC;IAErC;;gCAE4B;IAC5B,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC;;;;8BAI0B;IAC1B,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CAC5C;AAED;kDACkD;AAClD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,WAAW,EACjB,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,CAYpB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B,EAAE,MAAM,CAAC;IACnC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,IAAI,eAAe,CAaxD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAYnE;AAED;;+BAE+B;AAC/B,wBAAsB,aAAa,CACjC,EAAE,EAAE,aAAa,EACjB,cAAc,EAAE,kBAAkB,EAClC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAmHjC"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAGT,aAAa,EACb,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG5D,kDAAkD;AAClD,MAAM,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,SAAS,EAAE,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC;;cAEU;IACV,QAAQ,CAAC,aAAa,EAAE,UAAU,EAAE,CAAC;IAErC;;gCAE4B;IAC5B,QAAQ,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAEzC;;;;8BAI0B;IAC1B,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CAC5C;AAED;kDACkD;AAClD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,WAAW,EACjB,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,CAYpB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0BAA0B,EAAE,MAAM,CAAC;IACnC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,IAAI,eAAe,CAaxD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAYnE;AAED;;+BAE+B;AAC/B,wBAAsB,aAAa,CACjC,EAAE,EAAE,aAAa,EACjB,cAAc,EAAE,kBAAkB,EAClC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAmHjC"}
@@ -1,11 +1,13 @@
1
1
  import { PlTreeEntry } from './accessors';
2
- import { PlClient, ResourceId } from '@milaboratories/pl-client';
3
- import { FinalPredicate } from './state';
2
+ import { FinalResourceDataPredicate, PlClient, ResourceId } from '@milaboratories/pl-client';
4
3
  import { PruningFunction } from './sync';
5
4
  import { MiLogger } from '@milaboratories/ts-helpers';
6
5
  type StatLoggingMode = 'cumulative' | 'per-request';
7
6
  export type SynchronizedTreeOps = {
8
- finalPredicate?: FinalPredicate;
7
+ /** Override final predicate from the PlClient */
8
+ finalPredicateOverride?: FinalResourceDataPredicate;
9
+ /** Pruning function to limit set of fields through which tree will
10
+ * traverse during state synchronization */
9
11
  pruning?: PruningFunction;
10
12
  /** Interval after last sync to sleep before the next one */
11
13
  pollingInterval: number;
@@ -1 +1 @@
1
- {"version":3,"file":"synchronized_tree.d.ts","sourceRoot":"","sources":["../src/synchronized_tree.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAA0B,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAE,cAAc,EAAqC,MAAM,SAAS,CAAC;AAC5E,OAAO,EAIL,eAAe,EAEhB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEtD,KAAK,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,eAAe,CAAC;IAE1B,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;IAEzB,wEAAwE;IACxE,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B,CAAC;AAOF,qBAAa,qBAAqB;IAU9B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAZ1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEzD,OAAO;IAoBP,sCAAsC;IAC/B,QAAQ,CAAC,GAAG,GAAE,UAAsB,GAAG,WAAW;IAKlD,KAAK,CAAC,GAAG,GAAE,UAAsB,GAAG,WAAW;IAKtD;wDACoD;IACvC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAK1C,OAAO,CAAC,oBAAoB,CAA0B;IAEtD,OAAO,CAAC,mBAAmB;IAK3B,2BAA2B;IAC3B,OAAO,CAAC,aAAa;IAMrB,2BAA2B;IAC3B,OAAO,CAAC,YAAY;IAIpB,yDAAyD;IACzD,OAAO,CAAC,WAAW,CAAS;IAC5B,iCAAiC;IACjC,OAAO,CAAC,WAAW,CAAwC;IAE3D,iEAAiE;YACnD,OAAO;IASrB,wDAAwD;IACxD,OAAO,CAAC,UAAU,CAAS;YAEb,QAAQ;IAqEtB;;;SAGK;IACQ,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAWvC,kBAAkB;IACL,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;WAKlC,IAAI,CACtB,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,mBAAmB,EACxB,MAAM,CAAC,EAAE,QAAQ;CAapB"}
1
+ {"version":3,"file":"synchronized_tree.d.ts","sourceRoot":"","sources":["../src/synchronized_tree.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,0BAA0B,EAE1B,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAIL,eAAe,EAEhB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEtD,KAAK,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,iDAAiD;IACjD,sBAAsB,CAAC,EAAE,0BAA0B,CAAC;IAEpD;+CAC2C;IAC3C,OAAO,CAAC,EAAE,eAAe,CAAC;IAE1B,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;IAEzB,wEAAwE;IACxE,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B,CAAC;AAOF,qBAAa,qBAAqB;IAU9B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAZ1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEzD,OAAO;IAoBP,sCAAsC;IAC/B,QAAQ,CAAC,GAAG,GAAE,UAAsB,GAAG,WAAW;IAKlD,KAAK,CAAC,GAAG,GAAE,UAAsB,GAAG,WAAW;IAKtD;wDACoD;IACvC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAK1C,OAAO,CAAC,oBAAoB,CAA0B;IAEtD,OAAO,CAAC,mBAAmB;IAK3B,2BAA2B;IAC3B,OAAO,CAAC,aAAa;IAMrB,2BAA2B;IAC3B,OAAO,CAAC,YAAY;IAIpB,yDAAyD;IACzD,OAAO,CAAC,WAAW,CAAS;IAC5B,iCAAiC;IACjC,OAAO,CAAC,WAAW,CAAwC;IAE3D,iEAAiE;YACnD,OAAO;IASrB,wDAAwD;IACxD,OAAO,CAAC,UAAU,CAAS;YAEb,QAAQ;IAqEtB;;;SAGK;IACQ,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAWvC,kBAAkB;IACL,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;WAKlC,IAAI,CACtB,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,mBAAmB,EACxB,MAAM,CAAC,EAAE,QAAQ;CAapB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-tree",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Reactive pl tree state",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "denque": "^2.1.0",
21
21
  "utility-types": "^3.11.0",
22
22
  "zod": "^3.23.8",
23
- "@milaboratories/computable": "^2.1.13",
24
- "@milaboratories/pl-client": "^2.5.1",
23
+ "@milaboratories/pl-client": "^2.5.3",
24
+ "@milaboratories/computable": "^2.2.0",
25
25
  "@milaboratories/ts-helpers": "^1.1.0"
26
26
  },
27
27
  "devDependencies": {
package/src/accessors.ts CHANGED
@@ -407,7 +407,7 @@ export class PlTreeNodeAccessor {
407
407
  }
408
408
 
409
409
  /**
410
- * Can be used to passe a higher level accessor that will wrap the resource and throw its
410
+ * Can be used to pass a higher level accessor that will wrap the resource and throw its
411
411
  * errors on node resolution.
412
412
  * */
413
413
  public toEntryAccessor(): PlTreeEntryAccessor {
@@ -11,7 +11,7 @@ import {
11
11
  } from './test_utils';
12
12
  import { PlTreeState } from './state';
13
13
  import { Computable } from '@milaboratories/computable';
14
- import { ResourceId } from '@milaboratories/pl-client';
14
+ import { DefaultFinalResourceDataPredicate, ResourceId } from '@milaboratories/pl-client';
15
15
 
16
16
  // schema definition
17
17
  const MyTestResourceState = rsSchema({
@@ -26,7 +26,7 @@ const MyTestResourceState = rsSchema({
26
26
  type MyTestResourceState = InferSnapshot<typeof MyTestResourceState>;
27
27
 
28
28
  test('simple snapshot test', async () => {
29
- const tree = new PlTreeState(TestDynamicRootId1);
29
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
30
30
 
31
31
  const c1 = Computable.make((ctx) => {
32
32
  const accessor = ctx.accessor(tree.entry()).node().traverse('a');
package/src/snapshot.ts CHANGED
@@ -132,6 +132,8 @@ export function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneri
132
132
 
133
133
  if (schema.fields !== undefined) {
134
134
  const fields: Record<string, ResourceId | undefined> = {};
135
+ // even if field is not defined, corresponding object field
136
+ // with "undefined" value will still be added
135
137
  for (const [fieldName, required] of Object.entries(schema.fields))
136
138
  fields[fieldName] = node.traverse({
137
139
  field: fieldName,
package/src/state.test.ts CHANGED
@@ -11,14 +11,14 @@ import {
11
11
  TestErrorResourceState2
12
12
  } from './test_utils';
13
13
  import { Computable } from '@milaboratories/computable';
14
- import { NullResourceId, ResourceId } from '@milaboratories/pl-client';
14
+ import { DefaultFinalResourceDataPredicate, NullResourceId, ResourceId } from '@milaboratories/pl-client';
15
15
 
16
16
  function rid(id: bigint): ResourceId {
17
17
  return id as ResourceId;
18
18
  }
19
19
 
20
20
  test('simple tree test 1', async () => {
21
- const tree = new PlTreeState(TestDynamicRootId1);
21
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
22
22
  const entry = tree.entry();
23
23
  expect(isPlTreeEntry(entry)).toStrictEqual(true);
24
24
  const c1 = Computable.make((c) => {
@@ -68,7 +68,7 @@ test('simple tree test 1', async () => {
68
68
  });
69
69
 
70
70
  test('simple tree kv test', async () => {
71
- const tree = new PlTreeState(TestDynamicRootId1);
71
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
72
72
  const c1 = Computable.make((c) =>
73
73
  c.accessor(tree.entry()).node().traverse('a', 'b')?.getKeyValueAsString('thekey')
74
74
  );
@@ -121,7 +121,7 @@ test('simple tree kv test', async () => {
121
121
  });
122
122
 
123
123
  test('partial tree update', async () => {
124
- const tree = new PlTreeState(TestDynamicRootId1);
124
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
125
125
  const c1 = Computable.make((c) =>
126
126
  c
127
127
  .accessor(tree.entry())
@@ -157,7 +157,7 @@ test('partial tree update', async () => {
157
157
  });
158
158
 
159
159
  test('resource error', async () => {
160
- const tree = new PlTreeState(TestDynamicRootId1);
160
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
161
161
  const c1 = Computable.make((c) =>
162
162
  c.accessor(tree.entry()).node().traverse('a', 'b')?.getKeyValueAsString('thekey')
163
163
  );
@@ -180,7 +180,7 @@ test('resource error', async () => {
180
180
  });
181
181
 
182
182
  test('field error', async () => {
183
- const tree = new PlTreeState(TestDynamicRootId1);
183
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
184
184
  const c1 = Computable.make((c) =>
185
185
  c.accessor(tree.entry()).node().traverse('b', 'a')?.getKeyValueAsString('thekey')
186
186
  );
@@ -206,7 +206,7 @@ test('field error', async () => {
206
206
  });
207
207
 
208
208
  test('exception - deletion of input field', () => {
209
- const tree = new PlTreeState(TestDynamicRootId1);
209
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
210
210
 
211
211
  tree.updateFromResourceData([
212
212
  { ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
@@ -224,7 +224,7 @@ test('exception - deletion of input field', () => {
224
224
  });
225
225
 
226
226
  test('exception - addition of input field', () => {
227
- const tree = new PlTreeState(TestDynamicRootId1);
227
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
228
228
 
229
229
  tree.updateFromResourceData([
230
230
  { ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
@@ -254,7 +254,7 @@ test('exception - addition of input field', () => {
254
254
  });
255
255
 
256
256
  test('exception - ready without locks 1', () => {
257
- const tree = new PlTreeState(TestDynamicRootId1);
257
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
258
258
 
259
259
  expect(() =>
260
260
  tree.updateFromResourceData([
@@ -278,7 +278,7 @@ test('exception - ready without locks 1', () => {
278
278
  });
279
279
 
280
280
  test('exception - ready without locks 2', () => {
281
- const tree = new PlTreeState(TestDynamicRootId1);
281
+ const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate);
282
282
 
283
283
  tree.updateFromResourceData([
284
284
  { ...TestDynamicRootState1, fields: [dField('b'), dField('a', rid(1n))] },
package/src/state.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  BasicResourceData,
3
+ FieldData,
4
+ FieldStatus,
3
5
  FieldType,
4
6
  isNotNullResourceId,
5
7
  isNullResourceId,
@@ -17,7 +19,8 @@ import { ChangeSource, Watcher } from '@milaboratories/computable';
17
19
  import { PlTreeEntry } from './accessors';
18
20
  import { ValueAndError } from './value_and_error';
19
21
  import { MiLogger, notEmpty } from '@milaboratories/ts-helpers';
20
- import { FieldTraversalStep, GetFieldStep, ResourceTraversalOps } from './traversal_ops';
22
+ import { FieldTraversalStep, GetFieldStep } from './traversal_ops';
23
+ import { FinalResourceDataPredicate } from '@milaboratories/pl-client';
21
24
 
22
25
  export type ExtendedResourceData = ResourceData & {
23
26
  kv: KeyValue[];
@@ -29,20 +32,16 @@ export class TreeStateUpdateError extends Error {
29
32
  }
30
33
  }
31
34
 
32
- /** Interface of PlTreeResource exposed to outer world, like {@link FinalPredicate}. */
33
- export interface PlTreeFieldI {
34
- readonly type: FieldType;
35
- readonly value: OptionalResourceId;
36
- readonly error: OptionalResourceId;
37
- }
38
-
39
- class PlTreeField {
35
+ class PlTreeField implements FieldData {
40
36
  readonly change = new ChangeSource();
41
37
 
42
38
  constructor(
39
+ public readonly name: string,
43
40
  public type: FieldType,
44
41
  public value: OptionalResourceId,
45
42
  public error: OptionalResourceId,
43
+ public status: FieldStatus,
44
+ public valueIsFinal: boolean,
46
45
  /** Last version of resource this field was observed, used to garbage collect fields in tree patching procedure */
47
46
  public resourceVersion: number
48
47
  ) {}
@@ -52,18 +51,12 @@ const InitialResourceVersion = 0;
52
51
 
53
52
  const decoder = new TextDecoder();
54
53
 
55
- /** Interface of PlTreeResource exposed to outer world, like {@link FinalPredicate}. */
56
- export type PlTreeResourceI = BasicResourceData & {
57
- readonly final: boolean;
58
- readonly fields: Map<string, PlTreeFieldI>;
54
+ export type ResourceDataWithFinalState = ResourceData & {
55
+ finalState: boolean;
59
56
  };
60
57
 
61
- /** Predicate of resource state used to determine if it's state is considered to be final,
62
- * and not expected to change in the future. */
63
- export type FinalPredicate = (r: Omit<PlTreeResourceI, 'final'>) => boolean;
64
-
65
58
  /** Never store instances of this class, always get fresh instance from {@link PlTreeState} */
66
- export class PlTreeResource implements PlTreeResourceI {
59
+ export class PlTreeResource implements ResourceDataWithFinalState {
67
60
  /** Tracks number of other resources referencing this resource. Used to perform garbage collection in tree patching procedure */
68
61
  refCount: number = 0;
69
62
 
@@ -72,7 +65,7 @@ export class PlTreeResource implements PlTreeResourceI {
72
65
  /** Set to resource version when resource state, or it's fields have changed */
73
66
  dataVersion: number = InitialResourceVersion;
74
67
 
75
- readonly fields: Map<string, PlTreeField> = new Map();
68
+ readonly fieldsMap: Map<string, PlTreeField> = new Map();
76
69
 
77
70
  readonly kv = new Map<string, Uint8Array>();
78
71
 
@@ -108,8 +101,8 @@ export class PlTreeResource implements PlTreeResourceI {
108
101
  resourceReady: boolean;
109
102
  finalFlag: boolean;
110
103
 
111
- /** Set externally by the tree, using {@link FinalPredicate} */
112
- _final: boolean = false;
104
+ /** Set externally by the tree, using {@link FinalResourceDataPredicate} */
105
+ _finalState: boolean = false;
113
106
 
114
107
  private readonly logger?: MiLogger;
115
108
 
@@ -138,7 +131,15 @@ export class PlTreeResource implements PlTreeResourceI {
138
131
  }
139
132
 
140
133
  get final(): boolean {
141
- return this._final;
134
+ return this.finalFlag;
135
+ }
136
+
137
+ get finalState(): boolean {
138
+ return this._finalState;
139
+ }
140
+
141
+ get fields(): FieldData[] {
142
+ return [...this.fieldsMap.values()];
142
143
  }
143
144
 
144
145
  public getField(
@@ -160,7 +161,7 @@ export class PlTreeResource implements PlTreeResourceI {
160
161
  ): ValueAndError<ResourceId> | undefined {
161
162
  const step: FieldTraversalStep = typeof _step === 'string' ? { field: _step } : _step;
162
163
 
163
- const field = this.fields.get(step.field);
164
+ const field = this.fieldsMap.get(step.field);
164
165
  if (field === undefined) {
165
166
  if (step.errorIfFieldNotFound || step.errorIfFieldNotSet)
166
167
  throw new Error(
@@ -184,7 +185,7 @@ export class PlTreeResource implements PlTreeResourceI {
184
185
  }
185
186
 
186
187
  this.dynamicFieldListChanged?.attachWatcher(watcher);
187
- if (!this._final && !step.stableIfNotFound) onUnstable('field_not_found:' + step.field);
188
+ if (!this._finalState && !step.stableIfNotFound) onUnstable('field_not_found:' + step.field);
188
189
 
189
190
  return undefined;
190
191
  } else {
@@ -198,7 +199,7 @@ export class PlTreeResource implements PlTreeResourceI {
198
199
  if (isNotNullResourceId(field.error)) ret.error = field.error;
199
200
  if (ret.value === undefined && ret.error === undefined)
200
201
  // this method returns value and error of the field, thus those values are considered to be accessed;
201
- // any existing but not resolved field here is considered to be unstable, in the sence it is
202
+ // any existing but not resolved field here is considered to be unstable, in the sense it is
202
203
  // considered to acquire some resolved value eventually
203
204
  onUnstable('field_not_resolved:' + step.field);
204
205
  field.change.attachWatcher(watcher);
@@ -230,7 +231,7 @@ export class PlTreeResource implements PlTreeResourceI {
230
231
 
231
232
  public getIsFinal(watcher: Watcher): boolean {
232
233
  this.finalChanged?.attachWatcher(watcher);
233
- return this._final;
234
+ return this._finalState;
234
235
  }
235
236
 
236
237
  public getIsReadyOrError(watcher: Watcher): boolean {
@@ -252,7 +253,7 @@ export class PlTreeResource implements PlTreeResourceI {
252
253
 
253
254
  public listInputFields(watcher: Watcher): string[] {
254
255
  const ret: string[] = [];
255
- this.fields.forEach((field, name) => {
256
+ this.fieldsMap.forEach((field, name) => {
256
257
  if (field.type === 'Input' || field.type === 'Service') ret.push(name);
257
258
  });
258
259
  if (!this.inputsLocked) this.inputAndServiceFieldListChanged?.attachWatcher(watcher);
@@ -262,7 +263,7 @@ export class PlTreeResource implements PlTreeResourceI {
262
263
 
263
264
  public listOutputFields(watcher: Watcher): string[] {
264
265
  const ret: string[] = [];
265
- this.fields.forEach((field, name) => {
266
+ this.fieldsMap.forEach((field, name) => {
266
267
  if (field.type === 'Output') ret.push(name);
267
268
  });
268
269
  if (!this.outputsLocked) this.outputFieldListChanged?.attachWatcher(watcher);
@@ -272,7 +273,7 @@ export class PlTreeResource implements PlTreeResourceI {
272
273
 
273
274
  public listDynamicFields(watcher: Watcher): string[] {
274
275
  const ret: string[] = [];
275
- this.fields.forEach((field, name) => {
276
+ this.fieldsMap.forEach((field, name) => {
276
277
  if (field.type !== 'Input' && field.type !== 'Output') ret.push(name);
277
278
  });
278
279
  this.dynamicFieldListChanged?.attachWatcher(watcher);
@@ -323,10 +324,11 @@ export class PlTreeResource implements PlTreeResourceI {
323
324
  };
324
325
  }
325
326
 
327
+ /** Called when {@link FinalResourceDataPredicate} returns true for the state. */
326
328
  markFinal() {
327
- if (this._final) return;
329
+ if (this._finalState) return;
328
330
 
329
- this._final = true;
331
+ this._finalState = true;
330
332
  notEmpty(this.finalChanged).markChanged();
331
333
  this.finalChanged = undefined;
332
334
  this.resourceStateChange = undefined;
@@ -338,7 +340,7 @@ export class PlTreeResource implements PlTreeResourceI {
338
340
 
339
341
  /** Used for invalidation */
340
342
  markAllChanged() {
341
- this.fields.forEach((field) => field.change.markChanged());
343
+ this.fieldsMap.forEach((field) => field.change.markChanged());
342
344
  this.finalChanged?.markChanged();
343
345
  this.resourceStateChange?.markChanged();
344
346
  this.lockedChange?.markChanged();
@@ -350,9 +352,6 @@ export class PlTreeResource implements PlTreeResourceI {
350
352
  }
351
353
  }
352
354
 
353
- // TODO implement invalidate tree
354
- // TODO make invalid state permanent
355
- // TODO invalidate on update errors
356
355
  export class PlTreeState {
357
356
  /** resource heap */
358
357
  private resources: Map<ResourceId, PlTreeResource> = new Map();
@@ -365,10 +364,10 @@ export class PlTreeState {
365
364
  constructor(
366
365
  /** This will be the only resource not deleted during GC round */
367
366
  public readonly root: ResourceId,
368
- public readonly isFinalPredicate: FinalPredicate = (r) => false
367
+ public readonly isFinalPredicate: FinalResourceDataPredicate
369
368
  ) {}
370
369
 
371
- public forEachResource(cb: (res: PlTreeResourceI) => void): void {
370
+ public forEachResource(cb: (res: ResourceDataWithFinalState) => void): void {
372
371
  this.resources.forEach((v) => cb(v));
373
372
  }
374
373
 
@@ -414,7 +413,7 @@ export class PlTreeState {
414
413
  if (resource !== undefined) {
415
414
  // updating existing resource
416
415
 
417
- if (resource.final)
416
+ if (resource.finalState)
418
417
  unexpectedTransitionError('resource state can\t be updated after it is marked as final');
419
418
 
420
419
  let changed = false;
@@ -443,12 +442,20 @@ export class PlTreeState {
443
442
 
444
443
  // updating fields
445
444
  for (const fd of rd.fields) {
446
- let field = resource.fields.get(fd.name);
445
+ let field = resource.fieldsMap.get(fd.name);
447
446
 
448
447
  if (!field) {
449
448
  // new field
450
449
 
451
- field = new PlTreeField(fd.type, fd.value, fd.error, resource.version);
450
+ field = new PlTreeField(
451
+ fd.name,
452
+ fd.type,
453
+ fd.value,
454
+ fd.error,
455
+ fd.status,
456
+ fd.valueIsFinal,
457
+ resource.version
458
+ );
452
459
  if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value);
453
460
  if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error);
454
461
 
@@ -468,7 +475,7 @@ export class PlTreeState {
468
475
  notEmpty(resource.dynamicFieldListChanged).markChanged();
469
476
  }
470
477
 
471
- resource.fields.set(fd.name, field);
478
+ resource.fieldsMap.set(fd.name, field);
472
479
 
473
480
  changed = true;
474
481
  } else {
@@ -516,12 +523,26 @@ export class PlTreeState {
516
523
  changed = true;
517
524
  }
518
525
 
526
+ // field status
527
+ if (field.status !== fd.status) {
528
+ field.status = fd.status;
529
+ field.change.markChanged();
530
+ changed = true;
531
+ }
532
+
533
+ // field valueIsFinal flag
534
+ if (field.valueIsFinal !== fd.valueIsFinal) {
535
+ field.valueIsFinal = fd.valueIsFinal;
536
+ field.change.markChanged();
537
+ changed = true;
538
+ }
539
+
519
540
  field.resourceVersion = resource.version;
520
541
  }
521
542
  }
522
543
 
523
544
  // detecting removed fields
524
- resource.fields.forEach((field, fieldName, fields) => {
545
+ resource.fieldsMap.forEach((field, fieldName, fields) => {
525
546
  if (field.resourceVersion !== resource!.version) {
526
547
  if (field.type === 'Input' || field.type === 'Service' || field.type === 'Output')
527
548
  unexpectedTransitionError(`removal of ${field.type} field ${fieldName}`);
@@ -604,10 +625,18 @@ export class PlTreeState {
604
625
  resource.verifyReadyState();
605
626
  if (isNotNullResourceId(resource.error)) incrementRefs.push(resource.error);
606
627
  for (const fd of rd.fields) {
607
- const field = new PlTreeField(fd.type, fd.value, fd.error, InitialResourceVersion);
628
+ const field = new PlTreeField(
629
+ fd.name,
630
+ fd.type,
631
+ fd.value,
632
+ fd.error,
633
+ fd.status,
634
+ fd.valueIsFinal,
635
+ InitialResourceVersion
636
+ );
608
637
  if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value);
609
638
  if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error);
610
- resource.fields.set(fd.name, field);
639
+ resource.fieldsMap.set(fd.name, field);
611
640
  }
612
641
 
613
642
  // adding kv
@@ -647,7 +676,7 @@ export class PlTreeState {
647
676
  // garbage collection
648
677
  if (res.refCount === 0 && res.id !== this.root) {
649
678
  // removing fields
650
- res.fields.forEach((field) => {
679
+ res.fieldsMap.forEach((field) => {
651
680
  if (isNotNullResourceId(field.value)) nextRefs.push(field.value);
652
681
  if (isNotNullResourceId(field.error)) nextRefs.push(field.error);
653
682
  field.change.markChanged();
package/src/sync.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { test, expect } from '@jest/globals';
2
- import { field, TestHelpers } from '@milaboratories/pl-client';
2
+ import { DefaultFinalResourceDataPredicate, field, TestHelpers } from '@milaboratories/pl-client';
3
3
  import { PlTreeState } from './state';
4
4
  import { constructTreeLoadingRequest, loadTreeState } from './sync';
5
5
  import { Computable } from '@milaboratories/computable';
@@ -21,7 +21,7 @@ test('load resources', async () => {
21
21
  { sync: true }
22
22
  );
23
23
 
24
- const treeState = new PlTreeState(r1);
24
+ const treeState = new PlTreeState(r1, DefaultFinalResourceDataPredicate);
25
25
 
26
26
  const theComputable = Computable.make((c) =>
27
27
  c.accessor(treeState.entry()).node().traverse('a', 'b')?.getDataAsString()
package/src/sync.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  FieldData,
3
3
  isNullResourceId,
4
- NullResourceId,
5
4
  OptionalResourceId,
6
5
  PlTransaction,
7
6
  ResourceId
@@ -41,7 +40,7 @@ export function constructTreeLoadingRequest(
41
40
  const seedResources: ResourceId[] = [];
42
41
  const finalResources = new Set<ResourceId>();
43
42
  tree.forEachResource((res) => {
44
- if (res.final) finalResources.add(res.id);
43
+ if (res.finalState) finalResources.add(res.id);
45
44
  else seedResources.push(res.id);
46
45
  });
47
46
 
@@ -53,13 +52,13 @@ export function constructTreeLoadingRequest(
53
52
 
54
53
  export type TreeLoadingStat = {
55
54
  requests: number;
56
- roundtrips: number;
55
+ roundTrips: number;
57
56
  retrievedResources: number;
58
57
  retrievedFields: number;
59
58
  retrievedKeyValues: number;
60
59
  retrievedResourceDataBytes: number;
61
60
  retrievedKeyValueBytes: number;
62
- prunnedFields: number;
61
+ prunedFields: number;
63
62
  finalResourcesSkipped: number;
64
63
  millisSpent: number;
65
64
  };
@@ -67,13 +66,13 @@ export type TreeLoadingStat = {
67
66
  export function initialTreeLoadingStat(): TreeLoadingStat {
68
67
  return {
69
68
  requests: 0,
70
- roundtrips: 0,
69
+ roundTrips: 0,
71
70
  retrievedResources: 0,
72
71
  retrievedFields: 0,
73
72
  retrievedKeyValues: 0,
74
73
  retrievedResourceDataBytes: 0,
75
74
  retrievedKeyValueBytes: 0,
76
- prunnedFields: 0,
75
+ prunedFields: 0,
77
76
  finalResourcesSkipped: 0,
78
77
  millisSpent: 0
79
78
  };
@@ -82,13 +81,13 @@ export function initialTreeLoadingStat(): TreeLoadingStat {
82
81
  export function formatTreeLoadingStat(stat: TreeLoadingStat): string {
83
82
  let result = `Requests: ${stat.requests}\n`;
84
83
  result += `Total time: ${msToHumanReadable(stat.millisSpent)}\n`;
85
- result += `Roundtrips: ${stat.roundtrips}\n`;
84
+ result += `Round-trips: ${stat.roundTrips}\n`;
86
85
  result += `Resources: ${stat.retrievedResources}\n`;
87
86
  result += `Fields: ${stat.retrievedFields}\n`;
88
87
  result += `KV: ${stat.retrievedKeyValues}\n`;
89
88
  result += `Data Bytes: ${stat.retrievedResourceDataBytes}\n`;
90
89
  result += `KV Bytes: ${stat.retrievedKeyValueBytes}\n`;
91
- result += `Pruned fields: ${stat.prunnedFields}\n`;
90
+ result += `Pruned fields: ${stat.prunedFields}\n`;
92
91
  result += `Final resources skipped: ${stat.finalResourcesSkipped}`;
93
92
  return result;
94
93
  }
@@ -104,7 +103,7 @@ export async function loadTreeState(
104
103
  // saving start timestamp to add time spent in this function to the stats at the end of the method
105
104
  const startTimestamp = Date.now();
106
105
 
107
- // countind the request
106
+ // counting the request
108
107
  if (stats) stats.requests++;
109
108
 
110
109
  const { seedResources, finalResources, pruningFunction } = loadingRequest;
@@ -117,8 +116,8 @@ export async function loadTreeState(
117
116
  const pending = new Denque<Promise<ExtendedResourceData | undefined>>();
118
117
 
119
118
  // vars to calculate number of roundtrips for stats
120
- let roundtripToggle: boolean = true;
121
- let numberOfRoundtrips = 0;
119
+ let roundTripToggle: boolean = true;
120
+ let numberOfRoundTrips = 0;
122
121
 
123
122
  // tracking resources we already requested
124
123
  const requested = new Set<ResourceId>();
@@ -139,19 +138,19 @@ export async function loadTreeState(
139
138
  const resourceData = tx.getResourceDataIfExists(rid, true);
140
139
  const kvData = tx.listKeyValuesIfResourceExists(rid);
141
140
 
142
- // counting roundrip (begin)
143
- const addRT = roundtripToggle;
144
- if (roundtripToggle) roundtripToggle = false;
141
+ // counting round-trip (begin)
142
+ const addRT = roundTripToggle;
143
+ if (roundTripToggle) roundTripToggle = false;
145
144
 
146
145
  // pushing combined promise
147
146
  pending.push(
148
147
  (async () => {
149
148
  const [resource, kv] = await Promise.all([resourceData, kvData]);
150
149
 
151
- // counting roundrip, actually incrementing counter and returning toggle back, so the next request can acquire it
150
+ // counting round-trip, actually incrementing counter and returning toggle back, so the next request can acquire it
152
151
  if (addRT) {
153
- numberOfRoundtrips++;
154
- roundtripToggle = true;
152
+ numberOfRoundTrips++;
153
+ roundTripToggle = true;
155
154
  }
156
155
 
157
156
  if (resource === undefined) return undefined;
@@ -184,7 +183,7 @@ export async function loadTreeState(
184
183
  // apply field pruning, if requested
185
184
  const fieldsAfterPruning = pruningFunction(nextResource);
186
185
  // collecting stats
187
- if (stats) stats.prunnedFields += nextResource.fields.length - fieldsAfterPruning.length;
186
+ if (stats) stats.prunedFields += nextResource.fields.length - fieldsAfterPruning.length;
188
187
  nextResource = { ...nextResource, fields: fieldsAfterPruning };
189
188
  }
190
189
 
@@ -211,7 +210,7 @@ export async function loadTreeState(
211
210
  // adding the time we spent in this method to stats
212
211
  if (stats) {
213
212
  stats.millisSpent += Date.now() - startTimestamp;
214
- stats.roundtrips += numberOfRoundtrips;
213
+ stats.roundTrips += numberOfRoundTrips;
215
214
  }
216
215
 
217
216
  return result;