@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.
Files changed (102) hide show
  1. package/README.md +159 -51
  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/services/state/state-service-client.js +56 -0
  28. package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
  29. package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
  30. package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
  31. package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  32. package/dist/cjs/transports/json/json-transport.js +1 -1
  33. package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
  34. package/dist/core/transports/transport.interface.d.ts +4 -4
  35. package/dist/core/types/prepared-command-submission.d.ts +3 -3
  36. package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
  37. package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
  38. package/dist/index.d.ts +5 -2
  39. package/dist/index.js +2 -1
  40. package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
  41. package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
  42. package/dist/query/canonical/public-identity.d.ts +14 -0
  43. package/dist/query/canonical/public-identity.js +35 -0
  44. package/dist/query/canonical/query-ast.d.ts +104 -0
  45. package/dist/query/canonical/query-ast.js +1 -0
  46. package/dist/query/canonical/query-dataset.d.ts +63 -0
  47. package/dist/query/canonical/query-dataset.js +343 -0
  48. package/dist/query/canonical/query-normalizer.d.ts +10 -0
  49. package/dist/query/canonical/query-normalizer.js +646 -0
  50. package/dist/query/canonical/query-schema.d.ts +25 -0
  51. package/dist/query/canonical/query-schema.js +83 -0
  52. package/dist/query/canton-manager.js +14 -5
  53. package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
  54. package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
  55. package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
  56. package/dist/query/grpc/grpc-contract-cache.js +479 -0
  57. package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
  58. package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
  59. package/dist/query/grpc/grpc-query-client.d.ts +42 -0
  60. package/dist/query/grpc/grpc-query-client.js +245 -0
  61. package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
  62. package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
  63. package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
  64. package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
  65. package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
  66. package/dist/query/grpc/grpc-relation-mapper.js +789 -0
  67. package/dist/query/pqs/pqs-query-client.d.ts +10 -20
  68. package/dist/query/pqs/pqs-query-client.js +154 -585
  69. package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
  70. package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
  71. package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
  72. package/dist/query/pqs/pqs-result-shape.js +25 -0
  73. package/dist/query/pqs/pqs-schema-profile.js +49 -48
  74. package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
  75. package/dist/query/pqs/pqs-sql-compiler.js +337 -288
  76. package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
  77. package/dist/query/pqs/pqs-sql-syntax.js +6 -0
  78. package/dist/query/query-client.d.ts +15 -0
  79. package/dist/services/command/command-service-client.d.ts +9 -4
  80. package/dist/services/command/command-service-client.js +16 -3
  81. package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
  82. package/dist/services/commands/command-payload-builder.d.ts +2 -2
  83. package/dist/services/commands/command-payload-builder.js +1 -1
  84. package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
  85. package/dist/services/commands/command-submission-pipeline.js +21 -6
  86. package/dist/services/state/state-service-client.d.ts +4 -0
  87. package/dist/services/state/state-service-client.js +56 -0
  88. package/dist/testing/runtime/declarative-action-executor.js +3 -3
  89. package/dist/transports/grpc/grpc-transport.d.ts +4 -4
  90. package/dist/transports/grpc/grpc-transport.js +3 -3
  91. package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
  92. package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
  93. package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
  94. package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
  95. package/dist/transports/json/json-transport.d.ts +2 -2
  96. package/dist/transports/json/json-transport.js +2 -2
  97. package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
  98. package/dist/transports/json/mappers/commands-mapper.js +2 -2
  99. package/package.json +8 -3
  100. package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
  101. package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
  102. package/dist/query/grpc/grpc-contract-query-client.js +0 -174
