@distrohelena/canton-typescript-sdk 0.1.16 → 0.1.18

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 (76) hide show
  1. package/README.md +43 -0
  2. package/dist/client/canton-client-options.d.ts +2 -0
  3. package/dist/client/canton-client-options.js +2 -0
  4. package/dist/client/external-party-activation-client.js +34 -20
  5. package/dist/client/external-party-activation-response.d.ts +1 -1
  6. package/dist/core/transports/transport.interface.d.ts +16 -22
  7. package/dist/debugger/replay/ledger-replay-environment-builder.js +1 -1
  8. package/dist/index.d.ts +1 -19
  9. package/dist/index.js +0 -18
  10. package/dist/protobuf/index.d.ts +4075 -0
  11. package/dist/query/grpc/grpc-contract-query-client.d.ts +4 -3
  12. package/dist/query/grpc/grpc-contract-query-client.js +12 -4
  13. package/dist/query/model-types.d.ts +232 -14
  14. package/dist/query/model-types.js +2 -2
  15. package/dist/query/pqs/pqs-query-client.d.ts +14 -3
  16. package/dist/query/pqs/pqs-query-client.js +381 -25
  17. package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +23 -0
  18. package/dist/query/pqs/pqs-relational-sql-compiler.js +146 -0
  19. package/dist/query/pqs/pqs-schema-profile.d.ts +10 -0
  20. package/dist/query/pqs/pqs-schema-profile.js +46 -0
  21. package/dist/query/pqs/pqs-sql-compiler.d.ts +2 -1
  22. package/dist/query/pqs/pqs-sql-compiler.js +212 -10
  23. package/dist/query/query-client.d.ts +20 -16
  24. package/dist/services/party-management/decentralized-party-lifecycle.js +96 -18
  25. package/dist/services/party-management/party-management-service-client.d.ts +2 -2
  26. package/dist/services/party-management/party-management-service-client.js +34 -9
  27. package/dist/services/topology-manager-write/topology-manager-write-service-client.d.ts +5 -16
  28. package/dist/transports/grpc/grpc-call-options-factory.d.ts +1 -1
  29. package/dist/transports/grpc/grpc-call-options-factory.js +8 -4
  30. package/dist/transports/grpc/grpc-channel-factory.js +13 -8
  31. package/dist/transports/grpc/grpc-transport.d.ts +9 -9
  32. package/dist/transports/grpc/grpc-transport.js +12 -13
  33. package/dist/transports/grpc/mappers/topology-common-mapper.d.ts +5 -1
  34. package/dist/transports/grpc/mappers/topology-common-mapper.js +17 -0
  35. package/dist/transports/json/json-transport.d.ts +18 -16
  36. package/node/start-local.sh +197 -2
  37. package/node/stop-local.sh +25 -1
  38. package/node/test-start-local.sh +101 -4
  39. package/node/test-stop-local.sh +8 -1
  40. package/package.json +1 -1
  41. package/dist/core/types/requests/add-topology-transactions-request.d.ts +0 -16
  42. package/dist/core/types/requests/add-topology-transactions-request.js +0 -12
  43. package/dist/core/types/requests/allocate-external-party-request.d.ts +0 -18
  44. package/dist/core/types/requests/allocate-external-party-request.js +0 -16
  45. package/dist/core/types/requests/authorize-topology-transactions-request.d.ts +0 -37
  46. package/dist/core/types/requests/authorize-topology-transactions-request.js +0 -35
  47. package/dist/core/types/requests/generate-external-party-topology-request.d.ts +0 -19
  48. package/dist/core/types/requests/generate-external-party-topology-request.js +0 -21
  49. package/dist/core/types/requests/generate-topology-transactions-request.d.ts +0 -26
  50. package/dist/core/types/requests/generate-topology-transactions-request.js +0 -22
  51. package/dist/core/types/requests/import-topology-snapshot-request.d.ts +0 -12
  52. package/dist/core/types/requests/import-topology-snapshot-request.js +0 -10
  53. package/dist/core/types/requests/import-topology-snapshot-v2-request.d.ts +0 -12
  54. package/dist/core/types/requests/import-topology-snapshot-v2-request.js +0 -10
  55. package/dist/core/types/requests/sign-topology-transactions-request.d.ts +0 -15
  56. package/dist/core/types/requests/sign-topology-transactions-request.js +0 -12
  57. package/dist/core/types/responses/add-topology-transactions-response.d.ts +0 -3
  58. package/dist/core/types/responses/add-topology-transactions-response.js +0 -5
  59. package/dist/core/types/responses/allocate-external-party-response.d.ts +0 -6
  60. package/dist/core/types/responses/allocate-external-party-response.js +0 -6
  61. package/dist/core/types/responses/authorize-topology-transactions-response.d.ts +0 -7
  62. package/dist/core/types/responses/authorize-topology-transactions-response.js +0 -6
  63. package/dist/core/types/responses/generate-external-party-topology-response.d.ts +0 -12
  64. package/dist/core/types/responses/generate-external-party-topology-response.js +0 -12
  65. package/dist/core/types/responses/generate-topology-transactions-response.d.ts +0 -7
  66. package/dist/core/types/responses/generate-topology-transactions-response.js +0 -6
  67. package/dist/core/types/responses/import-topology-snapshot-response.d.ts +0 -3
  68. package/dist/core/types/responses/import-topology-snapshot-response.js +0 -5
  69. package/dist/core/types/responses/import-topology-snapshot-v2-response.d.ts +0 -3
  70. package/dist/core/types/responses/import-topology-snapshot-v2-response.js +0 -5
  71. package/dist/core/types/responses/sign-topology-transactions-response.d.ts +0 -7
  72. package/dist/core/types/responses/sign-topology-transactions-response.js +0 -6
  73. package/dist/transports/grpc/mappers/external-party-management-mapper.d.ts +0 -9
  74. package/dist/transports/grpc/mappers/external-party-management-mapper.js +0 -126
  75. package/dist/transports/grpc/mappers/topology-manager-write-mapper.d.ts +0 -25
  76. package/dist/transports/grpc/mappers/topology-manager-write-mapper.js +0 -475
