@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.
Files changed (99) hide show
  1. package/README.md +104 -34
  2. package/dist/cjs/core/types/requests/{submit-command-request.js → submit-commands-request.js} +10 -6
  3. package/dist/cjs/index.js +10 -8
  4. package/dist/cjs/query/canonical/in-memory-query-evaluator.js +291 -0
  5. package/dist/cjs/query/canonical/public-identity.js +41 -0
  6. package/dist/cjs/query/canonical/query-ast.js +2 -0
  7. package/dist/cjs/query/canonical/query-dataset.js +351 -0
  8. package/dist/cjs/query/canonical/query-normalizer.js +655 -0
  9. package/dist/cjs/query/canonical/query-schema.js +86 -0
  10. package/dist/cjs/query/canton-manager.js +14 -5
  11. package/dist/cjs/query/errors/query-snapshot-incomplete-error.js +19 -0
  12. package/dist/cjs/query/grpc/grpc-contract-cache.js +484 -0
  13. package/dist/cjs/query/grpc/grpc-package-relation-reader.js +218 -0
  14. package/dist/cjs/query/grpc/grpc-query-client.js +249 -0
  15. package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +238 -0
  16. package/dist/cjs/query/grpc/grpc-query-value-mapper.js +204 -0
  17. package/dist/cjs/query/grpc/grpc-relation-mapper.js +794 -0
  18. package/dist/cjs/query/pqs/pqs-query-client.js +151 -582
  19. package/dist/cjs/query/pqs/pqs-relational-sql-compiler.js +138 -95
  20. package/dist/cjs/query/pqs/pqs-result-shape.js +28 -0
  21. package/dist/cjs/query/pqs/pqs-schema-profile.js +49 -48
  22. package/dist/cjs/query/pqs/pqs-sql-compiler.js +343 -289
  23. package/dist/cjs/query/pqs/pqs-sql-syntax.js +10 -0
  24. package/dist/cjs/services/command/command-service-client.js +16 -3
  25. package/dist/cjs/services/commands/command-payload-builder.js +1 -1
  26. package/dist/cjs/services/commands/command-submission-pipeline.js +21 -6
  27. package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
  28. package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
  29. package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
  30. package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  31. package/dist/cjs/transports/json/json-transport.js +1 -1
  32. package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
  33. package/dist/core/transports/transport.interface.d.ts +4 -4
  34. package/dist/core/types/prepared-command-submission.d.ts +3 -3
  35. package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
  36. package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.js +2 -1
  39. package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
  40. package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
  41. package/dist/query/canonical/public-identity.d.ts +14 -0
  42. package/dist/query/canonical/public-identity.js +35 -0
  43. package/dist/query/canonical/query-ast.d.ts +104 -0
  44. package/dist/query/canonical/query-ast.js +1 -0
  45. package/dist/query/canonical/query-dataset.d.ts +63 -0
  46. package/dist/query/canonical/query-dataset.js +343 -0
  47. package/dist/query/canonical/query-normalizer.d.ts +10 -0
  48. package/dist/query/canonical/query-normalizer.js +646 -0
  49. package/dist/query/canonical/query-schema.d.ts +25 -0
  50. package/dist/query/canonical/query-schema.js +83 -0
  51. package/dist/query/canton-manager.js +14 -5
  52. package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
  53. package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
  54. package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
  55. package/dist/query/grpc/grpc-contract-cache.js +479 -0
  56. package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
  57. package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
  58. package/dist/query/grpc/grpc-query-client.d.ts +42 -0
  59. package/dist/query/grpc/grpc-query-client.js +245 -0
  60. package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
  61. package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
  62. package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
  63. package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
  64. package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
  65. package/dist/query/grpc/grpc-relation-mapper.js +789 -0
  66. package/dist/query/pqs/pqs-query-client.d.ts +10 -20
  67. package/dist/query/pqs/pqs-query-client.js +154 -585
  68. package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
  69. package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
  70. package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
  71. package/dist/query/pqs/pqs-result-shape.js +25 -0
  72. package/dist/query/pqs/pqs-schema-profile.js +49 -48
  73. package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
  74. package/dist/query/pqs/pqs-sql-compiler.js +337 -288
  75. package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
  76. package/dist/query/pqs/pqs-sql-syntax.js +6 -0
  77. package/dist/query/query-client.d.ts +15 -0
  78. package/dist/services/command/command-service-client.d.ts +9 -4
  79. package/dist/services/command/command-service-client.js +16 -3
  80. package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
  81. package/dist/services/commands/command-payload-builder.d.ts +2 -2
  82. package/dist/services/commands/command-payload-builder.js +1 -1
  83. package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
  84. package/dist/services/commands/command-submission-pipeline.js +21 -6
  85. package/dist/testing/runtime/declarative-action-executor.js +3 -3
  86. package/dist/transports/grpc/grpc-transport.d.ts +4 -4
  87. package/dist/transports/grpc/grpc-transport.js +3 -3
  88. package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
  89. package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
  90. package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
  91. package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  92. package/dist/transports/json/json-transport.d.ts +2 -2
  93. package/dist/transports/json/json-transport.js +2 -2
  94. package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
  95. package/dist/transports/json/mappers/commands-mapper.js +2 -2
  96. package/package.json +4 -3
  97. package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
  98. package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
  99. package/dist/query/grpc/grpc-contract-query-client.js +0 -174
