@prisma-next/sql-contract-ts 0.5.0-dev.6 → 0.5.0-dev.60

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,6 +1,7 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-ts",
3
- "version": "0.5.0-dev.6",
3
+ "version": "0.5.0-dev.60",
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",
@@ -8,12 +9,12 @@
8
9
  "arktype": "^2.1.25",
9
10
  "pathe": "^2.0.3",
10
11
  "ts-toolbelt": "^9.6.0",
11
- "@prisma-next/contract": "0.5.0-dev.6",
12
- "@prisma-next/sql-contract": "0.5.0-dev.6",
13
- "@prisma-next/framework-components": "0.5.0-dev.6",
14
- "@prisma-next/config": "0.5.0-dev.6",
15
- "@prisma-next/contract-authoring": "0.5.0-dev.6",
16
- "@prisma-next/utils": "0.5.0-dev.6"
12
+ "@prisma-next/config": "0.5.0-dev.60",
13
+ "@prisma-next/contract-authoring": "0.5.0-dev.60",
14
+ "@prisma-next/contract": "0.5.0-dev.60",
15
+ "@prisma-next/sql-contract": "0.5.0-dev.60",
16
+ "@prisma-next/framework-components": "0.5.0-dev.60",
17
+ "@prisma-next/utils": "0.5.0-dev.60"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@types/pg": "8.16.0",
@@ -22,8 +23,8 @@
22
23
  "typescript": "5.9.3",
23
24
  "vitest": "4.0.17",
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",
@@ -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,4 +1,4 @@
1
- import type { ColumnDefault, ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
1
+ import type { ColumnDefault, ExecutionMutationDefaultPhases } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  ColumnTypeDescriptor,
4
4
  ForeignKeyDefaultsState,
@@ -6,13 +6,15 @@ import type {
6
6
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
7
7
  import type { ReferentialAction, StorageTypeInstance } from '@prisma-next/sql-contract/types';
8
8
 
9
+ export type { ExecutionMutationDefaultPhases };
10
+
9
11
  export interface FieldNode {
10
12
  readonly fieldName: string;
11
13
  readonly columnName: string;
12
14
  readonly descriptor: ColumnTypeDescriptor;
13
15
  readonly nullable: boolean;
14
16
  readonly default?: ColumnDefault;
15
- readonly executionDefault?: ExecutionMutationDefaultValue;
17
+ readonly executionDefaults?: ExecutionMutationDefaultPhases;
16
18
  readonly many?: boolean;
17
19
  }
18
20
 
@@ -71,7 +73,7 @@ export interface ValueObjectFieldNode {
71
73
  readonly valueObjectName: string;
72
74
  readonly nullable: boolean;
73
75
  readonly default?: ColumnDefault;
74
- readonly executionDefault?: ExecutionMutationDefaultValue;
76
+ readonly executionDefaults?: ExecutionMutationDefaultPhases;
75
77
  readonly many?: boolean;
76
78
  }
77
79
 
@@ -1,8 +1,10 @@
1
1
  import type {
2
2
  ColumnDefault,
3
3
  ColumnDefaultLiteralInputValue,
4
+ ExecutionMutationDefaultPhases,
4
5
  ExecutionMutationDefaultValue,
5
6
  } from '@prisma-next/contract/types';
7
+ import { isColumnDefault } from '@prisma-next/contract/types';
6
8
  import type {
7
9
  ColumnTypeDescriptor,
8
10
  ForeignKeyDefaultsState,
@@ -46,7 +48,7 @@ export type ScalarFieldState<
46
48
  readonly nullable: Nullable;
47
49
  readonly columnName?: ColumnName | undefined;
48
50
  readonly default?: ColumnDefault | undefined;
49
- readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
51
+ readonly executionDefaults?: ExecutionMutationDefaultPhases | undefined;
50
52
  } & (IdSpec extends NamedConstraintSpec ? { readonly id: IdSpec } : { readonly id?: undefined }) &
51
53
  (UniqueSpec extends NamedConstraintSpec
52
54
  ? { readonly unique: UniqueSpec }
@@ -59,7 +61,7 @@ type AnyScalarFieldState = {
59
61
  readonly nullable: boolean;
60
62
  readonly columnName?: string | undefined;
61
63
  readonly default?: ColumnDefault | undefined;
62
- readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
64
+ readonly executionDefaults?: ExecutionMutationDefaultPhases | undefined;
63
65
  readonly id?: NamedConstraintSpec | undefined;
64
66
  readonly unique?: NamedConstraintSpec | undefined;
65
67
  };
@@ -136,12 +138,6 @@ export type GeneratedFieldSpec = {
136
138
  readonly generated: ExecutionMutationDefaultValue;
137
139
  };
138
140
 
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
141
  function toColumnDefault(value: ColumnDefaultLiteralInputValue | ColumnDefault): ColumnDefault {
146
142
  if (isColumnDefault(value)) {
147
143
  return value;
@@ -348,7 +344,7 @@ function generatedField<Descriptor extends ColumnTypeDescriptor>(
348
344
  ...(spec.typeParams ? { typeParams: spec.typeParams } : {}),
349
345
  },
350
346
  nullable: false,
351
- executionDefault: spec.generated,
347
+ executionDefaults: { onCreate: spec.generated },
352
348
  });
353
349
  }
354
350
 
@@ -379,11 +375,8 @@ export function buildFieldPreset(
379
375
  kind: 'scalar',
380
376
  descriptor: preset.descriptor,
381
377
  nullable: preset.nullable,
382
- ...ifDefined('default', preset.default as ColumnDefault | undefined),
383
- ...ifDefined(
384
- 'executionDefault',
385
- preset.executionDefault as ExecutionMutationDefaultValue | undefined,
386
- ),
378
+ ...ifDefined('default', preset.default),
379
+ ...ifDefined('executionDefaults', preset.executionDefaults),
387
380
  ...(preset.id
388
381
  ? {
389
382
  id: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
@@ -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