@sequenceholdings/orm 2.1.0 → 2.2.0

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 (77) hide show
  1. package/README.md +40 -1
  2. package/dist/authoring.cjs +1619 -121
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +252 -8
  5. package/dist/authoring.d.ts +252 -8
  6. package/dist/authoring.js +1607 -123
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +713 -41
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +1520 -51
  11. package/dist/canonical.d.ts +1520 -51
  12. package/dist/canonical.js +693 -42
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +7103 -3901
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +7104 -3902
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +122 -40
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +85 -25
  21. package/dist/client.d.ts +85 -25
  22. package/dist/client.js +119 -41
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +2464 -450
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +79 -4
  27. package/dist/compiler.d.ts +79 -4
  28. package/dist/compiler.js +2457 -451
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/constants-C6EG6Wq7.d.cts +99 -0
  31. package/dist/constants-C6EG6Wq7.d.ts +99 -0
  32. package/dist/definition-DjfC7Lll.d.cts +91 -0
  33. package/dist/definition-DjfC7Lll.d.ts +91 -0
  34. package/dist/dynamic-input.cjs +2 -1
  35. package/dist/dynamic-input.cjs.map +1 -1
  36. package/dist/dynamic-input.d.cts +9 -1
  37. package/dist/dynamic-input.d.ts +9 -1
  38. package/dist/dynamic-input.js +2 -1
  39. package/dist/dynamic-input.js.map +1 -1
  40. package/dist/index.cjs +2656 -418
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +693 -116
  43. package/dist/index.d.ts +693 -116
  44. package/dist/index.js +2622 -420
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +811 -48
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +670 -27
  49. package/dist/ir.d.ts +670 -27
  50. package/dist/ir.js +802 -49
  51. package/dist/ir.js.map +1 -1
  52. package/dist/{naming-DCn94LJ4.d.ts → naming-CPfx7EfF.d.ts} +14 -1
  53. package/dist/{naming-DPglwzkB.d.cts → naming-CU9ThP_E.d.cts} +14 -1
  54. package/dist/policy-1iZbGnZZ.d.cts +24 -0
  55. package/dist/policy-1iZbGnZZ.d.ts +24 -0
  56. package/dist/predicate-v2.cjs +804 -51
  57. package/dist/predicate-v2.cjs.map +1 -1
  58. package/dist/predicate-v2.d.cts +2 -1
  59. package/dist/predicate-v2.d.ts +2 -1
  60. package/dist/predicate-v2.js +804 -51
  61. package/dist/predicate-v2.js.map +1 -1
  62. package/dist/sdl.cjs +1014 -75
  63. package/dist/sdl.cjs.map +1 -1
  64. package/dist/sdl.d.cts +14 -3
  65. package/dist/sdl.d.ts +14 -3
  66. package/dist/sdl.js +1012 -76
  67. package/dist/sdl.js.map +1 -1
  68. package/package.json +13 -7
  69. package/src/codegen-gql/server-fns.cjs +2 -2
  70. package/templates/orm-namespace/AGENTS.md +10 -0
  71. package/templates/orm-namespace/CLAUDE.md +1 -82
  72. package/templates/orm-namespace/codegen.preset.d.cts +7 -0
  73. package/templates/orm-namespace/codegen.ts +2 -1
  74. package/templates/orm-namespace/graphql/example/queries.ts +4 -3
  75. package/templates/orm-namespace/package.json +2 -1
  76. package/templates/orm-namespace/schema/tables.ts +3 -2
  77. package/templates/orm-namespace/sequence.config.ts +7 -11
package/dist/client.d.cts CHANGED
@@ -1,8 +1,23 @@
1
+ export { N as NAMESPACE_PATTERN, O as ORM_DATA_TOKEN_HEADER, d as ORM_SERVICE_DATA_TOKEN_HEADER } from './constants-C6EG6Wq7.cjs';
2
+
1
3
  declare class OrmClientError extends Error {
2
4
  readonly status: number | null;
3
5
  readonly name: string;
4
6
  constructor(message: string, status?: number | null);
5
7
  }
