@prisma-next/sql-contract-ts 0.5.0-dev.6 → 0.5.0-dev.61
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 +7 -7
- 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 +8 -7
- package/src/build-contract.ts +10 -20
- package/src/composed-authoring-helpers.ts +14 -54
- package/src/contract-definition.ts +7 -7
- package/src/contract-dsl.ts +11 -26
- package/src/contract-lowering.ts +2 -2
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.61",
|
|
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/
|
|
12
|
+
"@prisma-next/config": "0.5.0-dev.61",
|
|
13
|
+
"@prisma-next/framework-components": "0.5.0-dev.61",
|
|
14
|
+
"@prisma-next/contract-authoring": "0.5.0-dev.61",
|
|
15
|
+
"@prisma-next/sql-contract": "0.5.0-dev.61",
|
|
16
|
+
"@prisma-next/utils": "0.5.0-dev.61",
|
|
17
|
+
"@prisma-next/contract": "0.5.0-dev.61"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@types/pg": "8.16.0",
|
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,18 +1,18 @@
|
|
|
1
|
-
import type { ColumnDefault,
|
|
2
|
-
import type {
|
|
3
|
-
|
|
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
|
|
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
|
|
74
|
+
readonly executionDefaults?: ExecutionMutationDefaultPhases;
|
|
75
75
|
readonly many?: boolean;
|
|
76
76
|
}
|
|
77
77
|
|
package/src/contract-dsl.ts
CHANGED
|
@@ -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
|
|
7
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/contract-lowering.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColumnTypeDescriptor } from '@prisma-next/
|
|
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.
|
|
565
|
+
...(fieldState.executionDefaults ? { executionDefaults: fieldState.executionDefaults } : {}),
|
|
566
566
|
});
|
|
567
567
|
}
|
|
568
568
|
|