@distrohelena/canton-typescript-sdk 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +43 -0
  2. package/dist/client/canton-client-options.d.ts +2 -0
  3. package/dist/client/canton-client-options.js +2 -0
  4. package/dist/client/external-party-activation-client.js +34 -20
  5. package/dist/client/external-party-activation-response.d.ts +1 -1
  6. package/dist/core/transports/transport.interface.d.ts +16 -22
  7. package/dist/debugger/replay/ledger-replay-environment-builder.js +1 -1
  8. package/dist/index.d.ts +1 -19
  9. package/dist/index.js +0 -18
  10. package/dist/protobuf/index.d.ts +4075 -0
  11. package/dist/query/grpc/grpc-contract-query-client.d.ts +4 -3
  12. package/dist/query/grpc/grpc-contract-query-client.js +12 -4
  13. package/dist/query/model-types.d.ts +232 -14
  14. package/dist/query/model-types.js +2 -2
  15. package/dist/query/pqs/pqs-query-client.d.ts +14 -3
  16. package/dist/query/pqs/pqs-query-client.js +381 -25
  17. package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +23 -0
  18. package/dist/query/pqs/pqs-relational-sql-compiler.js +146 -0
  19. package/dist/query/pqs/pqs-schema-profile.d.ts +10 -0
  20. package/dist/query/pqs/pqs-schema-profile.js +46 -0
  21. package/dist/query/pqs/pqs-sql-compiler.d.ts +2 -1
  22. package/dist/query/pqs/pqs-sql-compiler.js +212 -10
  23. package/dist/query/query-client.d.ts +20 -16
  24. package/dist/services/party-management/decentralized-party-lifecycle.js +96 -18
  25. package/dist/services/party-management/party-management-service-client.d.ts +2 -2
  26. package/dist/services/party-management/party-management-service-client.js +34 -9
  27. package/dist/services/topology-manager-write/topology-manager-write-service-client.d.ts +5 -16
  28. package/dist/transports/grpc/grpc-call-options-factory.d.ts +1 -1
  29. package/dist/transports/grpc/grpc-call-options-factory.js +8 -4
  30. package/dist/transports/grpc/grpc-channel-factory.js +13 -8
  31. package/dist/transports/grpc/grpc-transport.d.ts +9 -9
  32. package/dist/transports/grpc/grpc-transport.js +12 -13
  33. package/dist/transports/grpc/mappers/topology-common-mapper.d.ts +5 -1
  34. package/dist/transports/grpc/mappers/topology-common-mapper.js +17 -0
  35. package/dist/transports/json/json-transport.d.ts +18 -16
  36. package/node/start-local.sh +197 -2
  37. package/node/stop-local.sh +25 -1
  38. package/node/test-start-local.sh +101 -4
  39. package/node/test-stop-local.sh +8 -1
  40. package/package.json +1 -1
  41. package/dist/core/types/requests/add-topology-transactions-request.d.ts +0 -16
  42. package/dist/core/types/requests/add-topology-transactions-request.js +0 -12
  43. package/dist/core/types/requests/allocate-external-party-request.d.ts +0 -18
  44. package/dist/core/types/requests/allocate-external-party-request.js +0 -16
  45. package/dist/core/types/requests/authorize-topology-transactions-request.d.ts +0 -37
  46. package/dist/core/types/requests/authorize-topology-transactions-request.js +0 -35
  47. package/dist/core/types/requests/generate-external-party-topology-request.d.ts +0 -19
  48. package/dist/core/types/requests/generate-external-party-topology-request.js +0 -21
  49. package/dist/core/types/requests/generate-topology-transactions-request.d.ts +0 -26
  50. package/dist/core/types/requests/generate-topology-transactions-request.js +0 -22
  51. package/dist/core/types/requests/import-topology-snapshot-request.d.ts +0 -12
  52. package/dist/core/types/requests/import-topology-snapshot-request.js +0 -10
  53. package/dist/core/types/requests/import-topology-snapshot-v2-request.d.ts +0 -12
  54. package/dist/core/types/requests/import-topology-snapshot-v2-request.js +0 -10
  55. package/dist/core/types/requests/sign-topology-transactions-request.d.ts +0 -15
  56. package/dist/core/types/requests/sign-topology-transactions-request.js +0 -12
  57. package/dist/core/types/responses/add-topology-transactions-response.d.ts +0 -3
  58. package/dist/core/types/responses/add-topology-transactions-response.js +0 -5
  59. package/dist/core/types/responses/allocate-external-party-response.d.ts +0 -6
  60. package/dist/core/types/responses/allocate-external-party-response.js +0 -6
  61. package/dist/core/types/responses/authorize-topology-transactions-response.d.ts +0 -7
  62. package/dist/core/types/responses/authorize-topology-transactions-response.js +0 -6
  63. package/dist/core/types/responses/generate-external-party-topology-response.d.ts +0 -12
  64. package/dist/core/types/responses/generate-external-party-topology-response.js +0 -12
  65. package/dist/core/types/responses/generate-topology-transactions-response.d.ts +0 -7
  66. package/dist/core/types/responses/generate-topology-transactions-response.js +0 -6
  67. package/dist/core/types/responses/import-topology-snapshot-response.d.ts +0 -3
  68. package/dist/core/types/responses/import-topology-snapshot-response.js +0 -5
  69. package/dist/core/types/responses/import-topology-snapshot-v2-response.d.ts +0 -3
  70. package/dist/core/types/responses/import-topology-snapshot-v2-response.js +0 -5
  71. package/dist/core/types/responses/sign-topology-transactions-response.d.ts +0 -7
  72. package/dist/core/types/responses/sign-topology-transactions-response.js +0 -6
  73. package/dist/transports/grpc/mappers/external-party-management-mapper.d.ts +0 -9
  74. package/dist/transports/grpc/mappers/external-party-management-mapper.js +0 -126
  75. package/dist/transports/grpc/mappers/topology-manager-write-mapper.d.ts +0 -25
  76. package/dist/transports/grpc/mappers/topology-manager-write-mapper.js +0 -475
