@distrohelena/canton-typescript-sdk 0.1.35 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -51
- package/dist/cjs/core/types/requests/{submit-command-request.js → submit-commands-request.js} +10 -6
- package/dist/cjs/index.js +10 -8
- package/dist/cjs/query/canonical/in-memory-query-evaluator.js +291 -0
- package/dist/cjs/query/canonical/public-identity.js +41 -0
- package/dist/cjs/query/canonical/query-ast.js +2 -0
- package/dist/cjs/query/canonical/query-dataset.js +351 -0
- package/dist/cjs/query/canonical/query-normalizer.js +655 -0
- package/dist/cjs/query/canonical/query-schema.js +86 -0
- package/dist/cjs/query/canton-manager.js +14 -5
- package/dist/cjs/query/errors/query-snapshot-incomplete-error.js +19 -0
- package/dist/cjs/query/grpc/grpc-contract-cache.js +484 -0
- package/dist/cjs/query/grpc/grpc-package-relation-reader.js +218 -0
- package/dist/cjs/query/grpc/grpc-query-client.js +249 -0
- package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +238 -0
- package/dist/cjs/query/grpc/grpc-query-value-mapper.js +204 -0
- package/dist/cjs/query/grpc/grpc-relation-mapper.js +794 -0
- package/dist/cjs/query/pqs/pqs-query-client.js +151 -582
- package/dist/cjs/query/pqs/pqs-relational-sql-compiler.js +138 -95
- package/dist/cjs/query/pqs/pqs-result-shape.js +28 -0
- package/dist/cjs/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/cjs/query/pqs/pqs-sql-compiler.js +343 -289
- package/dist/cjs/query/pqs/pqs-sql-syntax.js +10 -0
- package/dist/cjs/services/command/command-service-client.js +16 -3
- package/dist/cjs/services/commands/command-payload-builder.js +1 -1
- package/dist/cjs/services/commands/command-submission-pipeline.js +21 -6
- package/dist/cjs/services/state/state-service-client.js +56 -0
- package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
- package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
- package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/cjs/transports/json/json-transport.js +1 -1
- package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
- package/dist/core/transports/transport.interface.d.ts +4 -4
- package/dist/core/types/prepared-command-submission.d.ts +3 -3
- package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
- package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
- package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
- package/dist/query/canonical/public-identity.d.ts +14 -0
- package/dist/query/canonical/public-identity.js +35 -0
- package/dist/query/canonical/query-ast.d.ts +104 -0
- package/dist/query/canonical/query-ast.js +1 -0
- package/dist/query/canonical/query-dataset.d.ts +63 -0
- package/dist/query/canonical/query-dataset.js +343 -0
- package/dist/query/canonical/query-normalizer.d.ts +10 -0
- package/dist/query/canonical/query-normalizer.js +646 -0
- package/dist/query/canonical/query-schema.d.ts +25 -0
- package/dist/query/canonical/query-schema.js +83 -0
- package/dist/query/canton-manager.js +14 -5
- package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
- package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
- package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
- package/dist/query/grpc/grpc-contract-cache.js +479 -0
- package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
- package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
- package/dist/query/grpc/grpc-query-client.d.ts +42 -0
- package/dist/query/grpc/grpc-query-client.js +245 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
- package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
- package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
- package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
- package/dist/query/grpc/grpc-relation-mapper.js +789 -0
- package/dist/query/pqs/pqs-query-client.d.ts +10 -20
- package/dist/query/pqs/pqs-query-client.js +154 -585
- package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
- package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
- package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
- package/dist/query/pqs/pqs-result-shape.js +25 -0
- package/dist/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
- package/dist/query/pqs/pqs-sql-compiler.js +337 -288
- package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
- package/dist/query/pqs/pqs-sql-syntax.js +6 -0
- package/dist/query/query-client.d.ts +15 -0
- package/dist/services/command/command-service-client.d.ts +9 -4
- package/dist/services/command/command-service-client.js +16 -3
- package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.js +1 -1
- package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
- package/dist/services/commands/command-submission-pipeline.js +21 -6
- package/dist/services/state/state-service-client.d.ts +4 -0
- package/dist/services/state/state-service-client.js +56 -0
- package/dist/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/transports/grpc/grpc-transport.d.ts +4 -4
- package/dist/transports/grpc/grpc-transport.js +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
- package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/transports/json/json-transport.d.ts +2 -2
- package/dist/transports/json/json-transport.js +2 -2
- package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
- package/dist/transports/json/mappers/commands-mapper.js +2 -2
- package/package.json +8 -3
- package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
- package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
- package/dist/query/grpc/grpc-contract-query-client.js +0 -174
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryValidationError = void 0;
|
|
4
|
+
exports.normalizeFindMany = normalizeFindMany;
|
|
5
|
+
exports.normalizeFindUnique = normalizeFindUnique;
|
|
6
|
+
exports.normalizeCount = normalizeCount;
|
|
7
|
+
exports.normalizeAggregate = normalizeAggregate;
|
|
8
|
+
exports.normalizeGroupBy = normalizeGroupBy;
|
|
9
|
+
const model_types_js_1 = require("../model-types.js");
|
|
10
|
+
const query_schema_js_1 = require("./query-schema.js");
|
|
11
|
+
const scalarOperators = new Set([
|
|
12
|
+
"equals", "in", "is", "isNot", "lt", "lte", "gt", "gte", "like", "ilike", "has",
|
|
13
|
+
]);
|
|
14
|
+
const orderedOperators = new Set(["equals", "in", "is", "isNot", "lt", "lte", "gt", "gte"]);
|
|
15
|
+
const stringOperators = new Set([...orderedOperators, "like", "ilike"]);
|
|
16
|
+
const payloadOperators = new Set(["equals", "lt", "lte", "gt", "gte", "like", "ilike"]);
|
|
17
|
+
const jsonScalarTypes = new Set(["text", "numeric", "boolean", "timestamp"]);
|
|
18
|
+
const buckets = new Set(["hour", "day", "week", "month"]);
|
|
19
|
+
class QueryValidationError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "QueryValidationError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.QueryValidationError = QueryValidationError;
|
|
26
|
+
function normalizeFindMany(relation, args = {}) {
|
|
27
|
+
return normalized(() => {
|
|
28
|
+
const source = object(args, "findMany args");
|
|
29
|
+
assertKnown(source, ["parties", "where", "select", "include", "orderBy", "skip", "take"], relation);
|
|
30
|
+
const page = normalizePage(source);
|
|
31
|
+
const predicate = source.where === undefined ? undefined : normalizePredicate(relation, source.where);
|
|
32
|
+
return {
|
|
33
|
+
kind: "findMany",
|
|
34
|
+
relation,
|
|
35
|
+
parties: normalizeParties(relation, source.parties),
|
|
36
|
+
predicate,
|
|
37
|
+
select: source.select === undefined ? undefined : normalizeSelection(relation, source.select),
|
|
38
|
+
includes: source.include === undefined ? [] : normalizeIncludes(relation, source.include),
|
|
39
|
+
orderBy: normalizeOrderBy(relation, source.orderBy, relation === "contracts" || source.orderBy !== undefined || page.skip > 0 || page.take !== undefined),
|
|
40
|
+
skip: page.skip,
|
|
41
|
+
take: page.take,
|
|
42
|
+
activeOnly: relation === "contracts" && provesActive(predicate),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function normalizeFindUnique(relation, args) {
|
|
47
|
+
return normalized(() => {
|
|
48
|
+
const source = object(args, "findUnique args");
|
|
49
|
+
assertKnown(source, ["where", "select", "include"], relation);
|
|
50
|
+
const where = object(source.where, "findUnique.where");
|
|
51
|
+
const metadata = relationMetadata(relation);
|
|
52
|
+
const fields = Object.keys(where);
|
|
53
|
+
if (!metadata.uniqueKeys.some((key) => key.length === fields.length && key.every((field) => fields.includes(field)))) {
|
|
54
|
+
throw new Error(`findUnique.where must contain one declared unique key of ${relation}`);
|
|
55
|
+
}
|
|
56
|
+
const children = fields.map((field) => {
|
|
57
|
+
assertUniqueEqualityValue(metadata, field, where[field]);
|
|
58
|
+
return scalar(field, where[field]);
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
kind: "findUnique",
|
|
62
|
+
relation,
|
|
63
|
+
predicate: combine("and", children) ?? fail("findUnique.where must not be empty"),
|
|
64
|
+
select: source.select === undefined ? undefined : normalizeSelection(relation, source.select),
|
|
65
|
+
includes: source.include === undefined ? [] : normalizeIncludes(relation, source.include),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function normalizeCount(relation, args = {}) {
|
|
70
|
+
return normalized(() => {
|
|
71
|
+
const source = object(args, "count args");
|
|
72
|
+
assertKnown(source, ["parties", "where"], relation);
|
|
73
|
+
const predicate = source.where === undefined ? undefined : normalizePredicate(relation, source.where);
|
|
74
|
+
return {
|
|
75
|
+
kind: "count",
|
|
76
|
+
relation,
|
|
77
|
+
parties: normalizeParties(relation, source.parties),
|
|
78
|
+
predicate,
|
|
79
|
+
activeOnly: relation === "contracts" && provesActive(predicate),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function normalizeAggregate(relation, args) {
|
|
84
|
+
return normalized(() => {
|
|
85
|
+
const source = object(args, "aggregate args");
|
|
86
|
+
assertKnown(source, ["where", "count", "min", "max", "sum"], relation);
|
|
87
|
+
return {
|
|
88
|
+
kind: "aggregate",
|
|
89
|
+
relation,
|
|
90
|
+
predicate: source.where === undefined ? undefined : normalizePredicate(relation, source.where),
|
|
91
|
+
aggregates: normalizeAggregates(relation, {
|
|
92
|
+
count: source.count,
|
|
93
|
+
min: source.min,
|
|
94
|
+
max: source.max,
|
|
95
|
+
sum: source.sum,
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function normalizeGroupBy(relation, args) {
|
|
101
|
+
return normalized(() => {
|
|
102
|
+
const source = object(args, "groupBy args");
|
|
103
|
+
assertKnown(source, ["where", "by", "aggregate"], relation);
|
|
104
|
+
if (!Array.isArray(source.by) || source.by.length === 0) {
|
|
105
|
+
throw new Error("groupBy.by must be a non-empty list");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
kind: "groupBy",
|
|
109
|
+
relation,
|
|
110
|
+
predicate: source.where === undefined ? undefined : normalizePredicate(relation, source.where),
|
|
111
|
+
by: source.by.map((key) => normalizeGroupKey(relation, key)),
|
|
112
|
+
aggregates: normalizeAggregates(relation, object(source.aggregate, "groupBy.aggregate"), true),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function normalizePredicate(relation, value) {
|
|
117
|
+
const expression = object(value, `${relation}.where`);
|
|
118
|
+
const children = [];
|
|
119
|
+
for (const [field, operand] of Object.entries(expression)) {
|
|
120
|
+
if (field === "and" || field === "or") {
|
|
121
|
+
if (!Array.isArray(operand)) {
|
|
122
|
+
throw new Error(`${field} must be an array`);
|
|
123
|
+
}
|
|
124
|
+
const logical = operand.map((child) => normalizePredicate(relation, child)).filter((child) => child !== undefined);
|
|
125
|
+
children.push({ kind: field, children: logical });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
else if (field === "not") {
|
|
129
|
+
const child = normalizePredicate(relation, operand);
|
|
130
|
+
if (child === undefined) {
|
|
131
|
+
throw new Error("not must contain a predicate");
|
|
132
|
+
}
|
|
133
|
+
children.push({ kind: "not", child });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
else if (relation === "contracts" && field === "templateId") {
|
|
137
|
+
children.push(...normalizeTemplateId(operand));
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
else if (relation === "contracts" && field === "payload") {
|
|
141
|
+
children.push(...normalizePayloadMatch(operand));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const edge = relationEdge(relation, field);
|
|
145
|
+
if (edge !== undefined) {
|
|
146
|
+
children.push(normalizeRelationPredicate(field, edge.target, edge.cardinality, operand));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
else if (!relationMetadata(relation).fields.includes(field)) {
|
|
150
|
+
throw new Error(`${field} is not a field of ${relation}`);
|
|
151
|
+
}
|
|
152
|
+
children.push(...normalizeScalarFilter(relation, field, operand));
|
|
153
|
+
}
|
|
154
|
+
return combine("and", children);
|
|
155
|
+
}
|
|
156
|
+
function normalizeTemplateId(value) {
|
|
157
|
+
const fields = object(value, "templateId filter");
|
|
158
|
+
assertKnown(fields, ["packageId", "moduleName", "entityName"], "templateId");
|
|
159
|
+
return Object.entries(fields).flatMap(([field, filter]) => normalizeTemplateScalarFilter(field, filter));
|
|
160
|
+
}
|
|
161
|
+
function normalizePayloadMatch(value, prefix = ["payload"]) {
|
|
162
|
+
const match = object(value, "payload filter");
|
|
163
|
+
if (Object.keys(match).length !== 1 || !Object.hasOwn(match, "match")) {
|
|
164
|
+
throw new Error("payload filter must contain match");
|
|
165
|
+
}
|
|
166
|
+
const walk = (node, path) => {
|
|
167
|
+
const record = object(node, "payload.match");
|
|
168
|
+
return Object.entries(record).flatMap(([name, child]) => {
|
|
169
|
+
if (isScalarFilter(child)) {
|
|
170
|
+
return normalizePayloadScalarFilter([...path, name], child);
|
|
171
|
+
}
|
|
172
|
+
return walk(child, [...path, name]);
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
return walk(match.match, prefix);
|
|
176
|
+
}
|
|
177
|
+
function normalizeRelationPredicate(edge, target, cardinality, value) {
|
|
178
|
+
const filter = object(value, `${edge} relation filter`);
|
|
179
|
+
if (cardinality === "one") {
|
|
180
|
+
if (["one", "some", "none", "every"].some((key) => key in filter)) {
|
|
181
|
+
throw new Error(`${edge} is a to-one relation and does not support relation quantifiers`);
|
|
182
|
+
}
|
|
183
|
+
const predicate = normalizePredicate(target, filter);
|
|
184
|
+
if (predicate === undefined) {
|
|
185
|
+
throw new Error(`${edge} relation predicate must not be empty`);
|
|
186
|
+
}
|
|
187
|
+
return { kind: "relation", edge, quantifier: "one", predicate };
|
|
188
|
+
}
|
|
189
|
+
const entries = Object.entries(filter);
|
|
190
|
+
if (entries.length !== 1 || !["some", "none", "every"].includes(entries[0][0])) {
|
|
191
|
+
throw new Error(`${edge} requires exactly one of some, none, or every`);
|
|
192
|
+
}
|
|
193
|
+
const predicate = normalizePredicate(target, entries[0][1]);
|
|
194
|
+
if (predicate === undefined) {
|
|
195
|
+
throw new Error(`${edge}.${entries[0][0]} must contain a predicate`);
|
|
196
|
+
}
|
|
197
|
+
return { kind: "relation", edge, quantifier: entries[0][0], predicate };
|
|
198
|
+
}
|
|
199
|
+
function normalizeScalarFilter(relation, field, value) {
|
|
200
|
+
if (relation === "contracts" && field === "witnesses") {
|
|
201
|
+
return normalizeContractWitnesses(value);
|
|
202
|
+
}
|
|
203
|
+
else if (relation === "contracts" && field === "active" && typeof value === "boolean") {
|
|
204
|
+
return [scalar(field, value)];
|
|
205
|
+
}
|
|
206
|
+
else if (!isFilter(value)) {
|
|
207
|
+
throw new Error(`${field} must be a filter object`);
|
|
208
|
+
}
|
|
209
|
+
const metadata = relationMetadata(relation);
|
|
210
|
+
const base = field.split(".")[0];
|
|
211
|
+
const path = field.split(".");
|
|
212
|
+
if (metadata.jsonFields.includes(base)) {
|
|
213
|
+
return normalizeJsonFilter(base, value);
|
|
214
|
+
}
|
|
215
|
+
const entries = Object.entries(value);
|
|
216
|
+
if (entries.length === 0) {
|
|
217
|
+
throw new Error(`${field} filter must not be empty`);
|
|
218
|
+
}
|
|
219
|
+
return entries.map(([operator, operand]) => {
|
|
220
|
+
if (!scalarOperators.has(operator)) {
|
|
221
|
+
throw new Error(`${operator} is not supported for ${field}`);
|
|
222
|
+
}
|
|
223
|
+
const kind = scalarKind(metadata, base);
|
|
224
|
+
const allowed = kind === "string-array"
|
|
225
|
+
? new Set(["equals", "in", "is", "isNot", "has"])
|
|
226
|
+
: kind === "string" ? stringOperators : kind === "numeric-string" || kind === "timestamp" ? orderedOperators : new Set(["equals", "in", "is", "isNot"]);
|
|
227
|
+
if (!allowed.has(operator)) {
|
|
228
|
+
throw new Error(`${operator} is not supported for ${field}`);
|
|
229
|
+
}
|
|
230
|
+
validateOperatorValue(operator, operand, field, kind, metadata.nullableFields.includes(base));
|
|
231
|
+
return { kind: "scalar", path, operator: operator, value: operand };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function normalizeJsonFilter(field, value) {
|
|
235
|
+
const filter = object(value, `${field} JSON filter`);
|
|
236
|
+
const path = jsonPath(filter.path, `${field}.path`);
|
|
237
|
+
const entries = Object.entries(filter).filter(([operator]) => operator !== "path");
|
|
238
|
+
if (entries.length === 0) {
|
|
239
|
+
throw new Error(`${field} JSON filter must contain an operator`);
|
|
240
|
+
}
|
|
241
|
+
return entries.map(([operator, operand]) => {
|
|
242
|
+
if (!scalarOperators.has(operator) || operator === "has") {
|
|
243
|
+
throw new Error(`${operator} is not supported for ${field}`);
|
|
244
|
+
}
|
|
245
|
+
validateOperatorValue(operator, operand, field, "string", true);
|
|
246
|
+
return { kind: "scalar", path: [field, ...path], operator: operator, value: operand };
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function normalizePayloadScalarFilter(path, value) {
|
|
250
|
+
const entries = Object.entries(value);
|
|
251
|
+
if (entries.length === 0) {
|
|
252
|
+
throw new Error("payload filter must not be empty");
|
|
253
|
+
}
|
|
254
|
+
return entries.map(([operator, operand]) => {
|
|
255
|
+
if (!payloadOperators.has(operator)) {
|
|
256
|
+
throw new Error(`${operator} is not supported for payload`);
|
|
257
|
+
}
|
|
258
|
+
validateOperatorValue(operator, operand, "payload", "string", false);
|
|
259
|
+
return { kind: "scalar", path, operator: operator, value: operand };
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function normalizeTemplateScalarFilter(field, value) {
|
|
263
|
+
if (!isFilter(value)) {
|
|
264
|
+
throw new Error(`templateId.${field} must be a filter object`);
|
|
265
|
+
}
|
|
266
|
+
const entries = Object.entries(value);
|
|
267
|
+
if (entries.length === 0) {
|
|
268
|
+
throw new Error(`templateId.${field} filter must not be empty`);
|
|
269
|
+
}
|
|
270
|
+
return entries.map(([operator, operand]) => {
|
|
271
|
+
if (!stringOperators.has(operator)) {
|
|
272
|
+
throw new Error(`${operator} is not supported for templateId.${field}`);
|
|
273
|
+
}
|
|
274
|
+
validateOperatorValue(operator, operand, `templateId.${field}`, "string", false);
|
|
275
|
+
return { kind: "scalar", path: ["templateId", field], operator: operator, value: operand };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function normalizeSelection(relation, value) {
|
|
279
|
+
const selection = object(value, "select");
|
|
280
|
+
const fields = [];
|
|
281
|
+
const json = [];
|
|
282
|
+
for (const [field, enabled] of Object.entries(selection)) {
|
|
283
|
+
if (field === "json") {
|
|
284
|
+
const projections = object(enabled, "select.json");
|
|
285
|
+
for (const [name, projection] of Object.entries(projections)) {
|
|
286
|
+
json.push(normalizeJsonSelection(relation, name, projection));
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
else if (!relationMetadata(relation).fields.includes(field)) {
|
|
291
|
+
throw new Error(`${field} is not a field of ${relation}`);
|
|
292
|
+
}
|
|
293
|
+
if (enabled !== true && enabled !== false) {
|
|
294
|
+
throw new Error(`select.${field} must be a boolean`);
|
|
295
|
+
}
|
|
296
|
+
else if (enabled) {
|
|
297
|
+
fields.push(field);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (fields.length === 0 && json.length === 0) {
|
|
301
|
+
throw new Error("select must include at least one field");
|
|
302
|
+
}
|
|
303
|
+
return { fields, json };
|
|
304
|
+
}
|
|
305
|
+
function normalizeJsonSelection(relation, name, value) {
|
|
306
|
+
const projection = object(value, `select.json.${name}`);
|
|
307
|
+
assertKnown(projection, ["field", "path", "as"], `select.json.${name}`);
|
|
308
|
+
if (typeof projection.field !== "string" || !relationMetadata(relation).jsonFields.includes(projection.field)) {
|
|
309
|
+
throw new Error(`${String(projection.field)} is not a JSON field of ${relation}`);
|
|
310
|
+
}
|
|
311
|
+
else if (typeof projection.as !== "string" || !jsonScalarTypes.has(projection.as)) {
|
|
312
|
+
throw new Error(`select.json.${name}.as is invalid`);
|
|
313
|
+
}
|
|
314
|
+
return { name, field: projection.field, path: jsonPath(projection.path, `select.json.${name}.path`), as: projection.as };
|
|
315
|
+
}
|
|
316
|
+
function normalizeIncludes(relation, value) {
|
|
317
|
+
const include = object(value, "include");
|
|
318
|
+
return Object.entries(include).map(([edgeName, option]) => {
|
|
319
|
+
const edge = relationEdge(relation, edgeName);
|
|
320
|
+
if (edge === undefined) {
|
|
321
|
+
throw new Error(`${edgeName} is not a relation of ${relation}`);
|
|
322
|
+
}
|
|
323
|
+
else if (edge.cardinality === "many" && option === true) {
|
|
324
|
+
throw new Error(`${edgeName} is a to-many relation and requires a non-negative take`);
|
|
325
|
+
}
|
|
326
|
+
const settings = option === true ? {} : object(option, `${edgeName} include option`);
|
|
327
|
+
assertKnown(settings, edge.cardinality === "many"
|
|
328
|
+
? ["where", "select", "include", "orderBy", "skip", "take"]
|
|
329
|
+
: ["where", "select", "include", "orderBy"], `${edgeName} include option`);
|
|
330
|
+
const page = edge.cardinality === "many" ? normalizePage(settings) : { skip: 0, take: undefined };
|
|
331
|
+
if (edge.cardinality === "many" && page.take === undefined) {
|
|
332
|
+
throw new Error(`${edgeName} is a to-many relation and requires a non-negative take`);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
edge: edgeName,
|
|
336
|
+
relation: edge.target,
|
|
337
|
+
cardinality: edge.cardinality,
|
|
338
|
+
predicate: settings.where === undefined ? undefined : normalizePredicate(edge.target, settings.where),
|
|
339
|
+
select: settings.select === undefined ? undefined : normalizeSelection(edge.target, settings.select),
|
|
340
|
+
includes: settings.include === undefined ? [] : normalizeIncludes(edge.target, settings.include),
|
|
341
|
+
orderBy: normalizeOrderBy(edge.target, settings.orderBy, edge.cardinality === "many" || settings.orderBy !== undefined),
|
|
342
|
+
skip: page.skip,
|
|
343
|
+
take: page.take,
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function normalizeOrderBy(relation, value, appendStable = false) {
|
|
348
|
+
const metadata = relationMetadata(relation);
|
|
349
|
+
const requested = [];
|
|
350
|
+
if (value !== undefined) {
|
|
351
|
+
if (!Array.isArray(value)) {
|
|
352
|
+
throw new Error("orderBy must be a non-empty list of one-field entries");
|
|
353
|
+
}
|
|
354
|
+
(0, model_types_js_1.assertQueryOrderBy)(value);
|
|
355
|
+
for (const entry of value) {
|
|
356
|
+
const [field, direction] = Object.entries(object(entry, "orderBy entry"))[0] ?? [];
|
|
357
|
+
if (!metadata.orderFields.includes(field)) {
|
|
358
|
+
throw new Error(`${field} is not orderable on ${relation}`);
|
|
359
|
+
}
|
|
360
|
+
else if (direction !== "asc" && direction !== "desc") {
|
|
361
|
+
throw new Error(`Invalid order direction for ${field}`);
|
|
362
|
+
}
|
|
363
|
+
requested.push({ path: [field], direction });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (appendStable) {
|
|
367
|
+
for (const field of metadata.stableOrder) {
|
|
368
|
+
if (!requested.some((order) => order.path.length === 1 && order.path[0] === field)) {
|
|
369
|
+
requested.push({ path: [field], direction: "asc" });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return requested;
|
|
374
|
+
}
|
|
375
|
+
function normalizePage(value) {
|
|
376
|
+
const skip = Object.hasOwn(value, "skip") ? value.skip : 0;
|
|
377
|
+
const take = Object.hasOwn(value, "take") ? value.take : undefined;
|
|
378
|
+
if (typeof skip !== "number" || (take !== undefined && typeof take !== "number")) {
|
|
379
|
+
throw new Error("skip and take must be numbers");
|
|
380
|
+
}
|
|
381
|
+
(0, model_types_js_1.assertQueryPageArgs)({ skip, take });
|
|
382
|
+
return { skip, take };
|
|
383
|
+
}
|
|
384
|
+
function normalizeParties(relation, value) {
|
|
385
|
+
if (value === undefined) {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
else if (relation !== "contracts") {
|
|
389
|
+
throw new Error(`parties is only supported for contracts`);
|
|
390
|
+
}
|
|
391
|
+
else if (!Array.isArray(value) || value.some((party) => typeof party !== "string")) {
|
|
392
|
+
throw new Error("parties must be an array of strings");
|
|
393
|
+
}
|
|
394
|
+
return [...new Set(value)].sort();
|
|
395
|
+
}
|
|
396
|
+
function normalizeAggregates(relation, value, numericOnly = false) {
|
|
397
|
+
const metadata = relationMetadata(relation);
|
|
398
|
+
const fields = numericOnly ? metadata.numericFields : metadata.fields;
|
|
399
|
+
assertKnown(value, ["count", "min", "max", "sum"], "aggregate");
|
|
400
|
+
const normalize = (name) => {
|
|
401
|
+
const selected = value[name];
|
|
402
|
+
if (selected === undefined) {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
else if (!Array.isArray(selected) || selected.some((field) => typeof field !== "string" || !fields.includes(field))) {
|
|
406
|
+
throw new Error(`${name} must contain fields of ${relation}`);
|
|
407
|
+
}
|
|
408
|
+
return [...new Set(selected)];
|
|
409
|
+
};
|
|
410
|
+
if (value.count !== undefined && value.count !== true) {
|
|
411
|
+
throw new Error("aggregate.count must be true");
|
|
412
|
+
}
|
|
413
|
+
const aggregates = { count: value.count === true, min: normalize("min"), max: normalize("max"), sum: normalize("sum") };
|
|
414
|
+
if (!aggregates.count && aggregates.min.length === 0 && aggregates.max.length === 0 && aggregates.sum.length === 0) {
|
|
415
|
+
throw new Error("aggregate requires at least one selection");
|
|
416
|
+
}
|
|
417
|
+
return aggregates;
|
|
418
|
+
}
|
|
419
|
+
function normalizeGroupKey(relation, value) {
|
|
420
|
+
const metadata = relationMetadata(relation);
|
|
421
|
+
if (typeof value === "string") {
|
|
422
|
+
if (!metadata.groupFields.includes(value)) {
|
|
423
|
+
throw new Error(`${value} is not a group key of ${relation}`);
|
|
424
|
+
}
|
|
425
|
+
return { kind: "field", path: [value] };
|
|
426
|
+
}
|
|
427
|
+
const key = object(value, "group key");
|
|
428
|
+
const entries = Object.entries(key);
|
|
429
|
+
if (entries.length !== 1) {
|
|
430
|
+
throw new Error("group key must contain exactly one field");
|
|
431
|
+
}
|
|
432
|
+
const [field, nested] = entries[0];
|
|
433
|
+
if (metadata.jsonFields.includes(field)) {
|
|
434
|
+
const projection = normalizeJsonGroup(field, nested);
|
|
435
|
+
return { kind: "json", field, name: projection.name, path: projection.path, as: projection.as };
|
|
436
|
+
}
|
|
437
|
+
else if (metadata.bucketFields.includes(field)) {
|
|
438
|
+
return { kind: "bucket", path: [field], bucket: bucket(nested) };
|
|
439
|
+
}
|
|
440
|
+
const edge = relationEdge(relation, field);
|
|
441
|
+
if (edge?.cardinality !== "one") {
|
|
442
|
+
throw new Error("group key must be a field, JSON projection, or a to-one time bucket");
|
|
443
|
+
}
|
|
444
|
+
const target = object(nested, "related group key");
|
|
445
|
+
const targetEntries = Object.entries(target);
|
|
446
|
+
if (targetEntries.length !== 1 || !relationMetadata(edge.target).bucketFields.includes(targetEntries[0][0])) {
|
|
447
|
+
throw new Error("group key must use a profiled time bucket");
|
|
448
|
+
}
|
|
449
|
+
return { kind: "bucket", path: [field, targetEntries[0][0]], bucket: bucket(targetEntries[0][1]) };
|
|
450
|
+
}
|
|
451
|
+
function normalizeJsonGroup(field, value) {
|
|
452
|
+
const projection = object(value, `${field} group key`);
|
|
453
|
+
assertKnown(projection, ["name", "path", "as"], `${field} group key`);
|
|
454
|
+
if (typeof projection.name !== "string" || projection.name.length === 0) {
|
|
455
|
+
throw new Error(`${field} group name must be non-empty`);
|
|
456
|
+
}
|
|
457
|
+
else if (typeof projection.as !== "string" || !jsonScalarTypes.has(projection.as)) {
|
|
458
|
+
throw new Error(`${field} group type is invalid`);
|
|
459
|
+
}
|
|
460
|
+
return { name: projection.name, path: jsonPath(projection.path, `${field} group path`), as: projection.as };
|
|
461
|
+
}
|
|
462
|
+
function scalar(field, value) {
|
|
463
|
+
return { kind: "scalar", path: field.split("."), operator: "equals", value };
|
|
464
|
+
}
|
|
465
|
+
function combine(kind, children) {
|
|
466
|
+
if (children.length === 0) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
return children.length === 1 ? children[0] : { kind, children };
|
|
470
|
+
}
|
|
471
|
+
function provesActive(predicate) {
|
|
472
|
+
if (predicate === undefined) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
else if (predicate.kind === "scalar") {
|
|
476
|
+
return predicate.path.length === 1 && predicate.path[0] === "active" && predicate.operator === "equals" && predicate.value === true;
|
|
477
|
+
}
|
|
478
|
+
else if (predicate.kind === "and") {
|
|
479
|
+
return predicate.children.some(provesActive);
|
|
480
|
+
}
|
|
481
|
+
else if (predicate.kind === "or") {
|
|
482
|
+
return predicate.children.length > 0 && predicate.children.every(provesActive);
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
function validateOperatorValue(operator, value, field, kind, nullable) {
|
|
487
|
+
if (operator === "is" || operator === "isNot") {
|
|
488
|
+
if (value !== null) {
|
|
489
|
+
throw new Error(`${operator} for ${field} must be null`);
|
|
490
|
+
}
|
|
491
|
+
else if (!nullable) {
|
|
492
|
+
throw new Error(`${field} is not nullable`);
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
else if (operator === "in") {
|
|
497
|
+
if (!Array.isArray(value)) {
|
|
498
|
+
throw new Error(`in for ${field} must be an array`);
|
|
499
|
+
}
|
|
500
|
+
value.forEach((item) => validateScalarValue(item, field, kind, nullable));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
else if (operator === "has") {
|
|
504
|
+
if (kind !== "string-array" || typeof value !== "string") {
|
|
505
|
+
throw new Error(`has for ${field} must be a string array membership value`);
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
validateScalarValue(value, field, kind, nullable);
|
|
510
|
+
}
|
|
511
|
+
function validateScalarValue(value, field, kind, nullable) {
|
|
512
|
+
if (value === null) {
|
|
513
|
+
if (!nullable) {
|
|
514
|
+
throw new Error(`${field} is not nullable`);
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const valid = kind === "string" || kind === "numeric-string" || kind === "json"
|
|
519
|
+
? typeof value === "string"
|
|
520
|
+
: kind === "boolean" ? typeof value === "boolean"
|
|
521
|
+
: kind === "timestamp" ? value instanceof Date && !Number.isNaN(value.getTime())
|
|
522
|
+
: kind === "string-array" ? Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
523
|
+
: kind === "binary" ? value instanceof Uint8Array
|
|
524
|
+
: false;
|
|
525
|
+
if (!valid) {
|
|
526
|
+
throw new Error(`${field} has an invalid ${kind} value`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function scalarKind(metadata, field) {
|
|
530
|
+
if (metadata.stringFields.includes(field)) {
|
|
531
|
+
return "string";
|
|
532
|
+
}
|
|
533
|
+
else if (metadata.numericFields.includes(field)) {
|
|
534
|
+
return "numeric-string";
|
|
535
|
+
}
|
|
536
|
+
else if (metadata.dateFields.includes(field)) {
|
|
537
|
+
return "timestamp";
|
|
538
|
+
}
|
|
539
|
+
else if (metadata.booleanFields.includes(field)) {
|
|
540
|
+
return "boolean";
|
|
541
|
+
}
|
|
542
|
+
else if (metadata.jsonFields.includes(field)) {
|
|
543
|
+
return "json";
|
|
544
|
+
}
|
|
545
|
+
else if (metadata.arrayFields.includes(field)) {
|
|
546
|
+
return "string-array";
|
|
547
|
+
}
|
|
548
|
+
else if (metadata.binaryFields.includes(field)) {
|
|
549
|
+
return "binary";
|
|
550
|
+
}
|
|
551
|
+
throw new Error(`${field} has no scalar kind`);
|
|
552
|
+
}
|
|
553
|
+
function assertUniqueEqualityValue(metadata, field, value) {
|
|
554
|
+
if (!metadata.fields.includes(field)) {
|
|
555
|
+
throw new Error(`${field} is not a field of a unique key`);
|
|
556
|
+
}
|
|
557
|
+
validateScalarValue(value, `findUnique.where.${field}`, scalarKind(metadata, field), metadata.nullableFields.includes(field));
|
|
558
|
+
}
|
|
559
|
+
function normalizeContractWitnesses(value) {
|
|
560
|
+
const filter = object(value, "witnesses filter");
|
|
561
|
+
if (Object.keys(filter).length !== 1 || typeof filter.has !== "string") {
|
|
562
|
+
throw new Error("witnesses must use { has: string }");
|
|
563
|
+
}
|
|
564
|
+
return [{ kind: "scalar", path: ["witnesses"], operator: "has", value: filter.has }];
|
|
565
|
+
}
|
|
566
|
+
function jsonPath(value, label) {
|
|
567
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((segment) => typeof segment !== "string" || segment.length === 0)) {
|
|
568
|
+
throw new Error(`${label} must be a non-empty JSON path`);
|
|
569
|
+
}
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
function object(value, label) {
|
|
573
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array) {
|
|
574
|
+
throw new Error(`${label} must be an object`);
|
|
575
|
+
}
|
|
576
|
+
const prototype = Object.getPrototypeOf(value);
|
|
577
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
578
|
+
throw new Error(`${label} must be a plain object`);
|
|
579
|
+
}
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
function isFilter(value) {
|
|
583
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
584
|
+
}
|
|
585
|
+
function isScalarFilter(value) {
|
|
586
|
+
return isFilter(value) && Object.keys(value).some((key) => scalarOperators.has(key));
|
|
587
|
+
}
|
|
588
|
+
function relationMetadata(relation) {
|
|
589
|
+
if (!Object.hasOwn(query_schema_js_1.queryRelationMetadata, relation)) {
|
|
590
|
+
throw new Error(`${String(relation)} is not a query relation`);
|
|
591
|
+
}
|
|
592
|
+
return query_schema_js_1.queryRelationMetadata[relation];
|
|
593
|
+
}
|
|
594
|
+
function relationEdge(relation, edge) {
|
|
595
|
+
const edges = Object.hasOwn(query_schema_js_1.queryRelationEdges, relation) ? query_schema_js_1.queryRelationEdges[relation] : undefined;
|
|
596
|
+
return edges !== undefined && Object.hasOwn(edges, edge) ? edges[edge] : undefined;
|
|
597
|
+
}
|
|
598
|
+
function normalized(build) {
|
|
599
|
+
try {
|
|
600
|
+
return deepFreeze(clone(build()));
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
if (error instanceof QueryValidationError) {
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
throw new QueryValidationError(error instanceof Error ? error.message : "Invalid query arguments");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function clone(value) {
|
|
610
|
+
if (Array.isArray(value)) {
|
|
611
|
+
return value.map((item) => clone(item));
|
|
612
|
+
}
|
|
613
|
+
else if (value instanceof Date) {
|
|
614
|
+
return value.toISOString();
|
|
615
|
+
}
|
|
616
|
+
else if (value instanceof Uint8Array) {
|
|
617
|
+
return Array.from(value);
|
|
618
|
+
}
|
|
619
|
+
else if (value !== null && typeof value === "object") {
|
|
620
|
+
const copy = {};
|
|
621
|
+
for (const [key, child] of Object.entries(value)) {
|
|
622
|
+
copy[key] = clone(child);
|
|
623
|
+
}
|
|
624
|
+
return copy;
|
|
625
|
+
}
|
|
626
|
+
return value;
|
|
627
|
+
}
|
|
628
|
+
function deepFreeze(value) {
|
|
629
|
+
if (value !== null && typeof value === "object" && !(value instanceof Uint8Array)) {
|
|
630
|
+
for (const child of Object.values(value)) {
|
|
631
|
+
deepFreeze(child);
|
|
632
|
+
}
|
|
633
|
+
Object.freeze(value);
|
|
634
|
+
}
|
|
635
|
+
return value;
|
|
636
|
+
}
|
|
637
|
+
function assertKnown(value, allowed, label) {
|
|
638
|
+
for (const key of Object.keys(value)) {
|
|
639
|
+
if (!allowed.includes(key)) {
|
|
640
|
+
throw new Error(`${key} is not supported by ${label}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function bucket(value) {
|
|
645
|
+
const source = object(value, "time bucket");
|
|
646
|
+
assertKnown(source, ["bucket"], "time bucket");
|
|
647
|
+
const bucketValue = Object.hasOwn(source, "bucket") ? source.bucket : undefined;
|
|
648
|
+
if (typeof bucketValue !== "string" || !buckets.has(bucketValue)) {
|
|
649
|
+
throw new Error("invalid time bucket");
|
|
650
|
+
}
|
|
651
|
+
return bucketValue;
|
|
652
|
+
}
|
|
653
|
+
function fail(message) {
|
|
654
|
+
throw new Error(message);
|
|
655
|
+
}
|