@@ -1,8 +1,21 @@
1
1
  import { QuerySource } from "../query-source.js";
2
2
  import { PqsQueryError } from "../errors/pqs-query-error.js";
3
- import { compileContractFindMany } from "./pqs-sql-compiler.js";
4
- import { pqsRelationMetadata, } from "./pqs-schema-profile.js";
3
+ import { compileContractFindMany, compileContractGroupBy } from "./pqs-sql-compiler.js";
4
+ import { compilePqsRelationGroupBy } from "./pqs-relational-sql-compiler.js";
5
+ import { PqsSchemaProfileV1, pqsRelationEdges, pqsRelationMetadata, } from "./pqs-schema-profile.js";
5
6
  import { assertReadOnlySql } from "./read-only-sql.js";
7
+ const logicalContractFields = [
8
+ "contractId",
9
+ "templateId",
10
+ "packageId",
11
+ "payload",
12
+ "witnesses",
13
+ "createdEventOffset",
14
+ "createdAt",
15
+ "archivedEventOffset",
16
+ "archivedAt",
17
+ "active",
18
+ ];
6
19
  export class PqsQueryClient {
7
20
  executor;
8
21
  profile;
@@ -13,10 +26,12 @@ export class PqsQueryClient {
13
26
  findUnique: (args) => this.findContractsAsync({
14
27
  where: { contractId: { equals: args.where.contractId } },
15
28
  select: args.select,
29
+ include: args.include,
16
30
  take: 1,
17
31
  }).then((rows) => rows[0]),
18
32
  count: async (args = {}) => (await this.findContractsAsync(args)).length,
19
33
  aggregate: async (args) => this.aggregateContractsAsync(args),
34
+ groupBy: async (args) => this.groupContractsAsync(args),
20
35
  };
21
36
  contractTypes = this.createPhysicalDelegate("__contract_tpe");
22
37
  events = this.createPhysicalDelegate("__events");
@@ -57,6 +72,7 @@ export class PqsQueryClient {
57
72
  }
58
73
  },
59
74
  aggregate: async (args) => this.aggregatePhysicalAsync(relation, metadata, args),
75
+ groupBy: async (args) => this.groupPhysicalAsync(relation, args),
60
76
  };
61
77
  if (!hasUnique) {
62
78
  return delegate;
@@ -68,11 +84,25 @@ export class PqsQueryClient {
68
84
  return findMany({
69
85
  where: Object.fromEntries(Object.entries(args.where).map(([field, value]) => [field, { equals: value }])),
70
86
  select: args.select,
87
+ include: args.include,
71
88
  take: 1,
72
89
  }).then((rows) => rows[0]);
73
90
  },
74
91
  };
75
92
  }
