@milaboratories/pl-tree 1.8.47 → 1.8.49

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.
Files changed (57) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  2. package/dist/accessors.cjs +236 -273
  3. package/dist/accessors.cjs.map +1 -1
  4. package/dist/accessors.d.ts +93 -93
  5. package/dist/accessors.js +235 -271
  6. package/dist/accessors.js.map +1 -1
  7. package/dist/dump.cjs +79 -80
  8. package/dist/dump.cjs.map +1 -1
  9. package/dist/dump.d.ts +16 -18
  10. package/dist/dump.js +79 -79
  11. package/dist/dump.js.map +1 -1
  12. package/dist/index.cjs +31 -35
  13. package/dist/index.d.ts +10 -10
  14. package/dist/index.js +9 -8
  15. package/dist/snapshot.cjs +50 -71
  16. package/dist/snapshot.cjs.map +1 -1
  17. package/dist/snapshot.d.ts +36 -38
  18. package/dist/snapshot.js +49 -69
  19. package/dist/snapshot.js.map +1 -1
  20. package/dist/state.cjs +487 -624
  21. package/dist/state.cjs.map +1 -1
  22. package/dist/state.d.ts +117 -116
  23. package/dist/state.js +486 -622
  24. package/dist/state.js.map +1 -1
  25. package/dist/sync.cjs +113 -133
  26. package/dist/sync.cjs.map +1 -1
  27. package/dist/sync.d.ts +37 -33
  28. package/dist/sync.js +111 -131
  29. package/dist/sync.js.map +1 -1
  30. package/dist/synchronized_tree.cjs +167 -232
  31. package/dist/synchronized_tree.cjs.map +1 -1
  32. package/dist/synchronized_tree.d.ts +66 -68
  33. package/dist/synchronized_tree.js +165 -211
  34. package/dist/synchronized_tree.js.map +1 -1
  35. package/dist/traversal_ops.d.ts +42 -42
  36. package/dist/value_and_error.cjs +9 -12
  37. package/dist/value_and_error.cjs.map +1 -1
  38. package/dist/value_and_error.d.ts +8 -5
  39. package/dist/value_and_error.js +9 -11
  40. package/dist/value_and_error.js.map +1 -1
  41. package/dist/value_or_error.d.ts +8 -5
  42. package/package.json +9 -9
  43. package/src/synchronized_tree.ts +10 -5
  44. package/dist/accessors.d.ts.map +0 -1
  45. package/dist/dump.d.ts.map +0 -1
  46. package/dist/index.cjs.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
  49. package/dist/snapshot.d.ts.map +0 -1
  50. package/dist/state.d.ts.map +0 -1
  51. package/dist/sync.d.ts.map +0 -1
  52. package/dist/synchronized_tree.d.ts.map +0 -1
  53. package/dist/test_utils.d.ts +0 -25
  54. package/dist/test_utils.d.ts.map +0 -1
  55. package/dist/traversal_ops.d.ts.map +0 -1
  56. package/dist/value_and_error.d.ts.map +0 -1
  57. package/dist/value_or_error.d.ts.map +0 -1
package/dist/sync.d.ts CHANGED
@@ -1,42 +1,46 @@
1
- import type { FieldData, PlTransaction, ResourceId } from "@milaboratories/pl-client";
2
- import type { ExtendedResourceData, PlTreeState } from "./state";
1
+ import { ExtendedResourceData, PlTreeState } from "./state.js";
2
+ import { FieldData, PlTransaction, ResourceId } from "@milaboratories/pl-client";
3
+
4
+ //#region src/sync.d.ts
3
5
  /** Applied to list of fields in resource data. */