@@ -1,23 +1,15 @@
1
1
  import { PqsRelation, PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
2
+ import type { NormalizedAggregateQuery, NormalizedCountQuery, NormalizedFindManyQuery, NormalizedGroupByQuery } from "../canonical/query-ast.js";
3
+ import { type PqsRelationResultShape } from "./pqs-result-shape.js";
4
+ export type { PqsIncludedResultShape, PqsJsonResultProjection, PqsRelationResultShape, PqsSelectedScalarField } from "./pqs-result-shape.js";
2
5
  export interface CompiledPqsRelationQuery {
3
6
  readonly text: string;
4
7
  readonly values: readonly unknown[];
5
8
  }
6
- interface RelationFindManyArgs {
7
- readonly where?: Readonly<Record<string, unknown>>;
8
- readonly select?: Readonly<Record<string, boolean>>;
9
- readonly orderBy?: readonly Readonly<Record<string, "asc" | "desc">>[];
10
- readonly take?: number;
11
- readonly skip?: number;
9
+ export interface CompiledPqsRelationFindManyQuery extends CompiledPqsRelationQuery {
10
+ readonly resultShape: PqsRelationResultShape;
12
11
  }
13
- export declare function compilePqsRelationFindMany(relation: PqsRelation, args: RelationFindManyArgs, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
14
- export declare function compilePqsRelationGroupBy(relation: PqsRelation, args: {
15
- readonly by: readonly unknown[];
16
- readonly aggregate: {
17
- readonly count?: true;
18
- readonly min?: readonly string[];
19
- readonly max?: readonly string[];
20
- readonly sum?: readonly string[];
21
- };
22
- }, profile: PqsSchemaProfileV1, parameterOffset?: number): CompiledPqsRelationQuery;
23
- export {};
12
+ export declare function compilePqsRelationFindMany(relation: PqsRelation, query: NormalizedFindManyQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationFindManyQuery;
13
+ export declare function compilePqsRelationCount(relation: PqsRelation, query: NormalizedCountQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
14
+ export declare function compilePqsRelationAggregate(relation: PqsRelation, query: NormalizedAggregateQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
15
+ export declare function compilePqsRelationGroupBy(relation: PqsRelation, query: NormalizedGroupByQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
@@ -1,137 +1,166 @@
1
1
  import { PqsSchemaProfileV1, pqsRelationMetadata } from "./pqs-schema-profile.js";
2
2
  import { pqsRelationEdges } from "./pqs-schema-profile.js";
3
- export function compilePqsRelationFindMany(relation, args, profile) {
4
- assertPage(args);
3
+ import { compileCanonicalPhysicalField, compileCanonicalPhysicalIncludes, compileCanonicalPhysicalPredicate } from "./pqs-sql-compiler.js";
4
+ import { quotePqsIdentifier } from "./pqs-sql-syntax.js";
5
+ import { compilePqsResultShape } from "./pqs-result-shape.js";
6
+ export function compilePqsRelationFindMany(relation, query, profile) {
7
+ assertCanonicalPhysicalFindMany(relation, query);
5
8
  const metadata = pqsRelationMetadata[relation];
9
+ const source = logicalTypeRootSource(relation, profile);
6
10
  const values = [];
7
11
  const add = (value) => {
8
12
  values.push(value);
9
13
  return `$${values.length}`;
10
14
  };
11
- const fields = selectedFields(relation, metadata, args.select);
12
- const where = compileWhere(relation, metadata, args.where, add);
13
- const orderBy = compileOrderBy(relation, metadata, args.orderBy);
14
- const limit = args.take === undefined ? "" : ` limit ${add(args.take)}`;
15
- const offset = args.skip === undefined ? "" : ` offset ${add(args.skip)}`;
15
+ const fields = selectedCanonicalFields(relation, metadata, query.select?.fields, (query.select?.json.length ?? 0) > 0);
16
+ const predicate = query.predicate === undefined ? "" : compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add);
17
+ const where = predicate.length === 0 ? "" : ` where ${predicate}`;
18
+ const orderBy = compileCanonicalOrderBy(relation, metadata, query.orderBy, profile);
19
+ const limit = query.take === undefined ? "" : ` limit ${add(query.take)}`;
20
+ const offset = query.skip === 0 ? "" : ` offset ${add(query.skip)}`;
21
+ const included = compileCanonicalPhysicalIncludes(relation, source.reference, query.includes, profile, add);
22
+ const json = compileCanonicalJsonSelections(relation, query.select, profile, add);
23
+ const shape = compilePqsResultShape(relation, "many", query.select, query.includes);
16
24
  return {
17
- text: `select ${fields.map(([field, column]) => `"${column}" as "${field}"`).join(", ")} from ${profile.relation(relation)}${where}${orderBy}${limit}${offset}`,
25
+ text: `${source.withClause}select ${[...fields.map(([field]) => `${compileCanonicalPhysicalField(relation, field, "", profile)} as "${field}"`), ...json, ...included.map((include) => include.selection)].join(", ")} from ${source.relation}${where}${orderBy}${limit}${offset}`,
18
26
  values,
27
+ resultShape: shape,
19
28
  };
20
29
  }
21
- export function compilePqsRelationGroupBy(relation, args, profile, parameterOffset = 0) {
22
- if (args.by.length === 0 || (!args.aggregate.count && !args.aggregate.min && !args.aggregate.max && !args.aggregate.sum))
30
+ export function compilePqsRelationCount(relation, query, profile) {
31
+ assertCanonicalPhysicalQuery(relation, query, "count");
32
+ const source = logicalTypeRootSource(relation, profile);
33
+ const values = [];
34
+ const add = (value) => {
35
+ values.push(value);
36
+ return `$${values.length}`;
37
+ };
38
+ const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add)}`;
39
+ return { text: `${source.withClause}select count(*)::text as count from ${source.relation}${predicate}`, values };
40
+ }
41
+ export function compilePqsRelationAggregate(relation, query, profile) {
42
+ assertCanonicalPhysicalQuery(relation, query, "aggregate");
43
+ const metadata = pqsRelationMetadata[relation];
44
+ const source = logicalTypeRootSource(relation, profile);
45
+ const values = [];
46
+ const add = (value) => {
47
+ values.push(value);
48
+ return `$${values.length}`;
49
+ };
50
+ const selected = [];
51
+ if (query.aggregates.count)
52
+ selected.push("count(*)::text as count");
53
+ for (const [operation, fields] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
54
+ for (const name of fields) {
55
+ if (!metadata.numericFields.includes(name))
56
+ throw new Error(`${name} is not a numeric aggregate field of ${relation}`);
57
+ selected.push(`${operation}(${compileCanonicalPhysicalField(relation, name, "", profile)})::text as "${operation}_${name}"`);
58
+ }
59
+ }
60
+ if (selected.length === 0)
61
+ throw new Error("aggregate must request at least one result");
62
+ const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add)}`;
63
+ return { text: `${source.withClause}select ${selected.join(", ")} from ${source.relation}${predicate}`, values };
64
+ }
65
+ export function compilePqsRelationGroupBy(relation, query, profile) {
66
+ assertCanonicalPhysicalQuery(relation, query, "groupBy");
67
+ if (query.by.length === 0 || (!query.aggregates.count && query.aggregates.min.length === 0 && query.aggregates.max.length === 0 && query.aggregates.sum.length === 0))
23
68
  throw new Error("groupBy requires a key and aggregate");
24
69
  const metadata = pqsRelationMetadata[relation];
70
+ const source = logicalTypeRootSource(relation, profile);
25
71
  const root = relation === "__events" ? "event" : "root";
26
72
  const joins = [];
27
73
  const expressions = [];
28
74
  const selected = [];
29
75
  const values = [];
30
- const add = (value) => { values.push(value); return `$${parameterOffset + values.length}`; };
31
- for (const key of args.by) {
32
- if (typeof key === "string") {
33
- const column = field(relation, metadata, key);
34
- const expression = metadata.arrayFields.includes(key)
35
- ? `"${key}".value`
36
- : `"${root}"."${column}"`;
37
- if (metadata.arrayFields.includes(key))
38
- joins.push(`cross join lateral unnest("${root}"."${column}") as "${key}"(value)`);
76
+ const add = (value) => { values.push(value); return `$${values.length}`; };
77
+ const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, `"${root}"`, profile, add)}`;
78
+ for (const key of query.by) {
79
+ if (key.kind === "field") {
80
+ const fieldName = key.path[0];
81
+ const column = field(relation, metadata, fieldName);
82
+ const expression = metadata.arrayFields.includes(fieldName)
83
+ ? `"${fieldName}".value`
84
+ : compileCanonicalPhysicalField(relation, fieldName, `"${root}"`, profile);
85
+ if (metadata.arrayFields.includes(fieldName))
86
+ joins.push(`cross join lateral unnest("${root}"."${column}") as "${fieldName}"(value)`);
39
87
  expressions.push(expression);
40
- selected.push(`${expression} as "${key}"`);
88
+ selected.push(`${expression} as "${fieldName}"`);
41
89
  continue;
42
90
  }
43
- if (key === null || typeof key !== "object")
44
- throw new Error("invalid group key");
45
- const [edgeName, nested] = Object.entries(key)[0] ?? [];
46
- if (nested !== null && typeof nested === "object" && "name" in nested && "path" in nested && "as" in nested) {
47
- const projection = nested;
48
- if (typeof projection.name !== "string" || !Array.isArray(projection.path) || !projection.path.every((segment) => typeof segment === "string" && segment.length > 0) || !["text", "numeric", "boolean", "timestamp"].includes(String(projection.as)) || !PqsSchemaProfileV1.jsonField(relation, edgeName))
49
- throw new Error("invalid profiled JSON group key");
50
- const column = field(relation, metadata, edgeName);
51
- const text = `"${root}"."${column}" #>> ${add(projection.path)}::text[]`;
52
- const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
91
+ if (key.kind === "json") {
92
+ field(relation, metadata, key.field);
93
+ const text = `${compileCanonicalPhysicalField(relation, key.field, `"${root}"`, profile)} #>> ${add(key.path)}::text[]`;
94
+ const expression = key.as === "text" ? text : key.as === "numeric" ? `(${text})::numeric` : key.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
53
95
  expressions.push(expression);
54
- selected.push(`${expression} as "${projection.name}"`);
96
+ selected.push(`${expression} as ${quotePqsIdentifier(key.name)}`);
55
97
  continue;
56
98
  }
57
- if (nested !== null && typeof nested === "object" && "bucket" in nested && PqsSchemaProfileV1.bucketField(relation, edgeName)) {
58
- const bucket = nested.bucket;
59
- if (typeof bucket !== "string" || !["hour", "day", "week", "month"].includes(bucket))
60
- throw new Error("invalid profiled time bucket");
61
- const column = field(relation, metadata, edgeName);
62
- const expression = `date_trunc('${bucket}', "${root}"."${column}")`;
99
+ if (key.kind === "bucket" && key.path.length === 1) {
100
+ field(relation, metadata, key.path[0]);
101
+ const expression = `date_trunc('${key.bucket}', ${compileCanonicalPhysicalField(relation, key.path[0], `"${root}"`, profile)})`;
63
102
  expressions.push(expression);
64
- selected.push(`${expression} as "${edgeName}_${bucket}"`);
103
+ selected.push(`${expression} as "${key.path[0]}_${key.bucket}"`);
65
104
  continue;
66
105
  }
106
+ const edgeName = key.path[0];
107
+ const fieldName = key.path[1];
67
108
  const edge = pqsRelationEdges[relation]?.[edgeName];
68
- if (edge === undefined || edge.cardinality !== "one" || nested === null || typeof nested !== "object")
109
+ if (edge === undefined || edge.cardinality !== "one")
69
110
  throw new Error("group key must follow a profiled to-one edge");
70
- const [fieldName, bucketValue] = Object.entries(nested)[0] ?? [];
71
- const bucket = bucketValue !== null && typeof bucketValue === "object" ? bucketValue.bucket : undefined;
72
- if (typeof bucket !== "string" || !["hour", "day", "week", "month"].includes(bucket) || !PqsSchemaProfileV1.bucketField(edge.target, fieldName))
73
- throw new Error("invalid profiled time bucket");
74
- const targetColumn = field(edge.target, pqsRelationMetadata[edge.target], fieldName);
111
+ field(edge.target, pqsRelationMetadata[edge.target], fieldName);
75
112
  joins.push(`join ${profile.relation(edge.target)} "${edgeName}" on "${edgeName}"."${edge.targetColumn}" = "${root}"."${edge.sourceColumn}"`);
76
- const expression = `date_trunc('${bucket}', "${edgeName}"."${targetColumn}")`;
113
+ const expression = `date_trunc('${key.bucket}', ${compileCanonicalPhysicalField(edge.target, fieldName, `"${edgeName}"`, profile)})`;
77
114
  expressions.push(expression);
78
- selected.push(`${expression} as "${edgeName}_${fieldName}_${bucket}"`);
115
+ selected.push(`${expression} as "${edgeName}_${fieldName}_${key.bucket}"`);
79
116
  }
80
- if (args.aggregate.count)
117
+ if (query.aggregates.count)
81
118
  selected.push("count(*)::text as count");
82
- for (const [operation, fields] of [["min", args.aggregate.min], ["max", args.aggregate.max], ["sum", args.aggregate.sum]])
83
- for (const name of fields ?? []) {
119
+ for (const [operation, fields] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]])
120
+ for (const name of fields) {
84
121
  if (!metadata.numericFields.includes(name))
85
122
  throw new Error(`${name} is not a numeric aggregate field of ${relation}`);
86
- selected.push(`${operation}("${root}"."${field(relation, metadata, name)}")::text as "${operation}_${name}"`);
123
+ field(relation, metadata, name);
124
+ selected.push(`${operation}(${compileCanonicalPhysicalField(relation, name, `"${root}"`, profile)})::text as "${operation}_${name}"`);
87
125
  }
88
- return { text: `select ${selected.join(", ")} from ${profile.relation(relation)} "${root}" ${joins.join(" ")} group by ${expressions.join(", ")}`, values };
126
+ return { text: `${source.withClause}select ${selected.join(", ")} from ${source.relation} "${root}"${joins.length === 0 ? "" : ` ${joins.join(" ")}`}${predicate} group by ${expressions.join(", ")}`, values };
127
+ }
128
+ function logicalTypeRootSource(relation, profile) {
129
+ if (relation !== "__contract_tpe" && relation !== "__exercise_tpe") {
130
+ const physical = profile.relation(relation);
131
+ return { withClause: "", relation: physical, reference: physical };
132
+ }
133
+ const publicKey = compileCanonicalPhysicalField(relation, "pk", '"physical_type"', profile);
134
+ const logicalRelation = '"logical_type_root"';
135
+ return {
136
+ withClause: `with ${logicalRelation} as (select distinct on (${publicKey}) "physical_type".* from ${profile.relation(relation)} "physical_type" order by ${publicKey}, "physical_type"."pk" asc) `,
137
+ relation: logicalRelation,
138
+ reference: logicalRelation,
139
+ };
89
140
  }
90
- function selectedFields(relation, metadata, select) {
91
- const fields = select === undefined
92
- ? Object.entries(metadata.fields)
93
- : Object.entries(select).filter(([, enabled]) => enabled).map(([name]) => [name, field(relation, metadata, name)]);
94
- if (fields.length === 0)
141
+ function selectedCanonicalFields(relation, metadata, selected, allowEmpty = false) {
142
+ const fields = selected === undefined ? Object.entries(metadata.fields) : selected.map((name) => [name, field(relation, metadata, name)]);
143
+ if (fields.length === 0 && !allowEmpty)
95
144
  throw new Error("select must include at least one field");
96
145
  return fields;
97
146
  }
98
- function compileWhere(relation, metadata, where, add) {
99
- const conditions = [];
100
- for (const [name, value] of Object.entries(where ?? {})) {
101
- const column = field(relation, metadata, name);
102
- if (value === null || Array.isArray(value) || typeof value !== "object")
103
- throw new Error(`${name} must be a filter`);
104
- const filter = value;
105
- for (const [operator, operand] of Object.entries(filter)) {
106
- if (operator === "equals")
107
- conditions.push(`"${column}" = ${add(operand)}`);
108
- else if (operator === "in" && Array.isArray(operand))
109
- conditions.push(operand.length === 0 ? "false" : `"${column}" = any(${add(operand)})`);
110
- else if (operator === "is" && operand === null)
111
- conditions.push(`"${column}" is null`);
112
- else if (operator === "isNot" && operand === null)
113
- conditions.push(`"${column}" is not null`);
114
- else if (["lt", "lte", "gt", "gte", "like", "ilike"].includes(operator)) {
115
- const sql = { lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[operator];
116
- conditions.push(`"${column}" ${sql} ${add(operand)}`);
117
- }
118
- else
119
- throw new Error(`${operator} is not supported for ${name}`);
120
- }
121
- }
122
- return conditions.length === 0 ? "" : ` where ${conditions.join(" and ")}`;
147
+ function compileCanonicalJsonSelections(relation, selection, profile, add) {
148
+ return (selection?.json ?? []).map((projection) => {
149
+ if (!PqsSchemaProfileV1.jsonField(relation, projection.field))
150
+ throw new Error(`${projection.field} is not a JSON field of ${relation}`);
151
+ field(relation, pqsRelationMetadata[relation], projection.field);
152
+ const text = `${compileCanonicalPhysicalField(relation, projection.field, "", profile)} #>> ${add(projection.path)}::text[]`;
153
+ const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
154
+ return `${expression} as ${quotePqsIdentifier(projection.name)}`;
155
+ });
123
156
  }
124
- function compileOrderBy(relation, metadata, orderBy) {
125
- if (orderBy === undefined)
157
+ function compileCanonicalOrderBy(relation, metadata, orderBy, profile) {
158
+ if (orderBy.length === 0)
126
159
  return "";
127
- const requested = orderBy.flatMap((entry) => Object.entries(entry));
128
- if (requested.length === 0 || requested.some(([, direction]) => direction !== "asc" && direction !== "desc"))
129
- throw new Error("orderBy must be a non-empty list of one-field entries");
130
- const ordered = requested.map(([name, direction]) => [field(relation, metadata, name), direction]);
131
- const stableKey = metadata.uniqueKeys[0]?.[0];
132
- if (stableKey !== undefined && !ordered.some(([column]) => column === metadata.fields[stableKey]))
133
- ordered.push([metadata.fields[stableKey], "asc"]);
134
- return ` order by ${ordered.map(([column, direction]) => `"${column}" ${direction}`).join(", ")}`;
160
+ return ` order by ${orderBy.map((order) => {
161
+ field(relation, metadata, order.path[0]);
162
+ return `${compileCanonicalPhysicalField(relation, order.path[0], "", profile)} ${order.direction}`;
163
+ }).join(", ")}`;
135
164
  }
136
165
  function field(relation, metadata, name) {
137
166
  const column = metadata.fields[name];
@@ -139,8 +168,20 @@ function field(relation, metadata, name) {
139
168
  throw new Error(`${name} is not a field of ${relation}`);
140
169
  return column;
141
170
  }
142
- function assertPage(args) {
143
- for (const [name, value] of [["take", args.take], ["skip", args.skip]])
144
- if (value !== undefined && (!Number.isInteger(value) || value < 0))
145
- throw new Error(`${name} must be a non-negative integer`);
171
+ function assertCanonicalPhysicalFindMany(relation, query) {
172
+ if (query === null || typeof query !== "object" || query.kind !== "findMany" || !Array.isArray(query.includes) || !Array.isArray(query.orderBy)) {
173
+ throw new Error("compilePqsRelationFindMany requires a canonical findMany query");
174
+ }
175
+ if (relationForCanonical(query.relation) !== relation)
176
+ throw new Error(`Canonical relation ${query.relation} does not match ${relation}`);
177
+ }
178
+ function assertCanonicalPhysicalQuery(relation, query, kind) {
179
+ if (query === null || typeof query !== "object" || query.kind !== kind) {
180
+ throw new Error(`compilePqsRelation${kind[0].toUpperCase()}${kind.slice(1)} requires a canonical ${kind} query`);
181
+ }
182
+ if (relationForCanonical(query.relation) !== relation)
183
+ throw new Error(`Canonical relation ${query.relation} does not match ${relation}`);
184
+ }
185
+ function relationForCanonical(relation) {
186
+ return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
146
187
  }
@@ -0,0 +1,25 @@
1
+ import type { NormalizedInclude, NormalizedSelection } from "../canonical/query-ast.js";
2
+ import { PqsRelation } from "./pqs-schema-profile.js";
3
+ export interface PqsSelectedScalarField {
4
+ readonly name: string;
5
+ }
6
+ export interface PqsJsonResultProjection {
7
+ readonly name: string;
8
+ readonly field: string;
9
+ readonly path: readonly string[];
10
+ readonly as: "text" | "numeric" | "boolean" | "timestamp";
11
+ }
12
+ export interface PqsIncludedResultShape {
13
+ readonly edge: string;
14
+ readonly target: PqsRelation;
15
+ readonly cardinality: "one" | "many";
16
+ readonly shape: PqsRelationResultShape;
17
+ }
18
+ export interface PqsRelationResultShape {
19
+ readonly relation: PqsRelation;
20
+ readonly cardinality: "one" | "many";
21
+ readonly fields: readonly PqsSelectedScalarField[];
22
+ readonly json: readonly PqsJsonResultProjection[];
23
+ readonly includes: readonly PqsIncludedResultShape[];
24
+ }
25
+ export declare function compilePqsResultShape(relation: PqsRelation, cardinality: "one" | "many", selection: NormalizedSelection | undefined, includes: readonly NormalizedInclude[]): PqsRelationResultShape;
@@ -0,0 +1,25 @@
1
+ import { pqsRelationEdges, pqsRelationMetadata } from "./pqs-schema-profile.js";
2
+ export function compilePqsResultShape(relation, cardinality, selection, includes) {
3
+ const fields = relation === "__contracts"
4
+ ? selection?.fields ?? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"]
5
+ : selection?.fields ?? Object.keys(pqsRelationMetadata[relation].fields);
6
+ const shape = {
7
+ relation,
8
+ cardinality,
9
+ fields: fields.map((name) => Object.freeze({ name })),
10
+ json: (selection?.json ?? []).map((projection) => Object.freeze({ ...projection, path: Object.freeze([...projection.path]) })),
11
+ includes: includes.map((include) => {
12
+ const edge = pqsRelationEdges[relation]?.[include.edge];
13
+ if (edge === undefined || edge.target !== relationForCanonical(include.relation) || edge.cardinality !== include.cardinality)
14
+ throw new Error(`Invalid canonical include ${include.edge}`);
15
+ return Object.freeze({ edge: include.edge, target: edge.target, cardinality: include.cardinality, shape: compilePqsResultShape(edge.target, include.cardinality, include.select, include.includes) });
16
+ }),
17
+ };
18
+ Object.freeze(shape.fields);
19
+ Object.freeze(shape.json);
20
+ Object.freeze(shape.includes);
21
+ return Object.freeze(shape);
22
+ }
23
+ function relationForCanonical(relation) {
24
+ return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
25
+ }
@@ -1,4 +1,5 @@
1
1
  import { PqsSchemaProfileError } from "../errors/pqs-schema-profile-error.js";
2
+ import { queryRelationEdges, queryRelationMetadata, } from "../canonical/query-schema.js";
2
3
  const schemaIdentifier = /^[A-Za-z_][A-Za-z0-9_]*$/;
3
4
  export const requiredPqsRelations = [
4
5
  "__contracts",
@@ -10,56 +11,56 @@ export const requiredPqsRelations = [
10
11
  "__transactions",
11
12
  "__watermark",
12
13
  ];
13
- export const pqsRelationEdges = {
14
- __contracts: {
15
- contractType: { target: "__contract_tpe", sourceColumn: "tpe_pk", targetColumn: "pk", cardinality: "one", nullable: false },
16
- createdTransaction: { target: "__transactions", sourceColumn: "created_at_ix", targetColumn: "ix", cardinality: "one", nullable: false },
17
- archivedTransaction: { target: "__transactions", sourceColumn: "archived_at_ix", targetColumn: "ix", cardinality: "one", nullable: true },
18
- exercises: { target: "__exercises", sourceColumn: "contract_id", targetColumn: "contract_id", cardinality: "many", nullable: false },
19
- },
20
- __contract_tpe: {
21
- contracts: { target: "__contracts", sourceColumn: "pk", targetColumn: "tpe_pk", cardinality: "many", nullable: false },
22
- exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "contract_tpe_pk", cardinality: "many", nullable: false },
23
- },
24
- __events: {
25
- transaction: { target: "__transactions", sourceColumn: "tx_ix", targetColumn: "ix", cardinality: "one", nullable: false },
26
- exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "exercise_event_pk", cardinality: "many", nullable: false },
27
- },
28
- __exercises: {
29
- exerciseType: { target: "__exercise_tpe", sourceColumn: "tpe_pk", targetColumn: "pk", cardinality: "one", nullable: false },
30
- contractType: { target: "__contract_tpe", sourceColumn: "contract_tpe_pk", targetColumn: "pk", cardinality: "one", nullable: false },
31
- event: { target: "__events", sourceColumn: "exercise_event_pk", targetColumn: "pk", cardinality: "one", nullable: true },
32
- transaction: { target: "__transactions", sourceColumn: "exercised_at_ix", targetColumn: "ix", cardinality: "one", nullable: true },
33
- package: { target: "__packages", sourceColumn: "package_pk", targetColumn: "pk", cardinality: "one", nullable: false },
34
- contract: { target: "__contracts", sourceColumn: "contract_id", targetColumn: "contract_id", cardinality: "one", nullable: false },
35
- },
36
- __exercise_tpe: { exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "tpe_pk", cardinality: "many", nullable: false } },
37
- __packages: { exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "package_pk", cardinality: "many", nullable: false } },
38
- __transactions: {
39
- events: { target: "__events", sourceColumn: "ix", targetColumn: "tx_ix", cardinality: "many", nullable: false },
40
- createdContracts: { target: "__contracts", sourceColumn: "ix", targetColumn: "created_at_ix", cardinality: "many", nullable: false },
41
- archivedContracts: { target: "__contracts", sourceColumn: "ix", targetColumn: "archived_at_ix", cardinality: "many", nullable: false },
42
- exercises: { target: "__exercises", sourceColumn: "ix", targetColumn: "exercised_at_ix", cardinality: "many", nullable: false },
43
- },
14
+ const queryRelationByPqsRelation = {
15
+ __contracts: "contracts",
16
+ __contract_tpe: "contractTypes",
17
+ __events: "events",
18
+ __exercises: "exercises",
19
+ __exercise_tpe: "exerciseTypes",
20
+ __packages: "packages",
21
+ __transactions: "transactions",
22
+ __watermark: "watermark",
44
23
  };
45
- const pqsJsonFields = {
46
- __contracts: ["payload"],
47
- __exercises: ["argument", "result"],
48
- __transactions: ["traceContext"],
24
+ const pqsRelationByQueryRelation = {
25
+ contracts: "__contracts",
26
+ contractTypes: "__contract_tpe",
27
+ events: "__events",
28
+ exercises: "__exercises",
29
+ exerciseTypes: "__exercise_tpe",
30
+ packages: "__packages",
31
+ transactions: "__transactions",
32
+ watermark: "__watermark",
49
33
  };
50
- const pqsBucketFields = {
51
- __transactions: ["effectiveAt"],
34
+ const pqsEdgeColumns = {
35
+ __contracts: { contractType: { sourceColumn: "tpe_pk", targetColumn: "pk" }, createdTransaction: { sourceColumn: "created_at_ix", targetColumn: "ix" }, archivedTransaction: { sourceColumn: "archived_at_ix", targetColumn: "ix" }, exercises: { sourceColumn: "contract_id", targetColumn: "contract_id" } },
36
+ __contract_tpe: { contracts: { sourceColumn: "pk", targetColumn: "tpe_pk" }, exercises: { sourceColumn: "pk", targetColumn: "contract_tpe_pk" } },
37
+ __events: { transaction: { sourceColumn: "tx_ix", targetColumn: "ix" }, exercises: { sourceColumn: "pk", targetColumn: "exercise_event_pk" } },
38
+ __exercises: { exerciseType: { sourceColumn: "tpe_pk", targetColumn: "pk" }, contractType: { sourceColumn: "contract_tpe_pk", targetColumn: "pk" }, event: { sourceColumn: "exercise_event_pk", targetColumn: "pk" }, transaction: { sourceColumn: "exercised_at_ix", targetColumn: "ix" }, package: { sourceColumn: "package_pk", targetColumn: "pk" }, contract: { sourceColumn: "contract_id", targetColumn: "contract_id" } },
39
+ __exercise_tpe: { exercises: { sourceColumn: "pk", targetColumn: "tpe_pk" } },
40
+ __packages: { exercises: { sourceColumn: "pk", targetColumn: "package_pk" } },
41
+ __transactions: { events: { sourceColumn: "ix", targetColumn: "tx_ix" }, createdContracts: { sourceColumn: "ix", targetColumn: "created_at_ix" }, archivedContracts: { sourceColumn: "ix", targetColumn: "archived_at_ix" }, exercises: { sourceColumn: "ix", targetColumn: "exercised_at_ix" } },
52
42
  };
53
- export const pqsRelationMetadata = {
54
- __contracts: { fields: {}, uniqueKeys: [["contractId"]], numericFields: ["createdEventOffset", "archivedEventOffset"], arrayFields: ["witnesses"], dateFields: ["createdAt", "archivedAt"], binaryFields: [] },
55
- __contract_tpe: { fields: { pk: "pk", payloadType: "payload_type", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn" }, uniqueKeys: [["pk"]], numericFields: ["pk"], arrayFields: ["aliases"], dateFields: [], binaryFields: [] },
56
- __events: { fields: { pk: "pk", txIx: "tx_ix", eventId: "event_id", type: "type" }, uniqueKeys: [["pk"]], numericFields: ["pk", "txIx"], arrayFields: [], dateFields: [], binaryFields: [], stringFields: ["eventId", "type"] },
57
- __exercises: { fields: { tpePk: "tpe_pk", contractTpePk: "contract_tpe_pk", exerciseEventPk: "exercise_event_pk", exercisedAtIx: "exercised_at_ix", contractId: "contract_id", argument: "argument", result: "result", redactionId: "redaction_id", packagePk: "package_pk", controllers: "controllers", lastDescendantNodeId: "last_descendant_node_id", witnesses: "witnesses" }, uniqueKeys: [], numericFields: ["tpePk", "contractTpePk", "exerciseEventPk", "exercisedAtIx", "packagePk", "lastDescendantNodeId"], arrayFields: ["controllers", "witnesses"], dateFields: [], binaryFields: [] },
58
- __exercise_tpe: { fields: { pk: "pk", choice: "choice", consuming: "consuming", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn", choiceFqn: "choice_fqn" }, uniqueKeys: [["pk"]], numericFields: ["pk"], arrayFields: ["aliases"], dateFields: [], binaryFields: [] },
59
- __packages: { fields: { pk: "pk", name: "name", version: "version", id: "id" }, uniqueKeys: [["pk"], ["id"]], numericFields: ["pk"], arrayFields: [], dateFields: [], binaryFields: [], stringFields: ["name", "version", "id"] },
60
- __transactions: { fields: { ix: "ix", offset: "offset", transactionId: "transaction_id", effectiveAt: "effective_at", workflowId: "workflow_id", domainId: "domain_id", traceContext: "trace_context", externalTransactionHash: "external_transaction_hash", paidTrafficCost: "paid_traffic_cost" }, uniqueKeys: [["ix"], ["offset"]], numericFields: ["ix", "offset", "paidTrafficCost"], arrayFields: [], dateFields: ["effectiveAt"], binaryFields: ["externalTransactionHash"] },
61
- __watermark: { fields: { singleton: "singleton", ix: "ix", offset: "offset", instanceId: "instance_id" }, uniqueKeys: [["singleton"]], numericFields: ["ix", "offset"], arrayFields: [], dateFields: [], binaryFields: [] },
43
+ export const pqsRelationEdges = Object.fromEntries(requiredPqsRelations.map((relation) => [relation, Object.fromEntries(Object.entries(queryRelationEdges[queryRelationByPqsRelation[relation]] ?? {}).map(([name, edge]) => {
44
+ const columns = pqsEdgeColumns[relation]?.[name];
45
+ if (columns === undefined) {
46
+ throw new Error(`Missing physical PQS edge mapping for ${relation}.${name}`);
47
+ }
48
+ return [name, { target: pqsRelationByQueryRelation[edge.target], ...columns, cardinality: edge.cardinality, nullable: edge.nullable }];
49
+ }))]));
50
+ const pqsFields = {
51
+ __contracts: {},
52
+ __contract_tpe: { pk: "pk", payloadType: "payload_type", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn" },
53
+ __events: { pk: "pk", txIx: "tx_ix", eventId: "event_id", type: "type" },
54
+ __exercises: { tpePk: "tpe_pk", contractTpePk: "contract_tpe_pk", exerciseEventPk: "exercise_event_pk", exercisedAtIx: "exercised_at_ix", contractId: "contract_id", argument: "argument", result: "result", redactionId: "redaction_id", packagePk: "package_pk", controllers: "controllers", lastDescendantNodeId: "last_descendant_node_id", witnesses: "witnesses" },
55
+ __exercise_tpe: { pk: "pk", choice: "choice", consuming: "consuming", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn", choiceFqn: "choice_fqn" },
56
+ __packages: { pk: "pk", name: "name", version: "version", id: "id" },
57
+ __transactions: { ix: "ix", offset: "offset", transactionId: "transaction_id", effectiveAt: "effective_at", workflowId: "workflow_id", domainId: "domain_id", traceContext: "trace_context", externalTransactionHash: "external_transaction_hash", paidTrafficCost: "paid_traffic_cost" },
58
+ __watermark: { singleton: "singleton", ix: "ix", offset: "offset", instanceId: "instance_id" },
62
59
  };
60
+ export const pqsRelationMetadata = Object.fromEntries(requiredPqsRelations.map((relation) => {
61
+ const logical = queryRelationMetadata[queryRelationByPqsRelation[relation]];
62
+ return [relation, { fields: pqsFields[relation], uniqueKeys: logical.uniqueKeys, numericFields: logical.numericFields, arrayFields: logical.arrayFields, dateFields: logical.dateFields, binaryFields: logical.binaryFields, stringFields: logical.stringFields }];
63
+ }));
63
64
  export const requiredPqsColumns = {
64
65
  __contracts: [
65
66
  "tpe_pk",
@@ -140,10 +141,10 @@ export class PqsSchemaProfileV1 {
140
141
  return `${quoteIdentifier(this.schema)}.${quoteIdentifier(relation)}`;
141
142
  }
142
143
  static jsonField(relation, field) {
143
- return pqsJsonFields[relation]?.includes(field) ?? false;
144
+ return queryRelationMetadata[queryRelationByPqsRelation[relation]].jsonFields.includes(field);
144
145
  }
145
146
  static bucketField(relation, field) {
146
- return pqsBucketFields[relation]?.includes(field) ?? false;
147
+ return queryRelationMetadata[queryRelationByPqsRelation[relation]].bucketFields.includes(field);
147
148
  }
148
149
  }
149
150
  export async function validatePqsSchemaAsync(client, profile) {
@@ -1,8 +1,23 @@
1
- import { ContractFindManyArgs, ContractGroupByArgs } from "../model-types.js";
1
+ import type { NormalizedAggregateQuery, NormalizedCountQuery, NormalizedFindManyQuery, NormalizedGroupByQuery, NormalizedInclude, QueryPredicate } from "../canonical/query-ast.js";
2
2
  import { PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
3
+ import { PqsRelation } from "./pqs-schema-profile.js";
4
+ import { type PqsRelationResultShape } from "./pqs-result-shape.js";
3
5
  export interface CompiledPqsQuery {
4
6
  readonly text: string;
5
7
  readonly values: readonly unknown[];
6
8
  }
7
- export declare function compileContractFindMany(args: ContractFindManyArgs, profile: PqsSchemaProfileV1): CompiledPqsQuery;
8
- export declare function compileContractGroupBy(args: ContractGroupByArgs, profile: PqsSchemaProfileV1): CompiledPqsQuery;
9
+ export interface CompiledPqsInclude {
10
+ readonly key: string;
11
+ readonly expression: string;
12
+ readonly selection: string;
13
+ }
14
+ export interface CompiledContractFindManyQuery extends CompiledPqsQuery {
15
+ readonly resultShape: PqsRelationResultShape;
16
+ }
17
+ export declare function compileCanonicalPhysicalField(relation: PqsRelation, field: string, alias: string, profile: PqsSchemaProfileV1): string;
18
+ export declare function compileContractFindMany(query: NormalizedFindManyQuery, profile: PqsSchemaProfileV1): CompiledContractFindManyQuery;
19
+ export declare function compileContractCount(query: NormalizedCountQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;
20
+ export declare function compileContractAggregate(query: NormalizedAggregateQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;
21
+ export declare function compileCanonicalPhysicalIncludes(source: PqsRelation, parentAlias: string, includes: readonly NormalizedInclude[], profile: PqsSchemaProfileV1, add: (value: unknown) => string): readonly CompiledPqsInclude[];
22
+ export declare function compileCanonicalPhysicalPredicate(relation: PqsRelation, predicate: QueryPredicate, alias: string, profile: PqsSchemaProfileV1, add: (value: unknown) => string): string;
23
+ export declare function compileContractGroupBy(query: NormalizedGroupByQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;