@distrohelena/canton-typescript-sdk 0.1.36 → 0.1.37
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/README.md +104 -34
- package/dist/cjs/core/types/requests/{submit-command-request.js → submit-commands-request.js} +10 -6
- package/dist/cjs/index.js +10 -8
- package/dist/cjs/query/canonical/in-memory-query-evaluator.js +291 -0
- package/dist/cjs/query/canonical/public-identity.js +41 -0
- package/dist/cjs/query/canonical/query-ast.js +2 -0
- package/dist/cjs/query/canonical/query-dataset.js +351 -0
- package/dist/cjs/query/canonical/query-normalizer.js +655 -0
- package/dist/cjs/query/canonical/query-schema.js +86 -0
- package/dist/cjs/query/canton-manager.js +14 -5
- package/dist/cjs/query/errors/query-snapshot-incomplete-error.js +19 -0
- package/dist/cjs/query/grpc/grpc-contract-cache.js +484 -0
- package/dist/cjs/query/grpc/grpc-package-relation-reader.js +218 -0
- package/dist/cjs/query/grpc/grpc-query-client.js +249 -0
- package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +238 -0
- package/dist/cjs/query/grpc/grpc-query-value-mapper.js +204 -0
- package/dist/cjs/query/grpc/grpc-relation-mapper.js +794 -0
- package/dist/cjs/query/pqs/pqs-query-client.js +151 -582
- package/dist/cjs/query/pqs/pqs-relational-sql-compiler.js +138 -95
- package/dist/cjs/query/pqs/pqs-result-shape.js +28 -0
- package/dist/cjs/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/cjs/query/pqs/pqs-sql-compiler.js +343 -289
- package/dist/cjs/query/pqs/pqs-sql-syntax.js +10 -0
- package/dist/cjs/services/command/command-service-client.js +16 -3
- package/dist/cjs/services/commands/command-payload-builder.js +1 -1
- package/dist/cjs/services/commands/command-submission-pipeline.js +21 -6
- package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
- package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
- package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/cjs/transports/json/json-transport.js +1 -1
- package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
- package/dist/core/transports/transport.interface.d.ts +4 -4
- package/dist/core/types/prepared-command-submission.d.ts +3 -3
- package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
- package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
- package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
- package/dist/query/canonical/public-identity.d.ts +14 -0
- package/dist/query/canonical/public-identity.js +35 -0
- package/dist/query/canonical/query-ast.d.ts +104 -0
- package/dist/query/canonical/query-ast.js +1 -0
- package/dist/query/canonical/query-dataset.d.ts +63 -0
- package/dist/query/canonical/query-dataset.js +343 -0
- package/dist/query/canonical/query-normalizer.d.ts +10 -0
- package/dist/query/canonical/query-normalizer.js +646 -0
- package/dist/query/canonical/query-schema.d.ts +25 -0
- package/dist/query/canonical/query-schema.js +83 -0
- package/dist/query/canton-manager.js +14 -5
- package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
- package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
- package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
- package/dist/query/grpc/grpc-contract-cache.js +479 -0
- package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
- package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
- package/dist/query/grpc/grpc-query-client.d.ts +42 -0
- package/dist/query/grpc/grpc-query-client.js +245 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
- package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
- package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
- package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
- package/dist/query/grpc/grpc-relation-mapper.js +789 -0
- package/dist/query/pqs/pqs-query-client.d.ts +10 -20
- package/dist/query/pqs/pqs-query-client.js +154 -585
- package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
- package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
- package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
- package/dist/query/pqs/pqs-result-shape.js +25 -0
- package/dist/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
- package/dist/query/pqs/pqs-sql-compiler.js +337 -288
- package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
- package/dist/query/pqs/pqs-sql-syntax.js +6 -0
- package/dist/query/query-client.d.ts +15 -0
- package/dist/services/command/command-service-client.d.ts +9 -4
- package/dist/services/command/command-service-client.js +16 -3
- package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.js +1 -1
- package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
- package/dist/services/commands/command-submission-pipeline.js +21 -6
- package/dist/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/transports/grpc/grpc-transport.d.ts +4 -4
- package/dist/transports/grpc/grpc-transport.js +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
- package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/transports/json/json-transport.d.ts +2 -2
- package/dist/transports/json/json-transport.js +2 -2
- package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
- package/dist/transports/json/mappers/commands-mapper.js +2 -2
- package/package.json +4 -3
- package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
- package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
- package/dist/query/grpc/grpc-contract-query-client.js +0 -174
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalPublicNumericIdentity = canonicalPublicNumericIdentity;
|
|
4
|
+
exports.canonicalPublicNumericIdentityParts = canonicalPublicNumericIdentityParts;
|
|
5
|
+
exports.compileCanonicalPublicNumericIdentitySql = compileCanonicalPublicNumericIdentitySql;
|
|
6
|
+
exports.compileCanonicalPublicNumericIdentityPartsSql = compileCanonicalPublicNumericIdentityPartsSql;
|
|
7
|
+
const node_buffer_1 = require("node:buffer");
|
|
8
|
+
const MAX_CANONICAL_PUBLIC_IDENTITY_BYTES = 43_690;
|
|
9
|
+
/**
|
|
10
|
+
* Encodes an arbitrary UTF-8 semantic identity as a positive decimal integer.
|
|
11
|
+
*
|
|
12
|
+
* The leading decimal digit is a sentinel and each byte occupies three decimal
|
|
13
|
+
* digits, so leading NUL bytes and the empty string remain distinct. This is an
|
|
14
|
+
* injective encoding, not a hash.
|
|
15
|
+
*/
|
|
16
|
+
function canonicalPublicNumericIdentity(identity) {
|
|
17
|
+
const bytes = node_buffer_1.Buffer.from(identity, "utf8");
|
|
18
|
+
if (bytes.length > MAX_CANONICAL_PUBLIC_IDENTITY_BYTES) {
|
|
19
|
+
throw new RangeError(`Canonical public identity exceeds ${MAX_CANONICAL_PUBLIC_IDENTITY_BYTES} UTF-8 bytes`);
|
|
20
|
+
}
|
|
21
|
+
let value = "1";
|
|
22
|
+
for (const byte of bytes) {
|
|
23
|
+
value += byte.toString().padStart(3, "0");
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/** Frames multiple semantic components without a delimiter ambiguity. */
|
|
28
|
+
function canonicalPublicNumericIdentityParts(parts) {
|
|
29
|
+
return canonicalPublicNumericIdentity(parts.map((part) => `${node_buffer_1.Buffer.byteLength(part, "utf8")}:${part}`).join(""));
|
|
30
|
+
}
|
|
31
|
+
/** PostgreSQL expression exactly equivalent to {@link canonicalPublicNumericIdentity}. */
|
|
32
|
+
function compileCanonicalPublicNumericIdentitySql(text) {
|
|
33
|
+
const bytes = `convert_to((${text})::text, 'UTF8')`;
|
|
34
|
+
const encoded = `('1' || coalesce((select string_agg(lpad(get_byte(canonical_identity_bytes, canonical_identity_byte)::text, 3, '0'), '' order by canonical_identity_byte) from generate_series(0, octet_length(canonical_identity_bytes) - 1) as canonical_identity_byte), ''))::numeric`;
|
|
35
|
+
const overflow = `('1' || repeat('0', octet_length(canonical_identity_bytes) * 3))::numeric`;
|
|
36
|
+
return `(select case when octet_length(canonical_identity_bytes) <= ${MAX_CANONICAL_PUBLIC_IDENTITY_BYTES} then ${encoded} else ${overflow} end from (select ${bytes} as canonical_identity_bytes) canonical_identity)`;
|
|
37
|
+
}
|
|
38
|
+
/** PostgreSQL framing equivalent to {@link canonicalPublicNumericIdentityParts}. */
|
|
39
|
+
function compileCanonicalPublicNumericIdentityPartsSql(parts) {
|
|
40
|
+
return `concat(${parts.flatMap((part) => [`octet_length(convert_to((${part})::text, 'UTF8'))`, "':'", `(${part})::text`]).join(", ")})`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImmutableQueryDate = exports.IncompleteQueryEdgeError = void 0;
|
|
4
|
+
exports.createQueryDataset = createQueryDataset;
|
|
5
|
+
exports.relatedQueryRows = relatedQueryRows;
|
|
6
|
+
exports.immutableQueryValue = immutableQueryValue;
|
|
7
|
+
const query_schema_js_1 = require("./query-schema.js");
|
|
8
|
+
/** An edge is declared by the schema but its targets were unavailable in this snapshot. */
|
|
9
|
+
class IncompleteQueryEdgeError extends Error {
|
|
10
|
+
relation;
|
|
11
|
+
edge;
|
|
12
|
+
constructor(relation, edge) {
|
|
13
|
+
super(`Dataset edge ${relation}.${edge} is incomplete`);
|
|
14
|
+
this.relation = relation;
|
|
15
|
+
this.edge = edge;
|
|
16
|
+
this.name = "IncompleteQueryEdgeError";
|
|
17
|
+
Object.freeze(this);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.IncompleteQueryEdgeError = IncompleteQueryEdgeError;
|
|
21
|
+
const datasetIndexes = new WeakMap();
|
|
22
|
+
const compiledSnapshots = new WeakMap();
|
|
23
|
+
const rawRowSnapshots = new WeakMap();
|
|
24
|
+
const compiledRowMembership = new WeakMap();
|
|
25
|
+
const emptyRows = Object.freeze([]);
|
|
26
|
+
const datasetPaths = Object.fromEntries(Object.keys(query_schema_js_1.queryRelationMetadata).map((relation) => [relation, [
|
|
27
|
+
...query_schema_js_1.queryRelationMetadata[relation].fields,
|
|
28
|
+
...(relation === "contracts" ? ["templateId.packageId", "templateId.moduleName", "templateId.entityName"] : []),
|
|
29
|
+
]]));
|
|
30
|
+
/** Validates and freezes source-neutral rows, then builds deterministic edge indexes once. */
|
|
31
|
+
function createQueryDataset(input) {
|
|
32
|
+
if (datasetIndexes.has(input)) {
|
|
33
|
+
return input;
|
|
34
|
+
}
|
|
35
|
+
const cached = compiledSnapshots.get(input);
|
|
36
|
+
if (cached !== undefined) {
|
|
37
|
+
return cached;
|
|
38
|
+
}
|
|
39
|
+
const dataset = immutableQueryValue(input);
|
|
40
|
+
const rowSnapshots = new Map();
|
|
41
|
+
const rowMembership = new Map();
|
|
42
|
+
for (const relation of Object.keys(query_schema_js_1.queryRelationMetadata)) {
|
|
43
|
+
if (!Array.isArray(input.rows[relation])) {
|
|
44
|
+
throw new Error(`Dataset is missing ${relation} rows`);
|
|
45
|
+
}
|
|
46
|
+
const snapshots = new WeakMap();
|
|
47
|
+
for (const [index, row] of input.rows[relation].entries()) {
|
|
48
|
+
snapshots.set(row, dataset.rows[relation][index]);
|
|
49
|
+
}
|
|
50
|
+
rowSnapshots.set(relation, snapshots);
|
|
51
|
+
rowMembership.set(relation, new WeakSet(dataset.rows[relation].map((row) => row)));
|
|
52
|
+
}
|
|
53
|
+
const relations = Object.keys(query_schema_js_1.queryRelationMetadata);
|
|
54
|
+
const indexes = new Map();
|
|
55
|
+
for (const relation of relations) {
|
|
56
|
+
const rows = dataset.rows[relation];
|
|
57
|
+
if (!Array.isArray(rows)) {
|
|
58
|
+
throw new Error(`Dataset is missing ${relation} rows`);
|
|
59
|
+
}
|
|
60
|
+
const localKeys = dataset.uniqueKeys[relation];
|
|
61
|
+
if (!Array.isArray(localKeys)) {
|
|
62
|
+
throw new Error(`Dataset is missing ${relation} unique keys`);
|
|
63
|
+
}
|
|
64
|
+
for (const key of localKeys) {
|
|
65
|
+
validateUniquePaths(relation, rows, key, `${relation} unique key`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const relation of relations) {
|
|
69
|
+
const rows = dataset.rows[relation];
|
|
70
|
+
if (!Array.isArray(rows)) {
|
|
71
|
+
throw new Error(`Dataset is missing ${relation} rows`);
|
|
72
|
+
}
|
|
73
|
+
const edgeDefinitions = query_schema_js_1.queryRelationEdges[relation] ?? {};
|
|
74
|
+
const lookups = dataset.edges[relation];
|
|
75
|
+
if (lookups === undefined) {
|
|
76
|
+
throw new Error(`Dataset is missing ${relation} edges`);
|
|
77
|
+
}
|
|
78
|
+
const relationIndexes = new Map();
|
|
79
|
+
for (const [edge, definition] of Object.entries(edgeDefinitions)) {
|
|
80
|
+
const lookup = lookups[edge];
|
|
81
|
+
if (lookup === undefined) {
|
|
82
|
+
throw new Error(`Dataset is missing ${relation}.${edge} edge`);
|
|
83
|
+
}
|
|
84
|
+
else if (lookup.complete !== undefined && typeof lookup.complete !== "boolean") {
|
|
85
|
+
throw new Error(`Dataset ${relation}.${edge} completeness marker is invalid`);
|
|
86
|
+
}
|
|
87
|
+
const targetRows = dataset.rows[definition.target];
|
|
88
|
+
const buckets = new Map();
|
|
89
|
+
let sourcePrivateKeys;
|
|
90
|
+
if (lookup.privateKeys !== undefined) {
|
|
91
|
+
if (lookup.from !== undefined || lookup.to !== undefined) {
|
|
92
|
+
throw new Error(`Dataset ${relation}.${edge} cannot mix public and private lookup keys`);
|
|
93
|
+
}
|
|
94
|
+
validatePrivateKeys(relation, edge, lookup.privateKeys, rows, targetRows);
|
|
95
|
+
sourcePrivateKeys = new Map(rows.map((row, index) => [row, lookup.privateKeys.source[index]]));
|
|
96
|
+
for (const [index, target] of targetRows.entries()) {
|
|
97
|
+
const key = compositeKey(lookup.privateKeys.target[index]);
|
|
98
|
+
buckets.set(key, [...(buckets.get(key) ?? []), target]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
if (lookup.from === undefined || lookup.to === undefined) {
|
|
103
|
+
throw new Error(`Dataset ${relation}.${edge} lookup is missing public paths`);
|
|
104
|
+
}
|
|
105
|
+
else if (lookup.from.length !== lookup.to.length) {
|
|
106
|
+
throw new Error(`Dataset ${relation}.${edge} lookup arity differs`);
|
|
107
|
+
}
|
|
108
|
+
validatePaths(relation, rows, lookup.from, `${relation}.${edge} source`);
|
|
109
|
+
validatePaths(definition.target, targetRows, lookup.to, `${relation}.${edge} target`);
|
|
110
|
+
for (const target of targetRows) {
|
|
111
|
+
const key = compositeKey(lookup.to.map((path) => atPath(target, path)));
|
|
112
|
+
buckets.set(key, [...(buckets.get(key) ?? []), target]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (definition.cardinality === "one" && [...buckets.values()].some((targets) => targets.length > 1)) {
|
|
116
|
+
throw new Error(`Dataset ${relation}.${edge} has multiple to-one targets`);
|
|
117
|
+
}
|
|
118
|
+
else if (definition.cardinality === "one" && !definition.nullable && lookup.complete !== false) {
|
|
119
|
+
for (const [index, source] of rows.entries()) {
|
|
120
|
+
const values = lookup.privateKeys === undefined
|
|
121
|
+
? lookup.from.map((path) => atPath(source, path))
|
|
122
|
+
: lookup.privateKeys.source[index];
|
|
123
|
+
if (values.some((value) => value === null || value === undefined) || !buckets.has(compositeKey(values))) {
|
|
124
|
+
throw new Error(`Dataset ${relation}.${edge} has no target`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
relationIndexes.set(edge, { targets: new Map([...buckets.entries()].map(([key, targets]) => [key, Object.freeze(targets)])), ...(sourcePrivateKeys === undefined ? {} : { sourcePrivateKeys }) });
|
|
129
|
+
}
|
|
130
|
+
if (Object.keys(lookups).some((edge) => edgeDefinitions[edge] === undefined)) {
|
|
131
|
+
throw new Error(`Dataset ${relation} declares an unknown edge`);
|
|
132
|
+
}
|
|
133
|
+
indexes.set(relation, relationIndexes);
|
|
134
|
+
}
|
|
135
|
+
datasetIndexes.set(dataset, indexes);
|
|
136
|
+
compiledSnapshots.set(input, dataset);
|
|
137
|
+
rawRowSnapshots.set(input, rowSnapshots);
|
|
138
|
+
compiledRowMembership.set(dataset, rowMembership);
|
|
139
|
+
return dataset;
|
|
140
|
+
}
|
|
141
|
+
/** Uses the factory index, compiling a legacy dataset on first use for compatibility. */
|
|
142
|
+
function relatedQueryRows(dataset, relation, row, edge) {
|
|
143
|
+
const compiled = createQueryDataset(dataset);
|
|
144
|
+
const lookup = compiled.edges[relation]?.[edge];
|
|
145
|
+
const index = datasetIndexes.get(compiled)?.get(relation)?.get(edge);
|
|
146
|
+
if (lookup === undefined || index === undefined) {
|
|
147
|
+
throw new Error(`Missing deterministic lookup for ${relation}.${edge}`);
|
|
148
|
+
}
|
|
149
|
+
const snapshotRow = dataset === compiled
|
|
150
|
+
? compiledRowMembership.get(compiled)?.get(relation)?.has(row) ? row : undefined
|
|
151
|
+
: rawRowSnapshots.get(dataset)?.get(relation)?.get(row);
|
|
152
|
+
if (snapshotRow === undefined) {
|
|
153
|
+
throw new Error(`Query row does not belong to ${relation} dataset`);
|
|
154
|
+
}
|
|
155
|
+
else if (lookup.complete === false) {
|
|
156
|
+
throw new IncompleteQueryEdgeError(relation, edge);
|
|
157
|
+
}
|
|
158
|
+
const values = lookup.privateKeys === undefined
|
|
159
|
+
? lookup.from.map((path) => atPath(snapshotRow, path))
|
|
160
|
+
: index.sourcePrivateKeys?.get(snapshotRow);
|
|
161
|
+
return values === undefined || values.some((value) => value === null || value === undefined) ? emptyRows : index.targets.get(compositeKey(values)) ?? emptyRows;
|
|
162
|
+
}
|
|
163
|
+
function validatePrivateKeys(relation, edge, keys, sourceRows, targetRows) {
|
|
164
|
+
if (!Array.isArray(keys.source) || keys.source.length !== sourceRows.length) {
|
|
165
|
+
throw new Error(`Dataset ${relation}.${edge} private source length differs`);
|
|
166
|
+
}
|
|
167
|
+
else if (!Array.isArray(keys.target) || keys.target.length !== targetRows.length) {
|
|
168
|
+
throw new Error(`Dataset ${relation}.${edge} private target length differs`);
|
|
169
|
+
}
|
|
170
|
+
if (keys.source.length === 0 && keys.target.length === 0) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const arity = keys.source[0]?.length ?? keys.target[0]?.length;
|
|
174
|
+
if (arity === undefined || arity === 0 || !keys.source.every((key) => Array.isArray(key) && key.length === arity) || !keys.target.every((key) => Array.isArray(key) && key.length === arity)) {
|
|
175
|
+
throw new Error(`Dataset ${relation}.${edge} private lookup arity differs`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function validateUniquePaths(relation, rows, paths, label) {
|
|
179
|
+
if (paths.length === 0) {
|
|
180
|
+
throw new Error(`Dataset ${label} is empty`);
|
|
181
|
+
}
|
|
182
|
+
validatePaths(relation, rows, paths, label);
|
|
183
|
+
const seen = new Set();
|
|
184
|
+
for (const row of rows) {
|
|
185
|
+
const key = compositeKey(paths.map((path) => atPath(row, path)));
|
|
186
|
+
if (seen.has(key)) {
|
|
187
|
+
throw new Error(`Dataset ${label} is not unique`);
|
|
188
|
+
}
|
|
189
|
+
seen.add(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function validatePaths(relation, rows, paths, label) {
|
|
193
|
+
for (const path of paths) {
|
|
194
|
+
if (!datasetPaths[relation].includes(path)) {
|
|
195
|
+
throw new Error(`Dataset ${label} path ${path} is invalid`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
for (const path of paths) {
|
|
200
|
+
if (!hasPath(row, path)) {
|
|
201
|
+
throw new Error(`Dataset ${label} path ${path} is invalid`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function hasPath(value, path) {
|
|
207
|
+
let current = value;
|
|
208
|
+
for (const segment of path.split(".")) {
|
|
209
|
+
if (current === null || typeof current !== "object" || !Object.hasOwn(current, segment)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
current = current[segment];
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
function atPath(value, path) {
|
|
217
|
+
return path.split(".").reduce((current, segment) => current !== null && typeof current === "object" ? current[segment] : undefined, value);
|
|
218
|
+
}
|
|
219
|
+
function compositeKey(values) {
|
|
220
|
+
return JSON.stringify(values.map((value) => value instanceof Date ? value.toISOString() : value instanceof Uint8Array ? Array.from(value) : value));
|
|
221
|
+
}
|
|
222
|
+
/** A Date which preserves Date's public read API but rejects mutation. */
|
|
223
|
+
class ImmutableQueryDate extends Date {
|
|
224
|
+
/** Legacy Date API, absent from newer TypeScript lib declarations. */
|
|
225
|
+
setYear() {
|
|
226
|
+
throw new TypeError("Query dates are immutable");
|
|
227
|
+
}
|
|
228
|
+
setTime() {
|
|
229
|
+
throw new TypeError("Query dates are immutable");
|
|
230
|
+
}
|
|
231
|
+
setMilliseconds() {
|
|
232
|
+
throw new TypeError("Query dates are immutable");
|
|
233
|
+
}
|
|
234
|
+
setSeconds() {
|
|
235
|
+
throw new TypeError("Query dates are immutable");
|
|
236
|
+
}
|
|
237
|
+
setMinutes() {
|
|
238
|
+
throw new TypeError("Query dates are immutable");
|
|
239
|
+
}
|
|
240
|
+
setHours() {
|
|
241
|
+
throw new TypeError("Query dates are immutable");
|
|
242
|
+
}
|
|
243
|
+
setDate() {
|
|
244
|
+
throw new TypeError("Query dates are immutable");
|
|
245
|
+
}
|
|
246
|
+
setMonth() {
|
|
247
|
+
throw new TypeError("Query dates are immutable");
|
|
248
|
+
}
|
|
249
|
+
setFullYear() {
|
|
250
|
+
throw new TypeError("Query dates are immutable");
|
|
251
|
+
}
|
|
252
|
+
setUTCMilliseconds() {
|
|
253
|
+
throw new TypeError("Query dates are immutable");
|
|
254
|
+
}
|
|
255
|
+
setUTCSeconds() {
|
|
256
|
+
throw new TypeError("Query dates are immutable");
|
|
257
|
+
}
|
|
258
|
+
setUTCMinutes() {
|
|
259
|
+
throw new TypeError("Query dates are immutable");
|
|
260
|
+
}
|
|
261
|
+
setUTCHours() {
|
|
262
|
+
throw new TypeError("Query dates are immutable");
|
|
263
|
+
}
|
|
264
|
+
setUTCDate() {
|
|
265
|
+
throw new TypeError("Query dates are immutable");
|
|
266
|
+
}
|
|
267
|
+
setUTCMonth() {
|
|
268
|
+
throw new TypeError("Query dates are immutable");
|
|
269
|
+
}
|
|
270
|
+
setUTCFullYear() {
|
|
271
|
+
throw new TypeError("Query dates are immutable");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
exports.ImmutableQueryDate = ImmutableQueryDate;
|
|
275
|
+
/** Recursively clones and freezes query values without retaining mutable input references. */
|
|
276
|
+
function immutableQueryValue(value) {
|
|
277
|
+
if (value instanceof Date) {
|
|
278
|
+
return immutableQueryDate(value);
|
|
279
|
+
}
|
|
280
|
+
else if (value instanceof Uint8Array) {
|
|
281
|
+
return immutableQueryBytes(value);
|
|
282
|
+
}
|
|
283
|
+
else if (Array.isArray(value)) {
|
|
284
|
+
return Object.freeze(value.map((entry) => immutableQueryValue(entry)));
|
|
285
|
+
}
|
|
286
|
+
else if (value !== null && typeof value === "object") {
|
|
287
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, immutableQueryValue(entry)])));
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
/** A proxy has no [[DateValue]], so Date.prototype mutators cannot bypass this wrapper. */
|
|
292
|
+
function immutableQueryDate(value) {
|
|
293
|
+
const target = new Date(value.getTime());
|
|
294
|
+
const mutation = () => {
|
|
295
|
+
throw new TypeError("Query dates are immutable");
|
|
296
|
+
};
|
|
297
|
+
const setters = new Set([
|
|
298
|
+
"setDate", "setFullYear", "setHours", "setMilliseconds", "setMinutes", "setMonth", "setSeconds", "setTime", "setUTCDate",
|
|
299
|
+
"setUTCFullYear", "setUTCHours", "setUTCMilliseconds", "setUTCMinutes", "setUTCMonth", "setUTCSeconds", "setYear",
|
|
300
|
+
]);
|
|
301
|
+
return Object.freeze(new Proxy(target, {
|
|
302
|
+
set: mutation,
|
|
303
|
+
defineProperty: mutation,
|
|
304
|
+
deleteProperty: mutation,
|
|
305
|
+
setPrototypeOf: mutation,
|
|
306
|
+
get(inner, property) {
|
|
307
|
+
if (setters.has(String(property))) {
|
|
308
|
+
return mutation;
|
|
309
|
+
}
|
|
310
|
+
else if (property === Symbol.toStringTag) {
|
|
311
|
+
return "Date";
|
|
312
|
+
}
|
|
313
|
+
const member = Reflect.get(inner, property, inner);
|
|
314
|
+
return typeof member === "function" ? member.bind(inner) : member;
|
|
315
|
+
},
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
/** Typed arrays cannot be frozen by JavaScript engines, so guard every write path. */
|
|
319
|
+
function immutableQueryBytes(value) {
|
|
320
|
+
const bytes = new Uint8Array(value);
|
|
321
|
+
const mutation = () => {
|
|
322
|
+
throw new TypeError("Query bytes are immutable");
|
|
323
|
+
};
|
|
324
|
+
let proxy;
|
|
325
|
+
proxy = new Proxy(bytes, {
|
|
326
|
+
set: mutation,
|
|
327
|
+
defineProperty: mutation,
|
|
328
|
+
deleteProperty: mutation,
|
|
329
|
+
setPrototypeOf: mutation,
|
|
330
|
+
get(target, property) {
|
|
331
|
+
if (["copyWithin", "fill", "reverse", "set", "sort"].includes(String(property))) {
|
|
332
|
+
return mutation;
|
|
333
|
+
}
|
|
334
|
+
else if (property === "buffer") {
|
|
335
|
+
return target.buffer.slice(0);
|
|
336
|
+
}
|
|
337
|
+
else if (property === "subarray") {
|
|
338
|
+
return (...args) => immutableQueryBytes(target.subarray(...args));
|
|
339
|
+
}
|
|
340
|
+
else if (property === "slice") {
|
|
341
|
+
return (...args) => immutableQueryBytes(target.slice(...args));
|
|
342
|
+
}
|
|
343
|
+
else if (property === "valueOf") {
|
|
344
|
+
return () => proxy;
|
|
345
|
+
}
|
|
346
|
+
const member = Reflect.get(target, property, target);
|
|
347
|
+
return typeof member === "function" ? member.bind(target) : member;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
return proxy;
|
|
351
|
+
}
|