4
- export type PruningFunction = (resource: ExtendedResourceData) => FieldData[];
5
- export interface TreeLoadingRequest {
6
- /** Resource to prime the traversal algorithm. It is ok, if some of them
7
- * doesn't exist anymore. Should not contain elements from final resource
8
- * set. */
9
- readonly seedResources: ResourceId[];
10
- /** Resource ids for which state is already known and not expected to change.
11
- * Algorithm will not continue traversal over those ids, and states will not
12
- * be retrieved for them. */
13
- readonly finalResources: Set<ResourceId>;
14
- /** This function is applied to each resource data field list, before
15
- * using it continue traversal. This modification also is applied to
16
- * output data to make result self-consistent in terms that it will contain
17
- * all referenced resources, this is required to be able to pass it to tree
18
- * to update the state. */
19
- readonly pruningFunction?: PruningFunction;
6
+ type PruningFunction = (resource: ExtendedResourceData) => FieldData[];
7
+ interface TreeLoadingRequest {
8
+ /** Resource to prime the traversal algorithm. It is ok, if some of them
9
+ * doesn't exist anymore. Should not contain elements from final resource
10
+ * set. */
11
+ readonly seedResources: ResourceId[];
12
+ /** Resource ids for which state is already known and not expected to change.
13
+ * Algorithm will not continue traversal over those ids, and states will not
14
+ * be retrieved for them. */
15
+ readonly finalResources: Set<ResourceId>;
16
+ /** This function is applied to each resource data field list, before
17
+ * using it continue traversal. This modification also is applied to
18
+ * output data to make result self-consistent in terms that it will contain
19
+ * all referenced resources, this is required to be able to pass it to tree
20
+ * to update the state. */
21
+ readonly pruningFunction?: PruningFunction;
20
22
  }
21
23
  /** Given the current tree state, build the request object to pass to
22
24
  * {@link loadTreeState} to load updated state. */
23
- export declare function constructTreeLoadingRequest(tree: PlTreeState, pruningFunction?: PruningFunction): TreeLoadingRequest;
24
- export type TreeLoadingStat = {
25
- requests: number;
26
- roundTrips: number;
27
- retrievedResources: number;
28
- retrievedFields: number;
29
- retrievedKeyValues: number;
30
- retrievedResourceDataBytes: number;
31
- retrievedKeyValueBytes: number;
32
- prunedFields: number;
33
- finalResourcesSkipped: number;
34
- millisSpent: number;
25
+ declare function constructTreeLoadingRequest(tree: PlTreeState, pruningFunction?: PruningFunction): TreeLoadingRequest;
26
+ type TreeLoadingStat = {
27
+ requests: number;
28
+ roundTrips: number;
29
+ retrievedResources: number;
30
+ retrievedFields: number;
31
+ retrievedKeyValues: number;
32
+ retrievedResourceDataBytes: number;
33
+ retrievedKeyValueBytes: number;
34
+ prunedFields: number;
35
+ finalResourcesSkipped: number;
36
+ millisSpent: number;
35
37
  };
36
- export declare function initialTreeLoadingStat(): TreeLoadingStat;
37
- export declare function formatTreeLoadingStat(stat: TreeLoadingStat): string;
38
+ declare function initialTreeLoadingStat(): TreeLoadingStat;
39
+ declare function formatTreeLoadingStat(stat: TreeLoadingStat): string;
38
40
  /** Given the transaction (preferably read-only) and loading request, executes
39
41
  * the tree traversal algorithm, and collects fresh states of resources
40
42
  * to update the tree state. */
41
- export declare function loadTreeState(tx: PlTransaction, loadingRequest: TreeLoadingRequest, stats?: TreeLoadingStat): Promise<ExtendedResourceData[]>;
43
+ declare function loadTreeState(tx: PlTransaction, loadingRequest: TreeLoadingRequest, stats?: TreeLoadingStat): Promise<ExtendedResourceData[]>;
44
+ //#endregion
45
+ export { PruningFunction, TreeLoadingRequest, TreeLoadingStat, constructTreeLoadingRequest, formatTreeLoadingStat, initialTreeLoadingStat, loadTreeState };
42
46
  //# sourceMappingURL=sync.d.ts.map
package/dist/sync.js CHANGED
@@ -1,144 +1,124 @@
1
- import Denque from 'denque';
2
- import { isNullResourceId } from '@milaboratories/pl-client';
3
- import { msToHumanReadable, ConcurrencyLimitingExecutor } from '@milaboratories/ts-helpers';
1
+ import { isNullResourceId } from "@milaboratories/pl-client";
2
+ import { ConcurrencyLimitingExecutor, msToHumanReadable } from "@milaboratories/ts-helpers";
3
+ import Denque from "denque";
4
4
 
5
+ //#region src/sync.ts
5
6
  /** Given the current tree state, build the request object to pass to
6
- * {@link loadTreeState} to load updated state. */
7
+ * {@link loadTreeState} to load updated state. */
7
8
  function constructTreeLoadingRequest(tree, pruningFunction) {
8
- const seedResources = [];
9
- const finalResources = new Set();
10
- tree.forEachResource((res) => {
11
- if (res.finalState)
12
- finalResources.add(res.id);
13
- else
14
- seedResources.push(res.id);
15
- });
16
- // if tree is empty, seeding tree reconstruction from the specified root
17
- if (seedResources.length === 0 && finalResources.size === 0)
18
- seedResources.push(tree.root);
19
- return { seedResources, finalResources, pruningFunction };
9
+ const seedResources = [];
10
+ const finalResources = /* @__PURE__ */ new Set();
11
+ tree.forEachResource((res) => {
12
+ if (res.finalState) finalResources.add(res.id);
13
+ else seedResources.push(res.id);
14
+ });
15
+ if (seedResources.length === 0 && finalResources.size === 0) seedResources.push(tree.root);
16
+ return {
17
+ seedResources,
18
+ finalResources,
19
+ pruningFunction
20
+ };
20
21
  }
21
22
  function initialTreeLoadingStat() {
22
- return {
23
- requests: 0,
24
- roundTrips: 0,
25
- retrievedResources: 0,
26
- retrievedFields: 0,
27
- retrievedKeyValues: 0,
28
- retrievedResourceDataBytes: 0,
29
- retrievedKeyValueBytes: 0,
30
- prunedFields: 0,
31
- finalResourcesSkipped: 0,
32
- millisSpent: 0,
33
- };
23
+ return {
24
+ requests: 0,
25
+ roundTrips: 0,
26
+ retrievedResources: 0,
27
+ retrievedFields: 0,
28
+ retrievedKeyValues: 0,
29
+ retrievedResourceDataBytes: 0,
30
+ retrievedKeyValueBytes: 0,
31
+ prunedFields: 0,
32
+ finalResourcesSkipped: 0,
33
+ millisSpent: 0
34
+ };
34
35
  }
35
36
  function formatTreeLoadingStat(stat) {
36
- let result = `Requests: ${stat.requests}\n`;
37
- result += `Total time: ${msToHumanReadable(stat.millisSpent)}\n`;
38
- result += `Round-trips: ${stat.roundTrips}\n`;
39
- result += `Resources: ${stat.retrievedResources}\n`;
40
- result += `Fields: ${stat.retrievedFields}\n`;
41
- result += `KV: ${stat.retrievedKeyValues}\n`;
42
- result += `Data Bytes: ${stat.retrievedResourceDataBytes}\n`;
43
- result += `KV Bytes: ${stat.retrievedKeyValueBytes}\n`;
44
- result += `Pruned fields: ${stat.prunedFields}\n`;
45
- result += `Final resources skipped: ${stat.finalResourcesSkipped}`;
46
- return result;
37
+ let result = `Requests: ${stat.requests}\n`;
38
+ result += `Total time: ${msToHumanReadable(stat.millisSpent)}\n`;
39
+ result += `Round-trips: ${stat.roundTrips}\n`;
40
+ result += `Resources: ${stat.retrievedResources}\n`;
41
+ result += `Fields: ${stat.retrievedFields}\n`;
42
+ result += `KV: ${stat.retrievedKeyValues}\n`;
43
+ result += `Data Bytes: ${stat.retrievedResourceDataBytes}\n`;
44
+ result += `KV Bytes: ${stat.retrievedKeyValueBytes}\n`;
45
+ result += `Pruned fields: ${stat.prunedFields}\n`;
46
+ result += `Final resources skipped: ${stat.finalResourcesSkipped}`;
47
+ return result;
47
48
  }
48
49
  /** Given the transaction (preferably read-only) and loading request, executes
49
- * the tree traversal algorithm, and collects fresh states of resources
50
- * to update the tree state. */
50
+ * the tree traversal algorithm, and collects fresh states of resources
51
+ * to update the tree state. */
51
52
  async function loadTreeState(tx, loadingRequest, stats) {
52
- // saving start timestamp to add time spent in this function to the stats at the end of the method
53
- const startTimestamp = Date.now();
54
- // counting the request
55
- if (stats)
56
- stats.requests++;
57
- const { seedResources, finalResources, pruningFunction } = loadingRequest;
58
- // Limits the number of concurrent gRPC fetches to bound peak memory
59
- // from in-flight request/response buffers.
60
- const limiter = new ConcurrencyLimitingExecutor(100);
61
- // Promises of resource states, in the order they were requested.
62
- const pending = new Denque();
63
- // vars to calculate number of roundtrips for stats
64
- let roundTripToggle = true;
65
- let numberOfRoundTrips = 0;
66
- // tracking resources we already requested or queued
67
- const requested = new Set();
68
- /** Mark a resource for fetching. Deduplicates and respects final-resource set. */
69
- const requestState = (rid) => {
70
- if (isNullResourceId(rid) || requested.has(rid))
71
- return;
72
- if (finalResources.has(rid)) {
73
- if (stats)
74
- stats.finalResourcesSkipped++;
75
- return;
76
- }
77
- requested.add(rid);
78
- pending.push(limiter.run(async () => {
79
- const resourceData = tx.getResourceDataIfExists(rid, true);
80
- const kvData = tx.listKeyValuesIfResourceExists(rid);
81
- // counting round-trip (begin)
82
- const addRT = roundTripToggle;
83
- if (roundTripToggle)
84
- roundTripToggle = false;
85
- const [resource, kv] = await Promise.all([resourceData, kvData]);
86
- // counting round-trip, actually incrementing counter and returning toggle back,
87
- // so the next request can acquire it
88
- if (addRT) {
89
- numberOfRoundTrips++;
90
- roundTripToggle = true;
91
- }
92
- if (resource === undefined)
93
- return undefined;
94
- if (kv === undefined)
95
- throw new Error("Inconsistent replies");
96
- return { ...resource, kv };
97
- }));
98
- };
99
- // sending seed requests
100
- seedResources.forEach((rid) => requestState(rid));
101
- const result = [];
102
- let nextPromise;
103
- while ((nextPromise = pending.shift()) !== undefined) {
104
- // at this point we pause and wait for the next requested resource state to arrive
105
- let nextResource = await nextPromise;
106
- if (nextResource === undefined)
107
- // ignoring resources that were not found (this may happen for seed resource ids)
108
- continue;
109
- if (pruningFunction !== undefined) {
110
- // apply field pruning, if requested
111
- const fieldsAfterPruning = pruningFunction(nextResource);
112
- // collecting stats
113
- if (stats)
114
- stats.prunedFields += nextResource.fields.length - fieldsAfterPruning.length;
115
- nextResource = { ...nextResource, fields: fieldsAfterPruning };
116
- }
117
- // continue traversal over the referenced resources
118
- requestState(nextResource.error);
119
- for (const field of nextResource.fields) {
120
- requestState(field.value);
121
- requestState(field.error);
122
- }
123
- // collecting stats
124
- if (stats) {
125
- stats.retrievedResources++;
126
- stats.retrievedFields += nextResource.fields.length;
127
- stats.retrievedKeyValues += nextResource.kv.length;
128
- stats.retrievedResourceDataBytes += nextResource.data?.length ?? 0;
129
- for (const kv of nextResource.kv)
130
- stats.retrievedKeyValueBytes += kv.value.length;
131
- }
132
- // aggregating the state
133
- result.push(nextResource);
134
- }
135
- // adding the time we spent in this method to stats
136
- if (stats) {
137
- stats.millisSpent += Date.now() - startTimestamp;
138
- stats.roundTrips += numberOfRoundTrips;
139
- }
140
- return result;
53
+ const startTimestamp = Date.now();
54
+ if (stats) stats.requests++;
55
+ const { seedResources, finalResources, pruningFunction } = loadingRequest;
56
+ const limiter = new ConcurrencyLimitingExecutor(100);
57
+ const pending = new Denque();
58
+ let roundTripToggle = true;
59
+ let numberOfRoundTrips = 0;
60
+ const requested = /* @__PURE__ */ new Set();
61
+ /** Mark a resource for fetching. Deduplicates and respects final-resource set. */
62
+ const requestState = (rid) => {
63
+ if (isNullResourceId(rid) || requested.has(rid)) return;
64
+ if (finalResources.has(rid)) {
65
+ if (stats) stats.finalResourcesSkipped++;
66
+ return;
67
+ }
68
+ requested.add(rid);
69
+ pending.push(limiter.run(async () => {
70
+ const resourceData = tx.getResourceDataIfExists(rid, true);
71
+ const kvData = tx.listKeyValuesIfResourceExists(rid);
72
+ const addRT = roundTripToggle;
73
+ if (roundTripToggle) roundTripToggle = false;
74
+ const [resource, kv] = await Promise.all([resourceData, kvData]);
75
+ if (addRT) {
76
+ numberOfRoundTrips++;
77
+ roundTripToggle = true;
78
+ }
79
+ if (resource === void 0) return void 0;
80
+ if (kv === void 0) throw new Error("Inconsistent replies");
81
+ return {
82
+ ...resource,
83
+ kv
84
+ };
85
+ }));
86
+ };
87
+ seedResources.forEach((rid) => requestState(rid));
88
+ const result = [];
89
+ let nextPromise;
90
+ while ((nextPromise = pending.shift()) !== void 0) {
91
+ let nextResource = await nextPromise;
92
+ if (nextResource === void 0) continue;
93
+ if (pruningFunction !== void 0) {
94
+ const fieldsAfterPruning = pruningFunction(nextResource);
95
+ if (stats) stats.prunedFields += nextResource.fields.length - fieldsAfterPruning.length;
96
+ nextResource = {
97
+ ...nextResource,
98
+ fields: fieldsAfterPruning
99
+ };
100
+ }
101
+ requestState(nextResource.error);
102
+ for (const field of nextResource.fields) {
103
+ requestState(field.value);
104
+ requestState(field.error);
105
+ }
106
+ if (stats) {
107
+ stats.retrievedResources++;
108
+ stats.retrievedFields += nextResource.fields.length;
109
+ stats.retrievedKeyValues += nextResource.kv.length;
110
+ stats.retrievedResourceDataBytes += nextResource.data?.length ?? 0;
111
+ for (const kv of nextResource.kv) stats.retrievedKeyValueBytes += kv.value.length;
112
+ }
113
+ result.push(nextResource);
114
+ }
115
+ if (stats) {
116
+ stats.millisSpent += Date.now() - startTimestamp;
117
+ stats.roundTrips += numberOfRoundTrips;
118
+ }
119
+ return result;
141
120
  }
142
121
 
122
+ //#endregion
143
123
  export { constructTreeLoadingRequest, formatTreeLoadingStat, initialTreeLoadingStat, loadTreeState };
144
- //# sourceMappingURL=sync.js.map
124
+ //# sourceMappingURL=sync.js.map
package/dist/sync.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sync.js","sources":["../src/sync.ts"],"sourcesContent":["import type {\n FieldData,\n OptionalResourceId,\n PlTransaction,\n ResourceId,\n} from \"@milaboratories/pl-client\";\nimport Denque from \"denque\";\nimport { isNullResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData, PlTreeState } from \"./state\";\nimport { ConcurrencyLimitingExecutor, msToHumanReadable } from \"@milaboratories/ts-helpers\";\n\n/** Applied to list of fields in resource data. */\nexport type PruningFunction = (resource: ExtendedResourceData) => FieldData[];\n\nexport interface TreeLoadingRequest {\n /** Resource to prime the traversal algorithm. It is ok, if some of them\n * doesn't exist anymore. Should not contain elements from final resource\n * set. */\n readonly seedResources: ResourceId[];\n\n /** Resource ids for which state is already known and not expected to change.\n * Algorithm will not continue traversal over those ids, and states will not\n * be retrieved for them. */\n readonly finalResources: Set<ResourceId>;\n\n /** This function is applied to each resource data field list, before\n * using it continue traversal. This modification also is applied to\n * output data to make result self-consistent in terms that it will contain\n * all referenced resources, this is required to be able to pass it to tree\n * to update the state. */\n readonly pruningFunction?: PruningFunction;\n}\n\n/** Given the current tree state, build the request object to pass to\n * {@link loadTreeState} to load updated state. */\nexport function constructTreeLoadingRequest(\n tree: PlTreeState,\n pruningFunction?: PruningFunction,\n): TreeLoadingRequest {\n const seedResources: ResourceId[] = [];\n const finalResources = new Set<ResourceId>();\n tree.forEachResource((res) => {\n if (res.finalState) finalResources.add(res.id);\n else seedResources.push(res.id);\n });\n\n // if tree is empty, seeding tree reconstruction from the specified root\n if (seedResources.length === 0 && finalResources.size === 0) seedResources.push(tree.root);\n\n return { seedResources, finalResources, pruningFunction };\n}\n\nexport type TreeLoadingStat = {\n requests: number;\n roundTrips: number;\n retrievedResources: number;\n retrievedFields: number;\n retrievedKeyValues: number;\n retrievedResourceDataBytes: number;\n retrievedKeyValueBytes: number;\n prunedFields: number;\n finalResourcesSkipped: number;\n millisSpent: number;\n};\n\nexport function initialTreeLoadingStat(): TreeLoadingStat {\n return {\n requests: 0,\n roundTrips: 0,\n retrievedResources: 0,\n retrievedFields: 0,\n retrievedKeyValues: 0,\n retrievedResourceDataBytes: 0,\n retrievedKeyValueBytes: 0,\n prunedFields: 0,\n finalResourcesSkipped: 0,\n millisSpent: 0,\n };\n}\n\nexport function formatTreeLoadingStat(stat: TreeLoadingStat): string {\n let result = `Requests: ${stat.requests}\\n`;\n result += `Total time: ${msToHumanReadable(stat.millisSpent)}\\n`;\n result += `Round-trips: ${stat.roundTrips}\\n`;\n result += `Resources: ${stat.retrievedResources}\\n`;\n result += `Fields: ${stat.retrievedFields}\\n`;\n result += `KV: ${stat.retrievedKeyValues}\\n`;\n result += `Data Bytes: ${stat.retrievedResourceDataBytes}\\n`;\n result += `KV Bytes: ${stat.retrievedKeyValueBytes}\\n`;\n result += `Pruned fields: ${stat.prunedFields}\\n`;\n result += `Final resources skipped: ${stat.finalResourcesSkipped}`;\n return result;\n}\n\n/** Given the transaction (preferably read-only) and loading request, executes\n * the tree traversal algorithm, and collects fresh states of resources\n * to update the tree state. */\nexport async function loadTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n): Promise<ExtendedResourceData[]> {\n // saving start timestamp to add time spent in this function to the stats at the end of the method\n const startTimestamp = Date.now();\n\n // counting the request\n if (stats) stats.requests++;\n\n const { seedResources, finalResources, pruningFunction } = loadingRequest;\n\n // Limits the number of concurrent gRPC fetches to bound peak memory\n // from in-flight request/response buffers.\n const limiter = new ConcurrencyLimitingExecutor(100);\n\n // Promises of resource states, in the order they were requested.\n const pending = new Denque<Promise<ExtendedResourceData | undefined>>();\n\n // vars to calculate number of roundtrips for stats\n let roundTripToggle: boolean = true;\n let numberOfRoundTrips = 0;\n\n // tracking resources we already requested or queued\n const requested = new Set<ResourceId>();\n\n /** Mark a resource for fetching. Deduplicates and respects final-resource set. */\n const requestState = (rid: OptionalResourceId) => {\n if (isNullResourceId(rid) || requested.has(rid)) return;\n\n if (finalResources.has(rid)) {\n if (stats) stats.finalResourcesSkipped++;\n return;\n }\n\n requested.add(rid);\n\n pending.push(\n limiter.run(async () => {\n const resourceData = tx.getResourceDataIfExists(rid, true);\n const kvData = tx.listKeyValuesIfResourceExists(rid);\n\n // counting round-trip (begin)\n const addRT = roundTripToggle;\n if (roundTripToggle) roundTripToggle = false;\n\n const [resource, kv] = await Promise.all([resourceData, kvData]);\n\n // counting round-trip, actually incrementing counter and returning toggle back,\n // so the next request can acquire it\n if (addRT) {\n numberOfRoundTrips++;\n roundTripToggle = true;\n }\n\n if (resource === undefined) return undefined;\n if (kv === undefined) throw new Error(\"Inconsistent replies\");\n\n return { ...resource, kv };\n }),\n );\n };\n\n // sending seed requests\n seedResources.forEach((rid) => requestState(rid));\n\n const result: ExtendedResourceData[] = [];\n let nextPromise: Promise<ExtendedResourceData | undefined> | undefined;\n while ((nextPromise = pending.shift()) !== undefined) {\n // at this point we pause and wait for the next requested resource state to arrive\n let nextResource = await nextPromise;\n if (nextResource === undefined)\n // ignoring resources that were not found (this may happen for seed resource ids)\n continue;\n\n if (pruningFunction !== undefined) {\n // apply field pruning, if requested\n const fieldsAfterPruning = pruningFunction(nextResource);\n // collecting stats\n if (stats) stats.prunedFields += nextResource.fields.length - fieldsAfterPruning.length;\n nextResource = { ...nextResource, fields: fieldsAfterPruning };\n }\n\n // continue traversal over the referenced resources\n requestState(nextResource.error);\n for (const field of nextResource.fields) {\n requestState(field.value);\n requestState(field.error);\n }\n\n // collecting stats\n if (stats) {\n stats.retrievedResources++;\n stats.retrievedFields += nextResource.fields.length;\n stats.retrievedKeyValues += nextResource.kv.length;\n stats.retrievedResourceDataBytes += nextResource.data?.length ?? 0;\n for (const kv of nextResource.kv) stats.retrievedKeyValueBytes += kv.value.length;\n }\n\n // aggregating the state\n result.push(nextResource);\n }\n\n // adding the time we spent in this method to stats\n if (stats) {\n stats.millisSpent += Date.now() - startTimestamp;\n stats.roundTrips += numberOfRoundTrips;\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.js","names":[],"sources":["../src/sync.ts"],"sourcesContent":["import type {\n FieldData,\n OptionalResourceId,\n PlTransaction,\n ResourceId,\n} from \"@milaboratories/pl-client\";\nimport Denque from \"denque\";\nimport { isNullResourceId } from \"@milaboratories/pl-client\";\nimport type { ExtendedResourceData, PlTreeState } from \"./state\";\nimport { ConcurrencyLimitingExecutor, msToHumanReadable } from \"@milaboratories/ts-helpers\";\n\n/** Applied to list of fields in resource data. */\nexport type PruningFunction = (resource: ExtendedResourceData) => FieldData[];\n\nexport interface TreeLoadingRequest {\n /** Resource to prime the traversal algorithm. It is ok, if some of them\n * doesn't exist anymore. Should not contain elements from final resource\n * set. */\n readonly seedResources: ResourceId[];\n\n /** Resource ids for which state is already known and not expected to change.\n * Algorithm will not continue traversal over those ids, and states will not\n * be retrieved for them. */\n readonly finalResources: Set<ResourceId>;\n\n /** This function is applied to each resource data field list, before\n * using it continue traversal. This modification also is applied to\n * output data to make result self-consistent in terms that it will contain\n * all referenced resources, this is required to be able to pass it to tree\n * to update the state. */\n readonly pruningFunction?: PruningFunction;\n}\n\n/** Given the current tree state, build the request object to pass to\n * {@link loadTreeState} to load updated state. */\nexport function constructTreeLoadingRequest(\n tree: PlTreeState,\n pruningFunction?: PruningFunction,\n): TreeLoadingRequest {\n const seedResources: ResourceId[] = [];\n const finalResources = new Set<ResourceId>();\n tree.forEachResource((res) => {\n if (res.finalState) finalResources.add(res.id);\n else seedResources.push(res.id);\n });\n\n // if tree is empty, seeding tree reconstruction from the specified root\n if (seedResources.length === 0 && finalResources.size === 0) seedResources.push(tree.root);\n\n return { seedResources, finalResources, pruningFunction };\n}\n\nexport type TreeLoadingStat = {\n requests: number;\n roundTrips: number;\n retrievedResources: number;\n retrievedFields: number;\n retrievedKeyValues: number;\n retrievedResourceDataBytes: number;\n retrievedKeyValueBytes: number;\n prunedFields: number;\n finalResourcesSkipped: number;\n millisSpent: number;\n};\n\nexport function initialTreeLoadingStat(): TreeLoadingStat {\n return {\n requests: 0,\n roundTrips: 0,\n retrievedResources: 0,\n retrievedFields: 0,\n retrievedKeyValues: 0,\n retrievedResourceDataBytes: 0,\n retrievedKeyValueBytes: 0,\n prunedFields: 0,\n finalResourcesSkipped: 0,\n millisSpent: 0,\n };\n}\n\nexport function formatTreeLoadingStat(stat: TreeLoadingStat): string {\n let result = `Requests: ${stat.requests}\\n`;\n result += `Total time: ${msToHumanReadable(stat.millisSpent)}\\n`;\n result += `Round-trips: ${stat.roundTrips}\\n`;\n result += `Resources: ${stat.retrievedResources}\\n`;\n result += `Fields: ${stat.retrievedFields}\\n`;\n result += `KV: ${stat.retrievedKeyValues}\\n`;\n result += `Data Bytes: ${stat.retrievedResourceDataBytes}\\n`;\n result += `KV Bytes: ${stat.retrievedKeyValueBytes}\\n`;\n result += `Pruned fields: ${stat.prunedFields}\\n`;\n result += `Final resources skipped: ${stat.finalResourcesSkipped}`;\n return result;\n}\n\n/** Given the transaction (preferably read-only) and loading request, executes\n * the tree traversal algorithm, and collects fresh states of resources\n * to update the tree state. */\nexport async function loadTreeState(\n tx: PlTransaction,\n loadingRequest: TreeLoadingRequest,\n stats?: TreeLoadingStat,\n): Promise<ExtendedResourceData[]> {\n // saving start timestamp to add time spent in this function to the stats at the end of the method\n const startTimestamp = Date.now();\n\n // counting the request\n if (stats) stats.requests++;\n\n const { seedResources, finalResources, pruningFunction } = loadingRequest;\n\n // Limits the number of concurrent gRPC fetches to bound peak memory\n // from in-flight request/response buffers.\n const limiter = new ConcurrencyLimitingExecutor(100);\n\n // Promises of resource states, in the order they were requested.\n const pending = new Denque<Promise<ExtendedResourceData | undefined>>();\n\n // vars to calculate number of roundtrips for stats\n let roundTripToggle: boolean = true;\n let numberOfRoundTrips = 0;\n\n // tracking resources we already requested or queued\n const requested = new Set<ResourceId>();\n\n /** Mark a resource for fetching. Deduplicates and respects final-resource set. */\n const requestState = (rid: OptionalResourceId) => {\n if (isNullResourceId(rid) || requested.has(rid)) return;\n\n if (finalResources.has(rid)) {\n if (stats) stats.finalResourcesSkipped++;\n return;\n }\n\n requested.add(rid);\n\n pending.push(\n limiter.run(async () => {\n const resourceData = tx.getResourceDataIfExists(rid, true);\n const kvData = tx.listKeyValuesIfResourceExists(rid);\n\n // counting round-trip (begin)\n const addRT = roundTripToggle;\n if (roundTripToggle) roundTripToggle = false;\n\n const [resource, kv] = await Promise.all([resourceData, kvData]);\n\n // counting round-trip, actually incrementing counter and returning toggle back,\n // so the next request can acquire it\n if (addRT) {\n numberOfRoundTrips++;\n roundTripToggle = true;\n }\n\n if (resource === undefined) return undefined;\n if (kv === undefined) throw new Error(\"Inconsistent replies\");\n\n return { ...resource, kv };\n }),\n );\n };\n\n // sending seed requests\n seedResources.forEach((rid) => requestState(rid));\n\n const result: ExtendedResourceData[] = [];\n let nextPromise: Promise<ExtendedResourceData | undefined> | undefined;\n while ((nextPromise = pending.shift()) !== undefined) {\n // at this point we pause and wait for the next requested resource state to arrive\n let nextResource = await nextPromise;\n if (nextResource === undefined)\n // ignoring resources that were not found (this may happen for seed resource ids)\n continue;\n\n if (pruningFunction !== undefined) {\n // apply field pruning, if requested\n const fieldsAfterPruning = pruningFunction(nextResource);\n // collecting stats\n if (stats) stats.prunedFields += nextResource.fields.length - fieldsAfterPruning.length;\n nextResource = { ...nextResource, fields: fieldsAfterPruning };\n }\n\n // continue traversal over the referenced resources\n requestState(nextResource.error);\n for (const field of nextResource.fields) {\n requestState(field.value);\n requestState(field.error);\n }\n\n // collecting stats\n if (stats) {\n stats.retrievedResources++;\n stats.retrievedFields += nextResource.fields.length;\n stats.retrievedKeyValues += nextResource.kv.length;\n stats.retrievedResourceDataBytes += nextResource.data?.length ?? 0;\n for (const kv of nextResource.kv) stats.retrievedKeyValueBytes += kv.value.length;\n }\n\n // aggregating the state\n result.push(nextResource);\n }\n\n // adding the time we spent in this method to stats\n if (stats) {\n stats.millisSpent += Date.now() - startTimestamp;\n stats.roundTrips += numberOfRoundTrips;\n }\n\n return result;\n}\n"],"mappings":"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"}