@distrohelena/canton-typescript-sdk 0.1.36 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +104 -34
  2. package/dist/cjs/core/types/requests/{submit-command-request.js → submit-commands-request.js} +10 -6
  3. package/dist/cjs/index.js +10 -8
  4. package/dist/cjs/query/canonical/in-memory-query-evaluator.js +291 -0
  5. package/dist/cjs/query/canonical/public-identity.js +41 -0
  6. package/dist/cjs/query/canonical/query-ast.js +2 -0
  7. package/dist/cjs/query/canonical/query-dataset.js +351 -0
  8. package/dist/cjs/query/canonical/query-normalizer.js +655 -0
  9. package/dist/cjs/query/canonical/query-schema.js +86 -0
  10. package/dist/cjs/query/canton-manager.js +14 -5
  11. package/dist/cjs/query/errors/query-snapshot-incomplete-error.js +19 -0
  12. package/dist/cjs/query/grpc/grpc-contract-cache.js +484 -0
  13. package/dist/cjs/query/grpc/grpc-package-relation-reader.js +218 -0
  14. package/dist/cjs/query/grpc/grpc-query-client.js +249 -0
  15. package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +238 -0
  16. package/dist/cjs/query/grpc/grpc-query-value-mapper.js +204 -0
  17. package/dist/cjs/query/grpc/grpc-relation-mapper.js +794 -0
  18. package/dist/cjs/query/pqs/pqs-query-client.js +151 -582
  19. package/dist/cjs/query/pqs/pqs-relational-sql-compiler.js +138 -95
  20. package/dist/cjs/query/pqs/pqs-result-shape.js +28 -0
  21. package/dist/cjs/query/pqs/pqs-schema-profile.js +49 -48
  22. package/dist/cjs/query/pqs/pqs-sql-compiler.js +343 -289
  23. package/dist/cjs/query/pqs/pqs-sql-syntax.js +10 -0
  24. package/dist/cjs/services/command/command-service-client.js +16 -3
  25. package/dist/cjs/services/commands/command-payload-builder.js +1 -1
  26. package/dist/cjs/services/commands/command-submission-pipeline.js +21 -6
  27. package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
  28. package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
  29. package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
  30. package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  31. package/dist/cjs/transports/json/json-transport.js +1 -1
  32. package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
  33. package/dist/core/transports/transport.interface.d.ts +4 -4
  34. package/dist/core/types/prepared-command-submission.d.ts +3 -3
  35. package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
  36. package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.js +2 -1
  39. package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
  40. package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
  41. package/dist/query/canonical/public-identity.d.ts +14 -0
  42. package/dist/query/canonical/public-identity.js +35 -0
  43. package/dist/query/canonical/query-ast.d.ts +104 -0
  44. package/dist/query/canonical/query-ast.js +1 -0
  45. package/dist/query/canonical/query-dataset.d.ts +63 -0
  46. package/dist/query/canonical/query-dataset.js +343 -0
  47. package/dist/query/canonical/query-normalizer.d.ts +10 -0
  48. package/dist/query/canonical/query-normalizer.js +646 -0
  49. package/dist/query/canonical/query-schema.d.ts +25 -0
  50. package/dist/query/canonical/query-schema.js +83 -0
  51. package/dist/query/canton-manager.js +14 -5
  52. package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
  53. package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
  54. package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
  55. package/dist/query/grpc/grpc-contract-cache.js +479 -0
  56. package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
  57. package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
  58. package/dist/query/grpc/grpc-query-client.d.ts +42 -0
  59. package/dist/query/grpc/grpc-query-client.js +245 -0
  60. package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
  61. package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
  62. package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
  63. package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
  64. package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
  65. package/dist/query/grpc/grpc-relation-mapper.js +789 -0
  66. package/dist/query/pqs/pqs-query-client.d.ts +10 -20
  67. package/dist/query/pqs/pqs-query-client.js +154 -585
  68. package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
  69. package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
  70. package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
  71. package/dist/query/pqs/pqs-result-shape.js +25 -0
  72. package/dist/query/pqs/pqs-schema-profile.js +49 -48
  73. package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
  74. package/dist/query/pqs/pqs-sql-compiler.js +337 -288
  75. package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
  76. package/dist/query/pqs/pqs-sql-syntax.js +6 -0
  77. package/dist/query/query-client.d.ts +15 -0
  78. package/dist/services/command/command-service-client.d.ts +9 -4
  79. package/dist/services/command/command-service-client.js +16 -3
  80. package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
  81. package/dist/services/commands/command-payload-builder.d.ts +2 -2
  82. package/dist/services/commands/command-payload-builder.js +1 -1
  83. package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
  84. package/dist/services/commands/command-submission-pipeline.js +21 -6
  85. package/dist/testing/runtime/declarative-action-executor.js +3 -3
  86. package/dist/transports/grpc/grpc-transport.d.ts +4 -4
  87. package/dist/transports/grpc/grpc-transport.js +3 -3
  88. package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
  89. package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
  90. package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
  91. package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  92. package/dist/transports/json/json-transport.d.ts +2 -2
  93. package/dist/transports/json/json-transport.js +2 -2
  94. package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
  95. package/dist/transports/json/mappers/commands-mapper.js +2 -2
  96. package/package.json +4 -3
  97. package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
  98. package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
  99. package/dist/query/grpc/grpc-contract-query-client.js +0 -174
@@ -1,367 +1,421 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileCanonicalPhysicalField = compileCanonicalPhysicalField;
3
4
  exports.compileContractFindMany = compileContractFindMany;
5
+ exports.compileContractCount = compileContractCount;
6
+ exports.compileContractAggregate = compileContractAggregate;
7
+ exports.compileCanonicalPhysicalIncludes = compileCanonicalPhysicalIncludes;
8
+ exports.compileCanonicalPhysicalPredicate = compileCanonicalPhysicalPredicate;
4
9
  exports.compileContractGroupBy = compileContractGroupBy;
5
- const model_types_js_1 = require("../model-types.js");
6
10
  const pqs_schema_profile_js_1 = require("./pqs-schema-profile.js");
