@prisma-next/sql-contract-ts 0.5.0-dev.8 → 0.5.0-dev.81

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.
package/package.json CHANGED
@@ -1,29 +1,30 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-ts",
3
- "version": "0.5.0-dev.8",
3
+ "version": "0.5.0-dev.81",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
7
  "description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
7
8
  "dependencies": {
8
- "arktype": "^2.1.25",
9
+ "arktype": "^2.1.29",
9
10
  "pathe": "^2.0.3",
10
11
  "ts-toolbelt": "^9.6.0",
11
- "@prisma-next/config": "0.5.0-dev.8",
12
- "@prisma-next/contract-authoring": "0.5.0-dev.8",
13
- "@prisma-next/framework-components": "0.5.0-dev.8",
14
- "@prisma-next/contract": "0.5.0-dev.8",
15
- "@prisma-next/utils": "0.5.0-dev.8",
16
- "@prisma-next/sql-contract": "0.5.0-dev.8"
12
+ "@prisma-next/config": "0.5.0-dev.81",
13
+ "@prisma-next/contract-authoring": "0.5.0-dev.81",
14
+ "@prisma-next/framework-components": "0.5.0-dev.81",
15
+ "@prisma-next/utils": "0.5.0-dev.81",
16
+ "@prisma-next/contract": "0.5.0-dev.81",
17
+ "@prisma-next/sql-contract": "0.5.0-dev.81"
17
18
  },
18
19
  "devDependencies": {
19
- "@types/pg": "8.16.0",
20
- "pg": "8.16.3",
21
- "tsdown": "0.18.4",
20
+ "@types/pg": "8.20.0",
21
+ "pg": "8.20.0",
22
+ "tsdown": "0.22.0",
22
23
  "typescript": "5.9.3",
23
- "vitest": "4.0.17",
24
+ "vitest": "4.1.5",
24
25
  "@prisma-next/test-utils": "0.0.1",
25
- "@prisma-next/tsconfig": "0.0.0",
26
- "@prisma-next/tsdown": "0.0.0"
26
+ "@prisma-next/tsdown": "0.0.0",
27
+ "@prisma-next/tsconfig": "0.0.0"
27
28
  },
28
29
  "files": [
29
30
  "dist",
@@ -38,19 +38,21 @@ export type ArgTypeFromDescriptor<Arg extends AuthoringArgumentDescriptor> = Arg
38
38
  readonly kind: 'string';
39
39
  }
40
40
  ? string
41
- : Arg extends { readonly kind: 'number' }
42
- ? number
43
- : Arg extends { readonly kind: 'stringArray' }
44
- ? readonly string[]
45
- : Arg extends {
46
- readonly kind: 'object';
47
- readonly properties: infer Properties extends Record<
48
- string,
49
- AuthoringArgumentDescriptor
50
- >;
51
- }
52
- ? ObjectArgumentType<Properties>
53
- : never;
41
+ : Arg extends { readonly kind: 'boolean' }
42
+ ? boolean
43
+ : Arg extends { readonly kind: 'number' }
44
+ ? number
45
+ : Arg extends { readonly kind: 'stringArray' }
46
+ ? readonly string[]
47
+ : Arg extends {
48
+ readonly kind: 'object';
49
+ readonly properties: infer Properties extends Record<
50
+ string,
51
+ AuthoringArgumentDescriptor
52
+ >;
53
+ }
54
+ ? ObjectArgumentType<Properties>
55
+ : never;
54
56
 