@@ -0,0 +1,146 @@
1
+ import { PqsSchemaProfileV1, pqsRelationMetadata } from "./pqs-schema-profile.js";
2
+ import { pqsRelationEdges } from "./pqs-schema-profile.js";
3
+ export function compilePqsRelationFindMany(relation, args, profile) {
4
+ assertPage(args);
5
+ const metadata = pqsRelationMetadata[relation];
6
+ const values = [];
7
+ const add = (value) => {
8
+ values.push(value);
9
+ return `$${values.length}`;
10
+ };
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)}`;
16
+ return {
17
+ text: `select ${fields.map(([field, column]) => `"${column}" as "${field}"`).join(", ")} from ${profile.relation(relation)}${where}${orderBy}${limit}${offset}`,
18
+ values,
19
+ };
20
+ }
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))
23
+ throw new Error("groupBy requires a key and aggregate");
24
+ const metadata = pqsRelationMetadata[relation];
25
+ const root = relation === "__events" ? "event" : "root";
26
+ const joins = [];
27
+ const expressions = [];
28
+ const selected = [];
29
+ 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)`);
39
+ expressions.push(expression);
40
+ selected.push(`${expression} as "${key}"`);
41
+ continue;
42
+ }
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`;
53
+ expressions.push(expression);
54
+ selected.push(`${expression} as "${projection.name}"`);
55
+ continue;
56
+ }
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}")`;
63
+ expressions.push(expression);
64
+ selected.push(`${expression} as "${edgeName}_${bucket}"`);
65
+ continue;
66
+ }
67
+ const edge = pqsRelationEdges[relation]?.[edgeName];
68
+ if (edge === undefined || edge.cardinality !== "one" || nested === null || typeof nested !== "object")
69
+ 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);
75
+ joins.push(`join ${profile.relation(edge.target)} "${edgeName}" on "${edgeName}"."${edge.targetColumn}" = "${root}"."${edge.sourceColumn}"`);
76
+ const expression = `date_trunc('${bucket}', "${edgeName}"."${targetColumn}")`;
77
+ expressions.push(expression);
78
+ selected.push(`${expression} as "${edgeName}_${fieldName}_${bucket}"`);
79
+ }
80
+ if (args.aggregate.count)
81
+ 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 ?? []) {
84
+ if (!metadata.numericFields.includes(name))
85
+ 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}"`);
87
+ }
88
+ return { text: `select ${selected.join(", ")} from ${profile.relation(relation)} "${root}" ${joins.join(" ")} group by ${expressions.join(", ")}`, values };
89
+ }
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)
95
+ throw new Error("select must include at least one field");
96
+ return fields;
97
+ }
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 ")}`;
123
+ }
124
+ function compileOrderBy(relation, metadata, orderBy) {
125
+ if (orderBy === undefined)
126
+ 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(", ")}`;
135
+ }
136
+ function field(relation, metadata, name) {
137
+ const column = metadata.fields[name];
138
+ if (column === undefined)
139
+ throw new Error(`${name} is not a field of ${relation}`);
140
+ return column;
141
+ }
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`);
146
+ }
@@ -1,5 +1,13 @@
1
1
  export declare const requiredPqsRelations: readonly ["__contracts", "__contract_tpe", "__events", "__exercises", "__exercise_tpe", "__packages", "__transactions", "__watermark"];
2
2
  export type PqsRelation = (typeof requiredPqsRelations)[number];
