@prisma-next/sql-runtime 0.3.0-dev.3 → 0.3.0-dev.31

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 (53) hide show
  1. package/README.md +1 -2
  2. package/dist/{accelerate-EEKAFGN3-SHR4XFVV.js → accelerate-EEKAFGN3-P6A6XJWJ.js} +28 -28
  3. package/dist/{accelerate-EEKAFGN3-SHR4XFVV.js.map → accelerate-EEKAFGN3-P6A6XJWJ.js.map} +1 -1
  4. package/dist/{chunk-C6I3V3DM.js → chunk-APA6GHYY.js} +84 -2
  5. package/dist/chunk-APA6GHYY.js.map +1 -0
  6. package/dist/{dist-LCVVJCGI.js → dist-AQ3LWXOX.js} +13 -13
  7. package/dist/{dist-LCVVJCGI.js.map → dist-AQ3LWXOX.js.map} +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/src/codecs/decoding.d.ts +4 -0
  10. package/dist/src/codecs/decoding.d.ts.map +1 -0
  11. package/dist/src/codecs/encoding.d.ts +5 -0
  12. package/dist/src/codecs/encoding.d.ts.map +1 -0
  13. package/dist/src/codecs/validation.d.ts +6 -0
  14. package/dist/src/codecs/validation.d.ts.map +1 -0
  15. package/dist/src/exports/index.d.ts +11 -0
  16. package/dist/src/exports/index.d.ts.map +1 -0
  17. package/dist/src/index.d.ts +2 -0
  18. package/dist/src/index.d.ts.map +1 -0
  19. package/dist/src/lower-sql-plan.d.ts +15 -0
  20. package/dist/src/lower-sql-plan.d.ts.map +1 -0
  21. package/dist/src/sql-context.d.ts +130 -0
  22. package/dist/src/sql-context.d.ts.map +1 -0
  23. package/dist/src/sql-family-adapter.d.ts +10 -0
  24. package/dist/src/sql-family-adapter.d.ts.map +1 -0
  25. package/dist/src/sql-marker.d.ts +22 -0
  26. package/dist/src/sql-marker.d.ts.map +1 -0
  27. package/dist/src/sql-runtime.d.ts +25 -0
  28. package/dist/src/sql-runtime.d.ts.map +1 -0
  29. package/dist/test/utils.d.ts +20 -24
  30. package/dist/test/utils.d.ts.map +1 -0
  31. package/dist/test/utils.js +26 -26
  32. package/dist/test/utils.js.map +1 -1
  33. package/package.json +25 -22
  34. package/src/codecs/decoding.ts +140 -0
  35. package/src/codecs/encoding.ts +76 -0
  36. package/src/codecs/validation.ts +67 -0
  37. package/src/exports/index.ts +40 -0
  38. package/src/index.ts +1 -0
  39. package/src/lower-sql-plan.ts +32 -0
  40. package/src/sql-context.ts +402 -0
  41. package/src/sql-family-adapter.ts +43 -0
  42. package/src/sql-marker.ts +105 -0
  43. package/src/sql-runtime.ts +166 -0
  44. package/test/async-iterable-result.test.ts +136 -0
  45. package/test/context.types.test-d.ts +70 -0
  46. package/test/parameterized-types.test.ts +553 -0
  47. package/test/sql-context.test.ts +217 -0
  48. package/test/sql-family-adapter.test.ts +86 -0
  49. package/test/sql-runtime.test.ts +155 -0
  50. package/test/utils.ts +266 -0
  51. package/dist/chunk-C6I3V3DM.js.map +0 -1
  52. package/dist/index.d.ts +0 -29
  53. package/dist/sql-runtime-DgEbg2OP.d.ts +0 -109