@@ -0,0 +1,233 @@
1
+ import { ValidationError } from "../../core/errors/validation-error.js";
2
+ import { GetActiveContractsResponse, } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/state_service.js";
3
+ import { GetUpdateResponse, } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/update_service.js";
4
+ import { TransactionShape } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/transaction_filter.js";
5
+ import { mapGrpcQueryContractsRequest } from "../../transports/grpc/mappers/contracts-mapper.js";
6
+ import { immutableQueryValue } from "../canonical/query-dataset.js";
7
+ import { QuerySnapshotIncompleteError } from "../errors/query-snapshot-incomplete-error.js";
8
+ const DEFAULT_OPTIONS = {
9
+ maxHistoryPages: 10_000,
10
+ maxHistoryUpdates: 1_000_000,
11
+ maxActiveContractPages: 10_000,
12
+ maxActiveContracts: 1_000_000,
13
+ };
14
+ const LEDGER_BEGIN = "0";
15
+ export class GrpcQuerySnapshotReader {
16
+ stateService;
17
+ updateService;
18
+ options;
19
+ constructor(stateService, updateService, options = {}) {
20
+ this.stateService = stateService;
21
+ this.updateService = updateService;
22
+ this.options = validateOptions(options);
23
+ }
24
+ async readCurrentHistoryAsync() {
25
+ const ledgerEnd = await this.stateService.getLedgerEndAsync({});
26
+ return this.readHistoryAsync(ledgerEnd.offset);
27
+ }
28
+ async readHistoryAsync(endInclusive) {
29
+ const end = parseOffset(endInclusive);
30
+ if (end === undefined) {
31
+ throw this.historyError(endInclusive, "invalid-offset");
32
+ }
33
+ const pruned = await this.stateService.getLatestPrunedOffsetsAsync({});
34
+ const prunedUpTo = parseOffset(pruned.participantPrunedUpToInclusive);
35
+ if (prunedUpTo === undefined || prunedUpTo !== 0n) {
36
+ throw this.historyError(endInclusive, "participant-pruned");
37
+ }
38
+ const updateFormat = freezeDeep(createHistoryUpdateFormat());
39
+ const updates = [];
40
+ const observedPageTokens = new Set();
41
+ let expectedLowestExclusive = 0n;
42
+ let pageToken;
43
+ let pagesRead = 0;
44
+ let previousUpdateOffset;
45
+ while (true) {
46
+ if (pagesRead >= this.options.maxHistoryPages) {
47
+ throw this.historyError(endInclusive, "max-pages-exceeded");
48
+ }
49
+ const request = {
50
+ beginOffsetExclusive: LEDGER_BEGIN,
51
+ endOffsetInclusive: endInclusive,
52
+ updateFormat,
53
+ descendingOrder: false,
54
+ pageToken: pageToken === undefined ? undefined : Uint8Array.from(pageToken),
55
+ };
56
+ const response = await this.updateService.getUpdatesPageAsync(request);
57
+ pagesRead += 1;
58
+ const lowest = parseOffset(response.lowestPageOffsetExclusive);
59
+ const highest = parseOffset(response.highestPageOffsetInclusive);
60
+ if (lowest === undefined || highest === undefined) {
61
+ throw this.historyError(endInclusive, "missing-boundary");
62
+ }
63
+ else if (lowest !== expectedLowestExclusive || highest < lowest || highest > end) {
64
+ throw this.historyError(endInclusive, "page-boundary-mismatch");
65
+ }
66
+ if (response.updates.length > this.options.maxHistoryUpdates - updates.length) {
67
+ throw this.historyError(endInclusive, "max-updates-exceeded");
68
+ }
69
+ for (const update of response.updates) {
70
+ const updateOffset = parseOffset(extractUpdateOffset(update));
71
+ if (updateOffset === undefined || updateOffset <= lowest || updateOffset > highest || (previousUpdateOffset !== undefined && updateOffset <= previousUpdateOffset)) {
72
+ throw this.historyError(endInclusive, "page-boundary-mismatch");
73
+ }
74
+ previousUpdateOffset = updateOffset;
75
+ }
76
+ updates.push(...response.updates.map(cloneFrozenUpdate));
77
+ const nextPageToken = response.nextPageToken;
78
+ if (nextPageToken === undefined || nextPageToken.length === 0) {
79
+ if (highest !== end) {
80
+ throw this.historyError(endInclusive, "nonterminal-page-without-token");
81
+ }
82
+ return freezeSnapshot({
83
+ endInclusive,
84
+ updates: Object.freeze(updates),
85
+ });
86
+ }
87
+ else if (highest >= end) {
88
+ throw this.historyError(endInclusive, "nonterminal-page-reaches-end");
89
+ }
90
+ else if (highest <= lowest) {
91
+ throw this.historyError(endInclusive, "page-boundary-mismatch");
92
+ }
93
+ const tokenKey = tokenKeyFor(nextPageToken);
94
+ if (observedPageTokens.has(tokenKey)) {
95
+ throw this.historyError(endInclusive, "repeated-page-token");
96
+ }
97
+ observedPageTokens.add(tokenKey);
98
+ expectedLowestExclusive = highest;
99
+ pageToken = Uint8Array.from(nextPageToken);
100
+ }
101
+ }
102
+ async readActiveContractsAsync(activeAtOffset, parties) {
103
+ if (parseOffset(activeAtOffset) === undefined) {
104
+ throw this.activeError(activeAtOffset, "invalid-offset");
105
+ }
106
+ const eventFormat = freezeDeep(parties === undefined ? createAllPartiesEventFormat() : mapGrpcQueryContractsRequest({ parties }).eventFormat);
107
+ const activeContracts = [];
108
+ const observedPageTokens = new Set();
109
+ let pageToken;
110
+ let pagesRead = 0;
111
+ while (true) {
112
+ if (pagesRead >= this.options.maxActiveContractPages) {
113
+ throw this.activeError(activeAtOffset, "max-pages-exceeded");
114
+ }
115
+ const request = {
116
+ activeAtOffset,
117
+ eventFormat,
118
+ pageToken: pageToken === undefined ? undefined : Uint8Array.from(pageToken),
119
+ };
120
+ const response = await this.stateService.getActiveContractsPageAsync(request);
121
+ pagesRead += 1;
122
+ const responseActiveAtOffset = parseOffset(response.activeAtOffset);
123
+ if (responseActiveAtOffset === undefined) {
124
+ throw this.activeError(activeAtOffset, "missing-active-at-offset");
125
+ }
126
+ else if (parseOffset(activeAtOffset) !== responseActiveAtOffset) {
127
+ throw this.activeError(activeAtOffset, "active-at-offset-mismatch");
128
+ }
129
+ else if (response.activeContracts.length === 0 && response.nextPageToken?.length) {
130
+ throw this.activeError(activeAtOffset, "empty-active-contract-page");
131
+ }
132
+ if (response.activeContracts.length > this.options.maxActiveContracts - activeContracts.length) {
133
+ throw this.activeError(activeAtOffset, "max-active-contracts-exceeded");
134
+ }
135
+ activeContracts.push(...response.activeContracts.map(cloneFrozenActiveContract));
136
+ const nextPageToken = response.nextPageToken;
137
+ if (nextPageToken === undefined || nextPageToken.length === 0) {
138
+ return freezeSnapshot({
139
+ activeAtOffset,
140
+ activeContracts: Object.freeze(activeContracts),
141
+ });
142
+ }
143
+ const tokenKey = tokenKeyFor(nextPageToken);
144
+ if (observedPageTokens.has(tokenKey)) {
145
+ throw this.activeError(activeAtOffset, "repeated-page-token");
146
+ }
147
+ observedPageTokens.add(tokenKey);
148
+ pageToken = Uint8Array.from(nextPageToken);
149
+ }
150
+ }
151
+ historyError(endInclusive, reason) {
152
+ return new QuerySnapshotIncompleteError({
153
+ beginExclusive: LEDGER_BEGIN,
154
+ endInclusive,
155
+ reason,
156
+ });
157
+ }
158
+ activeError(activeAtOffset, reason) {
159
+ return new QuerySnapshotIncompleteError({
160
+ beginExclusive: LEDGER_BEGIN,
161
+ endInclusive: activeAtOffset ?? "",
162
+ activeAtOffset,
163
+ reason,
164
+ });
165
+ }
166
+ }
167
+ function validateOptions(options) {
168
+ const validated = { ...DEFAULT_OPTIONS, ...options };
169
+ for (const [name, value] of Object.entries(validated)) {
170
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
171
+ throw new ValidationError(`${name} must be a finite positive integer.`);
172
+ }
173
+ }
174
+ return Object.freeze(validated);
175
+ }
176
+ function createHistoryUpdateFormat() {
177
+ return {
178
+ includeTransactions: {
179
+ eventFormat: createAllPartiesEventFormat(),
180
+ transactionShape: TransactionShape.LEDGER_EFFECTS,
181
+ },
182
+ };
183
+ }
184
+ function createAllPartiesEventFormat() {
185
+ return mapGrpcQueryContractsRequest({ allParties: true }).eventFormat;
186
+ }
187
+ function parseOffset(value) {
188
+ if (typeof value !== "string" || !/^(?:0|[1-9]\d{0,18})$/.test(value)) {
189
+ return undefined;
190
+ }
191
+ const parsed = BigInt(value);
192
+ return parsed <= 9223372036854775807n ? parsed : undefined;
193
+ }
194
+ export function isCanonicalGrpcOffset(value) {
195
+ return parseOffset(value) !== undefined;
196
+ }
197
+ function extractUpdateOffset(update) {
198
+ const oneof = update.update;
199
+ switch (oneof.oneofKind) {
200
+ case "transaction": return oneof.transaction?.offset;
201
+ case "reassignment": return oneof.reassignment?.offset;
202
+ case "offsetCheckpoint": return oneof.offsetCheckpoint?.offset;
203
+ case "topologyTransaction": return oneof.topologyTransaction?.offset;
204
+ default: return undefined;
205
+ }
206
+ }
207
+ function tokenKeyFor(token) {
208
+ return Array.from(token).join(",");
209
+ }
210
+ function cloneFrozenUpdate(update) {
211
+ return freezeDeep(GetUpdateResponse.fromBinary(GetUpdateResponse.toBinary(update)));
212
+ }
213
+ function cloneFrozenActiveContract(activeContract) {
214
+ return freezeDeep(GetActiveContractsResponse.fromBinary(GetActiveContractsResponse.toBinary(activeContract)));
215
+ }
216
+ function freezeSnapshot(snapshot) {
217
+ return freezeDeep(snapshot);
218
+ }
219
+ function freezeDeep(value) {
220
+ if (value instanceof Uint8Array) {
221
+ return immutableQueryValue(value);
222
+ }
223
+ else if (value === null || typeof value !== "object") {
224
+ return value;
225
+ }
226
+ else if (Object.isFrozen(value)) {
227
+ return value;
228
+ }
229
+ for (const [property, child] of Object.entries(value)) {
230
+ value[property] = freezeDeep(child);
231
+ }
232
+ return Object.freeze(value);
233
+ }
@@ -0,0 +1,15 @@
1
+ import type { Value } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/value.js";
2
+ /** Maximum generated Value nesting accepted before recursive mapping becomes unsafe. */
3
+ export declare const MAX_GRPC_QUERY_VALUE_DEPTH = 256;
4
+ /** Maps a verbose Ledger API value to the JSON convention used by PQS predicates and rows. */
5
+ export declare function mapGrpcQueryValue(value: Value): unknown;
6
+ /** Validates a generated PartyIdString without applying those rules to ordinary Text values. */
7
+ export declare function validPartyId(value: string): string;
8
+ /** Validates a generated LedgerString used specifically as a contract identifier. */
9
+ export declare function validLedgerString(value: string, name?: string): string;
10
+ /** Validates a generated NameString used by identifiers, choices, and verbose value labels. */
11
+ export declare function validNameString(value: string, name?: string): string;
12
+ /** Validates an Identifier module or entity name as nonempty dot-separated NameString segments. */
13
+ export declare function validDottedNameString(value: string, name?: string): string;
14
+ /** Validates a generated PackageIdString without applying it to arbitrary package-name text. */
15
+ export declare function validPackageIdString(value: string, name?: string): string;
@@ -0,0 +1,195 @@
1
+ import { ValidationError } from "../../core/errors/validation-error.js";
2
+ import { immutableQueryValue } from "../canonical/query-dataset.js";
3
+ const MIN_DATE_EPOCH_DAY = -719_162;
4
+ const MAX_DATE_EPOCH_DAY = 2_932_896;
5
+ const INT64 = /^-?(?:0|[1-9]\d*)$/;
6
+ /** Maximum generated Value nesting accepted before recursive mapping becomes unsafe. */
7
+ export const MAX_GRPC_QUERY_VALUE_DEPTH = 256;
8
+ /** Maps a verbose Ledger API value to the JSON convention used by PQS predicates and rows. */
9
+ export function mapGrpcQueryValue(value) {
10
+ if (value === undefined || value.sum === undefined) {
11
+ throw new ValidationError("gRPC query value is missing its sum");
12
+ }
13
+ const mapped = mapValue(value, 0);
14
+ return immutableQueryValue(mapped);
15
+ }
16
+ function mapValue(value, depth) {
17
+ if (depth > MAX_GRPC_QUERY_VALUE_DEPTH) {
18
+ throw new ValidationError(`gRPC query value exceeds maximum nesting depth ${MAX_GRPC_QUERY_VALUE_DEPTH}`);
19
+ }
20
+ switch (value.sum.oneofKind) {
21
+ case "unit": return {};
22
+ case "bool": return value.sum.bool;
23
+ case "int64": return int64(value.sum.int64, "int64");
24
+ case "date": return date(value.sum.date);
25
+ case "timestamp": return timestamp(value.sum.timestamp);
26
+ case "numeric": return numeric(value.sum.numeric);
27
+ case "party": return validPartyId(value.sum.party);
28
+ case "text": return value.sum.text;
29
+ case "contractId": return validLedgerString(value.sum.contractId, "contract id");
30
+ case "optional": return value.sum.optional.value === undefined ? null : mapValue(value.sum.optional.value, depth + 1);
31
+ case "list": return value.sum.list.elements.map((entry) => mapValue(entry, depth + 1));
32
+ case "textMap": return mapTextMap(value.sum.textMap.entries, depth + 1);
33
+ case "genMap": return mapGenMap(value.sum.genMap.entries, depth + 1);
34
+ case "record": return mapRecord(value.sum.record.fields, depth + 1);
35
+ case "variant": {
36
+ if (value.sum.variant.value === undefined) {
37
+ throw new ValidationError("gRPC query variant is incomplete");
38
+ }
39
+ return { tag: validNameString(value.sum.variant.constructor, "variant constructor"), value: mapValue(value.sum.variant.value, depth + 1) };
40
+ }
41
+ case "enum": {
42
+ return validNameString(value.sum.enum.constructor, "enum constructor");
43
+ }
44
+ case undefined: throw new ValidationError("gRPC query value has no active sum");
45
+ }
46
+ }
47
+ function mapRecord(fields, depth) {
48
+ const output = Object.create(null);
49
+ for (const [index, field] of fields.entries()) {
50
+ if (field.label.length === 0) {
51
+ throw new ValidationError(`gRPC query record field ${index} is unlabeled; verbose values are required`);
52
+ }
53
+ else if (Object.hasOwn(output, field.label)) {
54
+ throw new ValidationError(`gRPC query record has duplicate label ${field.label}`);
55
+ }
56
+ else if (field.value === undefined) {
57
+ throw new ValidationError(`gRPC query record field ${field.label} has no value`);
58
+ }
59
+ defineData(output, validNameString(field.label, `record field ${index} label`), mapValue(field.value, depth));
60
+ }
61
+ return output;
62
+ }
63
+ function mapTextMap(entries, depth) {
64
+ const output = Object.create(null);
65
+ for (const [index, entry] of entries.entries()) {
66
+ if (Object.hasOwn(output, entry.key)) {
67
+ throw new ValidationError(`gRPC query text-map has duplicate key ${entry.key}`);
68
+ }
69
+ else if (entry.value === undefined) {
70
+ throw new ValidationError(`gRPC query text-map entry ${index} has no value`);
71
+ }
72
+ defineData(output, entry.key, mapValue(entry.value, depth));
73
+ }
74
+ return output;
75
+ }
76
+ function mapGenMap(entries, depth) {
77
+ const seenKeys = new Set();
78
+ return entries.map((entry, index) => {
79
+ if (entry.key === undefined || entry.value === undefined) {
80
+ throw new ValidationError(`gRPC query gen-map entry ${index} is incomplete`);
81
+ }
82
+ const key = mapValue(entry.key, depth);
83
+ const canonicalKey = canonicalLedgerKey(entry.key);
84
+ if (seenKeys.has(canonicalKey)) {
85
+ throw new ValidationError(`gRPC query gen-map has duplicate key at entry ${index}`);
86
+ }
87
+ seenKeys.add(canonicalKey);
88
+ return [key, mapValue(entry.value, depth)];
89
+ });
90
+ }
91
+ function canonicalLedgerKey(value) {
92
+ switch (value.sum.oneofKind) {
93
+ case "unit": return "unit";
94
+ case "bool": return `bool:${value.sum.bool}`;
95
+ case "int64": return `int64:${JSON.stringify(value.sum.int64)}`;
96
+ case "date": return `date:${value.sum.date}`;
97
+ case "timestamp": return `timestamp:${JSON.stringify(value.sum.timestamp)}`;
98
+ case "numeric": return `numeric:${canonicalNumeric(value.sum.numeric)}`;
99
+ case "party": return `party:${JSON.stringify(value.sum.party)}`;
100
+ case "text": return `text:${JSON.stringify(value.sum.text)}`;
101
+ case "contractId": return `contractId:${JSON.stringify(value.sum.contractId)}`;
102
+ case "optional": return value.sum.optional.value === undefined ? "optional:none" : `optional:some(${canonicalLedgerKey(value.sum.optional.value)})`;
103
+ case "list": return `list:[${value.sum.list.elements.map(canonicalLedgerKey).join(",")}]`;
104
+ case "textMap": return `textMap:{${[...value.sum.textMap.entries].sort((left, right) => left.key.localeCompare(right.key)).map((entry) => `${JSON.stringify(entry.key)}:${canonicalLedgerKey(entry.value)}`).join(",")}}`;
105
+ case "genMap": return `genMap:{${value.sum.genMap.entries.map((entry) => `${canonicalLedgerKey(entry.key)}:${canonicalLedgerKey(entry.value)}`).sort().join(",")}}`;
106
+ case "record": return `record:{${[...value.sum.record.fields].sort((left, right) => left.label.localeCompare(right.label)).map((field) => `${JSON.stringify(field.label)}:${canonicalLedgerKey(field.value)}`).join(",")}}`;
107
+ case "variant": return `variant:${JSON.stringify(value.sum.variant.constructor)}:${canonicalLedgerKey(value.sum.variant.value)}`;
108
+ case "enum": return `enum:${JSON.stringify(value.sum.enum.constructor)}`;
109
+ case undefined: return invalidGenMapKey();
110
+ }
111
+ }
112
+ function canonicalNumeric(value) {
113
+ const sign = value.startsWith("-") ? "-" : "";
114
+ const unsigned = value.replace(/^[+-]/, "");
115
+ const [whole, fraction = ""] = unsigned.split(".");
116
+ const normalizedWhole = whole.replace(/^0+(?=\d)/, "");
117
+ const normalizedFraction = fraction.replace(/0+$/, "");
118
+ const normalized = `${normalizedWhole}${normalizedFraction.length === 0 ? "" : `.${normalizedFraction}`}`;
119
+ return /^0(?:\.0*)?$/.test(normalized) ? "0" : `${sign}${normalized}`;
120
+ }
121
+ function invalidGenMapKey() {
122
+ throw new ValidationError("gRPC query gen-map key cannot be represented as ledger JSON");
123
+ }
124
+ function defineData(target, key, value) {
125
+ Object.defineProperty(target, key, { value, enumerable: true, configurable: false, writable: false });
126
+ }
127
+ function int64(value, name) {
128
+ if (!INT64.test(value)) {
129
+ throw new ValidationError(`gRPC query ${name} is not an integer`);
130
+ }
131
+ else if (BigInt(value) < -9223372036854775808n || BigInt(value) > 9223372036854775807n) {
132
+ throw new ValidationError(`gRPC query ${name} is outside the int64 range`);
133
+ }
134
+ return value;
135
+ }
136
+ function timestamp(value) {
137
+ int64(value, "timestamp");
138
+ if (BigInt(value) < -62135596800000000n || BigInt(value) > 253402300799999999n) {
139
+ throw new ValidationError("gRPC query timestamp is outside the Ledger API range");
140
+ }
141
+ return value;
142
+ }
143
+ function date(value) {
144
+ if (!Number.isInteger(value) || value < MIN_DATE_EPOCH_DAY || value > MAX_DATE_EPOCH_DAY) {
145
+ throw new ValidationError("gRPC query date is outside the Ledger API range");
146
+ }
147
+ return value;
148
+ }
149
+ function numeric(value) {
150
+ if (!/^[+-]?\d{1,38}(?:\.\d{0,37})?$/.test(value)) {
151
+ throw new ValidationError("gRPC query numeric is invalid");
152
+ }
153
+ const unsigned = value.replace(/^[+-]/, "");
154
+ const [whole, fractional = ""] = unsigned.split(".");
155
+ const significant = `${whole}${fractional}`.replace(/^0+/, "").length;
156
+ if (fractional.length > 37 || significant > 38) {
157
+ throw new ValidationError("gRPC query numeric exceeds DAML Numeric precision");
158
+ }
159
+ return value;
160
+ }
161
+ /** Validates a generated PartyIdString without applying those rules to ordinary Text values. */
162
+ export function validPartyId(value) {
163
+ if (!/^[A-Za-z0-9:\-_ ]{1,255}$/.test(value)) {
164
+ throw new ValidationError("gRPC query party is invalid");
165
+ }
166
+ return value;
167
+ }
168
+ /** Validates a generated LedgerString used specifically as a contract identifier. */
169
+ export function validLedgerString(value, name = "ledger string") {
170
+ if (!/^[A-Za-z0-9#:\-_/ ]{1,255}$/.test(value)) {
171
+ throw new ValidationError(`gRPC query ${name} is invalid`);
172
+ }
173
+ return value;
174
+ }
175
+ /** Validates a generated NameString used by identifiers, choices, and verbose value labels. */
176
+ export function validNameString(value, name = "name") {
177
+ if (!/^[A-Za-z$_][A-Za-z0-9$_]{0,999}$/.test(value)) {
178
+ throw new ValidationError(`gRPC query ${name} is invalid`);
179
+ }
180
+ return value;
181
+ }
182
+ /** Validates an Identifier module or entity name as nonempty dot-separated NameString segments. */
183
+ export function validDottedNameString(value, name = "dotted name") {
184
+ for (const segment of value.split(".")) {
185
+ validNameString(segment, name);
186
+ }
187
+ return value;
188
+ }
189
+ /** Validates a generated PackageIdString without applying it to arbitrary package-name text. */
190
+ export function validPackageIdString(value, name = "package id") {
191
+ if (!/^[A-Za-z0-9\-_ ]{1,64}$/.test(value)) {
192
+ throw new ValidationError(`gRPC query ${name} is invalid`);
193
+ }
194
+ return value;
195
+ }
@@ -0,0 +1,67 @@
1
+ import { type QueryDataset } from "../canonical/query-dataset.js";
2
+ import type { ContractRow, EventRow, ExerciseRow, TransactionRow } from "../model-types.js";
3
+ import type { GrpcPackageMetadata } from "./grpc-package-relation-reader.js";
4
+ import type { Transaction } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/transaction.js";
5
+ import type { GetActiveContractsResponse } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/state_service.js";
6
+ export interface GrpcQueryTypeIdentity {
7
+ readonly pk: string;
8
+ readonly templateId: Readonly<{
9
+ packageId: string;
10
+ moduleName: string;
11
+ entityName: string;
12
+ }>;
13
+ readonly packageId: string;
14
+ readonly choice?: string;
15
+ readonly consuming?: boolean;
16
+ }
17
+ export interface GrpcQueryPackageIdentity {
18
+ readonly pk: string;
19
+ readonly id: string;
20
+ }
21
+ export interface GrpcQueryCreationIdentity {
22
+ readonly contractId: string;
23
+ readonly offset: string;
24
+ readonly templateId: Readonly<{
25
+ packageId: string;
26
+ moduleName: string;
27
+ entityName: string;
28
+ }>;
29
+ readonly creationPackageId: string;
30
+ readonly representativePackageId: string | null;
31
+ readonly payload: unknown;
32
+ readonly witnesses: readonly string[];
33
+ readonly createdAt: Date;
34
+ }
35
+ /** Private Task 5 activation metadata retained for later relation construction. */
36
+ export interface GrpcActiveContractIdentity {
37
+ readonly contractId: string;
38
+ readonly synchronizerId: string;
39
+ readonly reassignmentCounter: string;
40
+ readonly activationOffset: string;
41
+ readonly activationNodeId: number;
42
+ }
43
+ /**
44
+ * The Task 5 transport-neutral core. Task 6 enriches its identity descriptors with
45
+ * package metadata and creates the complete QueryDataset/edges; public rows never
46
+ * receive private PQS join columns.
47
+ */
48
+ export interface GrpcQueryRelationFragment {
49
+ readonly contracts: readonly ContractRow[];
50
+ readonly transactions: readonly TransactionRow[];
51
+ readonly events: readonly EventRow[];
52
+ readonly exercises: readonly ExerciseRow[];
53
+ readonly typeIdentities: readonly GrpcQueryTypeIdentity[];
54
+ readonly packageIdentities: readonly GrpcQueryPackageIdentity[];
55
+ readonly creationIdentities: readonly GrpcQueryCreationIdentity[];
56
+ readonly activeContractIdentities: readonly GrpcActiveContractIdentity[];
57
+ }
58
+ /** Materializes ledger-effects transactions and optionally seeds still-active ACS contracts. */
59
+ export declare function mapGrpcQueryRelationFragment(source: readonly Transaction[], activeContracts?: readonly GetActiveContractsResponse[]): GrpcQueryRelationFragment;
60
+ /**
61
+ * Combines the Task 5 ledger fragment with decoded LF package metadata into the
62
+ * complete immutable eight-relation snapshot. Private edge keys retain the
63
+ * creation template identity without exposing a synthetic field in contract rows.
64
+ */
65
+ export declare function createGrpcQueryDataset(fragment: GrpcQueryRelationFragment, packages: readonly GrpcPackageMetadata[], endInclusive: string, instanceId: string): QueryDataset;
66
+ /** Package payloads required for a contract/history relation plan, excluding creation-only provenance. */
67
+ export declare function referencedGrpcPackageIds(fragment: GrpcQueryRelationFragment): readonly string[];