55
57
  export type TupleFromArgumentDescriptors<Args extends readonly AuthoringArgumentDescriptor[]> = {
56
58
  readonly [K in keyof Args]: Args[K] extends AuthoringArgumentDescriptor
@@ -13,7 +13,6 @@ import {
13
13
  type ContractValueObject,
14
14
  coreHash,
15
15
  type ExecutionMutationDefault,
16
- type ExecutionMutationDefaultValue,
17
16
  type JsonValue,
18
17
  type StorageHashBase,
19
18
  } from '@prisma-next/contract/types';
@@ -198,15 +197,19 @@ export function buildSqlContractFromDefinition(
198
197
  const domainFieldRefs: Record<string, DomainFieldRef> = {};
199
198
 
200
199
  for (const field of semanticModel.fields) {
201
- if (field.executionDefault) {
200
+ const executionDefaultPhases =
201
+ field.executionDefaults?.onCreate || field.executionDefaults?.onUpdate
202
+ ? field.executionDefaults
203
+ : undefined;
204
+ if (executionDefaultPhases) {
202
205
  if (field.default !== undefined) {
203
206
  throw new Error(
204
- `Field "${semanticModel.modelName}.${field.fieldName}" cannot define both default and executionDefault.`,
207
+ `Field "${semanticModel.modelName}.${field.fieldName}" cannot define both default and executionDefaults.`,
205
208
  );
206
209
  }
207
210
  if (field.nullable) {
208
211
  throw new Error(
209
- `Field "${semanticModel.modelName}.${field.fieldName}" cannot be nullable when executionDefault is present.`,
212
+ `Field "${semanticModel.modelName}.${field.fieldName}" cannot be nullable when executionDefaults are present.`,
210
213
  );
211
214
  }
212
215
  }
@@ -227,28 +230,15 @@ export function buildSqlContractFromDefinition(
227
230
  domainFieldRefs[field.fieldName] = { kind: 'scalar', many: true };
228
231
  }
229
232
 
230
- if ('executionDefault' in field && field.executionDefault) {
233
+ if (executionDefaultPhases) {
231
234
  executionDefaults.push({
232
235
  ref: { table: tableName, column: field.columnName },
233
- onCreate: field.executionDefault as ExecutionMutationDefaultValue,
236
+ ...ifDefined('onCreate', executionDefaultPhases.onCreate),
237
+ ...ifDefined('onUpdate', executionDefaultPhases.onUpdate),
234
238
  });
235
239
  }
236
240
  }
237
241
 
238
- if (semanticModel.id) {
239
- const fieldsByColumnName = new Map(
240
- semanticModel.fields.map((field) => [field.columnName, field]),
241
- );
242
- for (const columnName of semanticModel.id.columns) {
243
- const field = fieldsByColumnName.get(columnName);
244
- if (field?.nullable) {
245
- throw new Error(
246
- `Model "${semanticModel.modelName}" uses nullable field "${field.fieldName}" in its identity.`,
247
- );
248
- }
249
- }
250
- }
251
-
252
242
  const foreignKeys = (semanticModel.foreignKeys ?? []).map((fk) => {
253
243
  const targetModel = assertKnownTargetModel(
254
244
  modelsByName,
@@ -5,8 +5,10 @@ import type {
5
5
  AuthoringTypeNamespace,
6
6
  } from '@prisma-next/framework-components/authoring';
7
7
  import {
8
+ assertNoCrossRegistryCollisions,
8
9
  isAuthoringFieldPresetDescriptor,
9
10
  isAuthoringTypeConstructorDescriptor,
11
+ mergeAuthoringNamespaces,
10
12
  } from '@prisma-next/framework-components/authoring';
11
13
  import type {
12
14
  ExtensionPackRef,
@@ -121,54 +123,6 @@ function extractFieldNamespace<Pack>(pack: Pack): ExtractFieldNamespaceFromPack<
121
123
  {}) as ExtractFieldNamespaceFromPack<Pack>;
122
124
  }
123
125
 
124
- function mergeHelperNamespaces(
125
- target: Record<string, unknown>,
126
- source: Record<string, unknown>,
127
- path: readonly string[],
128
- leafGuard: (value: unknown) => boolean,
129
- label: string,
130
- ): void {
131
- const assertSafePath = (currentPath: readonly string[]) => {
132
- const blockedSegment = currentPath.find(
133
- (segment) => segment === '__proto__' || segment === 'constructor' || segment === 'prototype',
134
- );
135
- if (blockedSegment) {
136
- throw new Error(
137
- `Invalid authoring ${label} helper "${currentPath.join('.')}". Helper path segments must not use "${blockedSegment}".`,
138
- );
139
- }
140
- };
141
-
142
- for (const [key, sourceValue] of Object.entries(source)) {
143
- const currentPath = [...path, key];
144
- assertSafePath(currentPath);
145
- const hasExistingValue = Object.hasOwn(target, key);
146
- const existingValue = hasExistingValue ? target[key] : undefined;
147
-
148
- if (!hasExistingValue) {
149
- target[key] = sourceValue;
150
- continue;
151
- }
152
-
153
- const existingIsLeaf = leafGuard(existingValue);
154
- const sourceIsLeaf = leafGuard(sourceValue);
155
-
156
- if (existingIsLeaf || sourceIsLeaf) {
157
- throw new Error(
158
- `Duplicate authoring ${label} helper "${currentPath.join('.')}". Helper names must be unique across composed packs.`,
159
- );
160
- }
161
-
162
- mergeHelperNamespaces(
163
- existingValue as Record<string, unknown>,
164
- sourceValue as Record<string, unknown>,
165
- currentPath,
166
- leafGuard,
167
- label,
168
- );
169
- }
170
- }
171
-
172
126
  type AuthoringComponent = {
173
127
  readonly authoring?: { readonly type?: unknown; readonly field?: unknown };
174
128
  };
@@ -178,7 +132,7 @@ function composeTypeNamespace(components: readonly AuthoringComponent[]): Author
178
132
  for (const component of components) {
179
133
  const ns = extractTypeNamespace(component);
180
134
  if (Object.keys(ns).length > 0) {
181
- mergeHelperNamespaces(merged, ns, [], isAuthoringTypeConstructorDescriptor, 'type');
135
+ mergeAuthoringNamespaces(merged, ns, [], isAuthoringTypeConstructorDescriptor, 'type');
182
136
  }
183
137
  }
184
138
  return merged as AuthoringTypeNamespace;
@@ -189,17 +143,17 @@ function composeFieldNamespace(components: readonly AuthoringComponent[]): Autho
189
143
  for (const component of components) {
190
144
  const ns = extractFieldNamespace(component);
191
145
  if (Object.keys(ns).length > 0) {
192
- mergeHelperNamespaces(merged, ns, [], isAuthoringFieldPresetDescriptor, 'field');
146
+ mergeAuthoringNamespaces(merged, ns, [], isAuthoringFieldPresetDescriptor, 'field');
193
147
  }
194
148
  }
195
149
  return merged as AuthoringFieldNamespace;
196
150
  }
197
151
 
198
152
  function createComposedFieldHelpers(
199
- components: readonly AuthoringComponent[],
153
+ fieldNamespace: AuthoringFieldNamespace,
200
154
  ): CoreFieldHelpers & Record<string, unknown> {
201
155
  const helperNamespace = createFieldHelpersFromNamespace(
202
- composeFieldNamespace(components),
156
+ fieldNamespace,
203
157
  ({ helperPath, descriptor }) =>
204
158
  createFieldPresetHelper({
205
159
  helperPath,
@@ -247,10 +201,16 @@ export function createComposedAuthoringHelpers<
247
201
  ...extensionValues,
248
202
  ];
249
203
 
204
+ const typeNamespace = composeTypeNamespace(components);
205
+ const fieldNamespace = composeFieldNamespace(components);
206
+ // Mirrors the call in `assembleAuthoringContributions`: PSL composes via
207
+ // `createControlStack`, the TS DSL composes here. Both seams need the guard.
208
+ assertNoCrossRegistryCollisions(typeNamespace, fieldNamespace);
209
+
250
210
  return {
251
- field: createComposedFieldHelpers(components),
211
+ field: createComposedFieldHelpers(fieldNamespace),
252
212
  model,
253
213
  rel,
254
- type: createTypeHelpersFromNamespace(composeTypeNamespace(components)),
214
+ type: createTypeHelpersFromNamespace(typeNamespace),
255
215
  } as ComposedAuthoringHelpers<Family, Target, ExtensionPacks>;
256
216
  }
@@ -1,18 +1,18 @@
1
- import type { ColumnDefault, ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
2
- import type {
3
- ColumnTypeDescriptor,
4
- ForeignKeyDefaultsState,
5
- } from '@prisma-next/contract-authoring';
1
+ import type { ColumnDefault, ExecutionMutationDefaultPhases } from '@prisma-next/contract/types';
2
+ import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
3
+ import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
6
4
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
7
5
  import type { ReferentialAction, StorageTypeInstance } from '@prisma-next/sql-contract/types';
8
6
 
7
+ export type { ExecutionMutationDefaultPhases };
8
+
9
9
  export interface FieldNode {
10
10
  readonly fieldName: string;
11
11
  readonly columnName: string;
12
12
  readonly descriptor: ColumnTypeDescriptor;
13
13
  readonly nullable: boolean;
14
14
  readonly default?: ColumnDefault;
15
- readonly executionDefault?: ExecutionMutationDefaultValue;
15
+ readonly executionDefaults?: ExecutionMutationDefaultPhases;
16
16
  readonly many?: boolean;
17
17
  }
18
18
 
@@ -71,7 +71,7 @@ export interface ValueObjectFieldNode {
71
71
  readonly valueObjectName: string;
72
72
  readonly nullable: boolean;
73
73
  readonly default?: ColumnDefault;
74
- readonly executionDefault?: ExecutionMutationDefaultValue;
74
+ readonly executionDefaults?: ExecutionMutationDefaultPhases;
75
75
  readonly many?: boolean;
76
76
  }
77
77
 
@@ -1,15 +1,14 @@
1
1
  import type {
2
2
  ColumnDefault,
3
3
  ColumnDefaultLiteralInputValue,
4
+ ExecutionMutationDefaultPhases,
4
5
  ExecutionMutationDefaultValue,
5
6
  } from '@prisma-next/contract/types';
6
- import type {
7
- ColumnTypeDescriptor,
8
- ForeignKeyDefaultsState,
9
- } from '@prisma-next/contract-authoring';
7
+ import { isColumnDefault } from '@prisma-next/contract/types';
8
+ import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
10
9
  import type { AuthoringFieldPresetDescriptor } from '@prisma-next/framework-components/authoring';
11
10
  import { instantiateAuthoringFieldPreset } from '@prisma-next/framework-components/authoring';
12
- import type { CodecLookup } from '@prisma-next/framework-components/codec';
11
+ import type { CodecLookup, ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
13
12
  import type {
14
13
  ExtensionPackRef,
15
14
  FamilyPackRef,
@@ -46,7 +45,7 @@ export type ScalarFieldState<
46
45
  readonly nullable: Nullable;
47
46
  readonly columnName?: ColumnName | undefined;
48
47
  readonly default?: ColumnDefault | undefined;
49
- readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
48
+ readonly executionDefaults?: ExecutionMutationDefaultPhases | undefined;
50
49
  } & (IdSpec extends NamedConstraintSpec ? { readonly id: IdSpec } : { readonly id?: undefined }) &
51
50
  (UniqueSpec extends NamedConstraintSpec
52
51
  ? { readonly unique: UniqueSpec }
@@ -59,7 +58,7 @@ type AnyScalarFieldState = {
59
58
  readonly nullable: boolean;
60
59
  readonly columnName?: string | undefined;
61
60
  readonly default?: ColumnDefault | undefined;
62
- readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
61
+ readonly executionDefaults?: ExecutionMutationDefaultPhases | undefined;
63
62
  readonly id?: NamedConstraintSpec | undefined;
64
63
  readonly unique?: NamedConstraintSpec | undefined;
65
64
  };
@@ -136,12 +135,6 @@ export type GeneratedFieldSpec = {
136
135
  readonly generated: ExecutionMutationDefaultValue;
137
136
  };
138
137
 
139
- function isColumnDefault(value: unknown): value is ColumnDefault {
140
- if (typeof value !== 'object' || value === null) return false;
141
- const kind = (value as { kind?: unknown }).kind;
142
- return kind === 'literal' || kind === 'function';
143
- }
144
-
145
138
  function toColumnDefault(value: ColumnDefaultLiteralInputValue | ColumnDefault): ColumnDefault {
146
139
  if (isColumnDefault(value)) {
147
140
  return value;
@@ -149,10 +142,7 @@ function toColumnDefault(value: ColumnDefaultLiteralInputValue | ColumnDefault):
149
142
  return { kind: 'literal', value };
150
143
  }
151
144
 
152
- // Chaining methods use `as unknown as <ConditionalType>` because TypeScript cannot
153
- // narrow generic conditional return types through object spread. The runtime values
154
- // are correct — the casts bridge the gap between the spread result and the
155
- // compile-time conditional type that encodes the state transition.
145
+ // Chaining methods use `as unknown as <ConditionalType>` because TypeScript cannot narrow generic conditional return types through object spread. The runtime values are correct — the casts bridge the gap between the spread result and the compile-time conditional type that encodes the state transition.
156
146
  export class ScalarFieldBuilder<State extends AnyScalarFieldState = AnyScalarFieldState> {
157
147
  declare readonly __state: State;
158
148
 
@@ -348,7 +338,7 @@ function generatedField<Descriptor extends ColumnTypeDescriptor>(
348
338
  ...(spec.typeParams ? { typeParams: spec.typeParams } : {}),
349
339
  },
350
340
  nullable: false,
351
- executionDefault: spec.generated,
341
+ executionDefaults: { onCreate: spec.generated },
352
342
  });
353
343
  }
354
344
 
@@ -379,11 +369,8 @@ export function buildFieldPreset(
379
369
  kind: 'scalar',
380
370
  descriptor: preset.descriptor,
381
371
  nullable: preset.nullable,
382
- ...ifDefined('default', preset.default as ColumnDefault | undefined),
383
- ...ifDefined(
384
- 'executionDefault',
385
- preset.executionDefault as ExecutionMutationDefaultValue | undefined,
386
- ),
372
+ ...ifDefined('default', preset.default),
373
+ ...ifDefined('executionDefaults', preset.executionDefaults),
387
374
  ...(preset.id
388
375
  ? {
389
376
  id: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
@@ -1037,9 +1024,7 @@ export class ContractModelBuilder<
1037
1024
  ): [ValidateSqlStageSpec<Fields, AttributesSpec, NextSqlSpec>] extends [never]
1038
1025
  ? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, never>
1039
1026
  : ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, NextSqlSpec> {
1040
- // Conditional return type cannot be verified by the implementation; the
1041
- // runtime value is always a valid ContractModelBuilder regardless of the
1042
- // validation outcome (validation is type-level only).
1027
+ // Conditional return type cannot be verified by the implementation; the runtime value is always a valid ContractModelBuilder regardless of the validation outcome (validation is type-level only).
1043
1028
  return new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory) as never;
1044
1029
  }
1045
1030
 
@@ -1,4 +1,4 @@
1
- import type { ColumnTypeDescriptor } from '@prisma-next/contract-authoring';
1
+ import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
2
2
  import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
3
3
  import type {
4
4
  ContractDefinition,
@@ -562,7 +562,7 @@ function resolveModelNode(
562
562
  descriptor,
563
563
  nullable: fieldState.nullable,
564
564
  ...(fieldState.default ? { default: fieldState.default } : {}),
565
- ...(fieldState.executionDefault ? { executionDefault: fieldState.executionDefault } : {}),
565
+ ...(fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}),
566
566
  });
567
567
  }
568
568