3
+ export interface PqsRelationEdge {
4
+ readonly target: PqsRelation;
5
+ readonly sourceColumn: string;
6
+ readonly targetColumn: string;
7
+ readonly cardinality: "one" | "many";
8
+ readonly nullable: boolean;
9
+ }
10
+ export declare const pqsRelationEdges: Readonly<Partial<Record<PqsRelation, Readonly<Record<string, PqsRelationEdge>>>>>;
3
11
  export interface PqsRelationMetadata {
4
12
  readonly fields: Readonly<Record<string, string>>;
5
13
  readonly uniqueKeys: readonly (readonly string[])[];
@@ -15,6 +23,8 @@ export declare class PqsSchemaProfileV1 {
15
23
  readonly schema: string;
16
24
  constructor(schema?: string);
17
25
  relation(relation: PqsRelation): string;
26
+ static jsonField(relation: PqsRelation, field: string): boolean;
27
+ static bucketField(relation: PqsRelation, field: string): boolean;
18
28
  }
19
29
  export interface PqsSchemaClient {
20
30
  query(query: string, values: readonly unknown[]): Promise<{
@@ -10,6 +10,46 @@ export const requiredPqsRelations = [
10
10
  "__transactions",
11
11
  "__watermark",
12
12
  ];
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
+ },
44
+ };
45
+ const pqsJsonFields = {
46
+ __contracts: ["payload"],
47
+ __exercises: ["argument", "result"],
48
+ __transactions: ["traceContext"],
49
+ };
50
+ const pqsBucketFields = {
51
+ __transactions: ["effectiveAt"],
52
+ };
13
53
  export const pqsRelationMetadata = {
14
54
  __contracts: { fields: {}, uniqueKeys: [["contractId"]], numericFields: ["createdEventOffset", "archivedEventOffset"], arrayFields: ["witnesses"], dateFields: ["createdAt", "archivedAt"], binaryFields: [] },
15
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: [] },
@@ -99,6 +139,12 @@ export class PqsSchemaProfileV1 {
99
139
  relation(relation) {
100
140
  return `${quoteIdentifier(this.schema)}.${quoteIdentifier(relation)}`;
101
141
  }
142
+ static jsonField(relation, field) {
143
+ return pqsJsonFields[relation]?.includes(field) ?? false;
144
+ }
145
+ static bucketField(relation, field) {
146
+ return pqsBucketFields[relation]?.includes(field) ?? false;
147
+ }
102
148
  }
103
149
  export async function validatePqsSchemaAsync(client, profile) {
104
150
  const result = await client.query("select table_name, column_name from information_schema.columns where table_schema = $1 and table_name = any($2::text[])", [profile.schema, requiredPqsRelations]);
@@ -1,7 +1,8 @@
1
- import { ContractFindManyArgs } from "../model-types.js";
1
+ import { ContractFindManyArgs, ContractGroupByArgs } from "../model-types.js";
2
2
  import { PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
3
3
  export interface CompiledPqsQuery {
4
4
  readonly text: string;
5
5
  readonly values: readonly unknown[];
6
6
  }
7
7
  export declare function compileContractFindMany(args: ContractFindManyArgs, profile: PqsSchemaProfileV1): CompiledPqsQuery;
8
+ export declare function compileContractGroupBy(args: ContractGroupByArgs, profile: PqsSchemaProfileV1): CompiledPqsQuery;
@@ -1,4 +1,6 @@
1
1
  import { assertQueryPageArgs, } from "../model-types.js";
2
+ import { PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
3
+ import { pqsRelationEdges, pqsRelationMetadata } from "./pqs-schema-profile.js";
2
4
  export function compileContractFindMany(args, profile) {
3
5
  assertQueryPageArgs(args);
4
6
  const values = [];
@@ -7,7 +9,7 @@ export function compileContractFindMany(args, profile) {
7
9
  return `$${values.length}`;
8
10
  };
9
11
  const conditions = [];
10
- const where = args.where === undefined ? undefined : compileWhere(args.where, addValue);
12
+ const where = args.where === undefined ? undefined : compileWhere(args.where, addValue, profile);
11
13
  if (where !== undefined)
12
14
  conditions.push(where);
13
15
  if (args.parties !== undefined) {
@@ -17,6 +19,16 @@ export function compileContractFindMany(args, profile) {
17
19
  const whereSql = conditions.length === 0 ? "" : `where ${conditions.join(" and ")}`;
18
20
  const limitSql = args.take === undefined ? "" : `limit ${addValue(args.take)}`;
19
21
  const offsetSql = args.skip === undefined ? "" : `offset ${addValue(args.skip)}`;
22
+ const included = compileProfileIncludes("__contracts", "contract_row", args.include, profile, addValue);
23
+ const jsonProjection = Object.entries(args.select?.json ?? {}).map(([name, projection]) => {
24
+ if (projection.field !== "payload")
25
+ throw new Error(`${projection.field} is not a JSON field of contracts`);
26
+ if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
27
+ throw new Error(`${name}.path must be a non-empty JSON path`);
28
+ const text = `contract_row.payload #>> ${addValue(projection.path)}::text[]`;
29
+ const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
30
+ return `${expression} as "${name}"`;
31
+ });
20
32
  return {
21
33
  text: `select
22
34
  contract_row.contract_id as contract_id,
@@ -30,7 +42,7 @@ export function compileContractFindMany(args, profile) {
30
42
  contract_row.archived_at_ix is null as active,
31
43
  contract_row.creation_package_id as template_package_id,
32
44
  contract_tpe_row.module_name as template_module_name,
33
- contract_tpe_row.entity_name as template_entity_name
45
+ contract_tpe_row.entity_name as template_entity_name${[...included, ...jsonProjection].length === 0 ? "" : `,\n ${[...included, ...jsonProjection].join(",\n ")}`}
34
46
  from ${profile.relation("__contracts")} contract_row
35
47
  join ${profile.relation("__contract_tpe")} contract_tpe_row on contract_tpe_row.pk = contract_row.tpe_pk
36
48
  left join ${profile.relation("__transactions")} created_tx on created_tx.ix = contract_row.created_at_ix
@@ -42,18 +54,135 @@ ${offsetSql}`,
42
54
  values,
43
55
  };
44
56
  }
45
- function compileWhere(where, addValue) {
57
+ function compileProfileIncludes(source, parentAlias, include, profile, addValue) {
58
+ const selected = [];
59
+ for (const [name, option] of Object.entries(include ?? {})) {
60
+ const edge = pqsRelationEdges[source]?.[name];
61
+ if (edge === undefined)
62
+ throw new Error(`${name} is not a relation of ${source}`);
63
+ const settings = option === true ? {} : option;
64
+ if (settings === null || typeof settings !== "object" || Array.isArray(settings))
65
+ throw new Error(`${name} must be an include option`);
66
+ const options = settings;
67
+ if (edge.cardinality === "many" && (typeof options.take !== "number" || !Number.isInteger(options.take) || options.take < 0))
68
+ throw new Error(`${name} is a to-many relation and requires a non-negative take`);
69
+ const alias = `"${name}"`;
70
+ const nested = compileProfileIncludes(edge.target, alias, options.include, profile, addValue);
71
+ const metadata = pqsRelationMetadata[edge.target];
72
+ const requested = options.select === undefined
73
+ ? Object.entries(metadata.fields)
74
+ : Object.entries(options.select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => [field, metadata.fields[field]]);
75
+ const json = options.select?.json ?? {};
76
+ const jsonSelections = Object.entries(json).map(([projectionName, projection]) => {
77
+ if (!PqsSchemaProfileV1.jsonField(edge.target, projection.field))
78
+ throw new Error(`${projection.field} is not a JSON field of ${edge.target}`);
79
+ if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
80
+ throw new Error(`${projectionName}.path must be a non-empty JSON path`);
81
+ const column = metadata.fields[projection.field];
82
+ if (column === undefined)
83
+ throw new Error(`${projection.field} is not a field of ${edge.target}`);
84
+ const text = `${alias}."${column}" #>> ${addValue(projection.path)}::text[]`;
85
+ const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
86
+ return [`'${projectionName}'`, expression];
87
+ });
88
+ if (requested.length === 0 && jsonSelections.length === 0)
89
+ throw new Error(`Nested ${name}.select must include at least one field`);
90
+ if (requested.some(([, column]) => column === undefined))
91
+ throw new Error(`Nested ${name}.select references an unknown field`);
92
+ const fields = requested.flatMap(([field, column]) => [`'${field}'`, `${alias}."${column}"`]);
93
+ const nestedFields = nested.flatMap((selection) => {
94
+ const match = /^(.*) as "([^"]+)"$/.exec(selection);
95
+ return match === null ? [] : [`'${match[2]}'`, match[1]];
96
+ });
97
+ const object = `jsonb_build_object(${[...fields, ...jsonSelections.flat(), ...nestedFields].join(", ")})`;
98
+ const relationFilter = options.where === undefined ? "true" : compilePhysicalWhere(edge.target, options.where, alias, profile, addValue);
99
+ const condition = `${alias}."${edge.targetColumn}" = ${parentAlias}."${edge.sourceColumn}" and (${relationFilter})`;
100
+ const orderBy = compilePhysicalOrderBy(edge.target, options.orderBy, alias);
101
+ const expression = edge.cardinality === "one"
102
+ ? `(select ${object} from ${profile.relation(edge.target)} ${alias} where ${condition})`
103
+ : `(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")`;
104
+ selected.push(`${expression} as "${name}"`);
105
+ }
106
+ return selected;
107
+ }
108
+ function compilePhysicalOrderBy(relation, orderBy, alias) {
109
+ if (orderBy === undefined)
110
+ return "";
111
+ if (!Array.isArray(orderBy) || orderBy.length === 0)
112
+ throw new Error("Nested orderBy must be a non-empty list");
113
+ const metadata = pqsRelationMetadata[relation];
114
+ const entries = orderBy.flatMap((entry) => entry !== null && typeof entry === "object" ? Object.entries(entry) : []);
115
+ if (entries.length !== orderBy.length || entries.some(([, direction]) => direction !== "asc" && direction !== "desc"))
116
+ throw new Error("Nested orderBy entries must have one valid direction");
117
+ return ` order by ${entries.map(([field, direction]) => {
118
+ const column = metadata.fields[field];
119
+ if (column === undefined)
120
+ throw new Error(`${field} is not a field of ${relation}`);
121
+ return `${alias}."${column}" ${direction}`;
122
+ }).join(", ")}`;
123
+ }
124
+ export function compileContractGroupBy(args, profile) {
125
+ if (args.by.length === 0 || (!args.aggregate.count && !args.aggregate.min && !args.aggregate.max && !args.aggregate.sum))
126
+ throw new Error("groupBy requires keys and an aggregate");
127
+ const values = [];
128
+ const add = (value) => { values.push(value); return `$${values.length}`; };
129
+ const where = args.where === undefined ? undefined : compileWhere(args.where, add, profile);
130
+ const fields = {
131
+ contractId: "contract_row.contract_id",
132
+ createdEventOffset: "contract_row.created_at_ix",
133
+ archivedEventOffset: "contract_row.archived_at_ix",
134
+ };
135
+ const expressions = [];
136
+ const select = [];
137
+ let unnestWitnesses = false;
138
+ for (const key of args.by) {
139
+ if (key === "witnesses") {
140
+ unnestWitnesses = true;
141
+ expressions.push("witness.value");
142
+ select.push("witness.value as \"witnesses\"");
143
+ }
144
+ else if (typeof key === "string") {
145
+ const expression = fields[key];
146
+ if (expression === undefined)
147
+ throw new Error(`${key} is not a contract group key`);
148
+ expressions.push(expression);
149
+ select.push(`${expression} as "${key}"`);
150
+ }
151
+ else {
152
+ const payload = key.payload;
153
+ if (payload.path.length === 0 || payload.path.some((segment) => segment.length === 0))
154
+ throw new Error("payload group path must be non-empty");
155
+ const base = `contract_row.payload #>> ${add(payload.path)}::text[]`;
156
+ const cast = payload.as === "text" ? base : `(${base})::${payload.as === "numeric" ? "numeric" : payload.as === "boolean" ? "boolean" : "timestamptz"}`;
157
+ expressions.push(cast);
158
+ select.push(`${cast} as "${payload.name}"`);
159
+ }
160
+ }
161
+ if (args.aggregate.count)
162
+ select.push("count(*)::text as count");
163
+ for (const [operation, requested] of [["min", args.aggregate.min], ["max", args.aggregate.max], ["sum", args.aggregate.sum]]) {
164
+ for (const name of requested ?? []) {
165
+ const expression = fields[name];
166
+ select.push(`${operation}(${expression})::text as "${operation}_${name}"`);
167
+ }
168
+ }
169
+ return {
170
+ 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(", ")}`,
171
+ values,
172
+ };
173
+ }
174
+ function compileWhere(where, addValue, profile) {
46
175
  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" };
47
176
  const parts = [];
48
177
  for (const [key, value] of Object.entries(where)) {
49
178
  if (key === "and" || key === "or") {
50
179
  if (!Array.isArray(value))
51
180
  throw new Error(`${key} must be an array`);
52
- parts.push(value.length === 0 ? key === "and" ? "true" : "false" : `(${value.map((child) => compileWhere(child, addValue)).join(` ${key} `)})`);
181
+ parts.push(value.length === 0 ? key === "and" ? "true" : "false" : `(${value.map((child) => compileWhere(child, addValue, profile)).join(` ${key} `)})`);
53
182
  continue;
54
183
  }
55
184
  if (key === "not") {
56
- parts.push(`not (${compileWhere(value, addValue)})`);
185
+ parts.push(`not (${compileWhere(value, addValue, profile)})`);
57
186
  continue;
58
187
  }
59
188
  if (key === "active") {
@@ -94,6 +223,11 @@ function compileWhere(where, addValue) {
94
223
  }
95
224
  continue;
96
225
  }
226
+ const edge = pqsRelationEdges.__contracts?.[key];
227
+ if (edge !== undefined) {
228
+ parts.push(compilePhysicalRelationPredicate(edge.target, key, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, "contract_row", profile, addValue));
229
+ continue;
230
+ }
97
231
  const column = columns[key];
98
232
  if (column === undefined || value === null || typeof value !== "object")
99
233
  throw new Error(`${key} is not a supported contract filter`);
@@ -113,6 +247,75 @@ function compileWhere(where, addValue) {
113
247
  }
114
248
  return parts.length === 0 ? "true" : parts.join(" and ");
115
249
  }
250
+ function compilePhysicalRelationPredicate(target, edgeName, targetColumn, sourceColumn, cardinality, value, parentAlias, profile, addValue) {
251
+ if (value === null || typeof value !== "object" || Array.isArray(value))
252
+ throw new Error(`${edgeName} must be a relation filter`);
253
+ const relation = value;
254
+ const alias = `"${edgeName}"`;
255
+ const join = `${alias}."${targetColumn}" = ${parentAlias}."${sourceColumn}"`;
256
+ if (cardinality === "one") {
257
+ const predicate = compilePhysicalWhere(target, relation, alias, profile, addValue);
258
+ return `exists (select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate}))`;
259
+ }
260
+ const operators = ["some", "none", "every"].filter((name) => relation[name] !== undefined);
261
+ if (operators.length !== 1)
262
+ throw new Error(`${edgeName} requires exactly one of some, none, or every`);
263
+ const predicate = compilePhysicalWhere(target, relation[operators[0]], alias, profile, addValue);
264
+ const base = `select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate})`;
265
+ 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}))`;
266
+ }
267
+ function compilePhysicalWhere(relation, expression, alias, profile, addValue) {
268
+ if (expression === null || typeof expression !== "object" || Array.isArray(expression))
269
+ throw new Error(`${relation} relation predicate must be an expression`);
270
+ const metadata = pqsRelationMetadata[relation];
271
+ const conditions = [];
272
+ for (const [field, value] of Object.entries(expression)) {
273
+ if (field === "and" || field === "or") {
274
+ if (!Array.isArray(value))
275
+ throw new Error(`${field} must be an array`);
276
+ conditions.push(value.length === 0 ? field === "and" ? "true" : "false" : `(${value.map((child) => compilePhysicalWhere(relation, child, alias, profile, addValue)).join(` ${field} `)})`);
277
+ continue;
278
+ }
279
+ if (field === "not") {
280
+ conditions.push(`not (${compilePhysicalWhere(relation, value, alias, profile, addValue)})`);
281
+ continue;
282
+ }
283
+ const edge = pqsRelationEdges[relation]?.[field];
284
+ if (edge !== undefined) {
285
+ conditions.push(compilePhysicalRelationPredicate(edge.target, field, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, alias, profile, addValue));
286
+ continue;
287
+ }
288
+ const column = metadata.fields[field];
289
+ if (column === undefined || value === null || typeof value !== "object" || Array.isArray(value))
290
+ throw new Error(`${field} is not a field of ${relation}`);
291
+ const filter = value;
292
+ const text = PqsSchemaProfileV1.jsonField(relation, field) && Array.isArray(filter.path) ? `${alias}."${column}" #>> ${addValue(filter.path)}::text[]` : `${alias}."${column}"`;
293
+ if (Array.isArray(filter.path) && (filter.path.length === 0 || filter.path.some((segment) => typeof segment !== "string" || segment.length === 0)))
294
+ throw new Error(`${field}.path must be a non-empty JSON path`);
295
+ if (filter.is === null)
296
+ conditions.push(`${text} is null`);
297
+ if (filter.isNot === null)
298
+ conditions.push(`${text} is not null`);
299
+ if (filter.equals !== undefined)
300
+ conditions.push(`${text} = ${addValue(filter.equals)}`);
301
+ if (filter.in !== undefined) {
302
+ if (!Array.isArray(filter.in))
303
+ throw new Error(`${field}.in must be an array`);
304
+ conditions.push(filter.in.length === 0 ? "false" : `${text} = any(${addValue(filter.in)})`);
305
+ }
306
+ if (filter.has !== undefined) {
307
+ if (!metadata.arrayFields.includes(field))
308
+ throw new Error(`${field} is not an array field of ${relation}`);
309
+ conditions.push(`${addValue(filter.has)} = any(${alias}."${column}")`);
310
+ }
311
+ for (const [operator, operand] of Object.entries(filter)) {
312
+ const sql = { lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[operator];
313
+ if (sql !== undefined)
314
+ conditions.push(`${text} ${sql} ${addValue(operand)}`);
315
+ }
316
+ }
317
+ return conditions.length === 0 ? "true" : conditions.join(" and ");
318
+ }
116
319
  function compileOrderBy(orderBy) {
117
320
  if (orderBy === undefined) {
118
321
  return "order by contract_row.contract_id asc";
@@ -124,10 +327,9 @@ function compileOrderBy(orderBy) {
124
327
  archivedEventOffset: "contract_row.archived_at_ix",
125
328
  archivedAt: "archived_tx.effective_at",
126
329
  };
127
- const entries = Object.entries(orderBy);
128
- if (entries.length !== 1) {
129
- throw new Error("orderBy must specify exactly one field");
330
+ const entries = orderBy.flatMap((entry) => Object.entries(entry));
331
+ if (entries.some(([field, direction]) => fields[field] === undefined || (direction !== "asc" && direction !== "desc"))) {
332
+ throw new Error("orderBy must contain valid one-field entries");
130
333
  }
131
- const [field, direction] = entries[0];
132
- return `order by ${fields[field]} ${direction}`;
334
+ return `order by ${entries.map(([field, direction]) => `${fields[field]} ${direction}`).join(", ")}`;
133
335
  }
@@ -1,18 +1,20 @@
1
1
  import { QuerySource } from "./query-source.js";
2
- import { ContractTypeOrderBy, ContractTypeRow, ContractTypeSelect, ContractTypeUnique, ContractTypeWhere, ContractCountArgs, ContractFindManyArgs, ContractFindUniqueArgs, ContractRow, EventOrderBy, EventRow, EventSelect, EventUnique, EventWhere, ExerciseOrderBy, ExerciseRow, ExerciseSelect, ExerciseTypeOrderBy, ExerciseTypeRow, ExerciseTypeSelect, ExerciseTypeUnique, ExerciseTypeWhere, ExerciseWhere, PackageOrderBy, PackageRow, PackageSelect, PackageUnique, PackageWhere, TransactionOrderBy, TransactionRow, TransactionSelect, TransactionUnique, TransactionWhere, WatermarkOrderBy, WatermarkRow, WatermarkSelect, WatermarkUnique, WatermarkWhere } from "./model-types.js";
3
- export interface FindManyArgs<TWhere, TSelect, TOrderBy> {
2
+ import { ContractTypeOrderBy, ContractTypeResult, ContractTypeSelect, ContractTypeUnique, ContractTypeWhere, ContractTypeGroupByArgs, ContractTypeGroupRow, ContractCountArgs, ContractFindManyArgs, ContractFindUniqueArgs, ContractGroupByArgs, ContractGroupRow, ContractResult, EventOrderBy, EventResult, EventGroupByArgs, EventGroupRow, EventSelect, EventUnique, EventWhere, ExerciseOrderBy, ExerciseResult, ExerciseSelect, ExerciseTypeOrderBy, ExerciseTypeResult, ExerciseTypeSelect, ExerciseTypeUnique, ExerciseTypeWhere, ExerciseTypeGroupByArgs, ExerciseTypeGroupRow, ExerciseWhere, ExerciseGroupByArgs, ExerciseGroupRow, PackageOrderBy, PackageResult, PackageSelect, PackageUnique, PackageWhere, PackageGroupByArgs, PackageGroupRow, TransactionOrderBy, TransactionResult, TransactionSelect, TransactionUnique, TransactionWhere, TransactionGroupByArgs, TransactionGroupRow, WatermarkOrderBy, WatermarkRow, WatermarkSelect, WatermarkUnique, WatermarkWhere, WatermarkGroupByArgs, WatermarkGroupRow, ContractTypeInclude, EventInclude, ExerciseInclude, ExerciseTypeInclude, PackageInclude, TransactionInclude, JsonProjectionResult } from "./model-types.js";
3
+ export interface FindManyArgs<TWhere, TSelect, TOrderBy, TInclude = never> {
4
4
  readonly where?: TWhere;
5
5
  readonly select?: TSelect;
6
6
  readonly orderBy?: TOrderBy;
7
7
  readonly skip?: number;
8
8
  readonly take?: number;
9
+ readonly include?: TInclude;
9
10
  }
10
- export interface QueryDelegate<TRow, TWhere, TSelect, TOrderBy, TUnique> {
11
- findMany(args?: FindManyArgs<TWhere, TSelect, TOrderBy>): Promise<readonly TRow[]>;
12
- findUnique(args: {
11
+ export interface QueryDelegate<TRow, TWhere, TSelect, TOrderBy, TUnique, TInclude = never, TGroupBy = never, TGroupRow = never> {
12
+ findMany<TArgs extends FindManyArgs<TWhere, TSelect, TOrderBy, TInclude>>(args?: TArgs): Promise<readonly (TRow & JsonProjectionResult<TArgs>)[]>;
13
+ findUnique<TArgs extends {
13
14
  readonly where: TUnique;
14
15
  readonly select?: TSelect;
15
- }): Promise<TRow | undefined>;
16
+ readonly include?: TInclude;
17
+ }>(args: TArgs): Promise<(TRow & JsonProjectionResult<TArgs>) | undefined>;
16
18
  count(args?: {
17
19
  readonly where?: TWhere;
18
20
  }): Promise<number>;
@@ -28,14 +30,15 @@ export interface QueryDelegate<TRow, TWhere, TSelect, TOrderBy, TUnique> {
28
30
  readonly max?: Readonly<Record<string, string | null>>;
29
31
  readonly sum?: Readonly<Record<string, string | null>>;
30
32
  }>;
33
+ groupBy(args: TGroupBy): Promise<readonly TGroupRow[]>;
31
34
  }
32
- export type QueryCollectionDelegate<TRow, TWhere, TSelect, TOrderBy> = Omit<QueryDelegate<TRow, TWhere, TSelect, TOrderBy, never>, "findUnique">;
35
+ export type QueryCollectionDelegate<TRow, TWhere, TSelect, TOrderBy, TInclude = never, TGroupBy = never, TGroupRow = never> = Omit<QueryDelegate<TRow, TWhere, TSelect, TOrderBy, never, TInclude, TGroupBy, TGroupRow>, "findUnique">;
33
36
  export interface QueryClient {
34
37
  readonly source: QuerySource;
35
38
  $queryRaw<TRow>(sql: string, values?: readonly unknown[]): Promise<readonly TRow[]>;
36
39
  readonly contracts: {
37
- findMany(args?: ContractFindManyArgs): Promise<readonly ContractRow[]>;
38
- findUnique(args: ContractFindUniqueArgs): Promise<ContractRow | undefined>;
40
+ findMany<TArgs extends ContractFindManyArgs>(args?: TArgs): Promise<readonly (ContractResult & JsonProjectionResult<TArgs>)[]>;
41
+ findUnique<TArgs extends ContractFindUniqueArgs>(args: TArgs): Promise<(ContractResult & JsonProjectionResult<TArgs>) | undefined>;
39
42
  count(args?: ContractCountArgs): Promise<number>;
40
43
  aggregate(args: {
41
44
  readonly where?: ContractCountArgs["where"];
@@ -49,13 +52,14 @@ export interface QueryClient {
49
52
  readonly max?: Readonly<Partial<Record<ContractNumericField, string | null>>>;
50
53
  readonly sum?: Readonly<Partial<Record<ContractNumericField, string | null>>>;
51
54
  }>;
55
+ groupBy(args: ContractGroupByArgs): Promise<readonly ContractGroupRow[]>;
52
56
  };
53
- readonly contractTypes: QueryDelegate<ContractTypeRow, ContractTypeWhere, ContractTypeSelect, ContractTypeOrderBy, ContractTypeUnique>;
54
- readonly events: QueryDelegate<EventRow, EventWhere, EventSelect, EventOrderBy, EventUnique>;
55
- readonly exercises: QueryCollectionDelegate<ExerciseRow, ExerciseWhere, ExerciseSelect, ExerciseOrderBy>;
56
- readonly exerciseTypes: QueryDelegate<ExerciseTypeRow, ExerciseTypeWhere, ExerciseTypeSelect, ExerciseTypeOrderBy, ExerciseTypeUnique>;
57
- readonly packages: QueryDelegate<PackageRow, PackageWhere, PackageSelect, PackageOrderBy, PackageUnique>;
58
- readonly transactions: QueryDelegate<TransactionRow, TransactionWhere, TransactionSelect, TransactionOrderBy, TransactionUnique>;
59
- readonly watermark: QueryDelegate<WatermarkRow, WatermarkWhere, WatermarkSelect, WatermarkOrderBy, WatermarkUnique>;
57
+ readonly contractTypes: QueryDelegate<ContractTypeResult, ContractTypeWhere, ContractTypeSelect, ContractTypeOrderBy, ContractTypeUnique, ContractTypeInclude, ContractTypeGroupByArgs, ContractTypeGroupRow>;
58
+ readonly events: QueryDelegate<EventResult, EventWhere, EventSelect, EventOrderBy, EventUnique, EventInclude, EventGroupByArgs, EventGroupRow>;
59
+ readonly exercises: QueryCollectionDelegate<ExerciseResult, ExerciseWhere, ExerciseSelect, ExerciseOrderBy, ExerciseInclude, ExerciseGroupByArgs, ExerciseGroupRow>;
60
+ readonly exerciseTypes: QueryDelegate<ExerciseTypeResult, ExerciseTypeWhere, ExerciseTypeSelect, ExerciseTypeOrderBy, ExerciseTypeUnique, ExerciseTypeInclude, ExerciseTypeGroupByArgs, ExerciseTypeGroupRow>;
61
+ readonly packages: QueryDelegate<PackageResult, PackageWhere, PackageSelect, PackageOrderBy, PackageUnique, PackageInclude, PackageGroupByArgs, PackageGroupRow>;
62
+ readonly transactions: QueryDelegate<TransactionResult, TransactionWhere, TransactionSelect, TransactionOrderBy, TransactionUnique, TransactionInclude, TransactionGroupByArgs, TransactionGroupRow>;
63
+ readonly watermark: QueryDelegate<WatermarkRow, WatermarkWhere, WatermarkSelect, WatermarkOrderBy, WatermarkUnique, never, WatermarkGroupByArgs, WatermarkGroupRow>;
60
64
  }
61
65
  export type ContractNumericField = "createdEventOffset" | "archivedEventOffset";