@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/README.md +6 -3
- package/dist/contract-builder.d.mts +5 -5
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +21 -48
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +10 -9
- package/src/build-contract.ts +10 -20
- package/src/composed-authoring-helpers.ts +14 -54
- package/src/contract-definition.ts +5 -3
- package/src/contract-dsl.ts +7 -14
- package/src/contract-lowering.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-ts",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
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/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/
|
|
16
|
-
"@prisma-next/utils": "0.5.0-dev.
|
|
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/
|
|
26
|
-
"@prisma-next/
|
|
26
|
+
"@prisma-next/tsdown": "0.0.0",
|
|
27
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"dist",
|
package/src/build-contract.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
233
|
+
if (executionDefaultPhases) {
|
|
231
234
|
executionDefaults.push({
|
|
232
235
|
ref: { table: tableName, column: field.columnName },
|
|
233
|
-
onCreate
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
fieldNamespace: AuthoringFieldNamespace,
|
|
200
154
|
): CoreFieldHelpers & Record<string, unknown> {
|
|
201
155
|
const helperNamespace = createFieldHelpersFromNamespace(
|
|
202
|
-
|
|
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(
|
|
211
|
+
field: createComposedFieldHelpers(fieldNamespace),
|
|
252
212
|
model,
|
|
253
213
|
rel,
|
|
254
|
-
type: createTypeHelpersFromNamespace(
|
|
214
|
+
type: createTypeHelpersFromNamespace(typeNamespace),
|
|
255
215
|
} as ComposedAuthoringHelpers<Family, Target, ExtensionPacks>;
|
|
256
216
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColumnDefault,
|
|
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
|
|
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
|
|
76
|
+
readonly executionDefaults?: ExecutionMutationDefaultPhases;
|
|
75
77
|
readonly many?: boolean;
|
|
76
78
|
}
|
|
77
79
|
|
package/src/contract-dsl.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 } : {},
|
package/src/contract-lowering.ts
CHANGED
|
@@ -562,7 +562,7 @@ function resolveModelNode(
|
|
|
562
562
|
descriptor,
|
|
563
563
|
nullable: fieldState.nullable,
|
|
564
564
|
...(fieldState.default ? { default: fieldState.default } : {}),
|
|
565
|
-
...(fieldState.
|
|
565
|
+
...(fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}),
|
|
566
566
|
});
|
|
567
567
|
}
|
|
568
568
|
|