93
+ async groupPhysicalAsync(relation, args) {
94
+ const root = relation === "__events" ? '"event"' : '"root"';
95
+ const filter = this.compileWhere(relation, pqsRelationMetadata[relation], args.where, undefined, root);
96
+ const compiled = compilePqsRelationGroupBy(relation, args, this.profile, filter.values.length);
97
+ const text = filter.where.length === 0 ? compiled.text : compiled.text.replace(" group by ", `${filter.where} group by `);
98
+ try {
99
+ await this.ready;
100
+ return (await this.executor.query(text, [...filter.values, ...compiled.values])).rows.map((row) => Object.fromEntries(Object.entries(row).map(([name, value]) => [name, name === "count" ? Number(value) : value instanceof Date ? value : value === null ? null : String(value)])));
101
+ }
102
+ catch (cause) {
103
+ throw this.wrap(`${relation}.groupBy`, cause);
104
+ }
105
+ }
76
106
  async readPhysicalAsync(relation, metadata, args) {
77
107
  this.assertPage(args);
78
108
  const selected = this.selectedFields(relation, metadata, args.select);
@@ -86,10 +116,13 @@ export class PqsQueryClient {
86
116
  const limit = args.take === undefined ? "" : ` limit ${add(args.take)}`;
87
117
  const offset = args.skip === undefined ? "" : ` offset ${add(args.skip)}`;
88
118
  const selection = selected.map(([field, column]) => `"${column}" as "${field}"`).join(", ");
119
+ const includes = this.compileIncludes(relation, args.include, add);
120
+ const json = this.compileJsonSelections(relation, args.select?.json, add);
121
+ const fullSelection = [selection, ...json.selection, ...includes.selection].filter((value) => value.length > 0).join(", ");
89
122
  try {
90
123
  await this.ready;
91
- const result = await this.executor.query(`select ${selection} from ${this.profile.relation(relation)}${where}${orderBy}${limit}${offset}`, parameters);
92
- return result.rows.map((row) => this.mapPhysicalRow(row, metadata, selected));
124
+ const result = await this.executor.query(`select ${fullSelection} from ${this.profile.relation(relation)}${where}${orderBy}${limit}${offset}`, parameters);
125
+ return result.rows.map((row) => this.mapPhysicalRow(row, metadata, selected, includes.values, json.fields));
93
126
  }
94
127
  catch (cause) {
95
128
  throw this.wrap(`${relation}.findMany`, cause);
@@ -128,12 +161,16 @@ export class PqsQueryClient {
128
161
  throw this.wrap(`${relation}.aggregate`, cause);
129
162
  }
130
163
  }
131
- compileWhere(relation, metadata, filters) {
164
+ compileWhere(relation, metadata, filters, externalAdd, parentExpression = this.profile.relation(relation)) {
132
165
  const values = [];
133
- const add = (value) => {
166
+ const add = externalAdd ?? ((value) => {
134
167
  values.push(value);
135
168
  return `$${values.length}`;
136
- };
169
+ });
170
+ if (relation === "__contracts") {
171
+ const where = this.compileLogicalContractWhere(filters, add, parentExpression);
172
+ return { where: where === "true" ? "" : ` where ${where}`, values };
173
+ }
137
174
  const compile = (expression) => {
138
175
  const conditions = [];
139
176
  for (const [field, filter] of Object.entries(expression ?? {})) {
@@ -149,10 +186,61 @@ export class PqsQueryClient {
149
186
  conditions.push(`not (${compile(filter)})`);
150
187
  continue;
151
188
  }
189
+ const edge = pqsRelationEdges[relation]?.[field];
190
+ if (edge !== undefined) {
191
+ if (filter === null || Array.isArray(filter) || typeof filter !== "object")
192
+ throw new Error(`${field} must be a relation filter`);
193
+ const related = filter;
194
+ const alias = `"${field}"`;
195
+ const relatedCondition = (child) => {
196
+ const where = this.compileWhere(edge.target, pqsRelationMetadata[edge.target], child, add, alias).where;
197
+ return where.length === 0 ? "true" : where.slice(" where ".length);
198
+ };
199
+ const join = `${alias}."${edge.targetColumn}" = ${parentExpression}."${edge.sourceColumn}"`;
200
+ if (edge.cardinality === "one") {
201
+ if (Object.keys(related).some((name) => ["some", "none", "every"].includes(name)))
202
+ throw new Error(`${field} is a to-one relation`);
203
+ conditions.push(`exists (select 1 from ${this.profile.relation(edge.target)} ${alias} where ${join} and (${relatedCondition(related)}))`);
204
+ }
205
+ else {
206
+ const operators = ["some", "none", "every"].filter((name) => related[name] !== undefined);
207
+ if (operators.length !== 1)
208
+ throw new Error(`${field} requires exactly one of some, none, or every`);
209
+ const operator = operators[0];
210
+ const predicate = related[operator];
211
+ const condition = relatedCondition(predicate);
212
+ const subquery = `select 1 from ${this.profile.relation(edge.target)} ${alias} where ${join} and (${condition})`;
213
+ if (operator === "some")
214
+ conditions.push(`exists (${subquery})`);
215
+ else if (operator === "none")
216
+ conditions.push(`not exists (${subquery})`);
217
+ else
218
+ conditions.push(`not exists (select 1 from ${this.profile.relation(edge.target)} ${alias} where ${join} and not (${condition}))`);
219
+ }
220
+ continue;
221
+ }
152
222
  const column = this.field(relation, metadata, field);
153
223
  if (filter === null || Array.isArray(filter) || typeof filter !== "object")
154
224
  throw new Error(`${field} must be a filter`);
155
225
  const scalar = filter;
226
+ if (PqsSchemaProfileV1.jsonField(relation, field) && scalar.path !== undefined) {
227
+ if (scalar.path.length === 0 || scalar.path.some((segment) => segment.length === 0))
228
+ throw new Error(`${field}.path must be a non-empty JSON path`);
229
+ const expression = `"${column}" #>> ${add(scalar.path)}::text[]`;
230
+ if (scalar.is === null)
231
+ conditions.push(`${expression} is null`);
232
+ if (scalar.isNot === null)
233
+ conditions.push(`${expression} is not null`);
234
+ if (scalar.equals !== undefined)
235
+ conditions.push(`${expression} = ${add(scalar.equals)}`);
236
+ if (scalar.in !== undefined)
237
+ conditions.push(scalar.in.length === 0 ? "false" : `${expression} = any(${add(scalar.in)})`);
238
+ for (const [operator, value] of [["lt", scalar.lt], ["lte", scalar.lte], ["gt", scalar.gt], ["gte", scalar.gte], ["like", scalar.like], ["ilike", scalar.ilike]]) {
239
+ if (value !== undefined)
240
+ conditions.push(`${expression} ${operator === "lte" ? "<=" : operator === "gte" ? ">=" : operator} ${add(value)}`);
241
+ }
242
+ continue;
243
+ }
156
244
  if (scalar.is === null) {
157
245
  conditions.push(`"${column}" is null`);
158
246
  }
@@ -186,12 +274,65 @@ export class PqsQueryClient {
186
274
  const where = compile(filters);
187
275
  return { where: where === "true" ? "" : ` where ${where}`, values };
188
276
  }
277
+ compileLogicalContractWhere(filters, add, alias) {
278
+ const compile = (expression) => {
279
+ const conditions = [];
280
+ for (const [field, filter] of Object.entries(expression ?? {})) {
281
+ if (field === "and" || field === "or") {
282
+ if (!Array.isArray(filter))
283
+ throw new Error(`${field} must be an array`);
284
+ conditions.push(filter.length === 0 ? field === "and" ? "true" : "false" : `(${filter.map((child) => compile(child)).join(` ${field} `)})`);
285
+ continue;
286
+ }
287
+ if (field === "not") {
288
+ conditions.push(`not (${compile(filter)})`);
289
+ continue;
290
+ }
291
+ if (field === "active") {
292
+ const value = typeof filter === "boolean" ? filter : filter.equals;
293
+ if (typeof value !== "boolean")
294
+ throw new Error("active supports only equals");
295
+ conditions.push(`${alias}."archived_at_ix" is ${value ? "null" : "not null"}`);
296
+ continue;
297
+ }
298
+ if (field === "witnesses") {
299
+ const value = filter.has;
300
+ if (typeof value !== "string")
301
+ throw new Error("witnesses supports only has");
302
+ conditions.push(`${add(value)} = any(${alias}."witnesses")`);
303
+ continue;
304
+ }
305
+ if (field === "payload") {
306
+ const match = filter.match;
307
+ if (match === undefined || typeof match !== "object" || match === null)
308
+ throw new Error("payload requires match");
309
+ for (const [key, value] of Object.entries(match))
310
+ if (value.equals !== undefined)
311
+ conditions.push(`${alias}."payload" #>> ${add([key])}::text[] = ${add(value.equals)}`);
312
+ continue;
313
+ }
314
+ const columns = { contractId: "contract_id", packageId: "creation_package_id", createdEventOffset: "created_at_ix", archivedEventOffset: "archived_at_ix" };
315
+ const column = columns[field];
316
+ if (column === undefined || filter === null || typeof filter !== "object")
317
+ throw new Error(`${field} is not a supported contract filter`);
318
+ const value = filter;
319
+ if (value.equals !== undefined)
320
+ conditions.push(`${alias}."${column}" = ${add(value.equals)}`);
321
+ else if (value.in !== undefined)
322
+ conditions.push(value.in.length === 0 ? "false" : `${alias}."${column}" = any(${add(value.in)})`);
323
+ else
324
+ throw new Error(`${field} requires equals or in`);
325
+ }
326
+ return conditions.length === 0 ? "true" : conditions.join(" and ");
327
+ };
328
+ return compile(filters);
329
+ }
189
330
  selectedFields(relation, metadata, select) {
190
331
  if (select === undefined) {
191
332
  return Object.entries(metadata.fields);
192
333
  }
193
- const selected = Object.entries(select).filter(([, enabled]) => enabled).map(([field]) => [field, this.field(relation, metadata, field)]);
194
- if (selected.length === 0) {
334
+ const selected = Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => [field, this.field(relation, metadata, field)]);
335
+ if (selected.length === 0 && Object.keys(select.json ?? {}).length === 0) {
195
336
  throw new Error("select must include at least one field");
196
337
  }
197
338
  return selected;
@@ -200,15 +341,11 @@ export class PqsQueryClient {
200
341
  if (orderBy === undefined) {
201
342
  return "";
202
343
  }
203
- const entries = Object.entries(orderBy);
204
- if (entries.length !== 1) {
205
- throw new Error("orderBy must specify exactly one field");
344
+ const entries = orderBy.flatMap((entry) => Object.entries(entry));
345
+ if (entries.length === 0 || entries.some(([, direction]) => direction !== "asc" && direction !== "desc")) {
346
+ throw new Error("orderBy must be a non-empty list of one-field entries");
206
347
  }
207
- const [field, direction] = entries[0];
208
- if (direction !== "asc" && direction !== "desc") {
209
- throw new Error("orderBy direction must be asc or desc");
210
- }
211
- return ` order by "${this.field(relation, metadata, field)}" ${direction}`;
348
+ return ` order by ${entries.map(([field, direction]) => `"${this.field(relation, metadata, field)}" ${direction}`).join(", ")}`;
212
349
  }
213
350
  assertUniqueWhere(relation, metadata, where) {
214
351
  const fields = Object.keys(where).sort();
@@ -236,24 +373,168 @@ export class PqsQueryClient {
236
373
  }
237
374
  }
238
375
  }
239
- mapPhysicalRow(row, metadata, fields) {
240
- return Object.fromEntries(fields
376
+ mapPhysicalRow(row, metadata, fields, includes, json = {}) {
377
+ const scalar = Object.fromEntries(fields
241
378
  .filter(([field]) => Object.hasOwn(row, field))
242
379
  .map(([field]) => [field, mapPhysicalValue(row[field], metadata, field)]));
380
+ const related = Object.fromEntries(Object.entries(includes).map(([name, edge]) => [name, this.mapRelatedValue(row[name], edge.target, edge.options)]));
381
+ const projected = Object.fromEntries(Object.entries(json).map(([name, as]) => [name, mapJsonValue(row[name], as)]));
382
+ return { ...scalar, ...projected, ...related };
383
+ }
384
+ mapRelatedValue(value, relation, options) {
385
+ if (value === null || value === undefined)
386
+ return value ?? null;
387
+ if (Array.isArray(value))
388
+ return value.map((entry) => this.mapRelatedValue(entry, relation, options));
389
+ if (typeof value !== "object")
390
+ throw new Error(`Invalid included ${relation} row`);
391
+ if (relation === "__contracts")
392
+ return this.mapLogicalContractValue(value, options);
393
+ const metadata = pqsRelationMetadata[relation];
394
+ const row = value;
395
+ const fields = this.selectedFields(relation, metadata, options.select);
396
+ const nested = Object.fromEntries(Object.entries(options.include ?? {}).map(([name, option]) => {
397
+ const edge = pqsRelationEdges[relation]?.[name];
398
+ if (edge === undefined)
399
+ throw new Error(`${name} is not a relation of ${relation}`);
400
+ return [name, { target: edge.target, options: option === true ? {} : option }];
401
+ }));
402
+ const json = Object.fromEntries(Object.entries(options.select?.json ?? {}).map(([name, projection]) => [name, projection.as]));
403
+ const normalized = Object.fromEntries(fields
404
+ .filter(([field, column]) => Object.hasOwn(row, field) || Object.hasOwn(row, column))
405
+ .map(([field, column]) => [field, row[field] ?? row[column]]));
406
+ for (const name of Object.keys(nested))
407
+ normalized[name] = row[name];
408
+ for (const name of Object.keys(json))
409
+ normalized[name] = row[name];
410
+ return this.mapPhysicalRow(normalized, metadata, fields, nested, json);
411
+ }
412
+ mapLogicalContractValue(row, options) {
413
+ const fields = options.select === undefined
414
+ ? logicalContractFields
415
+ : Object.entries(options.select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => {
416
+ if (!logicalContractFields.includes(field))
417
+ throw new Error(`${field} is not a field of __contracts`);
418
+ return field;
419
+ });
420
+ const scalar = Object.fromEntries(fields.filter((field) => Object.hasOwn(row, field)).map((field) => [field, row[field]]));
421
+ const relations = Object.fromEntries(Object.entries(options.include ?? {}).map(([name, option]) => {
422
+ const edge = pqsRelationEdges.__contracts?.[name];
423
+ if (edge === undefined)
424
+ throw new Error(`${name} is not a relation of __contracts`);
425
+ return [name, this.mapRelatedValue(row[name], edge.target, option === true ? {} : option)];
426
+ }));
427
+ const json = Object.fromEntries(Object.entries(options.select?.json ?? {}).map(([name, projection]) => [name, mapJsonValue(row[name], projection.as)]));
428
+ return { ...scalar, ...json, ...relations };
429
+ }
430
+ compileIncludes(relation, include, add, parentExpression = this.profile.relation(relation)) {
431
+ const selections = [];
432
+ const values = {};
433
+ for (const [name, option] of Object.entries(include ?? {})) {
434
+ const edge = pqsRelationEdges[relation]?.[name];
435
+ if (edge === undefined)
436
+ throw new Error(`${name} is not a relation of ${relation}`);
437
+ const options = option === true ? {} : option;
438
+ if (edge.cardinality === "many" && options.take === undefined)
439
+ throw new Error(`${name} is a to-many relation and requires take`);
440
+ if (options.take !== undefined && (!Number.isInteger(options.take) || options.take < 0))
441
+ throw new Error(`${name}.take must be a non-negative integer`);
442
+ const expression = this.compileIncludedExpression(name, edge.target, edge.sourceColumn, edge.targetColumn, edge.cardinality, options, add, parentExpression);
443
+ selections.push(`${expression} as "${name}"`);
444
+ values[name] = { target: edge.target, options };
445
+ }
446
+ return { selection: selections, values };
447
+ }
448
+ compileJsonSelections(relation, selections, add, tableAlias) {
449
+ const selection = [];
450
+ const fields = {};
451
+ for (const [name, projection] of Object.entries(selections ?? {})) {
452
+ if (!PqsSchemaProfileV1.jsonField(relation, projection.field))
453
+ throw new Error(`${projection.field} is not a JSON field of ${relation}`);
454
+ if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
455
+ throw new Error(`${name}.path must be a non-empty JSON path`);
456
+ const column = this.field(relation, pqsRelationMetadata[relation], projection.field);
457
+ const text = `${tableAlias === undefined ? "" : `${tableAlias}.`}"${column}" #>> ${add(projection.path)}::text[]`;
458
+ const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
459
+ selection.push(`${expression} as "${name}"`);
460
+ fields[name] = projection.as;
461
+ }
462
+ return { selection, fields };
463
+ }
464
+ compileIncludedExpression(name, target, sourceColumn, targetColumn, cardinality, options, add, parentExpression) {
465
+ const alias = `"${name}"`;
466
+ const targetMetadata = pqsRelationMetadata[target];
467
+ const fields = target === "__contracts" ? [] : this.selectedFields(target, targetMetadata, options.select);
468
+ const json = target === "__contracts"
469
+ ? this.compileLogicalContractJsonSelections(options.select?.json, add, alias)
470
+ : this.compileJsonSelections(target, options.select?.json, add, alias).selection;
471
+ const nested = this.compileIncludes(target, options.include, add, alias).selection;
472
+ const scalarSelections = target === "__contracts"
473
+ ? this.compileLogicalContractSelections(alias, options.select)
474
+ : fields.map(([field, column]) => `'${field}', ${alias}."${column}"`);
475
+ const object = `jsonb_build_object(${[...scalarSelections, ...[...json, ...nested].flatMap((selection) => {
476
+ const match = /^(.*) as "([^"]+)"$/.exec(selection);
477
+ return match === null ? [] : [`'${match[2]}', ${match[1]}`];
478
+ })].join(", ")})`;
479
+ const { where } = this.compileWhere(target, targetMetadata, options.where, add, alias);
480
+ const condition = `${alias}."${targetColumn}" = ${parentExpression}."${sourceColumn}"`;
481
+ const childWhere = where.length === 0 ? ` where ${condition}` : `${where} and ${condition}`;
482
+ if (cardinality === "one")
483
+ return `(select ${object} from ${this.profile.relation(target)} ${alias}${childWhere})`;
484
+ const orderBy = this.compileOrderBy(target, targetMetadata, options.orderBy);
485
+ return `(select coalesce(jsonb_agg("${name}_limited".value), '[]'::jsonb) from (select ${object} as value from ${this.profile.relation(target)} ${alias}${childWhere}${orderBy} limit ${add(options.take)}) "${name}_limited")`;
486
+ }
487
+ compileLogicalContractSelections(alias, select) {
488
+ const selected = select === undefined
489
+ ? logicalContractFields
490
+ : Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => field);
491
+ if (selected.length === 0 && Object.keys(select?.json ?? {}).length === 0)
492
+ throw new Error("select must include at least one field");
493
+ const expressions = {
494
+ contractId: `${alias}."contract_id"`,
495
+ templateId: `jsonb_build_object('packageId', ${alias}."creation_package_id", 'moduleName', (select contract_type."module_name" from ${this.profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"), 'entityName', (select contract_type."entity_name" from ${this.profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"))`,
496
+ packageId: `${alias}."creation_package_id"`,
497
+ payload: `${alias}."payload"`,
498
+ witnesses: `${alias}."witnesses"`,
499
+ createdEventOffset: `${alias}."created_at_ix"::text`,
500
+ createdAt: `(select created_transaction."effective_at" from ${this.profile.relation("__transactions")} created_transaction where created_transaction."ix" = ${alias}."created_at_ix")`,
501
+ archivedEventOffset: `${alias}."archived_at_ix"::text`,
502
+ archivedAt: `(select archived_transaction."effective_at" from ${this.profile.relation("__transactions")} archived_transaction where archived_transaction."ix" = ${alias}."archived_at_ix")`,
503
+ active: `${alias}."archived_at_ix" is null`,
504
+ };
505
+ return selected.map((field) => {
506
+ const expression = expressions[field];
507
+ if (expression === undefined)
508
+ throw new Error(`${field} is not a field of __contracts`);
509
+ return `'${field}', ${expression}`;
510
+ });
511
+ }
512
+ compileLogicalContractJsonSelections(selections, add, alias) {
513
+ return Object.entries(selections ?? {}).map(([name, projection]) => {
514
+ if (projection.field !== "payload")
515
+ throw new Error(`${projection.field} is not a JSON field of __contracts`);
516
+ if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
517
+ throw new Error(`${name}.path must be a non-empty JSON path`);
518
+ const text = `${alias}."payload" #>> ${add(projection.path)}::text[]`;
519
+ const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
520
+ return `${expression} as "${name}"`;
521
+ });
243
522
  }
244
523
  async findContractsAsync(args) {
245
524
  const compiled = compileContractFindMany(args, this.profile);
246
525
  try {
247
- const rows = (await this.executor.query(compiled.text, compiled.values)).rows.map(mapContractRow);
526
+ const include = "include" in args ? args.include : undefined;
527
+ const rows = (await this.executor.query(compiled.text, compiled.values)).rows.map((row) => mapContractRow(row, include, "select" in args ? args.select?.json : undefined));
248
528
  const select = "select" in args ? args.select : undefined;
249
529
  if (select === undefined) {
250
530
  return rows;
251
531
  }
252
- const fields = Object.entries(select).filter(([, enabled]) => enabled).map(([field]) => field);
253
- if (fields.length === 0) {
532
+ const fields = Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => field);
533
+ const json = Object.keys(select.json ?? {});
534
+ if (fields.length === 0 && json.length === 0) {
254
535
  throw new Error("select must include at least one field");
255
536
  }
256
- return rows.map((row) => Object.fromEntries(fields.map((field) => [field, row[field]])));
537
+ return rows.map((row) => Object.fromEntries([...fields.map((field) => [field, row[field]]), ...json.map((field) => [field, row[field]])]));
257
538
  }
258
539
  catch (cause) {
259
540
  throw this.wrap("contracts.findMany", cause);
@@ -275,6 +556,16 @@ export class PqsQueryClient {
275
556
  }
276
557
  return result;
277
558
  }
559
+ async groupContractsAsync(args) {
560
+ const compiled = compileContractGroupBy(args, this.profile);
561
+ try {
562
+ await this.ready;
563
+ return (await this.executor.query(compiled.text, compiled.values)).rows.map((row) => Object.fromEntries(Object.entries(row).map(([name, value]) => [name, name === "count" ? Number(value) : value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date ? value : String(value)])));
564
+ }
565
+ catch (cause) {
566
+ throw this.wrap("contracts.groupBy", cause);
567
+ }
568
+ }
278
569
  wrap(operation, cause) {
279
570
  return new PqsQueryError({ operation, code: getPqsCode(cause), cause });
280
571
  }
@@ -297,8 +588,73 @@ function mapPhysicalValue(value, metadata, field) {
297
588
  }
298
589
  return value;
299
590
  }
300
- function mapContractRow(row) {
301
- return { contractId: String(row.contract_id), templateId: { packageId: String(row.template_package_id), moduleName: String(row.template_module_name), entityName: String(row.template_entity_name) }, packageId: nullableString(row.package_id), payload: row.payload, witnesses: stringArray(row.witnesses), createdEventOffset: String(row.created_event_offset), createdAt: nullableDate(row.created_at), archivedEventOffset: nullableString(row.archived_event_offset), archivedAt: nullableDate(row.archived_at), active: row.active === true };
591
+ function mapJsonValue(value, as) {
592
+ if (value === null || value === undefined)
593
+ return null;
594
+ if (as === "boolean")
595
+ return value === true || value === "true";
596
+ if (as === "timestamp")
597
+ return value instanceof Date ? value : new Date(String(value));
598
+ return String(value);
599
+ }
600
+ function mapProfileJsonRow(value, relation) {
601
+ if (value === null || value === undefined)
602
+ return null;
603
+ if (typeof value !== "object" || Array.isArray(value))
604
+ throw new Error(`Invalid included ${relation} row`);
605
+ const row = value;
606
+ const metadata = pqsRelationMetadata[relation];
607
+ return Object.fromEntries(Object.entries(metadata.fields).map(([field, column]) => [field, mapPhysicalValue(row[field] ?? row[column], metadata, field)]));
608
+ }
609
+ function mapSelectedProfileJsonRow(value, relation) {
610
+ if (value === null || value === undefined)
611
+ return null;
612
+ if (typeof value !== "object" || Array.isArray(value))
613
+ throw new Error(`Invalid included ${relation} row`);
614
+ const row = value;
615
+ const metadata = pqsRelationMetadata[relation];
616
+ return Object.fromEntries(Object.entries(metadata.fields)
617
+ .filter(([field, column]) => Object.hasOwn(row, field) || Object.hasOwn(row, column))
618
+ .map(([field, column]) => [field, mapPhysicalValue(row[field] ?? row[column], metadata, field)]));
619
+ }
620
+ function mapContractTypeJson(value) {
621
+ const row = mapProfileJsonRow(value, "__contract_tpe");
622
+ return row === null ? undefined : { pk: String(row.pk), payloadType: String(row.payloadType), aliases: stringArray(row.aliases), packageName: String(row.packageName), moduleName: String(row.moduleName), entityName: String(row.entityName), templateFqn: String(row.templateFqn) };
623
+ }
624
+ function mapTransactionJson(value) {
625
+ const row = mapProfileJsonRow(value, "__transactions");
626
+ return row === null ? undefined : { ix: String(row.ix), offset: String(row.offset), transactionId: nullableString(row.transactionId), effectiveAt: nullableDate(row.effectiveAt), workflowId: nullableString(row.workflowId), domainId: nullableString(row.domainId), traceContext: row.traceContext, externalTransactionHash: row.externalTransactionHash instanceof Uint8Array ? row.externalTransactionHash : null, paidTrafficCost: nullableString(row.paidTrafficCost) };
627
+ }
628
+ function mapEventJson(value) {
629
+ const row = mapSelectedProfileJsonRow(value, "__events");
630
+ return row ?? undefined;
631
+ }
632
+ function mapExerciseJson(value) {
633
+ const raw = value;
634
+ const row = mapSelectedProfileJsonRow(value, "__exercises");
635
+ if (row === null)
636
+ throw new Error("Included exercise cannot be null");
637
+ const base = row;
638
+ const knownFields = new Set(Object.entries(pqsRelationMetadata.__exercises.fields).flatMap(([field, column]) => [field, column]));
639
+ const knownRelations = new Set(Object.keys(pqsRelationEdges.__exercises ?? {}));
640
+ const projections = Object.fromEntries(Object.entries(raw).filter(([name]) => !knownFields.has(name) && !knownRelations.has(name)));
641
+ return {
642
+ ...base,
643
+ ...projections,
644
+ ...(raw.event === undefined ? {} : { event: raw.event === null ? null : mapEventJson(raw.event) ?? null }),
645
+ ...(raw.transaction === undefined ? {} : { transaction: raw.transaction === null ? null : mapTransactionJson(raw.transaction) ?? null }),
646
+ };
647
+ }
648
+ function mapContractRow(row, include, json) {
649
+ const base = { contractId: String(row.contract_id), templateId: { packageId: String(row.template_package_id), moduleName: String(row.template_module_name), entityName: String(row.template_entity_name) }, packageId: nullableString(row.package_id), payload: row.payload, witnesses: stringArray(row.witnesses), createdEventOffset: String(row.created_event_offset), createdAt: nullableDate(row.created_at), archivedEventOffset: nullableString(row.archived_event_offset), archivedAt: nullableDate(row.archived_at), active: row.active === true };
650
+ const relations = {
651
+ ...(include?.contractType === undefined ? {} : { contractType: mapContractTypeJson(row.contractType ?? row.contract_type) }),
652
+ ...(include?.createdTransaction === undefined ? {} : { createdTransaction: mapTransactionJson(row.createdTransaction ?? row.created_transaction) }),
653
+ ...(include?.archivedTransaction === undefined ? {} : { archivedTransaction: (row.archivedTransaction ?? row.archived_transaction) === null || (row.archivedTransaction ?? row.archived_transaction) === undefined ? null : mapTransactionJson(row.archivedTransaction ?? row.archived_transaction) ?? null }),
654
+ ...(include?.exercises === undefined ? {} : { exercises: Array.isArray(row.exercises) ? row.exercises.map(mapExerciseJson) : [] }),
655
+ };
656
+ const projections = Object.fromEntries(Object.entries(json ?? {}).map(([name, projection]) => [name, mapJsonValue(row[name], projection.as)]));
657
+ return { ...base, ...relations, ...projections };
302
658
  }
303
659
  function nullableString(value) {
304
660
  return value === null || value === undefined ? null : String(value);
@@ -0,0 +1,23 @@
1
+ import { PqsRelation, PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
2
+ export interface CompiledPqsRelationQuery {
3
+ readonly text: string;
4
+ readonly values: readonly unknown[];
5
+ }
6
+ interface RelationFindManyArgs {
7
+ readonly where?: Readonly<Record<string, unknown>>;
8
+ readonly select?: Readonly<Record<string, boolean>>;
9
+ readonly orderBy?: readonly Readonly<Record<string, "asc" | "desc">>[];
10
+ readonly take?: number;
11
+ readonly skip?: number;
12
+ }
13
+ export declare function compilePqsRelationFindMany(relation: PqsRelation, args: RelationFindManyArgs, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
14
+ export declare function compilePqsRelationGroupBy(relation: PqsRelation, args: {
15
+ readonly by: readonly unknown[];
16
+ readonly aggregate: {
17
+ readonly count?: true;
18
+ readonly min?: readonly string[];
19
+ readonly max?: readonly string[];
20
+ readonly sum?: readonly string[];
21
+ };
22
+ }, profile: PqsSchemaProfileV1, parameterOffset?: number): CompiledPqsRelationQuery;
23
+ export {};