8
+ interface OperationErrorEntry {
9
+ readonly message: string;
10
+ readonly extensions?: {
11
+ readonly code?: string;
12
+ };
13
+ }
14
+ declare class OrmOperationError extends OrmClientError {
15
+ readonly code: string | null;
16
+ readonly errors: readonly OperationErrorEntry[];
17
+ readonly name = "OrmOperationError";
18
+ constructor(message: string, status: number | null, code: string | null, errors?: readonly OperationErrorEntry[]);
19
+ }
20
+
6
21
  interface OrmTransport {
7
22
  get(path: string, params: URLSearchParams): Promise<unknown>;
8
23
  post(path: string, body: unknown): Promise<unknown>;
@@ -71,14 +86,6 @@ declare class TableQuery<Row extends Record<string, unknown>, Result = Row> {
71
86
  private filter;
72
87
  }
73
88
 
74
- /**
75
- * Persisted-operation client for the v2 data plane. The wire carries only
76
- * `{ op: <hash>, variables }` — never GraphQL text, never SQL. Like the rest
77
- * of `./client`, this module is deliberately free of node builtins and third-
78
- * party deps (no @apollo/client import): Apollo integration happens through a
79
- * fetch-compatible shim (`createOrmFetch`) handed to `HttpLink({ fetch })`.
80
- */
81
-
82
89
  /** One entry of `operations.manifest.json` (generated by `orm generate`). */
83
90
  interface ManifestOperation {
84
91
  readonly hash: string;
@@ -93,19 +100,6 @@ interface OperationsManifest {
93
100
  readonly opsHash?: string;
94
101
  readonly operations: Readonly<Record<string, ManifestOperation>>;
95
102
  }
96
- /** GraphQL-spec error envelope the data plane returns. */
97
- interface OperationErrorEntry {
98
- readonly message: string;
99
- readonly extensions?: {
100
- readonly code?: string;
101
- };
102
- }
103
- declare class OrmOperationError extends OrmClientError {
104
- readonly code: string | null;
105
- readonly errors: readonly OperationErrorEntry[];
106
- readonly name = "OrmOperationError";
107
- constructor(message: string, status: number | null, code: string | null, errors?: readonly OperationErrorEntry[]);
108
- }
109
103
  interface OperationExecutorConfig {
110
104
  /** Deployment host root, e.g. `https://atlas.seqholdings.com`. */
111
105
  baseUrl: string;
@@ -117,11 +111,27 @@ interface OperationExecutorConfig {
117
111
  getDataToken?: () => Promise<string | null> | string | null;
118
112
  fetchImpl?: typeof globalThis.fetch;
119
113
  }
114
+ interface OperationExecutionOptions {
115
+ /** Reuse only when retrying the same logical mutation. */
116
+ idempotencyKey?: string;
117
+ }
120
118
  interface OperationExecutor {
121
119
  /** Execute a manifest operation by name (or a raw `sha256:` hash). */
122
- execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>): Promise<TData>;
120
+ execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>, options?: OperationExecutionOptions): Promise<TData>;
121
+ }
122
+ /** Additive factory result; existing host wrappers implementing OperationExecutor remain valid. */
123
+ interface TypedOperationExecutor extends OperationExecutor {
124
+ execute<TVariables extends Record<string, unknown>, TData>(op: OperationReference<TVariables, TData>, variables: TVariables, options?: OperationExecutionOptions): Promise<TData>;
125
+ execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>, options?: OperationExecutionOptions): Promise<TData>;
126
+ }
127
+ /** Generated handle identifies the registered contract; optional types are phantom. */
128
+ interface OperationReference<TVariables extends Record<string, unknown>, TData> {
129
+ readonly namespace: string;
130
+ readonly hash: string;
131
+ readonly variables?: TVariables;
132
+ readonly result?: TData;
123
133
  }
