@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.
- package/README.md +43 -0
- package/dist/client/canton-client-options.d.ts +2 -0
- package/dist/client/canton-client-options.js +2 -0
- package/dist/client/external-party-activation-client.js +34 -20
- package/dist/client/external-party-activation-response.d.ts +1 -1
- package/dist/core/transports/transport.interface.d.ts +16 -22
- package/dist/debugger/replay/ledger-replay-environment-builder.js +1 -1
- package/dist/index.d.ts +1 -19
- package/dist/index.js +0 -18
- package/dist/protobuf/index.d.ts +4075 -0
- package/dist/query/grpc/grpc-contract-query-client.d.ts +4 -3
- package/dist/query/grpc/grpc-contract-query-client.js +12 -4
- package/dist/query/model-types.d.ts +232 -14
- package/dist/query/model-types.js +2 -2
- package/dist/query/pqs/pqs-query-client.d.ts +14 -3
- package/dist/query/pqs/pqs-query-client.js +381 -25
- package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +23 -0
- package/dist/query/pqs/pqs-relational-sql-compiler.js +146 -0
- package/dist/query/pqs/pqs-schema-profile.d.ts +10 -0
- package/dist/query/pqs/pqs-schema-profile.js +46 -0
- package/dist/query/pqs/pqs-sql-compiler.d.ts +2 -1
- package/dist/query/pqs/pqs-sql-compiler.js +212 -10
- package/dist/query/query-client.d.ts +20 -16
- package/dist/services/party-management/decentralized-party-lifecycle.js +96 -18
- package/dist/services/party-management/party-management-service-client.d.ts +2 -2
- package/dist/services/party-management/party-management-service-client.js +34 -9
- package/dist/services/topology-manager-write/topology-manager-write-service-client.d.ts +5 -16
- package/dist/transports/grpc/grpc-call-options-factory.d.ts +1 -1
- package/dist/transports/grpc/grpc-call-options-factory.js +8 -4
- package/dist/transports/grpc/grpc-channel-factory.js +13 -8
- package/dist/transports/grpc/grpc-transport.d.ts +9 -9
- package/dist/transports/grpc/grpc-transport.js +12 -13
- package/dist/transports/grpc/mappers/topology-common-mapper.d.ts +5 -1
- package/dist/transports/grpc/mappers/topology-common-mapper.js +17 -0
- package/dist/transports/json/json-transport.d.ts +18 -16
- package/node/start-local.sh +197 -2
- package/node/stop-local.sh +25 -1
- package/node/test-start-local.sh +101 -4
- package/node/test-stop-local.sh +8 -1
- package/package.json +1 -1
- package/dist/core/types/requests/add-topology-transactions-request.d.ts +0 -16
- package/dist/core/types/requests/add-topology-transactions-request.js +0 -12
- package/dist/core/types/requests/allocate-external-party-request.d.ts +0 -18
- package/dist/core/types/requests/allocate-external-party-request.js +0 -16
- package/dist/core/types/requests/authorize-topology-transactions-request.d.ts +0 -37
- package/dist/core/types/requests/authorize-topology-transactions-request.js +0 -35
- package/dist/core/types/requests/generate-external-party-topology-request.d.ts +0 -19
- package/dist/core/types/requests/generate-external-party-topology-request.js +0 -21
- package/dist/core/types/requests/generate-topology-transactions-request.d.ts +0 -26
- package/dist/core/types/requests/generate-topology-transactions-request.js +0 -22
- package/dist/core/types/requests/import-topology-snapshot-request.d.ts +0 -12
- package/dist/core/types/requests/import-topology-snapshot-request.js +0 -10
- package/dist/core/types/requests/import-topology-snapshot-v2-request.d.ts +0 -12
- package/dist/core/types/requests/import-topology-snapshot-v2-request.js +0 -10
- package/dist/core/types/requests/sign-topology-transactions-request.d.ts +0 -15
- package/dist/core/types/requests/sign-topology-transactions-request.js +0 -12
- package/dist/core/types/responses/add-topology-transactions-response.d.ts +0 -3
- package/dist/core/types/responses/add-topology-transactions-response.js +0 -5
- package/dist/core/types/responses/allocate-external-party-response.d.ts +0 -6
- package/dist/core/types/responses/allocate-external-party-response.js +0 -6
- package/dist/core/types/responses/authorize-topology-transactions-response.d.ts +0 -7
- package/dist/core/types/responses/authorize-topology-transactions-response.js +0 -6
- package/dist/core/types/responses/generate-external-party-topology-response.d.ts +0 -12
- package/dist/core/types/responses/generate-external-party-topology-response.js +0 -12
- package/dist/core/types/responses/generate-topology-transactions-response.d.ts +0 -7
- package/dist/core/types/responses/generate-topology-transactions-response.js +0 -6
- package/dist/core/types/responses/import-topology-snapshot-response.d.ts +0 -3
- package/dist/core/types/responses/import-topology-snapshot-response.js +0 -5
- package/dist/core/types/responses/import-topology-snapshot-v2-response.d.ts +0 -3
- package/dist/core/types/responses/import-topology-snapshot-v2-response.js +0 -5
- package/dist/core/types/responses/sign-topology-transactions-response.d.ts +0 -7
- package/dist/core/types/responses/sign-topology-transactions-response.js +0 -6
- package/dist/transports/grpc/mappers/external-party-management-mapper.d.ts +0 -9
- package/dist/transports/grpc/mappers/external-party-management-mapper.js +0 -126
- package/dist/transports/grpc/mappers/topology-manager-write-mapper.d.ts +0 -25
- 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 {
|
|
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 ${
|
|
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(
|
|
204
|
-
if (entries.length !==
|
|
205
|
-
throw new Error("orderBy must
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
301
|
-
|
|
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 {};
|