@@ -0,0 +1,402 @@
1
+ import type {
2
+ RuntimeAdapterDescriptor,
3
+ RuntimeAdapterInstance,
4
+ RuntimeExtensionDescriptor,
5
+ RuntimeExtensionInstance,
6
+ RuntimeTargetDescriptor,
7
+ } from '@prisma-next/core-execution-plane/types';
8
+ import { createOperationRegistry } from '@prisma-next/operations';
9
+ import type { SqlContract, SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
10
+ import type { SqlOperationSignature } from '@prisma-next/sql-operations';
11
+ import type {
12
+ Adapter,
13
+ CodecRegistry,
14
+ LoweredStatement,
15
+ QueryAst,
16
+ } from '@prisma-next/sql-relational-core/ast';
17
+ import { createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
18
+ import type {
19
+ QueryLaneContext,
20
+ TypeHelperRegistry,
21
+ } from '@prisma-next/sql-relational-core/query-lane-context';
22
+ import type { Type } from 'arktype';
23
+ import { type as arktype } from 'arktype';
24
+
25
+ // ============================================================================
26
+ // Runtime Parameterized Codec Descriptor Types
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Runtime parameterized codec descriptor.
31
+ * Provides validation schema and optional init hook for codecs that support type parameters.
32
+ * Used at runtime to validate typeParams and create type helpers.
33
+ */
34
+ export interface RuntimeParameterizedCodecDescriptor<
35
+ TParams = Record<string, unknown>,
36
+ THelper = unknown,
37
+ > {
38
+ /** The codec ID this descriptor applies to (e.g., 'pg/vector@1') */
39
+ readonly codecId: string;
40
+
41
+ /**
42
+ * Arktype schema for validating typeParams.
43
+ * The schema is used to validate both storage.types entries and inline column typeParams.
44
+ */
45
+ readonly paramsSchema: Type<TParams>;
46
+
47
+ /**
48
+ * Optional init hook called during runtime context creation.
49
+ * Receives validated params and returns a helper object to be stored in context.types.
50
+ * If not provided, the validated params are stored directly.
51
+ */
52
+ readonly init?: (params: TParams) => THelper;
53
+ }
54
+
55
+ // ============================================================================
56
+ // SQL Runtime Extension Types
57
+ // ============================================================================
58
+
59
+ /**
60
+ * SQL runtime extension instance.
61
+ * Extends the framework RuntimeExtensionInstance with SQL-specific hooks
62
+ * for contributing codecs and operations to the runtime context.
63
+ *
64
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
65
+ */
66
+ export interface SqlRuntimeExtensionInstance<TTargetId extends string>
67
+ extends RuntimeExtensionInstance<'sql', TTargetId> {
68
+ /** Returns codecs to register in the runtime context. */
69
+ codecs?(): CodecRegistry;
70
+ /** Returns operations to register in the runtime context. */
71
+ operations?(): ReadonlyArray<SqlOperationSignature>;
72
+ /**
73
+ * Returns parameterized codec descriptors for type validation and helper creation.
74
+ * Uses unknown for type parameters to allow any concrete descriptor types.
75
+ */
76
+ // biome-ignore lint/suspicious/noExplicitAny: needed for covariance with concrete descriptor types
77
+ parameterizedCodecs?(): ReadonlyArray<RuntimeParameterizedCodecDescriptor<any, any>>;
78
+ }
79
+
80
+ /**
81
+ * SQL runtime extension descriptor.
82
+ * Extends the framework RuntimeExtensionDescriptor with SQL-specific instance type.
83
+ *
84
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
85
+ */
86
+ export interface SqlRuntimeExtensionDescriptor<TTargetId extends string>
87
+ extends RuntimeExtensionDescriptor<'sql', TTargetId, SqlRuntimeExtensionInstance<TTargetId>> {
88
+ create(): SqlRuntimeExtensionInstance<TTargetId>;
89
+ }
90
+
91
+ // ============================================================================
92
+ // SQL Runtime Adapter Instance
93
+ // ============================================================================
94
+
95
+ /**
96
+ * SQL runtime adapter instance interface.
97
+ * Combines RuntimeAdapterInstance identity with SQL Adapter behavior.
98
+ * The instance IS an Adapter (via intersection), not HAS an adapter property.
99
+ *
100
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
101
+ */
102
+ export type SqlRuntimeAdapterInstance<TTargetId extends string = string> = RuntimeAdapterInstance<
103
+ 'sql',
104
+ TTargetId
105
+ > &
106
+ Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>;
107
+
108
+ // ============================================================================
109
+ // SQL Runtime Context
110
+ // ============================================================================
111
+
112
+ export type { TypeHelperRegistry };
113
+
114
+ export interface RuntimeContext<TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>>
115
+ extends QueryLaneContext<TContract> {
116
+ readonly adapter:
117
+ | Adapter<QueryAst, TContract, LoweredStatement>
118
+ | Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>;
119
+
120
+ /**
121
+ * Initialized type helpers from storage.types.
122
+ * Each entry corresponds to a named type instance in the contract's storage.types.
123
+ * The value is the result of calling the codec's init hook (if provided)
124
+ * or the validated typeParams (if no init hook).
125
+ */
126
+ readonly types?: TypeHelperRegistry;
127
+ }
128
+
129
+ /**
130
+ * Descriptor-first options for creating a SQL runtime context.
131
+ * Takes the same framework composition as control-plane: target, adapter, extensionPacks.
132
+ *
133
+ * @template TContract - The SQL contract type
134
+ * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
135
+ */
136
+ export interface CreateRuntimeContextOptions<
137
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
138
+ TTargetId extends string = string,
139
+ > {
140
+ readonly contract: TContract;
141
+ readonly target: RuntimeTargetDescriptor<'sql', TTargetId>;
142
+ readonly adapter: RuntimeAdapterDescriptor<
143
+ 'sql',
144
+ TTargetId,
145
+ SqlRuntimeAdapterInstance<TTargetId>
146
+ >;
147
+ readonly extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<TTargetId>>;
148
+ }
149
+
150
+ // ============================================================================
151
+ // Runtime Error Types and Helpers
152
+ // ============================================================================
153
+
154
+ /**
155
+ * Structured error thrown by the SQL runtime.
156
+ *
157
+ * Aligns with the repository's error envelope convention:
158
+ * - `code`: Stable error code for programmatic handling (e.g., `RUNTIME.TYPE_PARAMS_INVALID`)
159
+ * - `category`: Error source category (`RUNTIME`)
160
+ * - `severity`: Error severity level (`error`)
161
+ * - `details`: Optional structured details for debugging
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * try {
166
+ * createRuntimeContext({ ... });
167
+ * } catch (e) {
168
+ * if ((e as RuntimeError).code === 'RUNTIME.TYPE_PARAMS_INVALID') {
169
+ * console.error('Invalid type parameters:', (e as RuntimeError).details);
170
+ * }
171
+ * }
172
+ * ```
173
+ */
174
+ export interface RuntimeError extends Error {
175
+ /** Stable error code for programmatic handling (e.g., `RUNTIME.TYPE_PARAMS_INVALID`) */
176
+ readonly code: string;
177
+ /** Error source category */
178
+ readonly category: 'RUNTIME';
179
+ /** Error severity level */
180
+ readonly severity: 'error';
181
+ /** Optional structured details for debugging */
182
+ readonly details?: Record<string, unknown>;
183
+ }
184
+
185
+ /**
186
+ * Creates a RuntimeError for invalid type parameters.
187
+ *
188
+ * Error code: `RUNTIME.TYPE_PARAMS_INVALID`
189
+ *
190
+ * Thrown when:
191
+ * - `storage.types` entries have typeParams that fail codec schema validation
192
+ * - Column inline typeParams fail codec schema validation
193
+ *
194
+ * @internal
195
+ */
196
+ function runtimeTypeParamsInvalid(
197
+ message: string,
198
+ details?: Record<string, unknown>,
199
+ ): RuntimeError {
200
+ const error = new Error(message) as RuntimeError;
201
+ Object.defineProperty(error, 'name', { value: 'RuntimeError', configurable: true });
202
+ return Object.assign(error, {
203
+ code: 'RUNTIME.TYPE_PARAMS_INVALID',
204
+ category: 'RUNTIME' as const,
205
+ severity: 'error' as const,
206
+ details,
207
+ });
208
+ }
209
+
210
+ // ============================================================================
211
+ // Parameterized Type Validation
212
+ // ============================================================================
213
+
214
+ /**
215
+ * Validates typeParams against the codec's paramsSchema.
216
+ * @throws RuntimeError with code RUNTIME.TYPE_PARAMS_INVALID if validation fails
217
+ */
218
+ function validateTypeParams(
219
+ typeParams: Record<string, unknown>,
220
+ codecDescriptor: RuntimeParameterizedCodecDescriptor,
221
+ context: { typeName?: string; tableName?: string; columnName?: string },
222
+ ): Record<string, unknown> {
223
+ const result = codecDescriptor.paramsSchema(typeParams);
224
+ if (result instanceof arktype.errors) {
225
+ const messages = result.map((p: { message: string }) => p.message).join('; ');
226
+ const locationInfo = context.typeName
227
+ ? `type '${context.typeName}'`
228
+ : `column '${context.tableName}.${context.columnName}'`;
229
+ throw runtimeTypeParamsInvalid(
230
+ `Invalid typeParams for ${locationInfo} (codecId: ${codecDescriptor.codecId}): ${messages}`,
231
+ { ...context, codecId: codecDescriptor.codecId, typeParams },
232
+ );
233
+ }
234
+ return result as Record<string, unknown>;
235
+ }
236
+
237
+ /**
238
+ * Collects parameterized codec descriptors from extension instances.
239
+ * Returns a map of codecId → descriptor for quick lookup.
240
+ */
241
+ function collectParameterizedCodecDescriptors(
242
+ extensionInstances: ReadonlyArray<SqlRuntimeExtensionInstance<string>>,
243
+ ): Map<string, RuntimeParameterizedCodecDescriptor> {
244
+ const descriptors = new Map<string, RuntimeParameterizedCodecDescriptor>();
245
+
246
+ for (const extInstance of extensionInstances) {
247
+ const paramCodecs = extInstance.parameterizedCodecs?.();
248
+ if (paramCodecs) {
249
+ for (const descriptor of paramCodecs) {
250
+ if (descriptors.has(descriptor.codecId)) {
251
+ console.warn(
252
+ `Duplicate parameterized codec descriptor for codecId '${descriptor.codecId}' - using later registration`,
253
+ );
254
+ }
255
+ descriptors.set(descriptor.codecId, descriptor);
256
+ }
257
+ }
258
+ }
259
+
260
+ return descriptors;
261
+ }
262
+
263
+ /**
264
+ * Initializes type helpers from storage.types using codec descriptors.
265
+ *
266
+ * For each named type instance in `storage.types`:
267
+ * - If a codec descriptor exists with an `init` hook: calls the hook and stores the result
268
+ * - Otherwise: stores the full type instance metadata directly (codecId, nativeType, typeParams)
269
+ *
270
+ * This matches the typing in `ExtractSchemaTypes<Contract>` which extracts
271
+ * `Contract['storage']['types']` directly, ensuring runtime values match static types
272
+ * when no init hook transforms the value.
273
+ */
274
+ function initializeTypeHelpers(
275
+ storageTypes: Record<string, StorageTypeInstance> | undefined,
276
+ codecDescriptors: Map<string, RuntimeParameterizedCodecDescriptor>,
277
+ ): TypeHelperRegistry {
278
+ const helpers: TypeHelperRegistry = {};
279
+
280
+ if (!storageTypes) {
281
+ return helpers;
282
+ }
283
+
284
+ for (const [typeName, typeInstance] of Object.entries(storageTypes)) {
285
+ const descriptor = codecDescriptors.get(typeInstance.codecId);
286
+
287
+ if (descriptor) {
288
+ // Validate typeParams against the codec's schema
289
+ const validatedParams = validateTypeParams(typeInstance.typeParams, descriptor, {
290
+ typeName,
291
+ });
292
+
293
+ // Call init hook if provided, otherwise store full type instance
294
+ if (descriptor.init) {
295
+ helpers[typeName] = descriptor.init(validatedParams);
296
+ } else {
297
+ // No init hook: expose full type instance metadata (matches contract typing)
298
+ helpers[typeName] = typeInstance;
299
+ }
300
+ } else {
301
+ // No descriptor found: expose full type instance (no validation possible)
302
+ helpers[typeName] = typeInstance;
303
+ }
304
+ }
305
+
306
+ return helpers;
307
+ }
308
+
309
+ /**
310
+ * Validates inline column typeParams across all tables.
311
+ * @throws RuntimeError with code RUNTIME.TYPE_PARAMS_INVALID if validation fails
312
+ */
313
+ function validateColumnTypeParams(
314
+ storage: SqlStorage,
315
+ codecDescriptors: Map<string, RuntimeParameterizedCodecDescriptor>,
316
+ ): void {
317
+ for (const [tableName, table] of Object.entries(storage.tables)) {
318
+ for (const [columnName, column] of Object.entries(table.columns)) {
319
+ if (column.typeParams) {
320
+ const descriptor = codecDescriptors.get(column.codecId);
321
+ if (descriptor) {
322
+ validateTypeParams(column.typeParams, descriptor, { tableName, columnName });
323
+ }
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Creates a SQL runtime context from descriptor-first composition.
331
+ *
332
+ * The context includes:
333
+ * - The validated contract
334
+ * - The adapter instance (created from descriptor)
335
+ * - Codec registry (populated from adapter + extension instances)
336
+ * - Operation registry (populated from extension instances)
337
+ * - Types registry (initialized helpers from storage.types)
338
+ *
339
+ * @param options - Descriptor-first composition options
340
+ * @returns RuntimeContext with registries wired from all components
341
+ */
342
+ export function createRuntimeContext<
343
+ TContract extends SqlContract<SqlStorage> = SqlContract<SqlStorage>,
344
+ TTargetId extends string = string,
345
+ >(options: CreateRuntimeContextOptions<TContract, TTargetId>): RuntimeContext<TContract> {
346
+ const { contract, adapter: adapterDescriptor, extensionPacks } = options;
347
+
348
+ // Create adapter instance from descriptor
349
+ // The adapter instance IS an Adapter (via intersection)
350
+ const adapterInstance = adapterDescriptor.create();
351
+
352
+ // Create registries
353
+ const codecRegistry = createCodecRegistry();
354
+ const operationRegistry = createOperationRegistry();
355
+
356
+ // Register adapter codecs (adapter instance has profile.codecs())
357
+ const adapterCodecs = adapterInstance.profile.codecs();
358
+ for (const codec of adapterCodecs.values()) {
359
+ codecRegistry.register(codec);
360
+ }
361
+
362
+ // Create extension instances and collect their contributions
363
+ const extensionInstances: SqlRuntimeExtensionInstance<TTargetId>[] = [];
364
+
365
+ for (const extDescriptor of extensionPacks ?? []) {
366
+ const extInstance = extDescriptor.create();
367
+ extensionInstances.push(extInstance);
368
+
369
+ const extCodecs = extInstance.codecs?.();
370
+ if (extCodecs) {
371
+ for (const codec of extCodecs.values()) {
372
+ codecRegistry.register(codec);
373
+ }
374
+ }
375
+
376
+ const extOperations = extInstance.operations?.();
377
+ if (extOperations) {
378
+ for (const operation of extOperations) {
379
+ operationRegistry.register(operation);
380
+ }
381
+ }
382
+ }
383
+
384
+ // Collect parameterized codec descriptors from extensions
385
+ const parameterizedCodecDescriptors = collectParameterizedCodecDescriptors(extensionInstances);
386
+
387
+ // Validate column typeParams if any descriptors are registered
388
+ if (parameterizedCodecDescriptors.size > 0) {
389
+ validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
390
+ }
391
+
392
+ // Initialize type helpers from storage.types
393
+ const types = initializeTypeHelpers(contract.storage.types, parameterizedCodecDescriptors);
394
+
395
+ return {
396
+ contract,
397
+ adapter: adapterInstance,
398
+ operations: operationRegistry,
399
+ codecs: codecRegistry,
400
+ types,
401
+ };
402
+ }
@@ -0,0 +1,43 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type {
3
+ MarkerReader,
4
+ MarkerStatement,
5
+ RuntimeFamilyAdapter,
6
+ } from '@prisma-next/runtime-executor';
7
+ import { runtimeError } from '@prisma-next/runtime-executor';
8
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
9
+ import { readContractMarker } from './sql-marker';
10
+
11
+ class SqlMarkerReader implements MarkerReader {
12
+ readMarkerStatement(): MarkerStatement {
13
+ return readContractMarker();
14
+ }
15
+ }
16
+
17
+ export class SqlFamilyAdapter<TContract extends SqlContract<SqlStorage>>
18
+ implements RuntimeFamilyAdapter<TContract>
19
+ {
20
+ readonly contract: TContract;
21
+ readonly markerReader: MarkerReader;
22
+
23
+ constructor(contract: TContract) {
24
+ this.contract = contract;
25
+ this.markerReader = new SqlMarkerReader();
26
+ }
27
+
28
+ validatePlan(plan: ExecutionPlan, contract: TContract): void {
29
+ if (plan.meta.target !== contract.target) {
30
+ throw runtimeError('PLAN.TARGET_MISMATCH', 'Plan target does not match runtime target', {
31
+ planTarget: plan.meta.target,
32
+ runtimeTarget: contract.target,
33
+ });
34
+ }
35
+
36
+ if (plan.meta.coreHash !== contract.coreHash) {
37
+ throw runtimeError('PLAN.HASH_MISMATCH', 'Plan core hash does not match runtime contract', {
38
+ planCoreHash: plan.meta.coreHash,
39
+ runtimeCoreHash: contract.coreHash,
40
+ });
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,105 @@
1
+ import type { MarkerStatement } from '@prisma-next/runtime-executor';
2
+
3
+ export interface SqlStatement {
4
+ readonly sql: string;
5
+ readonly params: readonly unknown[];
6
+ }
7
+
8
+ export interface WriteMarkerInput {
9
+ readonly coreHash: string;
10
+ readonly profileHash: string;
11
+ readonly contractJson?: unknown;
12
+ readonly canonicalVersion?: number;
13
+ readonly appTag?: string;
14
+ readonly meta?: Record<string, unknown>;
15
+ }
16
+
17
+ export const ensureSchemaStatement: SqlStatement = {
18
+ sql: 'create schema if not exists prisma_contract',
19
+ params: [],
20
+ };
21
+
22
+ export const ensureTableStatement: SqlStatement = {
23
+ sql: `create table if not exists prisma_contract.marker (
24
+ id smallint primary key default 1,
25
+ core_hash text not null,
26
+ profile_hash text not null,
27
+ contract_json jsonb,
28
+ canonical_version int,
29
+ updated_at timestamptz not null default now(),
30
+ app_tag text,
31
+ meta jsonb not null default '{}'
32
+ )`,
33
+ params: [],
34
+ };
35
+
36
+ export function readContractMarker(): MarkerStatement {
37
+ return {
38
+ sql: `select
39
+ core_hash,
40
+ profile_hash,
41
+ contract_json,
42
+ canonical_version,
43
+ updated_at,
44
+ app_tag,
45
+ meta
46
+ from prisma_contract.marker
47
+ where id = $1`,
48
+ params: [1],
49
+ };
50
+ }
51
+
52
+ export interface WriteContractMarkerStatements {
53
+ readonly insert: SqlStatement;
54
+ readonly update: SqlStatement;
55
+ }
56
+
57
+ export function writeContractMarker(input: WriteMarkerInput): WriteContractMarkerStatements {
58
+ const baseParams: readonly unknown[] = [
59
+ 1,
60
+ input.coreHash,
61
+ input.profileHash,
62
+ input.contractJson ?? null,
63
+ input.canonicalVersion ?? null,
64
+ input.appTag ?? null,
65
+ JSON.stringify(input.meta ?? {}),
66
+ ];
67
+
68
+ const insert: SqlStatement = {
69
+ sql: `insert into prisma_contract.marker (
70
+ id,
71
+ core_hash,
72
+ profile_hash,
73
+ contract_json,
74
+ canonical_version,
75
+ updated_at,
76
+ app_tag,
77
+ meta
78
+ ) values (
79
+ $1,
80
+ $2,
81
+ $3,
82
+ $4::jsonb,
83
+ $5,
84
+ now(),
85
+ $6,
86
+ $7::jsonb
87
+ )`,
88
+ params: baseParams,
89
+ };
90
+
91
+ const update: SqlStatement = {
92
+ sql: `update prisma_contract.marker set
93
+ core_hash = $2,
94
+ profile_hash = $3,
95
+ contract_json = $4::jsonb,
96
+ canonical_version = $5,
97
+ updated_at = now(),
98
+ app_tag = $6,
99
+ meta = $7::jsonb
100
+ where id = $1`,
101
+ params: baseParams,
102
+ };
103
+
104
+ return { insert, update };
105
+ }