124
- declare function createOperationExecutor({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): OperationExecutor;
134
+ declare function createOperationExecutor({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): TypedOperationExecutor;
125
135
  /**
126
136
  * Configure the process-wide executor the generated `db<Name>` server
127
137
  * functions call through. Call once at startup (server-side); browser apps
@@ -129,9 +139,10 @@ declare function createOperationExecutor({ baseUrl, namespace, manifest, getToke
129
139
  */
130
140
  declare function configureOrmOperations(config: OperationExecutorConfig): OperationExecutor;
131
141
  /** Used by generated code: `executeOperation({ op: 'getOpenDeals', variables })`. */
132
- declare function executeOperation<TData = Record<string, unknown>>({ op, variables, }: {
142
+ declare function executeOperation<TData = Record<string, unknown>>({ op, variables, idempotencyKey, }: {
133
143
  op: string;
134
144
  variables?: Record<string, unknown>;
145
+ idempotencyKey?: string;
135
146
  }): Promise<TData>;
136
147
  /**
137
148
  * A fetch-compatible function for `new HttpLink({ uri: '/unused', fetch: createOrmFetch(...) })`.
@@ -143,6 +154,55 @@ declare function executeOperation<TData = Record<string, unknown>>({ op, variabl
143
154
  */
144
155
  declare function createOrmFetch({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): typeof globalThis.fetch;
145
156
 
157
+ /** Runtime SQL inputs are values, never interpolated into the submitted SQL. */
158
+ type SqlQueryParameter = {
159
+ type: 'text' | 'uuid' | 'date' | 'timestamptz' | 'numeric' | 'bigint';
160
+ value: string | null;
161
+ } | {
162
+ type: 'integer' | 'double';
163
+ value: number | null;
164
+ } | {
165
+ type: 'boolean';
166
+ value: boolean | null;
167
+ };
168
+ type SqlQueryVariables = {
169
+ sql: string;
170
+ parameters?: Record<string, SqlQueryParameter>;
171
+ };
172
+ interface SqlQueryResult {
173
+ columns: Array<{
174
+ name: string;
175
+ type: string;
176
+ nullable: boolean;
177
+ }>;
178
+ rows: Array<Array<string | number | boolean | null>>;
179
+ truncated: boolean;
180
+ }
181
+
182
+ interface OperationTransportRequest {
183
+ namespace: string;
184
+ op: string;
185
+ variables: Record<string, unknown>;
186
+ idempotencyKey?: string;
187
+ }
188
+ interface OperationTransportResponse {
189
+ ok: boolean;
190
+ status: number;
191
+ body: unknown;
192
+ }
193
+ type OperationTransport = (request: OperationTransportRequest) => Promise<OperationTransportResponse>;
194
+ /**
195
+ * Execute generated operations through a host-owned authenticated transport.
196
+ * No credential handling, retries, or global configuration. The manifest is
197
+ * validated once and copied, so later caller mutation cannot alter the target.
198
+ */
199
+ declare function createOperationExecutorWithTransport({ namespace, manifest, transport }: {
200
+ namespace: string;
201
+ /** Accept generated JSON imports directly; validate them at construction. */
202
+ manifest: unknown;
203
+ transport: OperationTransport;
204
+ }): TypedOperationExecutor;
205
+
146
206
  /** Bindable scalar for `query()` placeholders ($1, $2, …). */
147
207
  type QueryParam = string | number | boolean | null;
148
208
  /** Shape of one action in the generated `<Ns>Actions` map — params in, result out. */
@@ -240,4 +300,4 @@ declare function createServiceOrmClient<Tables extends Record<string, Record<str
240
300
  transport?: OrmTransport;
241
301
  }): OrmClient<Tables, Actions>;
242
302
 
243
- export { type FilterValue, type ManifestOperation, type OperationErrorEntry, type OperationExecutor, type OperationExecutorConfig, type OperationsManifest, type OrmActionShape, type OrmActions, type OrmClient, OrmClientError, OrmOperationError, type OrmTransport, type QueryParam, type ReadResponse, TableQuery, configureOrmOperations, createFetchTransport, createOperationExecutor, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
303
+ export { type FilterValue, type ManifestOperation, type OperationErrorEntry, type OperationExecutor, type OperationExecutorConfig, type OperationReference, type OperationTransport, type OperationTransportRequest, type OperationTransportResponse, type OperationsManifest, type OrmActionShape, type OrmActions, type OrmClient, OrmClientError, OrmOperationError, type OrmTransport, type QueryParam, type ReadResponse, type SqlQueryParameter, type SqlQueryResult, type SqlQueryVariables, TableQuery, type TypedOperationExecutor, configureOrmOperations, createFetchTransport, createOperationExecutor, createOperationExecutorWithTransport, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
package/dist/client.d.ts CHANGED
@@ -1,8 +1,23 @@
1
+ export { N as NAMESPACE_PATTERN, O as ORM_DATA_TOKEN_HEADER, d as ORM_SERVICE_DATA_TOKEN_HEADER } from './constants-C6EG6Wq7.js';
2
+
1
3
  declare class OrmClientError extends Error {
2
4
  readonly status: number | null;
3
5
  readonly name: string;
4
6
  constructor(message: string, status?: number | null);
5
7
  }
8
+ interface OperationErrorEntry {
9
+ readonly message: string;
10
+ readonly extensions?: {
11
+ readonly code?: string;
12
+ };
13
+ }
14
+ declare class OrmOperationError extends OrmClientError {
15
+ readonly code: string | null;
16
+ readonly errors: readonly OperationErrorEntry[];
17
+ readonly name = "OrmOperationError";
18
+ constructor(message: string, status: number | null, code: string | null, errors?: readonly OperationErrorEntry[]);
19
+ }
20
+
6
21
  interface OrmTransport {
7
22
  get(path: string, params: URLSearchParams): Promise<unknown>;
8
23
  post(path: string, body: unknown): Promise<unknown>;
@@ -71,14 +86,6 @@ declare class TableQuery<Row extends Record<string, unknown>, Result = Row> {
71
86
  private filter;
72
87
  }
73
88
 
74
- /**
75
- * Persisted-operation client for the v2 data plane. The wire carries only
76
- * `{ op: <hash>, variables }` — never GraphQL text, never SQL. Like the rest
77
- * of `./client`, this module is deliberately free of node builtins and third-
78
- * party deps (no @apollo/client import): Apollo integration happens through a
79
- * fetch-compatible shim (`createOrmFetch`) handed to `HttpLink({ fetch })`.
80
- */
81
-
82
89
  /** One entry of `operations.manifest.json` (generated by `orm generate`). */
83
90
  interface ManifestOperation {
84
91
  readonly hash: string;
@@ -93,19 +100,6 @@ interface OperationsManifest {
93
100
  readonly opsHash?: string;
94
101
  readonly operations: Readonly<Record<string, ManifestOperation>>;
95
102
  }
96
- /** GraphQL-spec error envelope the data plane returns. */
97
- interface OperationErrorEntry {
98
- readonly message: string;
99
- readonly extensions?: {
100
- readonly code?: string;
101
- };
102
- }
103
- declare class OrmOperationError extends OrmClientError {
104
- readonly code: string | null;
105
- readonly errors: readonly OperationErrorEntry[];
106
- readonly name = "OrmOperationError";
107
- constructor(message: string, status: number | null, code: string | null, errors?: readonly OperationErrorEntry[]);
108
- }
109
103
  interface OperationExecutorConfig {
110
104
  /** Deployment host root, e.g. `https://atlas.seqholdings.com`. */
111
105
  baseUrl: string;
@@ -117,11 +111,27 @@ interface OperationExecutorConfig {
117
111
  getDataToken?: () => Promise<string | null> | string | null;
118
112
  fetchImpl?: typeof globalThis.fetch;
119
113
  }
114
+ interface OperationExecutionOptions {
115
+ /** Reuse only when retrying the same logical mutation. */
116
+ idempotencyKey?: string;
117
+ }
120
118
  interface OperationExecutor {
121
119
  /** Execute a manifest operation by name (or a raw `sha256:` hash). */
122
- execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>): Promise<TData>;
120
+ execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>, options?: OperationExecutionOptions): Promise<TData>;
121
+ }
122
+ /** Additive factory result; existing host wrappers implementing OperationExecutor remain valid. */
123
+ interface TypedOperationExecutor extends OperationExecutor {
124
+ execute<TVariables extends Record<string, unknown>, TData>(op: OperationReference<TVariables, TData>, variables: TVariables, options?: OperationExecutionOptions): Promise<TData>;
125
+ execute<TData = Record<string, unknown>>(op: string, variables?: Record<string, unknown>, options?: OperationExecutionOptions): Promise<TData>;
126
+ }
127
+ /** Generated handle identifies the registered contract; optional types are phantom. */
128
+ interface OperationReference<TVariables extends Record<string, unknown>, TData> {
129
+ readonly namespace: string;
130
+ readonly hash: string;
131
+ readonly variables?: TVariables;
132
+ readonly result?: TData;
123
133
  }
124
- declare function createOperationExecutor({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): OperationExecutor;
134
+ declare function createOperationExecutor({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): TypedOperationExecutor;
125
135
  /**
126
136
  * Configure the process-wide executor the generated `db<Name>` server
127
137
  * functions call through. Call once at startup (server-side); browser apps
@@ -129,9 +139,10 @@ declare function createOperationExecutor({ baseUrl, namespace, manifest, getToke
129
139
  */
130
140
  declare function configureOrmOperations(config: OperationExecutorConfig): OperationExecutor;
131
141
  /** Used by generated code: `executeOperation({ op: 'getOpenDeals', variables })`. */
132
- declare function executeOperation<TData = Record<string, unknown>>({ op, variables, }: {
142
+ declare function executeOperation<TData = Record<string, unknown>>({ op, variables, idempotencyKey, }: {
133
143
  op: string;
134
144
  variables?: Record<string, unknown>;
145
+ idempotencyKey?: string;
135
146
  }): Promise<TData>;
136
147
  /**
137
148
  * A fetch-compatible function for `new HttpLink({ uri: '/unused', fetch: createOrmFetch(...) })`.
@@ -143,6 +154,55 @@ declare function executeOperation<TData = Record<string, unknown>>({ op, variabl
143
154
  */
144
155
  declare function createOrmFetch({ baseUrl, namespace, manifest, getToken, getDataToken, fetchImpl, }: OperationExecutorConfig): typeof globalThis.fetch;
145
156
 
157
+ /** Runtime SQL inputs are values, never interpolated into the submitted SQL. */
158
+ type SqlQueryParameter = {
159
+ type: 'text' | 'uuid' | 'date' | 'timestamptz' | 'numeric' | 'bigint';
160
+ value: string | null;
161
+ } | {
162
+ type: 'integer' | 'double';
163
+ value: number | null;
164
+ } | {
165
+ type: 'boolean';
166
+ value: boolean | null;
167
+ };
168
+ type SqlQueryVariables = {
169
+ sql: string;
170
+ parameters?: Record<string, SqlQueryParameter>;
171
+ };
172
+ interface SqlQueryResult {
173
+ columns: Array<{
174
+ name: string;
175
+ type: string;
176
+ nullable: boolean;
177
+ }>;
178
+ rows: Array<Array<string | number | boolean | null>>;
179
+ truncated: boolean;
180
+ }
181
+
182
+ interface OperationTransportRequest {
183
+ namespace: string;
184
+ op: string;
185
+ variables: Record<string, unknown>;
186
+ idempotencyKey?: string;
187
+ }
188
+ interface OperationTransportResponse {
189
+ ok: boolean;
190
+ status: number;
191
+ body: unknown;
192
+ }
193
+ type OperationTransport = (request: OperationTransportRequest) => Promise<OperationTransportResponse>;
194
+ /**
195
+ * Execute generated operations through a host-owned authenticated transport.
196
+ * No credential handling, retries, or global configuration. The manifest is
197
+ * validated once and copied, so later caller mutation cannot alter the target.
198
+ */
199
+ declare function createOperationExecutorWithTransport({ namespace, manifest, transport }: {
200
+ namespace: string;
201
+ /** Accept generated JSON imports directly; validate them at construction. */
202
+ manifest: unknown;
203
+ transport: OperationTransport;
204
+ }): TypedOperationExecutor;
205
+
146
206
  /** Bindable scalar for `query()` placeholders ($1, $2, …). */
147
207
  type QueryParam = string | number | boolean | null;
148
208
  /** Shape of one action in the generated `<Ns>Actions` map — params in, result out. */
@@ -240,4 +300,4 @@ declare function createServiceOrmClient<Tables extends Record<string, Record<str
240
300
  transport?: OrmTransport;
241
301
  }): OrmClient<Tables, Actions>;
242
302
 
243
- export { type FilterValue, type ManifestOperation, type OperationErrorEntry, type OperationExecutor, type OperationExecutorConfig, type OperationsManifest, type OrmActionShape, type OrmActions, type OrmClient, OrmClientError, OrmOperationError, type OrmTransport, type QueryParam, type ReadResponse, TableQuery, configureOrmOperations, createFetchTransport, createOperationExecutor, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
303
+ export { type FilterValue, type ManifestOperation, type OperationErrorEntry, type OperationExecutor, type OperationExecutorConfig, type OperationReference, type OperationTransport, type OperationTransportRequest, type OperationTransportResponse, type OperationsManifest, type OrmActionShape, type OrmActions, type OrmClient, OrmClientError, OrmOperationError, type OrmTransport, type QueryParam, type ReadResponse, type SqlQueryParameter, type SqlQueryResult, type SqlQueryVariables, TableQuery, type TypedOperationExecutor, configureOrmOperations, createFetchTransport, createOperationExecutor, createOperationExecutorWithTransport, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
package/dist/client.js CHANGED
@@ -1,15 +1,28 @@
1
1
  // src/constants.ts
2
+ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
2
3
  var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
4
+ var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
5
+ var MATERIALIZE_ACTOR = "orm:materialize";
6
+ JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
3
7
 
4
- // src/client/transport.ts
8
+ // src/client/errors.ts
5
9
  var OrmClientError = class extends Error {
6
10
  constructor(message, status = null) {
7
11
  super(message);
8
12
  this.status = status;
9
13
  }
10
- // Widened to `string` so subclasses (e.g. OrmOperationError) can rename.
11
14
  name = "OrmClientError";
12
15
  };
16
+ var OrmOperationError = class extends OrmClientError {
17
+ constructor(message, status, code, errors = []) {
18
+ super(message, status);
19
+ this.code = code;
20
+ this.errors = errors;
21
+ }
22
+ name = "OrmOperationError";
23
+ };
24
+
25
+ // src/client/transport.ts
13
26
  function createFetchTransport({
14
27
  baseUrl,
15
28
  getToken,
@@ -146,15 +159,45 @@ var TableQuery = class _TableQuery {
146
159
  }
147
160
  };
148
161
 
162
+ // src/client/response.ts
163
+ function isRecord(value) {
164
+ return typeof value === "object" && value !== null && !Array.isArray(value);
165
+ }
166
+ function isOperationError(value) {
167
+ if (!isRecord(value) || typeof value.message !== "string") return false;
168
+ if (value.extensions === void 0) return true;
169
+ return isRecord(value.extensions) && (value.extensions.code === void 0 || typeof value.extensions.code === "string");
170
+ }
171
+ function readOperationResponse({
172
+ ok,
173
+ status,
174
+ body
175
+ }) {
176
+ const payload = isRecord(body) ? body : null;
177
+ const rawErrors = payload?.errors;
178
+ if (rawErrors !== void 0 && (!Array.isArray(rawErrors) || !rawErrors.every(isOperationError))) {
179
+ throw new OrmOperationError("malformed response: invalid errors", status, null);
180
+ }
181
+ const errors = rawErrors ?? [];
182
+ if (!ok || errors.length > 0) {
183
+ const first = errors[0];
184
+ throw new OrmOperationError(first?.message ?? `HTTP ${status}`, status, first?.extensions?.code ?? null, errors);
185
+ }
186
+ if (!payload || payload.data === void 0 || payload.data === null) {
187
+ throw new OrmOperationError("malformed response: missing data", status, null);
188
+ }
189
+ if (!isRecord(payload.data)) {
190
+ throw new OrmOperationError("malformed response: data must be an object", status, null);
191
+ }
192
+ return payload.data;
193
+ }
194
+
149
195
  // src/client/operations.ts
150
- var OrmOperationError = class extends OrmClientError {
151
- constructor(message, status, code, errors = []) {
152
- super(message, status);
153
- this.code = code;
154
- this.errors = errors;
196
+ function validateIdempotencyKey(key) {
197
+ if (key !== void 0 && !/^[A-Za-z0-9._:-]{1,128}$/.test(key)) {
198
+ throw new OrmClientError("idempotencyKey must be 1\u2013128 letters, digits, dots, underscores, colons or hyphens");
155
199
  }
156
- name = "OrmOperationError";
157
- };
200
+ }
158
201
  var dataPlanePath = (namespace) => `/_data/v2/${namespace}`;
159
202
  function resolveOpHash(manifest, op) {
160
203
  if (op.startsWith("sha256:")) return op;
@@ -180,34 +223,25 @@ function createOperationExecutor({
180
223
  throw new OrmClientError("provide exactly one of getToken (bearer) or getDataToken (delegated)");
181
224
  }
182
225
  const root = baseUrl.replace(/\/+$/, "");
183
- return {
184
- async execute(op, variables = {}) {
185
- const hash = resolveOpHash(manifest, op);
186
- const token = await (getDataToken ?? getToken)();
187
- if (!token) throw new OrmClientError("not authenticated \u2014 no token available");
188
- const auth = getDataToken ? { [ORM_DATA_TOKEN_HEADER]: token } : { Authorization: `Bearer ${token}` };
189
- const response = await fetchImpl(`${root}${dataPlanePath(namespace)}`, {
190
- method: "POST",
191
- headers: { ...auth, "Content-Type": "application/json" },
192
- body: JSON.stringify({ op: hash, variables })
193
- });
194
- const payload = await response.json().catch(() => null);
195
- const errors = payload?.errors ?? [];
196
- if (!response.ok || errors.length > 0) {
197
- const first = errors[0];
198
- throw new OrmOperationError(
199
- first?.message ?? `HTTP ${response.status}`,
200
- response.status,
201
- first?.extensions?.code ?? null,
202
- errors
203
- );
204
- }
205
- if (!payload || payload.data === void 0 || payload.data === null) {
206
- throw new OrmOperationError("malformed response: missing data", response.status, null);
207
- }
208
- return payload.data;
209
- }
210
- };
226
+ async function execute(op, variables = {}, options = {}) {
227
+ validateIdempotencyKey(options.idempotencyKey);
228
+ if (typeof op !== "string" && op.namespace !== namespace) throw new OrmClientError("operation belongs to a different namespace");
229
+ const hash = typeof op === "string" ? resolveOpHash(manifest, op) : op.hash;
230
+ const token = await (getDataToken ?? getToken)();
231
+ if (!token) throw new OrmClientError("not authenticated \u2014 no token available");
232
+ const auth = getDataToken ? { [ORM_DATA_TOKEN_HEADER]: token } : { Authorization: `Bearer ${token}` };
233
+ const response = await fetchImpl(`${root}${dataPlanePath(namespace)}`, {
234
+ method: "POST",
235
+ headers: { ...auth, "Content-Type": "application/json" },
236
+ body: JSON.stringify({ op: hash, variables, idempotencyKey: options.idempotencyKey })
237
+ });
238
+ return readOperationResponse({
239
+ ok: response.ok,
240
+ status: response.status,
241
+ body: await response.json().catch(() => null)
242
+ });
243
+ }
244
+ return { execute };
211
245
  }
212
246
  var configured = null;
213
247
  function configureOrmOperations(config) {
@@ -216,14 +250,15 @@ function configureOrmOperations(config) {
216
250
  }
217
251
  async function executeOperation({
218
252
  op,
219
- variables
253
+ variables,
254
+ idempotencyKey
220
255
  }) {
221
256
  if (!configured) {
222
257
  throw new OrmClientError(
223
258
  "ORM operations client not configured \u2014 call configureOrmOperations({ baseUrl, namespace, manifest, getToken }) at startup"
224
259
  );
225
260
  }
226
- return configured.execute(op, variables);
261
+ return configured.execute(op, variables, idempotencyKey === void 0 ? {} : { idempotencyKey });
227
262
  }
228
263
  function createOrmFetch({
229
264
  baseUrl,
@@ -249,17 +284,60 @@ function createOrmFetch({
249
284
  );
250
285
  }
251
286
  const hash = resolveOpHash(manifest, name);
287
+ const idempotencyKey = new Headers(init?.headers).get("Idempotency-Key") ?? void 0;
288
+ validateIdempotencyKey(idempotencyKey);
252
289
  const token = await (getDataToken ?? getToken)();
253
290
  if (!token) throw new OrmClientError("not authenticated \u2014 no token available");
254
291
  const auth = getDataToken ? { [ORM_DATA_TOKEN_HEADER]: token } : { Authorization: `Bearer ${token}` };
255
292
  return fetchImpl(url, {
256
293
  method: "POST",
257
294
  headers: { ...auth, "Content-Type": "application/json" },
258
- body: JSON.stringify({ op: hash, variables: parsed.variables ?? {} })
295
+ body: JSON.stringify({ op: hash, variables: parsed.variables ?? {}, idempotencyKey }),
296
+ ...init?.signal ? { signal: init.signal } : {}
259
297
  });
260
298
  };
261
299
  }
262
300
 
301
+ // src/client/transport-executor.ts
302
+ var operationHash = /^sha256:[a-f0-9]{64}$/;
303
+ function readManifestHashes(manifest) {
304
+ if (!isRecord(manifest) || typeof manifest.schemaHash !== "string" || !manifest.schemaHash || !isRecord(manifest.operations)) {
305
+ throw new OrmClientError("invalid operations manifest \u2014 use operations.manifest.json from seq-studio orm generate");
306
+ }
307
+ const hashes = /* @__PURE__ */ new Map();
308
+ for (const [name, entry] of Object.entries(manifest.operations)) {
309
+ if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(name) || !isRecord(entry) || typeof entry.hash !== "string" || !operationHash.test(entry.hash) || entry.kind !== "query" && entry.kind !== "mutation") {
310
+ throw new OrmClientError(`invalid operations manifest entry "${name}" \u2014 regenerate the manifest`);
311
+ }
312
+ hashes.set(name, entry.hash);
313
+ }
314
+ return hashes;
315
+ }
316
+ function createOperationExecutorWithTransport({ namespace, manifest, transport }) {
317
+ if (typeof namespace !== "string" || !NAMESPACE_PATTERN.test(namespace)) throw new OrmClientError("invalid ORM namespace \u2014 expected a lowercase identifier of at most 41 characters");
318
+ const hashes = readManifestHashes(manifest);
319
+ const knownHashes = new Set(hashes.values());
320
+ async function execute(op, variables = {}, options = {}) {
321
+ validateIdempotencyKey(options.idempotencyKey);
322
+ if (typeof op !== "string" && op.namespace !== namespace) throw new OrmClientError("operation belongs to a different namespace");
323
+ const selector = typeof op === "string" ? op : op.hash;
324
+ const hash = hashes.get(selector) ?? (knownHashes.has(selector) ? selector : void 0);
325
+ if (!hash) throw new OrmClientError(`unknown operation "${op}" \u2014 not in the manifest (${hashes.size} known). Re-run seq-studio orm generate.`);
326
+ if (!isRecord(variables)) throw new OrmClientError("operation variables must be an object");
327
+ const response = await transport({
328
+ namespace,
329
+ op: hash,
330
+ variables,
331
+ ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey }
332
+ });
333
+ if (!response || typeof response.ok !== "boolean" || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || response.ok !== (response.status >= 200 && response.status < 300)) {
334
+ throw new OrmOperationError("malformed transport response", null, null);
335
+ }
336
+ return readOperationResponse(response);
337
+ }
338
+ return { execute };
339
+ }
340
+
263
341
  // src/client/index.ts
264
342
  function createOrmClient({
265
343
  namespace,
@@ -327,6 +405,6 @@ function requireOption(value, name) {
327
405
  return value;
328
406
  }
329
407
 
330
- export { OrmClientError, OrmOperationError, TableQuery, configureOrmOperations, createFetchTransport, createOperationExecutor, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
408
+ export { NAMESPACE_PATTERN, ORM_DATA_TOKEN_HEADER, ORM_SERVICE_DATA_TOKEN_HEADER, OrmClientError, OrmOperationError, TableQuery, configureOrmOperations, createFetchTransport, createOperationExecutor, createOperationExecutorWithTransport, createOrmClient, createOrmFetch, createServiceOrmClient, executeOperation };
331
409
  //# sourceMappingURL=client.js.map
332
410
  //# sourceMappingURL=client.js.map