7
- const pqs_schema_profile_js_2 = require("./pqs-schema-profile.js");
8
- function compileContractFindMany(args, profile) {
9
- (0, model_types_js_1.assertQueryPageArgs)(args);
11
+ const pqs_sql_syntax_js_1 = require("./pqs-sql-syntax.js");
12
+ const pqs_result_shape_js_1 = require("./pqs-result-shape.js");
13
+ const public_identity_js_1 = require("../canonical/public-identity.js");
14
+ function compileCanonicalPhysicalField(relation, field, alias, profile) {
15
+ const column = pqs_schema_profile_js_1.pqsRelationMetadata[relation].fields[field];
16
+ if (column === undefined)
17
+ throw new Error(`${field} is not a field of ${relation}`);
18
+ const physical = qualified(alias, column);
19
+ if (relation === "__events" && field === "eventId") {
20
+ return eventIdExpression(physical);
21
+ }
22
+ if (relation === "__events" && field === "pk") {
23
+ return (0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)(eventIdExpression(qualified(alias, "event_id")));
24
+ }
25
+ if (relation === "__events" && field === "txIx") {
26
+ return transactionOffsetExpression(profile, physical);
27
+ }
28
+ if (relation === "__events" && field === "type") {
29
+ return `case ${physical}::text when 'create' then 'created' when 'exercise' then 'exercised' else ${physical}::text end`;
30
+ }
31
+ if (relation === "__transactions" && field === "workflowId") {
32
+ return `nullif(${physical}, '')`;
33
+ }
34
+ if (relation === "__transactions" && field === "traceContext") {
35
+ return `case when ${physical} is null then null else jsonb_strip_nulls(jsonb_build_object('traceparent', nullif(to_jsonb(${physical})->>'trace_parent', ''), 'tracestate', nullif(to_jsonb(${physical})->>'trace_state', ''))) end`;
36
+ }
37
+ if ((relation === "__transactions" || relation === "__watermark") && (field === "ix" || field === "offset")) {
38
+ return qualified(alias, "offset");
39
+ }
40
+ if (relation === "__packages" && field === "pk") {
41
+ return (0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)(qualified(alias, "id"));
42
+ }
43
+ if (relation === "__contract_tpe" && field === "pk") {
44
+ return (0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)((0, public_identity_js_1.compileCanonicalPublicNumericIdentityPartsSql)([qualified(alias, "payload_type"), qualified(alias, "template_fqn")]));
45
+ }
46
+ if (relation === "__exercise_tpe" && field === "pk") {
47
+ return (0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)(qualified(alias, "choice_fqn"));
48
+ }
49
+ if (relation === "__exercises" && field === "tpePk") {
50
+ return exerciseTypePublicKeyExpression(profile, physical);
51
+ }
52
+ if (relation === "__exercises" && field === "contractTpePk") {
53
+ return contractTypePublicKeyExpression(profile, physical);
54
+ }
55
+ if (relation === "__exercises" && field === "exerciseEventPk") {
56
+ return eventPublicKeyExpression(profile, physical);
57
+ }
58
+ if (relation === "__exercises" && field === "exercisedAtIx") {
59
+ return transactionOffsetExpression(profile, physical);
60
+ }
61
+ if (relation === "__exercises" && field === "packagePk") {
62
+ return packagePublicKeyExpression(profile, physical);
63
+ }
64
+ return physical;
65
+ }
66
+ function eventIdExpression(physical) {
67
+ return `(to_jsonb(${physical})->>'offset') || ':' || (to_jsonb(${physical})->>'node_id')`;
68
+ }
69
+ function transactionOffsetExpression(profile, physicalIx) {
70
+ return `(select "canonical_transaction"."offset" from ${profile.relation("__transactions")} "canonical_transaction" where "canonical_transaction"."ix" = ${physicalIx})`;
71
+ }
72
+ function eventPublicKeyExpression(profile, physicalPk) {
73
+ const eventId = eventIdExpression('"canonical_event"."event_id"');
74
+ return `(select ${(0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)(eventId)} from ${profile.relation("__events")} "canonical_event" where "canonical_event"."pk" = ${physicalPk})`;
75
+ }
76
+ function packagePublicKeyExpression(profile, physicalPk) {
77
+ return `(select ${(0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)('"canonical_package"."id"')} from ${profile.relation("__packages")} "canonical_package" where "canonical_package"."pk" = ${physicalPk})`;
78
+ }
79
+ function contractTypePublicKeyExpression(profile, physicalPk) {
80
+ const identity = (0, public_identity_js_1.compileCanonicalPublicNumericIdentityPartsSql)(['"canonical_contract_type"."payload_type"', '"canonical_contract_type"."template_fqn"']);
81
+ return `(select ${(0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)(identity)} from ${profile.relation("__contract_tpe")} "canonical_contract_type" where "canonical_contract_type"."pk" = ${physicalPk})`;
82
+ }
83
+ function exerciseTypePublicKeyExpression(profile, physicalPk) {
84
+ return `(select ${(0, public_identity_js_1.compileCanonicalPublicNumericIdentitySql)('"canonical_exercise_type"."choice_fqn"')} from ${profile.relation("__exercise_tpe")} "canonical_exercise_type" where "canonical_exercise_type"."pk" = ${physicalPk})`;
85
+ }
86
+ function compileContractFindMany(query, profile) {
87
+ if (query.relation !== "contracts")
88
+ throw new Error("compileContractFindMany requires a contracts query");
10
89
  const values = [];
11
90
  const addValue = (value) => {
12
91
  values.push(value);
13
92
  return `$${values.length}`;
14
93
  };
15
94
  const conditions = [];
16
- const where = args.where === undefined ? undefined : compileWhere(args.where, addValue, profile);
95
+ const where = query.predicate === undefined ? undefined : compileCanonicalContractPredicate(query.predicate, addValue, profile, "contract_row");
17
96
  if (where !== undefined)
18
97
  conditions.push(where);
19
- if (args.parties !== undefined) {
20
- conditions.push(`contract_row.witnesses && ${addValue(args.parties)}::text[]`);
98
+ if (query.parties !== undefined) {
99
+ conditions.push(`contract_row.witnesses && ${addValue(query.parties)}::text[]`);
21
100
  }
22
- const orderBy = compileOrderBy(args.orderBy);
101
+ const orderBy = compileCanonicalContractOrderBy(query.orderBy);
23
102
  const whereSql = conditions.length === 0 ? "" : `where ${conditions.join(" and ")}`;
24
- const limitSql = args.take === undefined ? "" : `limit ${addValue(args.take)}`;
25
- const offsetSql = args.skip === undefined ? "" : `offset ${addValue(args.skip)}`;
26
- const included = compileProfileIncludes("__contracts", "contract_row", args.include, profile, addValue);
27
- const jsonProjection = Object.entries(args.select?.json ?? {}).map(([name, projection]) => {
28
- if (projection.field !== "payload")
29
- throw new Error(`${projection.field} is not a JSON field of contracts`);
30
- if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
31
- throw new Error(`${name}.path must be a non-empty JSON path`);
103
+ const limitSql = query.take === undefined ? "" : `limit ${addValue(query.take)}`;
104
+ const offsetSql = query.skip === 0 ? "" : `offset ${addValue(query.skip)}`;
105
+ const included = compileCanonicalPhysicalIncludes("__contracts", "contract_row", query.includes, profile, addValue);
106
+ const jsonProjection = (query.select?.json ?? []).map((projection) => {
32
107
  const text = `contract_row.payload #>> ${addValue(projection.path)}::text[]`;
33
108
  const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
34
- return `${expression} as "${name}"`;
109
+ return `${expression} as ${(0, pqs_sql_syntax_js_1.quotePqsIdentifier)(projection.name)}`;
35
110
  });
36
111
  return {
37
112
  text: `select
38
113
  contract_row.contract_id as contract_id,
39
- contract_row.creation_package_id as package_id,
114
+ ${contractPackageIdExpression("contract_row", profile)} as package_id,
40
115
  contract_row.payload,
41
116
  contract_row.witnesses,
42
- contract_row.created_at_ix::text as created_event_offset,
117
+ created_tx.offset::text as created_event_offset,
43
118
  created_tx.effective_at as created_at,
44
- contract_row.archived_at_ix::text as archived_event_offset,
119
+ archived_tx.offset::text as archived_event_offset,
45
120
  archived_tx.effective_at as archived_at,
46
121
  contract_row.archived_at_ix is null as active,
47
- contract_row.creation_package_id as template_package_id,
122
+ ${contractPackageIdExpression("contract_row", profile)} as template_package_id,
48
123
  contract_tpe_row.module_name as template_module_name,
49
- contract_tpe_row.entity_name as template_entity_name${[...included, ...jsonProjection].length === 0 ? "" : `,\n ${[...included, ...jsonProjection].join(",\n ")}`}
124
+ contract_tpe_row.entity_name as template_entity_name${[...included.map((include) => include.selection), ...jsonProjection].length === 0 ? "" : `,\n ${[...included.map((include) => include.selection), ...jsonProjection].join(",\n ")}`}
50
125
  from ${profile.relation("__contracts")} contract_row
51
126
  join ${profile.relation("__contract_tpe")} contract_tpe_row on contract_tpe_row.pk = contract_row.tpe_pk
52
127
  left join ${profile.relation("__transactions")} created_tx on created_tx.ix = contract_row.created_at_ix
53
128
  left join ${profile.relation("__transactions")} archived_tx on archived_tx.ix = contract_row.archived_at_ix
129
+ left join ${profile.relation("__packages")} contract_package on contract_package.pk = contract_row.package_pk
54
130
  ${whereSql}
55
131
  ${orderBy}
56
132
  ${limitSql}
57
133
  ${offsetSql}`,
58
134
  values,
135
+ resultShape: (0, pqs_result_shape_js_1.compilePqsResultShape)("__contracts", "many", query.select, query.includes),
59
136
  };
60
137
  }
61
- function compileProfileIncludes(source, parentAlias, include, profile, addValue) {
138
+ function compileContractCount(query, profile) {
139
+ if (query.relation !== "contracts")
140
+ throw new Error("compileContractCount requires a contracts query");
141
+ const values = [];
142
+ const add = (value) => { values.push(value); return `$${values.length}`; };
143
+ const where = compileContractWhere(query.predicate, query.parties, profile, add);
144
+ return { text: `select count(*)::text as count ${contractFrom(profile)}${where}`, values };
145
+ }
146
+ function compileContractAggregate(query, profile) {
147
+ if (query.relation !== "contracts")
148
+ throw new Error("compileContractAggregate requires a contracts query");
149
+ const values = [];
150
+ const add = (value) => { values.push(value); return `$${values.length}`; };
62
151
  const selected = [];
63
- for (const [name, option] of Object.entries(include ?? {})) {
64
- const edge = pqs_schema_profile_js_2.pqsRelationEdges[source]?.[name];
65
- if (edge === undefined)
66
- throw new Error(`${name} is not a relation of ${source}`);
67
- const settings = option === true ? {} : option;
68
- if (settings === null || typeof settings !== "object" || Array.isArray(settings))
69
- throw new Error(`${name} must be an include option`);
70
- const options = settings;
71
- if (edge.cardinality === "many" && (typeof options.take !== "number" || !Number.isInteger(options.take) || options.take < 0))
72
- throw new Error(`${name} is a to-many relation and requires a non-negative take`);
73
- const alias = `"${name}"`;
74
- const nested = compileProfileIncludes(edge.target, alias, options.include, profile, addValue);
75
- const metadata = pqs_schema_profile_js_2.pqsRelationMetadata[edge.target];
76
- const requested = compileIncludedFields(edge.target, options.select, alias, profile);
77
- const json = options.select?.json ?? {};
78
- const jsonSelections = Object.entries(json).map(([projectionName, projection]) => {
79
- if (!pqs_schema_profile_js_1.PqsSchemaProfileV1.jsonField(edge.target, projection.field))
80
- throw new Error(`${projection.field} is not a JSON field of ${edge.target}`);
81
- if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
82
- throw new Error(`${projectionName}.path must be a non-empty JSON path`);
83
- const column = edge.target === "__contracts"
84
- ? projection.field === "payload" ? "payload" : undefined
85
- : metadata.fields[projection.field];
86
- if (column === undefined)
87
- throw new Error(`${projection.field} is not a field of ${edge.target}`);
88
- const text = `${alias}."${column}" #>> ${addValue(projection.path)}::text[]`;
152
+ if (query.aggregates.count)
153
+ selected.push("count(*)::text as count");
154
+ const fields = { createdEventOffset: "created_tx.offset", archivedEventOffset: "archived_tx.offset" };
155
+ for (const [operation, requested] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
156
+ for (const field of requested) {
157
+ const expression = fields[field];
158
+ if (expression === undefined)
159
+ throw new Error(`${field} is not a numeric aggregate field of contracts`);
160
+ selected.push(`${operation}(${expression})::text as ${(0, pqs_sql_syntax_js_1.quotePqsIdentifier)(`${operation}_${field}`)}`);
161
+ }
162
+ }
163
+ if (selected.length === 0)
164
+ throw new Error("aggregate must request at least one result");
165
+ return { text: `select ${selected.join(", ")} ${contractFrom(profile)}${compileContractWhere(query.predicate, undefined, profile, add)}`, values };
166
+ }
167
+ function contractFrom(profile) {
168
+ return `from ${profile.relation("__contracts")} contract_row join ${profile.relation("__contract_tpe")} contract_tpe_row on contract_tpe_row.pk = contract_row.tpe_pk left join ${profile.relation("__transactions")} created_tx on created_tx.ix = contract_row.created_at_ix left join ${profile.relation("__transactions")} archived_tx on archived_tx.ix = contract_row.archived_at_ix left join ${profile.relation("__packages")} contract_package on contract_package.pk = contract_row.package_pk`;
169
+ }
170
+ function compileContractWhere(predicate, parties, profile, add) {
171
+ const conditions = [
172
+ ...(predicate === undefined ? [] : [compileCanonicalContractPredicate(predicate, add, profile, "contract_row")]),
173
+ ...(parties === undefined ? [] : [`contract_row.witnesses && ${add(parties)}::text[]`]),
174
+ ];
175
+ return conditions.length === 0 ? "" : ` where ${conditions.join(" and ")}`;
176
+ }
177
+ function compileCanonicalPhysicalIncludes(source, parentAlias, includes, profile, add) {
178
+ return includes.map((include) => {
179
+ const edge = pqs_schema_profile_js_1.pqsRelationEdges[source]?.[include.edge];
180
+ if (edge === undefined || edge.target !== pqsRelation(include.relation))
181
+ throw new Error(`Invalid canonical include ${include.edge}`);
182
+ const alias = (0, pqs_sql_syntax_js_1.quotePqsIdentifier)(include.edge);
183
+ const fields = compileCanonicalIncludedFields(edge.target, include.select, alias, profile);
184
+ const json = (include.select?.json ?? []).flatMap((projection) => {
185
+ const base = edge.target === "__contracts"
186
+ ? projection.field === "payload" ? `${alias}."payload"` : undefined
187
+ : pqs_schema_profile_js_1.pqsRelationMetadata[edge.target].fields[projection.field] === undefined ? undefined : compileCanonicalPhysicalField(edge.target, projection.field, alias, profile);
188
+ if (base === undefined)
189
+ throw new Error(`Invalid canonical JSON projection ${projection.field}`);
190
+ const text = `${base} #>> ${add(projection.path)}::text[]`;
89
191
  const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
90
- return [`'${projectionName}'`, expression];
192
+ return [(0, pqs_sql_syntax_js_1.quotePqsString)(projection.name), expression];
91
193
  });
92
- if (requested.length === 0 && jsonSelections.length === 0)
93
- throw new Error(`Nested ${name}.select must include at least one field`);
94
- const fields = requested.flatMap(([field, expression]) => [`'${field}'`, expression]);
95
- const nestedFields = nested.flatMap((selection) => {
96
- const match = /^(.*) as "([^"]+)"$/.exec(selection);
97
- return match === null ? [] : [`'${match[2]}'`, match[1]];
98
- });
99
- const object = `jsonb_build_object(${[...fields, ...jsonSelections.flat(), ...nestedFields].join(", ")})`;
100
- const relationFilter = options.where === undefined ? "true" : compilePhysicalWhere(edge.target, options.where, alias, profile, addValue);
101
- const condition = `${alias}."${edge.targetColumn}" = ${parentAlias}."${edge.sourceColumn}" and (${relationFilter})`;
102
- const orderBy = compilePhysicalOrderBy(edge.target, options.orderBy, alias);
103
- const expression = edge.cardinality === "one"
194
+ const nested = compileCanonicalPhysicalIncludes(edge.target, alias, include.includes, profile, add);
195
+ const nestedFields = nested.flatMap((selection) => [(0, pqs_sql_syntax_js_1.quotePqsString)(selection.key), selection.expression]);
196
+ const object = `jsonb_build_object(${[...fields.flatMap(([field, expression]) => [`'${field}'`, expression]), ...json.flat(), ...nestedFields].join(", ")})`;
197
+ const filter = include.predicate === undefined ? "true" : compileCanonicalPhysicalPredicate(edge.target, include.predicate, alias, profile, add);
198
+ const condition = `${compileCanonicalPhysicalEdgeJoin(source, include.edge, parentAlias, alias, profile)} and (${filter})`;
199
+ const orderBy = compileCanonicalPhysicalOrderBy(edge.target, include.orderBy, alias, profile);
200
+ const limit = include.cardinality === "many" ? ` limit ${add(include.take)}` : "";
201
+ const offset = include.cardinality === "many" && include.skip > 0 ? ` offset ${add(include.skip)}` : "";
202
+ const expression = include.cardinality === "one"
104
203
  ? `(select ${object} from ${profile.relation(edge.target)} ${alias} where ${condition})`
105
- : `(select coalesce(jsonb_agg("${name}_limited".value), '[]'::jsonb) from (select ${object} as value from ${profile.relation(edge.target)} ${alias} where ${condition}${orderBy} limit ${addValue(options.take)}) "${name}_limited")`;
106
- selected.push(`${expression} as "${name}"`);
107
- }
108
- return selected;
204
+ : `(select coalesce(jsonb_agg("${include.edge}_limited".value), '[]'::jsonb) from (select ${object} as value from ${profile.relation(edge.target)} ${alias} where ${condition}${orderBy}${limit}${offset}) "${include.edge}_limited")`;
205
+ return { key: include.edge, expression, selection: `${expression} as ${(0, pqs_sql_syntax_js_1.quotePqsIdentifier)(include.edge)}` };
206
+ });
109
207
  }
110
- function compileIncludedFields(target, select, alias, profile) {
208
+ function compileCanonicalIncludedFields(target, select, alias, profile) {
111
209
  if (target !== "__contracts") {
112
- const metadata = pqs_schema_profile_js_2.pqsRelationMetadata[target];
113
- const selected = select === undefined
114
- ? Object.entries(metadata.fields)
115
- : Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => [field, metadata.fields[field]]);
116
- if (selected.some(([, column]) => column === undefined))
117
- throw new Error("Nested relation.select references an unknown field");
118
- return selected.map(([field, column]) => [field, `${alias}."${column}"`]);
210
+ const metadata = pqs_schema_profile_js_1.pqsRelationMetadata[target];
211
+ const fields = select === undefined ? Object.keys(metadata.fields) : select.fields;
212
+ return fields.map((field) => {
213
+ const expression = compileCanonicalPhysicalField(target, field, alias, profile);
214
+ return [field, metadata.numericFields.includes(field) ? `${expression}::text` : expression];
215
+ });
119
216
  }
120
- const selected = select === undefined
121
- ? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"]
122
- : Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => field);
217
+ const fields = select === undefined ? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"] : select.fields;
123
218
  const expressions = {
124
- contractId: `${alias}."contract_id"`,
125
- templateId: `jsonb_build_object('packageId', ${alias}."creation_package_id", 'moduleName', (select contract_type."module_name" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"), 'entityName', (select contract_type."entity_name" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"))`,
126
- packageId: `${alias}."creation_package_id"`,
127
- payload: `${alias}."payload"`,
128
- witnesses: `${alias}."witnesses"`,
129
- createdEventOffset: `${alias}."created_at_ix"::text`,
130
- createdAt: `(select created_transaction."effective_at" from ${profile.relation("__transactions")} created_transaction where created_transaction."ix" = ${alias}."created_at_ix")`,
131
- archivedEventOffset: `${alias}."archived_at_ix"::text`,
132
- archivedAt: `(select archived_transaction."effective_at" from ${profile.relation("__transactions")} archived_transaction where archived_transaction."ix" = ${alias}."archived_at_ix")`,
133
- active: `${alias}."archived_at_ix" is null`,
219
+ contractId: `${alias}."contract_id"`, templateId: `jsonb_build_object('packageId', ${contractPackageIdExpression(alias, profile)}, 'moduleName', ${contractTypeFieldExpression(alias, "module_name", profile)}, 'entityName', ${contractTypeFieldExpression(alias, "entity_name", profile)})`, packageId: contractPackageIdExpression(alias, profile), payload: `${alias}."payload"`, witnesses: `${alias}."witnesses"`, createdEventOffset: `${contractTransactionFieldExpression(alias, "created", "offset", profile)}::text`, createdAt: contractTransactionFieldExpression(alias, "created", "effective_at", profile), archivedEventOffset: `${contractTransactionFieldExpression(alias, "archived", "offset", profile)}::text`, archivedAt: contractTransactionFieldExpression(alias, "archived", "effective_at", profile), active: `${alias}."archived_at_ix" is null`,
134
220
  };
135
- const requested = selected.map((field) => [field, expressions[field]]);
136
- if (requested.some(([, expression]) => expression === undefined))
137
- throw new Error("Nested contract.select references an unknown field");
138
- return requested;
221
+ return fields.map((field) => [field, expressions[field]]);
222
+ }
223
+ function compileCanonicalContractPredicate(predicate, add, profile, alias) {
224
+ return compileCanonicalPredicate("__contracts", predicate, alias, profile, add, true);
225
+ }
226
+ function compileCanonicalPhysicalPredicate(relation, predicate, alias, profile, add) {
227
+ return compileCanonicalPredicate(relation, predicate, alias, profile, add, relation === "__contracts");
228
+ }
229
+ function compileCanonicalPredicate(relation, predicate, alias, profile, add, logicalContracts) {
230
+ if (predicate.kind === "and" || predicate.kind === "or")
231
+ return predicate.children.length === 0 ? predicate.kind === "and" ? "true" : "false" : `(${predicate.children.map((child) => compileCanonicalPredicate(relation, child, alias, profile, add, logicalContracts)).join(` ${predicate.kind} `)})`;
232
+ if (predicate.kind === "not")
233
+ return `not (${compileCanonicalPredicate(relation, predicate.child, alias, profile, add, logicalContracts)})`;
234
+ if (predicate.kind === "relation") {
235
+ const edge = pqs_schema_profile_js_1.pqsRelationEdges[relation]?.[predicate.edge];
236
+ if (edge === undefined)
237
+ throw new Error(`Invalid canonical edge ${predicate.edge}`);
238
+ const relatedAlias = `"${predicate.edge}"`;
239
+ const join = compileCanonicalPhysicalEdgeJoin(relation, predicate.edge, alias, relatedAlias, profile);
240
+ const condition = compileCanonicalPredicate(edge.target, predicate.predicate, relatedAlias, profile, add, edge.target === "__contracts");
241
+ const base = `select 1 from ${profile.relation(edge.target)} ${relatedAlias} where ${join} and (${condition})`;
242
+ return predicate.quantifier === "one" || predicate.quantifier === "some" ? `exists (${base})` : predicate.quantifier === "none" ? `not exists (${base})` : `not exists (select 1 from ${profile.relation(edge.target)} ${relatedAlias} where ${join} and not (${condition}))`;
243
+ }
244
+ if (predicate.kind !== "scalar")
245
+ throw new Error("Unknown canonical predicate");
246
+ const [field, ...path] = predicate.path;
247
+ if (logicalContracts && field === "active")
248
+ return `${logicalQualified(alias, "archived_at_ix")} is ${predicate.value === true ? "null" : "not null"}`;
249
+ if (logicalContracts && field === "witnesses")
250
+ return `${add(predicate.value)} = any(${logicalQualified(alias, "witnesses")})`;
251
+ const column = logicalContracts ? undefined : pqs_schema_profile_js_1.pqsRelationMetadata[relation].fields[field];
252
+ if (!logicalContracts && column === undefined)
253
+ throw new Error(`${field} is not a field of ${relation}`);
254
+ const canonicalExpression = logicalContracts
255
+ ? contractPredicateExpression(alias, field, path, profile, add)
256
+ : path.length > 0
257
+ ? `${compileCanonicalPhysicalField(relation, field, alias, profile)} #>> ${add(path)}::text[]`
258
+ : compileCanonicalPhysicalField(relation, field, alias, profile);
259
+ const inverseEventType = relation === "__events" && field === "type" && (predicate.operator === "equals" || predicate.operator === "in");
260
+ const expression = inverseEventType ? qualified(alias, column) : canonicalExpression;
261
+ const value = inverseEventType ? physicalEventType(predicate.value) : predicate.value;
262
+ const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[predicate.operator];
263
+ if (predicate.operator === "is")
264
+ return `${expression} is null`;
265
+ if (predicate.operator === "isNot")
266
+ return `${expression} is not null`;
267
+ if (predicate.operator === "in")
268
+ return value.length === 0 ? "false" : `${expression} = any(${add(value)})`;
269
+ if (predicate.operator === "has")
270
+ return `${add(value)} = any(${qualified(alias, column)})`;
271
+ if (sql === undefined)
272
+ throw new Error(`Unsupported canonical operator ${predicate.operator}`);
273
+ return `${expression} ${sql} ${add(value)}`;
274
+ }
275
+ function compileCanonicalPhysicalEdgeJoin(source, edgeName, parentAlias, targetAlias, profile) {
276
+ const edge = pqs_schema_profile_js_1.pqsRelationEdges[source]?.[edgeName];
277
+ if (edge === undefined)
278
+ throw new Error(`Invalid canonical edge ${edgeName}`);
279
+ if (source === "__contract_tpe" && edgeName === "contracts") {
280
+ return `${contractTypePublicKeyExpression(profile, qualified(targetAlias, "tpe_pk"))} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
281
+ }
282
+ if (source === "__contract_tpe" && edgeName === "exercises") {
283
+ return `${compileCanonicalPhysicalField("__exercises", "contractTpePk", targetAlias, profile)} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
284
+ }
285
+ if (source === "__exercise_tpe" && edgeName === "exercises") {
286
+ return `${compileCanonicalPhysicalField("__exercises", "tpePk", targetAlias, profile)} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
287
+ }
288
+ const parent = parentAlias.length === 0
289
+ ? `${profile.relation(source)}."${edge.sourceColumn}"`
290
+ : qualified(parentAlias, edge.sourceColumn);
291
+ return `${targetAlias}."${edge.targetColumn}" = ${parent}`;
292
+ }
293
+ function contractPredicateExpression(alias, field, path, profile, add) {
294
+ if (field === "payload")
295
+ return `${logicalQualified(alias, "payload")} #>> ${add(path)}::text[]`;
296
+ if (field === "contractId")
297
+ return logicalQualified(alias, "contract_id");
298
+ if (field === "packageId")
299
+ return contractPackageIdExpression(alias, profile);
300
+ if (field === "createdEventOffset")
301
+ return contractTransactionFieldExpression(alias, "created", "offset", profile);
302
+ if (field === "createdAt")
303
+ return contractTransactionFieldExpression(alias, "created", "effective_at", profile);
304
+ if (field === "archivedEventOffset")
305
+ return contractTransactionFieldExpression(alias, "archived", "offset", profile);
306
+ if (field === "archivedAt")
307
+ return contractTransactionFieldExpression(alias, "archived", "effective_at", profile);
308
+ if (field === "templateId" && path[0] === "packageId")
309
+ return contractPackageIdExpression(alias, profile);
310
+ if (field === "templateId" && path[0] === "moduleName")
311
+ return contractTypeFieldExpression(alias, "module_name", profile);
312
+ if (field === "templateId" && path[0] === "entityName")
313
+ return contractTypeFieldExpression(alias, "entity_name", profile);
314
+ throw new Error(`${field} is not a field of contracts`);
315
+ }
316
+ function physicalEventType(value) {
317
+ if (Array.isArray(value))
318
+ return value.map(physicalEventType);
319
+ if (value === "created")
320
+ return "create";
321
+ if (value === "exercised")
322
+ return "exercise";
323
+ return value;
324
+ }
325
+ function qualified(alias, column) {
326
+ return alias.length === 0 ? `"${column}"` : `${alias}."${column}"`;
327
+ }
328
+ function logicalQualified(alias, column) {
329
+ return alias === "contract_row" ? `${alias}.${column}` : qualified(alias, column);
139
330
  }
140
- function compilePhysicalOrderBy(relation, orderBy, alias) {
141
- if (orderBy === undefined)
142
- return "";
143
- if (!Array.isArray(orderBy) || orderBy.length === 0)
144
- throw new Error("Nested orderBy must be a non-empty list");
145
- const metadata = pqs_schema_profile_js_2.pqsRelationMetadata[relation];
146
- const entries = orderBy.flatMap((entry) => entry !== null && typeof entry === "object" ? Object.entries(entry) : []);
147
- if (entries.length !== orderBy.length || entries.some(([, direction]) => direction !== "asc" && direction !== "desc"))
148
- throw new Error("Nested orderBy entries must have one valid direction");
149
- return ` order by ${entries.map(([field, direction]) => {
150
- const column = metadata.fields[field];
151
- if (column === undefined)
152
- throw new Error(`${field} is not a field of ${relation}`);
153
- return `${alias}."${column}" ${direction}`;
154
- }).join(", ")}`;
331
+ function contractPackageIdExpression(alias, profile) {
332
+ const packageId = alias === "contract_row"
333
+ ? "contract_package.id"
334
+ : `(select contract_package."id" from ${profile.relation("__packages")} contract_package where contract_package."pk" = ${qualified(alias, "package_pk")})`;
335
+ return `coalesce(${logicalQualified(alias, "creation_package_id")}, ${packageId})`;
336
+ }
337
+ function contractTransactionFieldExpression(alias, lifecycle, field, profile) {
338
+ if (alias === "contract_row")
339
+ return `${lifecycle}_tx.${field}`;
340
+ const transaction = `${lifecycle}_transaction`;
341
+ return `(select ${transaction}."${field}" from ${profile.relation("__transactions")} ${transaction} where ${transaction}."ix" = ${qualified(alias, `${lifecycle}_at_ix`)})`;
342
+ }
343
+ function contractTypeFieldExpression(alias, field, profile) {
344
+ if (alias === "contract_row")
345
+ return `contract_tpe_row.${field}`;
346
+ return `(select contract_type."${field}" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${qualified(alias, "tpe_pk")})`;
347
+ }
348
+ function compileCanonicalContractOrderBy(orderBy) {
349
+ return compileCanonicalOrderBy(orderBy, { contractId: "contract_row.contract_id", createdEventOffset: "created_tx.offset", createdAt: "created_tx.effective_at", archivedEventOffset: "archived_tx.offset", archivedAt: "archived_tx.effective_at" });
350
+ }
351
+ function compileCanonicalPhysicalOrderBy(relation, orderBy, alias, profile) {
352
+ if (relation === "__contracts") {
353
+ return compileCanonicalOrderBy(orderBy, {
354
+ contractId: qualified(alias, "contract_id"),
355
+ createdEventOffset: contractTransactionFieldExpression(alias, "created", "offset", profile),
356
+ createdAt: contractTransactionFieldExpression(alias, "created", "effective_at", profile),
357
+ archivedEventOffset: contractTransactionFieldExpression(alias, "archived", "offset", profile),
358
+ archivedAt: contractTransactionFieldExpression(alias, "archived", "effective_at", profile),
359
+ });
360
+ }
361
+ return compileCanonicalOrderBy(orderBy, Object.fromEntries(Object.keys(pqs_schema_profile_js_1.pqsRelationMetadata[relation].fields).map((field) => [field, compileCanonicalPhysicalField(relation, field, alias, profile)])));
155
362
  }
156
- function compileContractGroupBy(args, profile) {
157
- if (args.by.length === 0 || (!args.aggregate.count && !args.aggregate.min && !args.aggregate.max && !args.aggregate.sum))
363
+ function compileCanonicalOrderBy(orderBy, fields) {
364
+ return orderBy.length === 0 ? "" : ` order by ${orderBy.map((order) => `${fields[order.path[0]]} ${order.direction}`).join(", ")}`;
365
+ }
366
+ function pqsRelation(relation) {
367
+ return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
368
+ }
369
+ function compileContractGroupBy(query, profile) {
370
+ if (query.relation !== "contracts")
371
+ throw new Error("compileContractGroupBy requires a contracts query");
372
+ if (query.by.length === 0 || (!query.aggregates.count && query.aggregates.min.length === 0 && query.aggregates.max.length === 0 && query.aggregates.sum.length === 0))
158
373
  throw new Error("groupBy requires keys and an aggregate");
159
374
  const values = [];
160
375
  const add = (value) => { values.push(value); return `$${values.length}`; };
161
- const where = args.where === undefined ? undefined : compileWhere(args.where, add, profile);
376
+ const where = query.predicate === undefined ? undefined : compileCanonicalContractPredicate(query.predicate, add, profile, "contract_row");
162
377
  const fields = {
163
378
  contractId: "contract_row.contract_id",
164
- createdEventOffset: "contract_row.created_at_ix",
165
- archivedEventOffset: "contract_row.archived_at_ix",
379
+ createdEventOffset: "created_tx.offset",
380
+ createdAt: "created_tx.effective_at",
381
+ archivedEventOffset: "archived_tx.offset",
382
+ archivedAt: "archived_tx.effective_at",
166
383
  };
167
384
  const expressions = [];
168
385
  const select = [];
169
386
  let unnestWitnesses = false;
170
- for (const key of args.by) {
171
- if (key === "witnesses") {
387
+ for (const key of query.by) {
388
+ if (key.kind === "field" && key.path[0] === "witnesses") {
172
389
  unnestWitnesses = true;
173
390
  expressions.push("witness.value");
174
391
  select.push("witness.value as \"witnesses\"");
175
392
  }
176
- else if (typeof key === "string") {
177
- const expression = fields[key];
393
+ else if (key.kind === "field") {
394
+ const expression = fields[key.path[0]];
178
395
  if (expression === undefined)
179
- throw new Error(`${key} is not a contract group key`);
396
+ throw new Error(`${key.path[0]} is not a contract group key`);
180
397
  expressions.push(expression);
181
- select.push(`${expression} as "${key}"`);
398
+ select.push(`${expression} as "${key.path[0]}"`);
182
399
  }
183
400
  else {
184
- const payload = key.payload;
185
- if (payload.path.length === 0 || payload.path.some((segment) => segment.length === 0))
186
- throw new Error("payload group path must be non-empty");
187
- const base = `contract_row.payload #>> ${add(payload.path)}::text[]`;
188
- const cast = payload.as === "text" ? base : `(${base})::${payload.as === "numeric" ? "numeric" : payload.as === "boolean" ? "boolean" : "timestamptz"}`;
401
+ if (key.kind !== "json" || key.field !== "payload")
402
+ throw new Error("invalid contract group key");
403
+ const base = `contract_row.payload #>> ${add(key.path)}::text[]`;
404
+ const cast = key.as === "text" ? base : `(${base})::${key.as === "numeric" ? "numeric" : key.as === "boolean" ? "boolean" : "timestamptz"}`;
189
405
  expressions.push(cast);
190
- select.push(`${cast} as "${payload.name}"`);
406
+ select.push(`${cast} as ${(0, pqs_sql_syntax_js_1.quotePqsIdentifier)(key.name)}`);
191
407
  }
192
408
  }
193
- if (args.aggregate.count)
409
+ if (query.aggregates.count)
194
410
  select.push("count(*)::text as count");
195
- for (const [operation, requested] of [["min", args.aggregate.min], ["max", args.aggregate.max], ["sum", args.aggregate.sum]]) {
196
- for (const name of requested ?? []) {
411
+ for (const [operation, requested] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
412
+ for (const name of requested) {
197
413
  const expression = fields[name];
198
414
  select.push(`${operation}(${expression})::text as "${operation}_${name}"`);
199
415
  }
200
416
  }
201
417
  return {
202
- text: `select ${select.join(", ")} from ${profile.relation("__contracts")} contract_row${unnestWitnesses ? " cross join lateral unnest(contract_row.witnesses) as witness(value)" : ""}${where === undefined ? "" : ` where ${where}`} group by ${expressions.join(", ")}`,
418
+ text: `select ${select.join(", ")} ${contractFrom(profile)}${unnestWitnesses ? " cross join lateral unnest(contract_row.witnesses) as witness(value)" : ""}${where === undefined ? "" : ` where ${where}`} group by ${expressions.join(", ")}`,
203
419
  values,
204
420
  };
205
421
  }
206
- function compileWhere(where, addValue, profile) {
207
- const columns = { contractId: "contract_row.contract_id", packageId: "contract_row.creation_package_id", createdEventOffset: "contract_row.created_at_ix", createdAt: "created_tx.effective_at", archivedEventOffset: "contract_row.archived_at_ix", archivedAt: "archived_tx.effective_at" };
208
- const parts = [];
209
- for (const [key, value] of Object.entries(where)) {
210
- if (key === "and" || key === "or") {
211
- if (!Array.isArray(value))
212
- throw new Error(`${key} must be an array`);
213
- parts.push(value.length === 0 ? key === "and" ? "true" : "false" : `(${value.map((child) => compileWhere(child, addValue, profile)).join(` ${key} `)})`);
214
- continue;
215
- }
216
- if (key === "not") {
217
- parts.push(`not (${compileWhere(value, addValue, profile)})`);
218
- continue;
219
- }
220
- if (key === "active") {
221
- const active = typeof value === "boolean" ? value : value.equals;
222
- if (typeof active !== "boolean")
223
- throw new Error("active supports only equals");
224
- parts.push(active ? "contract_row.archived_at_ix is null" : "contract_row.archived_at_ix is not null");
225
- continue;
226
- }
227
- if (key === "witnesses") {
228
- const has = value.has;
229
- if (has === undefined)
230
- throw new Error("witnesses supports only has");
231
- parts.push(`${addValue(has)} = any(contract_row.witnesses)`);
232
- continue;
233
- }
234
- if (key === "payload") {
235
- const payload = value;
236
- const compilePayload = (path, filter) => { const ops = ["equals", "lt", "lte", "gt", "gte", "like", "ilike"].filter((op) => filter[op] !== undefined); if (ops.length === 1) {
237
- const op = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[ops[0]];
238
- parts.push(`contract_row.payload #>> ${addValue(path)}::text[] ${op} ${addValue(filter[ops[0]])}`);
239
- return;
240
- } for (const [name, child] of Object.entries(filter))
241
- compilePayload([...path, name], child); };
242
- if (payload.match === undefined)
243
- throw new Error("payload requires match");
244
- compilePayload([], payload.match);
245
- continue;
246
- }
247
- if (key === "templateId") {
248
- const fields = { packageId: "contract_row.creation_package_id", moduleName: "contract_tpe_row.module_name", entityName: "contract_tpe_row.entity_name" };
249
- for (const [name, filter] of Object.entries(value))
250
- for (const [op, operand] of Object.entries(filter)) {
251
- const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[op];
252
- if (fields[name] === undefined || sql === undefined)
253
- throw new Error("invalid templateId filter");
254
- parts.push(`${fields[name]} ${sql} ${addValue(operand)}`);
255
- }
256
- continue;
257
- }
258
- const edge = pqs_schema_profile_js_2.pqsRelationEdges.__contracts?.[key];
259
- if (edge !== undefined) {
260
- parts.push(compilePhysicalRelationPredicate(edge.target, key, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, "contract_row", profile, addValue));
261
- continue;
262
- }
263
- const column = columns[key];
264
- if (column === undefined || value === null || typeof value !== "object")
265
- throw new Error(`${key} is not a supported contract filter`);
266
- for (const [op, operand] of Object.entries(value)) {
267
- const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[op];
268
- if (sql !== undefined)
269
- parts.push(`${column} ${sql} ${addValue(operand)}`);
270
- else if (op === "is" && operand === null)
271
- parts.push(`${column} is null`);
272
- else if (op === "isNot" && operand === null)
273
- parts.push(`${column} is not null`);
274
- else if (op === "in" && Array.isArray(operand))
275
- parts.push(operand.length ? `${column} = any(${addValue(operand)})` : "false");
276
- else
277
- throw new Error(`${op} is not supported for ${key}`);
278
- }
279
- }
280
- return parts.length === 0 ? "true" : parts.join(" and ");
281
- }
282
- function compilePhysicalRelationPredicate(target, edgeName, targetColumn, sourceColumn, cardinality, value, parentAlias, profile, addValue) {
283
- if (value === null || typeof value !== "object" || Array.isArray(value))
284
- throw new Error(`${edgeName} must be a relation filter`);
285
- const relation = value;
286
- const alias = `"${edgeName}"`;
287
- const join = `${alias}."${targetColumn}" = ${parentAlias}."${sourceColumn}"`;
288
- if (cardinality === "one") {
289
- const predicate = compilePhysicalWhere(target, relation, alias, profile, addValue);
290
- return `exists (select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate}))`;
291
- }
292
- const operators = ["some", "none", "every"].filter((name) => relation[name] !== undefined);
293
- if (operators.length !== 1)
294
- throw new Error(`${edgeName} requires exactly one of some, none, or every`);
295
- const predicate = compilePhysicalWhere(target, relation[operators[0]], alias, profile, addValue);
296
- const base = `select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate})`;
297
- return operators[0] === "some" ? `exists (${base})` : operators[0] === "none" ? `not exists (${base})` : `not exists (select 1 from ${profile.relation(target)} ${alias} where ${join} and not (${predicate}))`;
298
- }
299
- function compilePhysicalWhere(relation, expression, alias, profile, addValue) {
300
- if (expression === null || typeof expression !== "object" || Array.isArray(expression))
301
- throw new Error(`${relation} relation predicate must be an expression`);
302
- const metadata = pqs_schema_profile_js_2.pqsRelationMetadata[relation];
303
- const conditions = [];
304
- for (const [field, value] of Object.entries(expression)) {
305
- if (field === "and" || field === "or") {
306
- if (!Array.isArray(value))
307
- throw new Error(`${field} must be an array`);
308
- conditions.push(value.length === 0 ? field === "and" ? "true" : "false" : `(${value.map((child) => compilePhysicalWhere(relation, child, alias, profile, addValue)).join(` ${field} `)})`);
309
- continue;
310
- }
311
- if (field === "not") {
312
- conditions.push(`not (${compilePhysicalWhere(relation, value, alias, profile, addValue)})`);
313
- continue;
314
- }
315
- const edge = pqs_schema_profile_js_2.pqsRelationEdges[relation]?.[field];
316
- if (edge !== undefined) {
317
- conditions.push(compilePhysicalRelationPredicate(edge.target, field, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, alias, profile, addValue));
318
- continue;
319
- }
320
- const column = metadata.fields[field];
321
- if (column === undefined || value === null || typeof value !== "object" || Array.isArray(value))
322
- throw new Error(`${field} is not a field of ${relation}`);
323
- const filter = value;
324
- const text = pqs_schema_profile_js_1.PqsSchemaProfileV1.jsonField(relation, field) && Array.isArray(filter.path) ? `${alias}."${column}" #>> ${addValue(filter.path)}::text[]` : `${alias}."${column}"`;
325
- if (Array.isArray(filter.path) && (filter.path.length === 0 || filter.path.some((segment) => typeof segment !== "string" || segment.length === 0)))
326
- throw new Error(`${field}.path must be a non-empty JSON path`);
327
- if (filter.is === null)
328
- conditions.push(`${text} is null`);
329
- if (filter.isNot === null)
330
- conditions.push(`${text} is not null`);
331
- if (filter.equals !== undefined)
332
- conditions.push(`${text} = ${addValue(filter.equals)}`);
333
- if (filter.in !== undefined) {
334
- if (!Array.isArray(filter.in))
335
- throw new Error(`${field}.in must be an array`);
336
- conditions.push(filter.in.length === 0 ? "false" : `${text} = any(${addValue(filter.in)})`);
337
- }
338
- if (filter.has !== undefined) {
339
- if (!metadata.arrayFields.includes(field))
340
- throw new Error(`${field} is not an array field of ${relation}`);
341
- conditions.push(`${addValue(filter.has)} = any(${alias}."${column}")`);
342
- }
343
- for (const [operator, operand] of Object.entries(filter)) {
344
- const sql = { lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[operator];
345
- if (sql !== undefined)
346
- conditions.push(`${text} ${sql} ${addValue(operand)}`);
347
- }
348
- }
349
- return conditions.length === 0 ? "true" : conditions.join(" and ");
350
- }
351
- function compileOrderBy(orderBy) {
352
- if (orderBy === undefined) {
353
- return "order by contract_row.contract_id asc";
354
- }
355
- const fields = {
356
- contractId: "contract_row.contract_id",
357
- createdEventOffset: "contract_row.created_at_ix",
358
- createdAt: "created_tx.effective_at",
359
- archivedEventOffset: "contract_row.archived_at_ix",
360
- archivedAt: "archived_tx.effective_at",
361
- };
362
- const entries = orderBy.flatMap((entry) => Object.entries(entry));
363
- if (entries.some(([field, direction]) => fields[field] === undefined || (direction !== "asc" && direction !== "desc"))) {
364
- throw new Error("orderBy must contain valid one-field entries");
365
- }
366
- return `order by ${entries.map(([field, direction]) => `${fields[field]} ${direction}`).join(", ")}`;
367
- }