@prisma-next/sql-contract-ts 0.3.0-pr.99.6 → 0.3.0
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/LICENSE +201 -0
- package/README.md +206 -73
- package/dist/config-types.d.mts +8 -0
- package/dist/config-types.d.mts.map +1 -0
- package/dist/config-types.mjs +14 -0
- package/dist/config-types.mjs.map +1 -0
- package/dist/contract-builder.d.mts +769 -0
- package/dist/contract-builder.d.mts.map +1 -0
- package/dist/contract-builder.mjs +1288 -0
- package/dist/contract-builder.mjs.map +1 -0
- package/package.json +20 -17
- package/schemas/data-contract-sql-v1.json +189 -23
- package/src/authoring-helper-runtime.ts +139 -0
- package/src/authoring-type-utils.ts +168 -0
- package/src/build-contract.ts +463 -0
- package/src/composed-authoring-helpers.ts +256 -0
- package/src/config-types.ts +11 -0
- package/src/contract-builder.ts +232 -551
- package/src/contract-definition.ts +103 -0
- package/src/contract-dsl.ts +1492 -0
- package/src/contract-lowering.ts +703 -0
- package/src/contract-types.ts +534 -0
- package/src/contract-warnings.ts +242 -0
- package/src/exports/config-types.ts +2 -0
- package/src/exports/contract-builder.ts +23 -2
- package/dist/chunk-HTNUNGA2.js +0 -346
- package/dist/chunk-HTNUNGA2.js.map +0 -1
- package/dist/contract-builder.d.ts +0 -101
- package/dist/contract-builder.d.ts.map +0 -1
- package/dist/contract.d.ts +0 -50
- package/dist/contract.d.ts.map +0 -1
- package/dist/exports/contract-builder.d.ts +0 -3
- package/dist/exports/contract-builder.d.ts.map +0 -1
- package/dist/exports/contract-builder.js +0 -231
- package/dist/exports/contract-builder.js.map +0 -1
- package/dist/exports/contract.d.ts +0 -2
- package/dist/exports/contract.d.ts.map +0 -1
- package/dist/exports/contract.js +0 -9
- package/dist/exports/contract.js.map +0 -1
- package/src/contract.ts +0 -582
- package/src/exports/contract.ts +0 -1
|
@@ -0,0 +1,1492 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnDefault,
|
|
3
|
+
ColumnDefaultLiteralInputValue,
|
|
4
|
+
ExecutionMutationDefaultValue,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
6
|
+
import type {
|
|
7
|
+
ColumnTypeDescriptor,
|
|
8
|
+
ForeignKeyDefaultsState,
|
|
9
|
+
} from '@prisma-next/contract-authoring';
|
|
10
|
+
import type { AuthoringFieldPresetDescriptor } from '@prisma-next/framework-components/authoring';
|
|
11
|
+
import { instantiateAuthoringFieldPreset } from '@prisma-next/framework-components/authoring';
|
|
12
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
13
|
+
import type {
|
|
14
|
+
ExtensionPackRef,
|
|
15
|
+
FamilyPackRef,
|
|
16
|
+
TargetPackRef,
|
|
17
|
+
} from '@prisma-next/framework-components/components';
|
|
18
|
+
import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
19
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
20
|
+
import type { NamedConstraintSpec } from './authoring-type-utils';
|
|
21
|
+
|
|
22
|
+
export type NamingStrategy = 'identity' | 'snake_case';
|
|
23
|
+
|
|
24
|
+
export type NamingConfig = {
|
|
25
|
+
readonly tables?: NamingStrategy;
|
|
26
|
+
readonly columns?: NamingStrategy;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type NamedStorageTypeRef = string | StorageTypeInstance;
|
|
30
|
+
|
|
31
|
+
type NamedConstraintNameSpec<Name extends string = string> = {
|
|
32
|
+
readonly name: Name;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ScalarFieldState<
|
|
36
|
+
CodecId extends string = string,
|
|
37
|
+
TypeRef extends NamedStorageTypeRef | undefined = undefined,
|
|
38
|
+
Nullable extends boolean = boolean,
|
|
39
|
+
ColumnName extends string | undefined = string | undefined,
|
|
40
|
+
IdSpec extends NamedConstraintSpec | undefined = undefined,
|
|
41
|
+
UniqueSpec extends NamedConstraintSpec | undefined = undefined,
|
|
42
|
+
> = {
|
|
43
|
+
readonly kind: 'scalar';
|
|
44
|
+
readonly descriptor?: (ColumnTypeDescriptor & { readonly codecId: CodecId }) | undefined;
|
|
45
|
+
readonly typeRef?: TypeRef | undefined;
|
|
46
|
+
readonly nullable: Nullable;
|
|
47
|
+
readonly columnName?: ColumnName | undefined;
|
|
48
|
+
readonly default?: ColumnDefault | undefined;
|
|
49
|
+
readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
|
|
50
|
+
} & (IdSpec extends NamedConstraintSpec ? { readonly id: IdSpec } : { readonly id?: undefined }) &
|
|
51
|
+
(UniqueSpec extends NamedConstraintSpec
|
|
52
|
+
? { readonly unique: UniqueSpec }
|
|
53
|
+
: { readonly unique?: undefined });
|
|
54
|
+
|
|
55
|
+
type AnyScalarFieldState = {
|
|
56
|
+
readonly kind: 'scalar';
|
|
57
|
+
readonly descriptor?: (ColumnTypeDescriptor & { readonly codecId: string }) | undefined;
|
|
58
|
+
readonly typeRef?: NamedStorageTypeRef | undefined;
|
|
59
|
+
readonly nullable: boolean;
|
|
60
|
+
readonly columnName?: string | undefined;
|
|
61
|
+
readonly default?: ColumnDefault | undefined;
|
|
62
|
+
readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
|
|
63
|
+
readonly id?: NamedConstraintSpec | undefined;
|
|
64
|
+
readonly unique?: NamedConstraintSpec | undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type HasNamedConstraintId<State extends AnyScalarFieldState> =
|
|
68
|
+
State extends ScalarFieldState<
|
|
69
|
+
string,
|
|
70
|
+
NamedStorageTypeRef | undefined,
|
|
71
|
+
boolean,
|
|
72
|
+
string | undefined,
|
|
73
|
+
infer IdSpec,
|
|
74
|
+
NamedConstraintSpec | undefined
|
|
75
|
+
>
|
|
76
|
+
? IdSpec extends NamedConstraintSpec
|
|
77
|
+
? true
|
|
78
|
+
: false
|
|
79
|
+
: false;
|
|
80
|
+
|
|
81
|
+
type HasNamedConstraintUnique<State extends AnyScalarFieldState> =
|
|
82
|
+
State extends ScalarFieldState<
|
|
83
|
+
string,
|
|
84
|
+
NamedStorageTypeRef | undefined,
|
|
85
|
+
boolean,
|
|
86
|
+
string | undefined,
|
|
87
|
+
NamedConstraintSpec | undefined,
|
|
88
|
+
infer UniqueSpec
|
|
89
|
+
>
|
|
90
|
+
? UniqueSpec extends NamedConstraintSpec
|
|
91
|
+
? true
|
|
92
|
+
: false
|
|
93
|
+
: false;
|
|
94
|
+
|
|
95
|
+
type FieldSqlSpecForState<State extends AnyScalarFieldState> = {
|
|
96
|
+
readonly column?: string;
|
|
97
|
+
} & (HasNamedConstraintId<State> extends true
|
|
98
|
+
? { readonly id?: NamedConstraintNameSpec }
|
|
99
|
+
: Record<never, never>) &
|
|
100
|
+
(HasNamedConstraintUnique<State> extends true
|
|
101
|
+
? { readonly unique?: NamedConstraintNameSpec }
|
|
102
|
+
: Record<never, never>);
|
|
103
|
+
|
|
104
|
+
type ApplyFieldSqlSpec<
|
|
105
|
+
State extends AnyScalarFieldState,
|
|
106
|
+
Spec extends FieldSqlSpecForState<State>,
|
|
107
|
+
> = State extends ScalarFieldState<
|
|
108
|
+
infer CodecId,
|
|
109
|
+
infer TypeRef,
|
|
110
|
+
infer Nullable,
|
|
111
|
+
infer ColumnName,
|
|
112
|
+
infer IdSpec,
|
|
113
|
+
infer UniqueSpec
|
|
114
|
+
>
|
|
115
|
+
? ScalarFieldState<
|
|
116
|
+
CodecId,
|
|
117
|
+
TypeRef,
|
|
118
|
+
Nullable,
|
|
119
|
+
Spec extends { readonly column: infer NextColumn extends string } ? NextColumn : ColumnName,
|
|
120
|
+
Spec extends { readonly id: { readonly name: infer IdName extends string } }
|
|
121
|
+
? IdSpec extends NamedConstraintSpec
|
|
122
|
+
? NamedConstraintSpec<IdName>
|
|
123
|
+
: IdSpec
|
|
124
|
+
: IdSpec,
|
|
125
|
+
Spec extends { readonly unique: { readonly name: infer UniqueName extends string } }
|
|
126
|
+
? UniqueSpec extends NamedConstraintSpec
|
|
127
|
+
? NamedConstraintSpec<UniqueName>
|
|
128
|
+
: UniqueSpec
|
|
129
|
+
: UniqueSpec
|
|
130
|
+
>
|
|
131
|
+
: never;
|
|
132
|
+
|
|
133
|
+
export type GeneratedFieldSpec = {
|
|
134
|
+
readonly type: ColumnTypeDescriptor;
|
|
135
|
+
readonly typeParams?: Record<string, unknown>;
|
|
136
|
+
readonly generated: ExecutionMutationDefaultValue;
|
|
137
|
+
};
|
|
138
|
+
|
|
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
|
+
function toColumnDefault(value: ColumnDefaultLiteralInputValue | ColumnDefault): ColumnDefault {
|
|
146
|
+
if (isColumnDefault(value)) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
return { kind: 'literal', value };
|
|
150
|
+
}
|
|
151
|
+
|
|
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.
|
|
156
|
+
export class ScalarFieldBuilder<State extends AnyScalarFieldState = AnyScalarFieldState> {
|
|
157
|
+
declare readonly __state: State;
|
|
158
|
+
|
|
159
|
+
constructor(private readonly state: State) {}
|
|
160
|
+
|
|
161
|
+
optional(): ScalarFieldBuilder<
|
|
162
|
+
State extends ScalarFieldState<
|
|
163
|
+
infer CodecId,
|
|
164
|
+
infer TypeRef,
|
|
165
|
+
boolean,
|
|
166
|
+
infer ColumnName,
|
|
167
|
+
infer IdSpec,
|
|
168
|
+
infer UniqueSpec
|
|
169
|
+
>
|
|
170
|
+
? ScalarFieldState<CodecId, TypeRef, true, ColumnName, IdSpec, UniqueSpec>
|
|
171
|
+
: never
|
|
172
|
+
> {
|
|
173
|
+
return new ScalarFieldBuilder({
|
|
174
|
+
...this.state,
|
|
175
|
+
nullable: true,
|
|
176
|
+
} as unknown as State extends ScalarFieldState<
|
|
177
|
+
infer CodecId,
|
|
178
|
+
infer TypeRef,
|
|
179
|
+
boolean,
|
|
180
|
+
infer ColumnName,
|
|
181
|
+
infer IdSpec,
|
|
182
|
+
infer UniqueSpec
|
|
183
|
+
>
|
|
184
|
+
? ScalarFieldState<CodecId, TypeRef, true, ColumnName, IdSpec, UniqueSpec>
|
|
185
|
+
: never);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
column<ColumnName extends string>(
|
|
189
|
+
name: ColumnName,
|
|
190
|
+
): ScalarFieldBuilder<
|
|
191
|
+
State extends ScalarFieldState<
|
|
192
|
+
infer CodecId,
|
|
193
|
+
infer TypeRef,
|
|
194
|
+
infer Nullable,
|
|
195
|
+
string | undefined,
|
|
196
|
+
infer IdSpec,
|
|
197
|
+
infer UniqueSpec
|
|
198
|
+
>
|
|
199
|
+
? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, UniqueSpec>
|
|
200
|
+
: never
|
|
201
|
+
> {
|
|
202
|
+
return new ScalarFieldBuilder({
|
|
203
|
+
...this.state,
|
|
204
|
+
columnName: name,
|
|
205
|
+
} as unknown as State extends ScalarFieldState<
|
|
206
|
+
infer CodecId,
|
|
207
|
+
infer TypeRef,
|
|
208
|
+
infer Nullable,
|
|
209
|
+
string | undefined,
|
|
210
|
+
infer IdSpec,
|
|
211
|
+
infer UniqueSpec
|
|
212
|
+
>
|
|
213
|
+
? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, UniqueSpec>
|
|
214
|
+
: never);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
default(value: ColumnDefaultLiteralInputValue | ColumnDefault): ScalarFieldBuilder<State> {
|
|
218
|
+
return new ScalarFieldBuilder({
|
|
219
|
+
...this.state,
|
|
220
|
+
default: toColumnDefault(value),
|
|
221
|
+
}) as ScalarFieldBuilder<State>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
defaultSql(expression: string): ScalarFieldBuilder<State> {
|
|
225
|
+
return new ScalarFieldBuilder({
|
|
226
|
+
...this.state,
|
|
227
|
+
default: { kind: 'function', expression },
|
|
228
|
+
}) as ScalarFieldBuilder<State>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
id<const Name extends string | undefined = undefined>(
|
|
232
|
+
options?: NamedConstraintSpec<Name>,
|
|
233
|
+
): ScalarFieldBuilder<
|
|
234
|
+
State extends ScalarFieldState<
|
|
235
|
+
infer CodecId,
|
|
236
|
+
infer TypeRef,
|
|
237
|
+
infer Nullable,
|
|
238
|
+
infer ColumnName,
|
|
239
|
+
NamedConstraintSpec | undefined,
|
|
240
|
+
infer UniqueSpec
|
|
241
|
+
>
|
|
242
|
+
? ScalarFieldState<
|
|
243
|
+
CodecId,
|
|
244
|
+
TypeRef,
|
|
245
|
+
Nullable,
|
|
246
|
+
ColumnName,
|
|
247
|
+
NamedConstraintSpec<Name>,
|
|
248
|
+
UniqueSpec
|
|
249
|
+
>
|
|
250
|
+
: never
|
|
251
|
+
> {
|
|
252
|
+
return new ScalarFieldBuilder({
|
|
253
|
+
...this.state,
|
|
254
|
+
id: options?.name ? { name: options.name } : {},
|
|
255
|
+
} as unknown as State extends ScalarFieldState<
|
|
256
|
+
infer CodecId,
|
|
257
|
+
infer TypeRef,
|
|
258
|
+
infer Nullable,
|
|
259
|
+
infer ColumnName,
|
|
260
|
+
NamedConstraintSpec | undefined,
|
|
261
|
+
infer UniqueSpec
|
|
262
|
+
>
|
|
263
|
+
? ScalarFieldState<
|
|
264
|
+
CodecId,
|
|
265
|
+
TypeRef,
|
|
266
|
+
Nullable,
|
|
267
|
+
ColumnName,
|
|
268
|
+
NamedConstraintSpec<Name>,
|
|
269
|
+
UniqueSpec
|
|
270
|
+
>
|
|
271
|
+
: never);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
unique<const Name extends string | undefined = undefined>(
|
|
275
|
+
options?: NamedConstraintSpec<Name>,
|
|
276
|
+
): ScalarFieldBuilder<
|
|
277
|
+
State extends ScalarFieldState<
|
|
278
|
+
infer CodecId,
|
|
279
|
+
infer TypeRef,
|
|
280
|
+
infer Nullable,
|
|
281
|
+
infer ColumnName,
|
|
282
|
+
infer IdSpec,
|
|
283
|
+
NamedConstraintSpec | undefined
|
|
284
|
+
>
|
|
285
|
+
? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, NamedConstraintSpec<Name>>
|
|
286
|
+
: never
|
|
287
|
+
> {
|
|
288
|
+
return new ScalarFieldBuilder({
|
|
289
|
+
...this.state,
|
|
290
|
+
unique: options?.name ? { name: options.name } : {},
|
|
291
|
+
} as unknown as State extends ScalarFieldState<
|
|
292
|
+
infer CodecId,
|
|
293
|
+
infer TypeRef,
|
|
294
|
+
infer Nullable,
|
|
295
|
+
infer ColumnName,
|
|
296
|
+
infer IdSpec,
|
|
297
|
+
NamedConstraintSpec | undefined
|
|
298
|
+
>
|
|
299
|
+
? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, NamedConstraintSpec<Name>>
|
|
300
|
+
: never);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
sql<const Spec extends FieldSqlSpecForState<State>>(
|
|
304
|
+
spec: Spec,
|
|
305
|
+
): ScalarFieldBuilder<ApplyFieldSqlSpec<State, Spec>> {
|
|
306
|
+
const idSpec = 'id' in spec ? spec.id : undefined;
|
|
307
|
+
const uniqueSpec = 'unique' in spec ? spec.unique : undefined;
|
|
308
|
+
|
|
309
|
+
if (idSpec && !this.state.id) {
|
|
310
|
+
throw new Error('field.sql({ id }) requires an existing inline .id(...) declaration.');
|
|
311
|
+
}
|
|
312
|
+
if (uniqueSpec && !this.state.unique) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'field.sql({ unique }) requires an existing inline .unique(...) declaration.',
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return new ScalarFieldBuilder({
|
|
319
|
+
...this.state,
|
|
320
|
+
...(spec.column ? { columnName: spec.column } : {}),
|
|
321
|
+
...(idSpec ? { id: { name: idSpec.name } } : {}),
|
|
322
|
+
...(uniqueSpec ? { unique: { name: uniqueSpec.name } } : {}),
|
|
323
|
+
} as unknown as ApplyFieldSqlSpec<State, Spec>);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
build(): State {
|
|
327
|
+
return this.state;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function columnField<Descriptor extends ColumnTypeDescriptor>(
|
|
332
|
+
descriptor: Descriptor,
|
|
333
|
+
): ScalarFieldBuilder<ScalarFieldState<Descriptor['codecId'], undefined, false, undefined>> {
|
|
334
|
+
return new ScalarFieldBuilder({
|
|
335
|
+
kind: 'scalar',
|
|
336
|
+
descriptor,
|
|
337
|
+
nullable: false,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function generatedField<Descriptor extends ColumnTypeDescriptor>(
|
|
342
|
+
spec: GeneratedFieldSpec & { readonly type: Descriptor },
|
|
343
|
+
): ScalarFieldBuilder<ScalarFieldState<Descriptor['codecId'], undefined, false, undefined>> {
|
|
344
|
+
return new ScalarFieldBuilder({
|
|
345
|
+
kind: 'scalar',
|
|
346
|
+
descriptor: {
|
|
347
|
+
...spec.type,
|
|
348
|
+
...(spec.typeParams ? { typeParams: spec.typeParams } : {}),
|
|
349
|
+
},
|
|
350
|
+
nullable: false,
|
|
351
|
+
executionDefault: spec.generated,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function namedTypeField<TypeRef extends string>(
|
|
356
|
+
typeRef: TypeRef,
|
|
357
|
+
): ScalarFieldBuilder<ScalarFieldState<string, TypeRef, false, undefined>>;
|
|
358
|
+
function namedTypeField<TypeRef extends StorageTypeInstance>(
|
|
359
|
+
typeRef: TypeRef,
|
|
360
|
+
): ScalarFieldBuilder<ScalarFieldState<TypeRef['codecId'], TypeRef, false, undefined>>;
|
|
361
|
+
function namedTypeField(
|
|
362
|
+
typeRef: NamedStorageTypeRef,
|
|
363
|
+
): ScalarFieldBuilder<ScalarFieldState<string, NamedStorageTypeRef, false, undefined>> {
|
|
364
|
+
return new ScalarFieldBuilder({
|
|
365
|
+
kind: 'scalar',
|
|
366
|
+
typeRef,
|
|
367
|
+
nullable: false,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function buildFieldPreset(
|
|
372
|
+
descriptor: AuthoringFieldPresetDescriptor,
|
|
373
|
+
args: readonly unknown[],
|
|
374
|
+
namedConstraintOptions?: NamedConstraintSpec,
|
|
375
|
+
): ScalarFieldBuilder {
|
|
376
|
+
const preset = instantiateAuthoringFieldPreset(descriptor, args);
|
|
377
|
+
|
|
378
|
+
return new ScalarFieldBuilder({
|
|
379
|
+
kind: 'scalar',
|
|
380
|
+
descriptor: preset.descriptor,
|
|
381
|
+
nullable: preset.nullable,
|
|
382
|
+
...ifDefined('default', preset.default as ColumnDefault | undefined),
|
|
383
|
+
...ifDefined(
|
|
384
|
+
'executionDefault',
|
|
385
|
+
preset.executionDefault as ExecutionMutationDefaultValue | undefined,
|
|
386
|
+
),
|
|
387
|
+
...(preset.id
|
|
388
|
+
? {
|
|
389
|
+
id: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
|
|
390
|
+
}
|
|
391
|
+
: {}),
|
|
392
|
+
...(preset.unique
|
|
393
|
+
? {
|
|
394
|
+
unique: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
|
|
395
|
+
}
|
|
396
|
+
: {}),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
type RelationModelRefSource = 'string' | 'token' | 'lazyToken';
|
|
401
|
+
type TargetFieldRefSource = 'string' | 'token';
|
|
402
|
+
|
|
403
|
+
type EagerRelationModelName<
|
|
404
|
+
ModelName extends string = string,
|
|
405
|
+
Source extends Exclude<RelationModelRefSource, 'lazyToken'> = Exclude<
|
|
406
|
+
RelationModelRefSource,
|
|
407
|
+
'lazyToken'
|
|
408
|
+
>,
|
|
409
|
+
> = {
|
|
410
|
+
readonly kind: 'relationModelName';
|
|
411
|
+
readonly source: Source;
|
|
412
|
+
readonly modelName: ModelName;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
type LazyRelationModelName<ModelName extends string = string> = {
|
|
416
|
+
readonly kind: 'lazyRelationModelName';
|
|
417
|
+
readonly source: 'lazyToken';
|
|
418
|
+
readonly resolve: () => ModelName;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
type RelationModelSource<ModelName extends string = string> =
|
|
422
|
+
| EagerRelationModelName<ModelName>
|
|
423
|
+
| LazyRelationModelName<ModelName>;
|
|
424
|
+
|
|
425
|
+
type BelongsToRelation<
|
|
426
|
+
ToModel extends string = string,
|
|
427
|
+
FromField extends string | readonly string[] = string | readonly string[],
|
|
428
|
+
ToField extends string | readonly string[] = string | readonly string[],
|
|
429
|
+
SqlSpec extends BelongsToRelationSqlSpec | undefined = undefined,
|
|
430
|
+
> = {
|
|
431
|
+
readonly kind: 'belongsTo';
|
|
432
|
+
readonly toModel: RelationModelSource<ToModel>;
|
|
433
|
+
readonly from: FromField;
|
|
434
|
+
readonly to: ToField;
|
|
435
|
+
readonly sql?: SqlSpec;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
type HasManyRelation<
|
|
439
|
+
ToModel extends string = string,
|
|
440
|
+
ByField extends string | readonly string[] = string | readonly string[],
|
|
441
|
+
> = {
|
|
442
|
+
readonly kind: 'hasMany';
|
|
443
|
+
readonly toModel: RelationModelSource<ToModel>;
|
|
444
|
+
readonly by: ByField;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
type HasOneRelation<
|
|
448
|
+
ToModel extends string = string,
|
|
449
|
+
ByField extends string | readonly string[] = string | readonly string[],
|
|
450
|
+
> = {
|
|
451
|
+
readonly kind: 'hasOne';
|
|
452
|
+
readonly toModel: RelationModelSource<ToModel>;
|
|
453
|
+
readonly by: ByField;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
type ManyToManyRelation<
|
|
457
|
+
ToModel extends string = string,
|
|
458
|
+
ThroughModel extends string = string,
|
|
459
|
+
FromField extends string | readonly string[] = string | readonly string[],
|
|
460
|
+
ToField extends string | readonly string[] = string | readonly string[],
|
|
461
|
+
> = {
|
|
462
|
+
readonly kind: 'manyToMany';
|
|
463
|
+
readonly toModel: RelationModelSource<ToModel>;
|
|
464
|
+
readonly through: RelationModelSource<ThroughModel>;
|
|
465
|
+
readonly from: FromField;
|
|
466
|
+
readonly to: ToField;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export type RelationState =
|
|
470
|
+
| BelongsToRelation<
|
|
471
|
+
string,
|
|
472
|
+
string | readonly string[],
|
|
473
|
+
string | readonly string[],
|
|
474
|
+
BelongsToRelationSqlSpec | undefined
|
|
475
|
+
>
|
|
476
|
+
| HasManyRelation
|
|
477
|
+
| HasOneRelation
|
|
478
|
+
| ManyToManyRelation;
|
|
479
|
+
|
|
480
|
+
type AnyRelationState = RelationState;
|
|
481
|
+
type AnyRelationBuilder = RelationBuilder<AnyRelationState>;
|
|
482
|
+
|
|
483
|
+
type ApplyBelongsToRelationSqlSpec<
|
|
484
|
+
State extends RelationState,
|
|
485
|
+
SqlSpec extends BelongsToRelationSqlSpec,
|
|
486
|
+
> = State extends BelongsToRelation<
|
|
487
|
+
infer ToModel,
|
|
488
|
+
infer FromField,
|
|
489
|
+
infer ToField,
|
|
490
|
+
BelongsToRelationSqlSpec | undefined
|
|
491
|
+
>
|
|
492
|
+
? BelongsToRelation<ToModel, FromField, ToField, SqlSpec>
|
|
493
|
+
: never;
|
|
494
|
+
|
|
495
|
+
export class RelationBuilder<State extends RelationState = AnyRelationState> {
|
|
496
|
+
declare readonly __state: State;
|
|
497
|
+
|
|
498
|
+
constructor(private readonly state: State) {}
|
|
499
|
+
|
|
500
|
+
sql<const SqlSpec extends BelongsToRelationSqlSpec>(
|
|
501
|
+
this: State extends BelongsToRelation<
|
|
502
|
+
string,
|
|
503
|
+
string | readonly string[],
|
|
504
|
+
string | readonly string[],
|
|
505
|
+
BelongsToRelationSqlSpec | undefined
|
|
506
|
+
>
|
|
507
|
+
? RelationBuilder<State>
|
|
508
|
+
: never,
|
|
509
|
+
spec: SqlSpec,
|
|
510
|
+
): RelationBuilder<ApplyBelongsToRelationSqlSpec<State, SqlSpec>> {
|
|
511
|
+
if (this.state.kind !== 'belongsTo') {
|
|
512
|
+
throw new Error('relation.sql(...) is only supported for belongsTo relations.');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return new RelationBuilder({
|
|
516
|
+
...this.state,
|
|
517
|
+
sql: spec,
|
|
518
|
+
} as ApplyBelongsToRelationSqlSpec<State, SqlSpec>);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
build(): State {
|
|
522
|
+
return this.state;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export type ColumnRef<FieldName extends string = string> = {
|
|
527
|
+
readonly kind: 'columnRef';
|
|
528
|
+
readonly fieldName: FieldName;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
export type TargetFieldRef<
|
|
532
|
+
ModelName extends string = string,
|
|
533
|
+
FieldName extends string = string,
|
|
534
|
+
Source extends TargetFieldRefSource = TargetFieldRefSource,
|
|
535
|
+
> = {
|
|
536
|
+
readonly kind: 'targetFieldRef';
|
|
537
|
+
readonly source: Source;
|
|
538
|
+
readonly modelName: ModelName;
|
|
539
|
+
readonly fieldName: FieldName;
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
export type ModelTokenRefs<
|
|
543
|
+
ModelName extends string,
|
|
544
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
545
|
+
> = {
|
|
546
|
+
readonly [K in keyof Fields]: TargetFieldRef<ModelName, K & string>;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
type ConstraintOptions<Name extends string | undefined = string | undefined> = {
|
|
550
|
+
readonly name?: Name;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
type IndexOptions<Name extends string | undefined = string | undefined> =
|
|
554
|
+
ConstraintOptions<Name> & {
|
|
555
|
+
readonly using?: string;
|
|
556
|
+
readonly config?: Record<string, unknown>;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
type ForeignKeyOptions<Name extends string | undefined = string | undefined> =
|
|
560
|
+
ConstraintOptions<Name> & {
|
|
561
|
+
readonly onDelete?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
|
|
562
|
+
readonly onUpdate?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
|
|
563
|
+
readonly constraint?: boolean;
|
|
564
|
+
readonly index?: boolean;
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
type BelongsToRelationSqlSpec<Name extends string | undefined = string | undefined> = {
|
|
568
|
+
readonly fk?: ForeignKeyOptions<Name>;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
export type IdConstraint<
|
|
572
|
+
FieldNames extends readonly string[] = readonly string[],
|
|
573
|
+
Name extends string | undefined = string | undefined,
|
|
574
|
+
> = {
|
|
575
|
+
readonly kind: 'id';
|
|
576
|
+
readonly fields: FieldNames;
|
|
577
|
+
readonly name?: Name;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
export type UniqueConstraint<FieldNames extends readonly string[] = readonly string[]> = {
|
|
581
|
+
readonly kind: 'unique';
|
|
582
|
+
readonly fields: FieldNames;
|
|
583
|
+
readonly name?: string;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
export type IndexConstraint<
|
|
587
|
+
FieldNames extends readonly string[] = readonly string[],
|
|
588
|
+
Name extends string | undefined = string | undefined,
|
|
589
|
+
> = {
|
|
590
|
+
readonly kind: 'index';
|
|
591
|
+
readonly fields: FieldNames;
|
|
592
|
+
readonly name?: Name;
|
|
593
|
+
readonly using?: string;
|
|
594
|
+
readonly config?: Record<string, unknown>;
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
export type ForeignKeyConstraint<
|
|
598
|
+
SourceFieldNames extends readonly string[] = readonly string[],
|
|
599
|
+
TargetModelName extends string = string,
|
|
600
|
+
TargetFieldNames extends readonly string[] = readonly string[],
|
|
601
|
+
Name extends string | undefined = string | undefined,
|
|
602
|
+
> = {
|
|
603
|
+
readonly kind: 'fk';
|
|
604
|
+
readonly fields: SourceFieldNames;
|
|
605
|
+
readonly targetModel: TargetModelName;
|
|
606
|
+
readonly targetFields: TargetFieldNames;
|
|
607
|
+
readonly targetSource?: TargetFieldRefSource;
|
|
608
|
+
readonly name?: Name;
|
|
609
|
+
readonly onDelete?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
|
|
610
|
+
readonly onUpdate?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
|
|
611
|
+
readonly constraint?: boolean;
|
|
612
|
+
readonly index?: boolean;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
function normalizeFieldRefInput(input: ColumnRef | readonly ColumnRef[]): readonly string[] {
|
|
616
|
+
return (Array.isArray(input) ? input : [input]).map((ref) => ref.fieldName);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function normalizeTargetFieldRefInput(input: TargetFieldRef | readonly TargetFieldRef[]): {
|
|
620
|
+
readonly modelName: string;
|
|
621
|
+
readonly fieldNames: readonly string[];
|
|
622
|
+
readonly source: TargetFieldRefSource;
|
|
623
|
+
} {
|
|
624
|
+
const refs = Array.isArray(input) ? input : [input];
|
|
625
|
+
const [first] = refs;
|
|
626
|
+
if (!first) {
|
|
627
|
+
throw new Error('Expected at least one target ref');
|
|
628
|
+
}
|
|
629
|
+
if (refs.some((ref) => ref.modelName !== first.modelName)) {
|
|
630
|
+
throw new Error('All target refs in a foreign key must point to the same model');
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
modelName: first.modelName,
|
|
634
|
+
fieldNames: refs.map((ref) => ref.fieldName),
|
|
635
|
+
source: refs.some((ref) => ref.source === 'string') ? 'string' : 'token',
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function createConstraintsDsl() {
|
|
640
|
+
function ref<ModelName extends string, FieldName extends string>(
|
|
641
|
+
modelName: ModelName,
|
|
642
|
+
fieldName: FieldName,
|
|
643
|
+
): TargetFieldRef<ModelName, FieldName> {
|
|
644
|
+
return {
|
|
645
|
+
kind: 'targetFieldRef',
|
|
646
|
+
source: 'string',
|
|
647
|
+
modelName,
|
|
648
|
+
fieldName,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function id<FieldName extends string, Name extends string | undefined = undefined>(
|
|
653
|
+
field: ColumnRef<FieldName>,
|
|
654
|
+
options?: NamedConstraintSpec<Name>,
|
|
655
|
+
): IdConstraint<readonly [FieldName], Name>;
|
|
656
|
+
function id<FieldNames extends readonly string[], Name extends string | undefined = undefined>(
|
|
657
|
+
fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
|
|
658
|
+
options?: NamedConstraintSpec<Name>,
|
|
659
|
+
): IdConstraint<FieldNames, Name>;
|
|
660
|
+
function id(
|
|
661
|
+
fieldOrFields: ColumnRef | readonly ColumnRef[],
|
|
662
|
+
options?: NamedConstraintSpec,
|
|
663
|
+
): IdConstraint {
|
|
664
|
+
return {
|
|
665
|
+
kind: 'id',
|
|
666
|
+
fields: normalizeFieldRefInput(fieldOrFields),
|
|
667
|
+
...(options?.name ? { name: options.name } : {}),
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function unique<FieldName extends string>(
|
|
672
|
+
field: ColumnRef<FieldName>,
|
|
673
|
+
options?: ConstraintOptions,
|
|
674
|
+
): UniqueConstraint<readonly [FieldName]>;
|
|
675
|
+
function unique<FieldNames extends readonly string[]>(
|
|
676
|
+
fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
|
|
677
|
+
options?: ConstraintOptions,
|
|
678
|
+
): UniqueConstraint<FieldNames>;
|
|
679
|
+
function unique(
|
|
680
|
+
fieldOrFields: ColumnRef | readonly ColumnRef[],
|
|
681
|
+
options?: ConstraintOptions,
|
|
682
|
+
): UniqueConstraint {
|
|
683
|
+
return {
|
|
684
|
+
kind: 'unique',
|
|
685
|
+
fields: normalizeFieldRefInput(fieldOrFields),
|
|
686
|
+
...(options?.name ? { name: options.name } : {}),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function index<FieldName extends string, Name extends string | undefined = undefined>(
|
|
691
|
+
field: ColumnRef<FieldName>,
|
|
692
|
+
options?: IndexOptions<Name>,
|
|
693
|
+
): IndexConstraint<readonly [FieldName], Name>;
|
|
694
|
+
function index<FieldNames extends readonly string[], Name extends string | undefined = undefined>(
|
|
695
|
+
fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
|
|
696
|
+
options?: IndexOptions<Name>,
|
|
697
|
+
): IndexConstraint<FieldNames, Name>;
|
|
698
|
+
function index(
|
|
699
|
+
fieldOrFields: ColumnRef | readonly ColumnRef[],
|
|
700
|
+
options?: IndexOptions,
|
|
701
|
+
): IndexConstraint {
|
|
702
|
+
return {
|
|
703
|
+
kind: 'index',
|
|
704
|
+
fields: normalizeFieldRefInput(fieldOrFields),
|
|
705
|
+
...(options?.name ? { name: options.name } : {}),
|
|
706
|
+
...(options?.using ? { using: options.using } : {}),
|
|
707
|
+
...(options?.config ? { config: options.config } : {}),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function foreignKey<
|
|
712
|
+
SourceFieldName extends string,
|
|
713
|
+
TargetModelName extends string,
|
|
714
|
+
TargetFieldName extends string,
|
|
715
|
+
Name extends string | undefined = undefined,
|
|
716
|
+
>(
|
|
717
|
+
field: ColumnRef<SourceFieldName>,
|
|
718
|
+
target: TargetFieldRef<TargetModelName, TargetFieldName>,
|
|
719
|
+
options?: ForeignKeyOptions<Name>,
|
|
720
|
+
): ForeignKeyConstraint<
|
|
721
|
+
readonly [SourceFieldName],
|
|
722
|
+
TargetModelName,
|
|
723
|
+
readonly [TargetFieldName],
|
|
724
|
+
Name
|
|
725
|
+
>;
|
|
726
|
+
function foreignKey<
|
|
727
|
+
SourceFieldNames extends readonly string[],
|
|
728
|
+
TargetModelName extends string,
|
|
729
|
+
TargetFieldNames extends readonly string[],
|
|
730
|
+
Name extends string | undefined = undefined,
|
|
731
|
+
>(
|
|
732
|
+
fields: { readonly [K in keyof SourceFieldNames]: ColumnRef<SourceFieldNames[K] & string> },
|
|
733
|
+
target: {
|
|
734
|
+
readonly [K in keyof TargetFieldNames]: TargetFieldRef<
|
|
735
|
+
TargetModelName,
|
|
736
|
+
TargetFieldNames[K] & string
|
|
737
|
+
>;
|
|
738
|
+
},
|
|
739
|
+
options?: ForeignKeyOptions<Name>,
|
|
740
|
+
): ForeignKeyConstraint<SourceFieldNames, TargetModelName, TargetFieldNames, Name>;
|
|
741
|
+
function foreignKey(
|
|
742
|
+
fieldOrFields: ColumnRef | readonly ColumnRef[],
|
|
743
|
+
target: TargetFieldRef | readonly TargetFieldRef[],
|
|
744
|
+
options?: ForeignKeyOptions,
|
|
745
|
+
): ForeignKeyConstraint {
|
|
746
|
+
const normalizedTarget = normalizeTargetFieldRefInput(target);
|
|
747
|
+
return {
|
|
748
|
+
kind: 'fk',
|
|
749
|
+
fields: normalizeFieldRefInput(fieldOrFields),
|
|
750
|
+
targetModel: normalizedTarget.modelName,
|
|
751
|
+
targetFields: normalizedTarget.fieldNames,
|
|
752
|
+
targetSource: normalizedTarget.source,
|
|
753
|
+
...(options?.name ? { name: options.name } : {}),
|
|
754
|
+
...(options?.onDelete ? { onDelete: options.onDelete } : {}),
|
|
755
|
+
...(options?.onUpdate ? { onUpdate: options.onUpdate } : {}),
|
|
756
|
+
...(options?.constraint !== undefined ? { constraint: options.constraint } : {}),
|
|
757
|
+
...(options?.index !== undefined ? { index: options.index } : {}),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
ref,
|
|
763
|
+
id,
|
|
764
|
+
unique,
|
|
765
|
+
index,
|
|
766
|
+
foreignKey,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export type ConstraintsDsl = ReturnType<typeof createConstraintsDsl>;
|
|
771
|
+
|
|
772
|
+
export type ModelAttributesSpec = {
|
|
773
|
+
readonly id?: IdConstraint;
|
|
774
|
+
readonly uniques?: readonly UniqueConstraint[];
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
export type SqlStageSpec = {
|
|
778
|
+
readonly table?: string;
|
|
779
|
+
readonly indexes?: readonly IndexConstraint[];
|
|
780
|
+
readonly foreignKeys?: readonly ForeignKeyConstraint[];
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
type FieldRefs<Fields extends Record<string, ScalarFieldBuilder>> = {
|
|
784
|
+
readonly [K in keyof Fields]: ColumnRef<K & string>;
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
type AttributeContext<Fields extends Record<string, ScalarFieldBuilder>> = {
|
|
788
|
+
readonly fields: FieldRefs<Fields>;
|
|
789
|
+
readonly constraints: Pick<ConstraintsDsl, 'id' | 'unique'>;
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
type SqlContext<Fields extends Record<string, ScalarFieldBuilder>> = {
|
|
793
|
+
readonly cols: FieldRefs<Fields>;
|
|
794
|
+
readonly constraints: Pick<ConstraintsDsl, 'index' | 'foreignKey' | 'ref'>;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
function createFieldRefs<Fields extends Record<string, ScalarFieldBuilder>>(
|
|
798
|
+
fields: Fields,
|
|
799
|
+
): FieldRefs<Fields> {
|
|
800
|
+
const refs = {} as Record<string, ColumnRef>;
|
|
801
|
+
for (const fieldName of Object.keys(fields)) {
|
|
802
|
+
refs[fieldName] = { kind: 'columnRef', fieldName };
|
|
803
|
+
}
|
|
804
|
+
return refs as FieldRefs<Fields>;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function createModelTokenRefs<
|
|
808
|
+
ModelName extends string,
|
|
809
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
810
|
+
>(modelName: ModelName, fields: Fields): ModelTokenRefs<ModelName, Fields> {
|
|
811
|
+
const refs = {} as Record<string, TargetFieldRef>;
|
|
812
|
+
for (const fieldName of Object.keys(fields)) {
|
|
813
|
+
refs[fieldName] = {
|
|
814
|
+
kind: 'targetFieldRef',
|
|
815
|
+
source: 'token',
|
|
816
|
+
modelName,
|
|
817
|
+
fieldName,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
return refs as ModelTokenRefs<ModelName, Fields>;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
type StageInput<Context, Spec> = Spec | ((context: Context) => Spec);
|
|
824
|
+
|
|
825
|
+
function buildStageSpec<Context, Spec>(
|
|
826
|
+
stageInput: StageInput<Context, Spec>,
|
|
827
|
+
context: Context,
|
|
828
|
+
): Spec {
|
|
829
|
+
if (typeof stageInput === 'function') {
|
|
830
|
+
return (stageInput as (context: Context) => Spec)(context);
|
|
831
|
+
}
|
|
832
|
+
return stageInput;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function createAttributeConstraintsDsl(): AttributeContext<
|
|
836
|
+
Record<string, ScalarFieldBuilder>
|
|
837
|
+
>['constraints'] {
|
|
838
|
+
const constraints = createConstraintsDsl();
|
|
839
|
+
return {
|
|
840
|
+
id: constraints.id,
|
|
841
|
+
unique: constraints.unique,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function createSqlConstraintsDsl(): SqlContext<Record<string, ScalarFieldBuilder>>['constraints'] {
|
|
846
|
+
const constraints = createConstraintsDsl();
|
|
847
|
+
return {
|
|
848
|
+
index: constraints.index,
|
|
849
|
+
foreignKey: constraints.foreignKey,
|
|
850
|
+
ref: constraints.ref,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function createColumnRefs<Fields extends Record<string, ScalarFieldBuilder>>(
|
|
855
|
+
fields: Fields,
|
|
856
|
+
): SqlContext<Fields>['cols'] {
|
|
857
|
+
return createFieldRefs(fields);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
type StaticLiteralName<Name> = Name extends string ? (string extends Name ? never : Name) : never;
|
|
861
|
+
|
|
862
|
+
type NamedConstraintLiteralName<Constraint> = Constraint extends { readonly name?: infer Name }
|
|
863
|
+
? StaticLiteralName<Name>
|
|
864
|
+
: never;
|
|
865
|
+
|
|
866
|
+
type DuplicateLiteralNames<
|
|
867
|
+
Items extends readonly unknown[],
|
|
868
|
+
Seen extends string = never,
|
|
869
|
+
Duplicates extends string = never,
|
|
870
|
+
> = Items extends readonly [infer First, ...infer Rest extends readonly unknown[]]
|
|
871
|
+
? NamedConstraintLiteralName<First> extends infer Name extends string
|
|
872
|
+
? Name extends Seen
|
|
873
|
+
? DuplicateLiteralNames<Rest, Seen, Duplicates | Name>
|
|
874
|
+
: DuplicateLiteralNames<Rest, Seen | Name, Duplicates>
|
|
875
|
+
: DuplicateLiteralNames<Rest, Seen, Duplicates>
|
|
876
|
+
: Duplicates;
|
|
877
|
+
|
|
878
|
+
type InlineIdLiteralName<Fields extends Record<string, ScalarFieldBuilder>> = {
|
|
879
|
+
readonly [FieldName in keyof Fields]: FieldStateOf<Fields[FieldName]> extends {
|
|
880
|
+
readonly id: { readonly name?: infer Name };
|
|
881
|
+
}
|
|
882
|
+
? StaticLiteralName<Name>
|
|
883
|
+
: never;
|
|
884
|
+
}[keyof Fields];
|
|
885
|
+
|
|
886
|
+
type AttributeIdLiteralName<AttributesSpec extends ModelAttributesSpec | undefined> =
|
|
887
|
+
AttributesSpec extends {
|
|
888
|
+
readonly id?: { readonly name?: infer Name };
|
|
889
|
+
}
|
|
890
|
+
? StaticLiteralName<Name>
|
|
891
|
+
: never;
|
|
892
|
+
|
|
893
|
+
type ModelIdLiteralName<
|
|
894
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
895
|
+
AttributesSpec extends ModelAttributesSpec | undefined,
|
|
896
|
+
> = [AttributeIdLiteralName<AttributesSpec>] extends [never]
|
|
897
|
+
? InlineIdLiteralName<Fields>
|
|
898
|
+
: AttributeIdLiteralName<AttributesSpec>;
|
|
899
|
+
|
|
900
|
+
type SqlIndexes<SqlSpec extends SqlStageSpec> = SqlSpec extends {
|
|
901
|
+
readonly indexes?: infer Indexes extends readonly unknown[];
|
|
902
|
+
}
|
|
903
|
+
? Indexes
|
|
904
|
+
: readonly [];
|
|
905
|
+
|
|
906
|
+
type SqlForeignKeys<SqlSpec extends SqlStageSpec> = SqlSpec extends {
|
|
907
|
+
readonly foreignKeys?: infer ForeignKeys extends readonly unknown[];
|
|
908
|
+
}
|
|
909
|
+
? ForeignKeys
|
|
910
|
+
: readonly [];
|
|
911
|
+
|
|
912
|
+
type SqlNamedObjects<SqlSpec extends SqlStageSpec> = [
|
|
913
|
+
...SqlIndexes<SqlSpec>,
|
|
914
|
+
...SqlForeignKeys<SqlSpec>,
|
|
915
|
+
];
|
|
916
|
+
|
|
917
|
+
type ValidateSqlStageSpec<
|
|
918
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
919
|
+
AttributesSpec extends ModelAttributesSpec | undefined,
|
|
920
|
+
SqlSpec extends SqlStageSpec,
|
|
921
|
+
> = [DuplicateLiteralNames<SqlNamedObjects<SqlSpec>>] extends [never]
|
|
922
|
+
? [
|
|
923
|
+
Extract<
|
|
924
|
+
ModelIdLiteralName<Fields, AttributesSpec>,
|
|
925
|
+
NamedConstraintLiteralName<SqlNamedObjects<SqlSpec>[number]>
|
|
926
|
+
>,
|
|
927
|
+
] extends [never]
|
|
928
|
+
? SqlSpec
|
|
929
|
+
: never
|
|
930
|
+
: never;
|
|
931
|
+
|
|
932
|
+
type ValidateAttributesStageSpec<
|
|
933
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
934
|
+
SqlSpec extends SqlStageSpec | undefined,
|
|
935
|
+
AttributesSpec extends ModelAttributesSpec,
|
|
936
|
+
> = SqlSpec extends SqlStageSpec
|
|
937
|
+
? [
|
|
938
|
+
Extract<
|
|
939
|
+
ModelIdLiteralName<Fields, AttributesSpec>,
|
|
940
|
+
NamedConstraintLiteralName<SqlNamedObjects<SqlSpec>[number]>
|
|
941
|
+
>,
|
|
942
|
+
] extends [never]
|
|
943
|
+
? AttributesSpec
|
|
944
|
+
: never
|
|
945
|
+
: AttributesSpec;
|
|
946
|
+
|
|
947
|
+
function findDuplicateRelationName(
|
|
948
|
+
existingRelations: Record<string, AnyRelationBuilder>,
|
|
949
|
+
nextRelations: Record<string, AnyRelationBuilder>,
|
|
950
|
+
): string | undefined {
|
|
951
|
+
return Object.keys(nextRelations).find((relationName) =>
|
|
952
|
+
Object.hasOwn(existingRelations, relationName),
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export class ContractModelBuilder<
|
|
957
|
+
ModelName extends string | undefined,
|
|
958
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
959
|
+
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
|
|
960
|
+
AttributesSpec extends ModelAttributesSpec | undefined = undefined,
|
|
961
|
+
SqlSpec extends SqlStageSpec | undefined = undefined,
|
|
962
|
+
> {
|
|
963
|
+
declare readonly __name: ModelName;
|
|
964
|
+
declare readonly __fields: Fields;
|
|
965
|
+
declare readonly __relations: Relations;
|
|
966
|
+
declare readonly __attributes: AttributesSpec;
|
|
967
|
+
declare readonly __sql: SqlSpec;
|
|
968
|
+
readonly refs: ModelName extends string ? ModelTokenRefs<ModelName, Fields> : never;
|
|
969
|
+
|
|
970
|
+
constructor(
|
|
971
|
+
readonly stageOne: {
|
|
972
|
+
readonly modelName?: ModelName;
|
|
973
|
+
readonly fields: Fields;
|
|
974
|
+
readonly relations: Relations;
|
|
975
|
+
},
|
|
976
|
+
readonly attributesFactory?: StageInput<AttributeContext<Fields>, AttributesSpec>,
|
|
977
|
+
readonly sqlFactory?: StageInput<SqlContext<Fields>, SqlSpec>,
|
|
978
|
+
) {
|
|
979
|
+
this.refs = (
|
|
980
|
+
stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields) : undefined
|
|
981
|
+
) as ModelName extends string ? ModelTokenRefs<ModelName, Fields> : never;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
ref<FieldName extends keyof Fields & string>(
|
|
985
|
+
this: ModelName extends string
|
|
986
|
+
? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, SqlSpec>
|
|
987
|
+
: never,
|
|
988
|
+
fieldName: FieldName,
|
|
989
|
+
): TargetFieldRef<ModelName & string, FieldName> {
|
|
990
|
+
const modelName = this.stageOne.modelName;
|
|
991
|
+
if (!modelName) {
|
|
992
|
+
throw new Error('Model tokens require model("ModelName", ...) before calling .ref(...)');
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return {
|
|
996
|
+
kind: 'targetFieldRef',
|
|
997
|
+
source: 'token',
|
|
998
|
+
modelName,
|
|
999
|
+
fieldName,
|
|
1000
|
+
} as TargetFieldRef<ModelName & string, FieldName>;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
relations<const NextRelations extends Record<string, AnyRelationBuilder>>(
|
|
1004
|
+
relations: NextRelations,
|
|
1005
|
+
): ContractModelBuilder<ModelName, Fields, Relations & NextRelations, AttributesSpec, SqlSpec> {
|
|
1006
|
+
const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
|
|
1007
|
+
if (duplicateRelationName) {
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
`Model "${this.stageOne.modelName ?? '<anonymous>'}" already defines relation "${duplicateRelationName}".`,
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return new ContractModelBuilder(
|
|
1014
|
+
{
|
|
1015
|
+
...this.stageOne,
|
|
1016
|
+
relations: {
|
|
1017
|
+
...this.stageOne.relations,
|
|
1018
|
+
...relations,
|
|
1019
|
+
} as Relations & NextRelations,
|
|
1020
|
+
},
|
|
1021
|
+
this.attributesFactory,
|
|
1022
|
+
this.sqlFactory,
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
attributes<const NextAttributesSpec extends ModelAttributesSpec>(
|
|
1027
|
+
specOrFactory: StageInput<
|
|
1028
|
+
AttributeContext<Fields>,
|
|
1029
|
+
ValidateAttributesStageSpec<Fields, SqlSpec, NextAttributesSpec>
|
|
1030
|
+
>,
|
|
1031
|
+
): ContractModelBuilder<ModelName, Fields, Relations, NextAttributesSpec, SqlSpec> {
|
|
1032
|
+
return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
sql<const NextSqlSpec extends SqlStageSpec>(
|
|
1036
|
+
specOrFactory: StageInput<SqlContext<Fields>, NextSqlSpec>,
|
|
1037
|
+
): [ValidateSqlStageSpec<Fields, AttributesSpec, NextSqlSpec>] extends [never]
|
|
1038
|
+
? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, never>
|
|
1039
|
+
: 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).
|
|
1043
|
+
return new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory) as never;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
buildAttributesSpec(): AttributesSpec {
|
|
1047
|
+
if (!this.attributesFactory) {
|
|
1048
|
+
return undefined as AttributesSpec;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
return buildStageSpec(this.attributesFactory, {
|
|
1052
|
+
fields: createFieldRefs(this.stageOne.fields),
|
|
1053
|
+
constraints: createAttributeConstraintsDsl() as AttributeContext<Fields>['constraints'],
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
buildSqlSpec(): SqlSpec {
|
|
1058
|
+
if (!this.sqlFactory) {
|
|
1059
|
+
return undefined as SqlSpec;
|
|
1060
|
+
}
|
|
1061
|
+
return buildStageSpec(this.sqlFactory, {
|
|
1062
|
+
cols: createColumnRefs(this.stageOne.fields),
|
|
1063
|
+
constraints: createSqlConstraintsDsl() as SqlContext<Fields>['constraints'],
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
type NamedModelTokenShape<
|
|
1069
|
+
ModelName extends string = string,
|
|
1070
|
+
Fields extends Record<string, ScalarFieldBuilder> = Record<string, ScalarFieldBuilder>,
|
|
1071
|
+
> = {
|
|
1072
|
+
readonly stageOne: {
|
|
1073
|
+
readonly modelName?: ModelName;
|
|
1074
|
+
readonly fields: Fields;
|
|
1075
|
+
};
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
type AnyNamedModelToken = NamedModelTokenShape<string, Record<string, ScalarFieldBuilder>>;
|
|
1079
|
+
|
|
1080
|
+
type LazyNamedModelToken<Token extends AnyNamedModelToken = AnyNamedModelToken> = () => Token;
|
|
1081
|
+
|
|
1082
|
+
type RelationFieldSelection<FieldName extends string> = FieldName | readonly FieldName[];
|
|
1083
|
+
|
|
1084
|
+
type RelationModelName<Target> =
|
|
1085
|
+
Target extends NamedModelTokenShape<
|
|
1086
|
+
infer ModelName extends string,
|
|
1087
|
+
Record<string, ScalarFieldBuilder>
|
|
1088
|
+
>
|
|
1089
|
+
? ModelName
|
|
1090
|
+
: Target extends () => infer Token
|
|
1091
|
+
? RelationModelName<Token>
|
|
1092
|
+
: never;
|
|
1093
|
+
|
|
1094
|
+
type RelationModelFieldNames<Target> =
|
|
1095
|
+
Target extends NamedModelTokenShape<string, infer Fields>
|
|
1096
|
+
? keyof Fields & string
|
|
1097
|
+
: Target extends () => infer Token
|
|
1098
|
+
? RelationModelFieldNames<Token>
|
|
1099
|
+
: never;
|
|
1100
|
+
|
|
1101
|
+
function isLazyRelationModelName(value: unknown): value is LazyRelationModelName<string> {
|
|
1102
|
+
return (
|
|
1103
|
+
typeof value === 'object' &&
|
|
1104
|
+
value !== null &&
|
|
1105
|
+
'kind' in value &&
|
|
1106
|
+
(value as { kind?: unknown }).kind === 'lazyRelationModelName' &&
|
|
1107
|
+
'resolve' in value &&
|
|
1108
|
+
typeof (value as { resolve?: unknown }).resolve === 'function'
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function resolveNamedModelTokenName(token: {
|
|
1113
|
+
readonly stageOne: {
|
|
1114
|
+
readonly modelName?: string | undefined;
|
|
1115
|
+
};
|
|
1116
|
+
}): string {
|
|
1117
|
+
const modelName = token.stageOne.modelName;
|
|
1118
|
+
if (!modelName) {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
'Relation targets require named model tokens. Use model("ModelName", ...) before passing a token to rel.*(...).',
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return modelName;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function normalizeRelationModelSource<Token extends AnyNamedModelToken>(
|
|
1127
|
+
target: Token | LazyNamedModelToken<Token>,
|
|
1128
|
+
): RelationModelSource<RelationModelName<Token>>;
|
|
1129
|
+
function normalizeRelationModelSource<ToModel extends string>(
|
|
1130
|
+
target: ToModel,
|
|
1131
|
+
): RelationModelSource<ToModel>;
|
|
1132
|
+
function normalizeRelationModelSource(
|
|
1133
|
+
target: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1134
|
+
): RelationModelSource<string>;
|
|
1135
|
+
function normalizeRelationModelSource(
|
|
1136
|
+
target: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1137
|
+
): RelationModelSource<string> {
|
|
1138
|
+
if (typeof target === 'string') {
|
|
1139
|
+
return {
|
|
1140
|
+
kind: 'relationModelName',
|
|
1141
|
+
source: 'string',
|
|
1142
|
+
modelName: target,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (typeof target === 'function') {
|
|
1147
|
+
return {
|
|
1148
|
+
kind: 'lazyRelationModelName',
|
|
1149
|
+
source: 'lazyToken',
|
|
1150
|
+
resolve: () => resolveNamedModelTokenName(target()),
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return {
|
|
1155
|
+
kind: 'relationModelName',
|
|
1156
|
+
source: 'token',
|
|
1157
|
+
modelName: resolveNamedModelTokenName(target),
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export type ContractInput<
|
|
1162
|
+
Family extends FamilyPackRef<string> = FamilyPackRef<string>,
|
|
1163
|
+
Target extends TargetPackRef<'sql', string> = TargetPackRef<'sql', string>,
|
|
1164
|
+
Types extends Record<string, StorageTypeInstance> = Record<never, never>,
|
|
1165
|
+
Models extends Record<
|
|
1166
|
+
string,
|
|
1167
|
+
ContractModelBuilder<
|
|
1168
|
+
string | undefined,
|
|
1169
|
+
Record<string, ScalarFieldBuilder>,
|
|
1170
|
+
Record<string, AnyRelationBuilder>,
|
|
1171
|
+
ModelAttributesSpec | undefined,
|
|
1172
|
+
SqlStageSpec | undefined
|
|
1173
|
+
>
|
|
1174
|
+
> = Record<never, never>,
|
|
1175
|
+
ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
|
|
1176
|
+
Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,
|
|
1177
|
+
> = {
|
|
1178
|
+
readonly family: Family;
|
|
1179
|
+
readonly target: Target;
|
|
1180
|
+
readonly extensionPacks?: ExtensionPacks;
|
|
1181
|
+
readonly naming?: NamingConfig;
|
|
1182
|
+
readonly storageHash?: string;
|
|
1183
|
+
readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
|
|
1184
|
+
readonly capabilities?: Capabilities;
|
|
1185
|
+
readonly types?: Types;
|
|
1186
|
+
readonly models?: Models;
|
|
1187
|
+
readonly codecLookup?: CodecLookup;
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
export function model<
|
|
1191
|
+
const ModelName extends string,
|
|
1192
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
1193
|
+
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
|
|
1194
|
+
>(
|
|
1195
|
+
modelName: ModelName,
|
|
1196
|
+
input: {
|
|
1197
|
+
readonly fields: Fields;
|
|
1198
|
+
readonly relations?: Relations;
|
|
1199
|
+
},
|
|
1200
|
+
): ContractModelBuilder<ModelName, Fields, Relations>;
|
|
1201
|
+
|
|
1202
|
+
export function model<
|
|
1203
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
1204
|
+
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
|
|
1205
|
+
>(input: {
|
|
1206
|
+
readonly fields: Fields;
|
|
1207
|
+
readonly relations?: Relations;
|
|
1208
|
+
}): ContractModelBuilder<undefined, Fields, Relations>;
|
|
1209
|
+
|
|
1210
|
+
export function model<
|
|
1211
|
+
const ModelName extends string,
|
|
1212
|
+
Fields extends Record<string, ScalarFieldBuilder>,
|
|
1213
|
+
Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
|
|
1214
|
+
>(
|
|
1215
|
+
modelNameOrInput:
|
|
1216
|
+
| ModelName
|
|
1217
|
+
| {
|
|
1218
|
+
readonly fields: Fields;
|
|
1219
|
+
readonly relations?: Relations;
|
|
1220
|
+
},
|
|
1221
|
+
maybeInput?: {
|
|
1222
|
+
readonly fields: Fields;
|
|
1223
|
+
readonly relations?: Relations;
|
|
1224
|
+
},
|
|
1225
|
+
): ContractModelBuilder<ModelName | undefined, Fields, Relations> {
|
|
1226
|
+
const input = typeof modelNameOrInput === 'string' ? maybeInput : modelNameOrInput;
|
|
1227
|
+
|
|
1228
|
+
if (!input) {
|
|
1229
|
+
throw new Error('model("ModelName", ...) requires a model definition.');
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return new ContractModelBuilder({
|
|
1233
|
+
...(typeof modelNameOrInput === 'string' ? { modelName: modelNameOrInput } : {}),
|
|
1234
|
+
fields: input.fields,
|
|
1235
|
+
relations: (input.relations ?? {}) as Relations,
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function belongsTo<
|
|
1240
|
+
Token extends AnyNamedModelToken,
|
|
1241
|
+
FromField extends string | readonly string[],
|
|
1242
|
+
ToField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
|
|
1243
|
+
>(
|
|
1244
|
+
toModel: Token | LazyNamedModelToken<Token>,
|
|
1245
|
+
options: { readonly from: FromField; readonly to: ToField },
|
|
1246
|
+
): RelationBuilder<BelongsToRelation<RelationModelName<Token>, FromField, ToField>>;
|
|
1247
|
+
function belongsTo<
|
|
1248
|
+
ToModel extends string,
|
|
1249
|
+
FromField extends string | readonly string[],
|
|
1250
|
+
ToField extends string | readonly string[],
|
|
1251
|
+
>(
|
|
1252
|
+
toModel: ToModel,
|
|
1253
|
+
options: { readonly from: FromField; readonly to: ToField },
|
|
1254
|
+
): RelationBuilder<BelongsToRelation<ToModel, FromField, ToField>>;
|
|
1255
|
+
function belongsTo(
|
|
1256
|
+
toModel: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1257
|
+
options: {
|
|
1258
|
+
readonly from: string | readonly string[];
|
|
1259
|
+
readonly to: string | readonly string[];
|
|
1260
|
+
},
|
|
1261
|
+
): RelationBuilder<BelongsToRelation> {
|
|
1262
|
+
return new RelationBuilder({
|
|
1263
|
+
kind: 'belongsTo',
|
|
1264
|
+
toModel: normalizeRelationModelSource(toModel),
|
|
1265
|
+
from: options.from,
|
|
1266
|
+
to: options.to,
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function hasMany<
|
|
1271
|
+
Token extends AnyNamedModelToken,
|
|
1272
|
+
ByField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
|
|
1273
|
+
>(
|
|
1274
|
+
toModel: Token | LazyNamedModelToken<Token>,
|
|
1275
|
+
options: { readonly by: ByField },
|
|
1276
|
+
): RelationBuilder<HasManyRelation<RelationModelName<Token>, ByField>>;
|
|
1277
|
+
function hasMany<ToModel extends string, ByField extends string | readonly string[]>(
|
|
1278
|
+
toModel: ToModel,
|
|
1279
|
+
options: { readonly by: ByField },
|
|
1280
|
+
): RelationBuilder<HasManyRelation<ToModel, ByField>>;
|
|
1281
|
+
function hasMany(
|
|
1282
|
+
toModel: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1283
|
+
options: { readonly by: string | readonly string[] },
|
|
1284
|
+
): RelationBuilder<HasManyRelation> {
|
|
1285
|
+
return new RelationBuilder({
|
|
1286
|
+
kind: 'hasMany',
|
|
1287
|
+
toModel: normalizeRelationModelSource(toModel),
|
|
1288
|
+
by: options.by,
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function hasOne<
|
|
1293
|
+
Token extends AnyNamedModelToken,
|
|
1294
|
+
ByField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
|
|
1295
|
+
>(
|
|
1296
|
+
toModel: Token | LazyNamedModelToken<Token>,
|
|
1297
|
+
options: { readonly by: ByField },
|
|
1298
|
+
): RelationBuilder<HasOneRelation<RelationModelName<Token>, ByField>>;
|
|
1299
|
+
function hasOne<ToModel extends string, ByField extends string | readonly string[]>(
|
|
1300
|
+
toModel: ToModel,
|
|
1301
|
+
options: { readonly by: ByField },
|
|
1302
|
+
): RelationBuilder<HasOneRelation<ToModel, ByField>>;
|
|
1303
|
+
function hasOne(
|
|
1304
|
+
toModel: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1305
|
+
options: { readonly by: string | readonly string[] },
|
|
1306
|
+
): RelationBuilder<HasOneRelation> {
|
|
1307
|
+
return new RelationBuilder({
|
|
1308
|
+
kind: 'hasOne',
|
|
1309
|
+
toModel: normalizeRelationModelSource(toModel),
|
|
1310
|
+
by: options.by,
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function manyToMany<
|
|
1315
|
+
ToToken extends AnyNamedModelToken,
|
|
1316
|
+
ThroughToken extends AnyNamedModelToken,
|
|
1317
|
+
FromField extends RelationFieldSelection<RelationModelFieldNames<ThroughToken>>,
|
|
1318
|
+
ToField extends RelationFieldSelection<RelationModelFieldNames<ThroughToken>>,
|
|
1319
|
+
>(
|
|
1320
|
+
toModel: ToToken | LazyNamedModelToken<ToToken>,
|
|
1321
|
+
options: {
|
|
1322
|
+
readonly through: ThroughToken | LazyNamedModelToken<ThroughToken>;
|
|
1323
|
+
readonly from: FromField;
|
|
1324
|
+
readonly to: ToField;
|
|
1325
|
+
},
|
|
1326
|
+
): RelationBuilder<
|
|
1327
|
+
ManyToManyRelation<
|
|
1328
|
+
RelationModelName<ToToken>,
|
|
1329
|
+
RelationModelName<ThroughToken>,
|
|
1330
|
+
FromField,
|
|
1331
|
+
ToField
|
|
1332
|
+
>
|
|
1333
|
+
>;
|
|
1334
|
+
function manyToMany<
|
|
1335
|
+
ToModel extends string,
|
|
1336
|
+
ThroughModel extends string,
|
|
1337
|
+
FromField extends string | readonly string[],
|
|
1338
|
+
ToField extends string | readonly string[],
|
|
1339
|
+
>(
|
|
1340
|
+
toModel: ToModel,
|
|
1341
|
+
options: {
|
|
1342
|
+
readonly through: ThroughModel;
|
|
1343
|
+
readonly from: FromField;
|
|
1344
|
+
readonly to: ToField;
|
|
1345
|
+
},
|
|
1346
|
+
): RelationBuilder<ManyToManyRelation<ToModel, ThroughModel, FromField, ToField>>;
|
|
1347
|
+
function manyToMany(
|
|
1348
|
+
toModel: string | AnyNamedModelToken | LazyNamedModelToken,
|
|
1349
|
+
options: {
|
|
1350
|
+
readonly through: string | AnyNamedModelToken | LazyNamedModelToken;
|
|
1351
|
+
readonly from: string | readonly string[];
|
|
1352
|
+
readonly to: string | readonly string[];
|
|
1353
|
+
},
|
|
1354
|
+
): RelationBuilder<ManyToManyRelation> {
|
|
1355
|
+
return new RelationBuilder({
|
|
1356
|
+
kind: 'manyToMany',
|
|
1357
|
+
toModel: normalizeRelationModelSource(toModel),
|
|
1358
|
+
through: normalizeRelationModelSource(options.through),
|
|
1359
|
+
from: options.from,
|
|
1360
|
+
to: options.to,
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
export const rel = {
|
|
1365
|
+
belongsTo,
|
|
1366
|
+
hasMany,
|
|
1367
|
+
hasOne,
|
|
1368
|
+
manyToMany,
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
export const field = {
|
|
1372
|
+
column: columnField,
|
|
1373
|
+
generated: generatedField,
|
|
1374
|
+
namedType: namedTypeField,
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
export function isContractInput(value: unknown): value is ContractInput {
|
|
1378
|
+
if (typeof value !== 'object' || value === null || !('target' in value) || !('family' in value)) {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
const target = (value as { target: unknown }).target;
|
|
1382
|
+
const family = (value as { family: unknown }).family;
|
|
1383
|
+
return (
|
|
1384
|
+
typeof target === 'object' &&
|
|
1385
|
+
target !== null &&
|
|
1386
|
+
'kind' in target &&
|
|
1387
|
+
target.kind === 'target' &&
|
|
1388
|
+
typeof family === 'object' &&
|
|
1389
|
+
family !== null &&
|
|
1390
|
+
'kind' in family &&
|
|
1391
|
+
family.kind === 'family'
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function isRelationFieldArray(value: string | readonly string[]): value is readonly string[] {
|
|
1396
|
+
return Array.isArray(value);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
export function normalizeRelationFieldNames(value: string | readonly string[]): readonly string[] {
|
|
1400
|
+
if (isRelationFieldArray(value)) {
|
|
1401
|
+
return value;
|
|
1402
|
+
}
|
|
1403
|
+
return [value];
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
export function resolveRelationModelName(value: RelationModelSource<string>): string {
|
|
1407
|
+
if (isLazyRelationModelName(value)) {
|
|
1408
|
+
return value.resolve();
|
|
1409
|
+
}
|
|
1410
|
+
return value.modelName;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
export function applyNaming(name: string, strategy: NamingStrategy | undefined): string {
|
|
1414
|
+
if (!strategy || strategy === 'identity') {
|
|
1415
|
+
return name;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
let result = '';
|
|
1419
|
+
for (let index = 0; index < name.length; index += 1) {
|
|
1420
|
+
const char = name[index];
|
|
1421
|
+
if (!char) continue;
|
|
1422
|
+
const lower = char.toLowerCase();
|
|
1423
|
+
const isUpper = char !== lower;
|
|
1424
|
+
if (isUpper && index > 0) {
|
|
1425
|
+
const prev = name[index - 1];
|
|
1426
|
+
const next = name[index + 1];
|
|
1427
|
+
const prevIsLower = !!prev && prev === prev.toLowerCase();
|
|
1428
|
+
const nextIsLower = !!next && next === next.toLowerCase();
|
|
1429
|
+
if (prevIsLower || nextIsLower) {
|
|
1430
|
+
result += '_';
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
result += lower;
|
|
1434
|
+
}
|
|
1435
|
+
return result;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
export type FieldStateOf<T> = T extends ScalarFieldBuilder<infer State> ? State : never;
|
|
1439
|
+
export type RelationStateOf<T> = T extends RelationBuilder<infer State> ? State : never;
|
|
1440
|
+
|
|
1441
|
+
export type ModelFieldsOf<T> =
|
|
1442
|
+
T extends ContractModelBuilder<
|
|
1443
|
+
string | undefined,
|
|
1444
|
+
infer Fields,
|
|
1445
|
+
Record<string, AnyRelationBuilder>,
|
|
1446
|
+
ModelAttributesSpec | undefined,
|
|
1447
|
+
SqlStageSpec | undefined
|
|
1448
|
+
>
|
|
1449
|
+
? Fields
|
|
1450
|
+
: never;
|
|
1451
|
+
|
|
1452
|
+
export type ModelRelationsOf<T> =
|
|
1453
|
+
T extends ContractModelBuilder<
|
|
1454
|
+
string | undefined,
|
|
1455
|
+
Record<string, ScalarFieldBuilder>,
|
|
1456
|
+
infer Relations,
|
|
1457
|
+
ModelAttributesSpec | undefined,
|
|
1458
|
+
SqlStageSpec | undefined
|
|
1459
|
+
>
|
|
1460
|
+
? Relations
|
|
1461
|
+
: never;
|
|
1462
|
+
|
|
1463
|
+
export type ModelAttributesOf<T> =
|
|
1464
|
+
T extends ContractModelBuilder<
|
|
1465
|
+
string | undefined,
|
|
1466
|
+
Record<string, ScalarFieldBuilder>,
|
|
1467
|
+
Record<string, AnyRelationBuilder>,
|
|
1468
|
+
infer AttributesSpec,
|
|
1469
|
+
SqlStageSpec | undefined
|
|
1470
|
+
>
|
|
1471
|
+
? AttributesSpec
|
|
1472
|
+
: undefined;
|
|
1473
|
+
|
|
1474
|
+
export type ModelSqlOf<T> =
|
|
1475
|
+
T extends ContractModelBuilder<
|
|
1476
|
+
string | undefined,
|
|
1477
|
+
Record<string, ScalarFieldBuilder>,
|
|
1478
|
+
Record<string, AnyRelationBuilder>,
|
|
1479
|
+
ModelAttributesSpec | undefined,
|
|
1480
|
+
infer SqlSpec
|
|
1481
|
+
>
|
|
1482
|
+
? SqlSpec
|
|
1483
|
+
: undefined;
|
|
1484
|
+
|
|
1485
|
+
export type IdFieldNames<T> =
|
|
1486
|
+
T extends IdConstraint<infer FieldNames> ? FieldNames : readonly string[];
|
|
1487
|
+
|
|
1488
|
+
export type AttributeStageIdFieldNames<T> = T extends { readonly id?: infer I }
|
|
1489
|
+
? I extends IdConstraint
|
|
1490
|
+
? IdFieldNames<I>
|
|
1491
|
+
: undefined
|
|
1492
|
+
: undefined;
|