@prisma-next/emitter 0.3.0-dev.135 → 0.3.0-dev.146
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 +33 -30
- package/dist/domain-type-generation.d.mts +14 -2
- package/dist/domain-type-generation.d.mts.map +1 -1
- package/dist/domain-type-generation.mjs +139 -1
- package/dist/domain-type-generation.mjs.map +1 -1
- package/dist/exports/index.d.mts +39 -4
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +104 -3
- package/dist/exports/index.mjs.map +1 -0
- package/dist/test/utils.d.mts +16 -14
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +12 -51
- package/dist/test/utils.mjs.map +1 -1
- package/dist/type-expression-safety-7_1tfJXA.mjs +8 -0
- package/dist/type-expression-safety-7_1tfJXA.mjs.map +1 -0
- package/dist/type-expression-safety.d.mts +5 -0
- package/dist/type-expression-safety.d.mts.map +1 -0
- package/dist/type-expression-safety.mjs +3 -0
- package/package.json +12 -6
- package/src/domain-type-generation.ts +227 -1
- package/src/emit-types.ts +23 -0
- package/src/emit.ts +68 -0
- package/src/exports/index.ts +4 -9
- package/src/generate-contract-dts.ts +116 -0
- package/src/type-expression-safety.ts +3 -0
- package/test/canonicalization.test.ts +25 -28
- package/test/domain-type-generation.test.ts +509 -2
- package/test/emitter.integration.test.ts +81 -139
- package/test/emitter.roundtrip.test.ts +114 -184
- package/test/emitter.test.ts +82 -467
- package/test/hashing.test.ts +8 -30
- package/test/mock-spi.ts +18 -0
- package/test/type-expression-safety.test.ts +34 -0
- package/test/utils.ts +30 -156
- package/src/target-family.ts +0 -7
- package/test/factories.test.ts +0 -274
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Contract, ContractModel, ContractValueObject } from '@prisma-next/contract/types';
|
|
2
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
3
|
+
import type {
|
|
4
|
+
EmissionSpi,
|
|
5
|
+
GenerateContractTypesOptions,
|
|
6
|
+
TypesImportSpec,
|
|
7
|
+
} from '@prisma-next/framework-components/emission';
|
|
8
|
+
import {
|
|
9
|
+
deduplicateImports,
|
|
10
|
+
generateCodecTypeIntersection,
|
|
11
|
+
generateFieldOutputTypesMap,
|
|
12
|
+
generateHashTypeAliases,
|
|
13
|
+
generateImportLines,
|
|
14
|
+
generateModelsType,
|
|
15
|
+
generateRootsType,
|
|
16
|
+
generateValueObjectsDescriptorType,
|
|
17
|
+
generateValueObjectTypeAliases,
|
|
18
|
+
serializeExecutionType,
|
|
19
|
+
serializeValue,
|
|
20
|
+
} from './domain-type-generation';
|
|
21
|
+
|
|
22
|
+
export function generateContractDts(
|
|
23
|
+
contract: Contract,
|
|
24
|
+
emitter: EmissionSpi,
|
|
25
|
+
codecTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
26
|
+
operationTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
27
|
+
hashes: {
|
|
28
|
+
readonly storageHash: string;
|
|
29
|
+
readonly executionHash?: string;
|
|
30
|
+
readonly profileHash: string;
|
|
31
|
+
},
|
|
32
|
+
options?: GenerateContractTypesOptions,
|
|
33
|
+
codecLookup?: CodecLookup,
|
|
34
|
+
): string {
|
|
35
|
+
const allImports: TypesImportSpec[] = [...codecTypeImports, ...operationTypeImports];
|
|
36
|
+
if (options?.queryOperationTypeImports) {
|
|
37
|
+
allImports.push(...options.queryOperationTypeImports);
|
|
38
|
+
}
|
|
39
|
+
const uniqueImports = deduplicateImports(allImports);
|
|
40
|
+
const importLines = generateImportLines(uniqueImports);
|
|
41
|
+
|
|
42
|
+
const familyImportLines = emitter.getFamilyImports();
|
|
43
|
+
|
|
44
|
+
const hashAliases = generateHashTypeAliases(hashes);
|
|
45
|
+
|
|
46
|
+
const codecTypes = generateCodecTypeIntersection(codecTypeImports, 'CodecTypes');
|
|
47
|
+
const operationTypes = generateCodecTypeIntersection(operationTypeImports, 'OperationTypes');
|
|
48
|
+
|
|
49
|
+
const familyTypeAliases = emitter.getFamilyTypeAliases(options);
|
|
50
|
+
|
|
51
|
+
const typeMapsExpr = emitter.getTypeMapsExpression();
|
|
52
|
+
|
|
53
|
+
const storageType = emitter.generateStorageType(contract, 'StorageHash');
|
|
54
|
+
|
|
55
|
+
const modelsType = generateModelsType(
|
|
56
|
+
contract.models as Record<string, ContractModel>,
|
|
57
|
+
(name, model) => emitter.generateModelStorageType(name, model),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const rootsType = generateRootsType(contract.roots);
|
|
61
|
+
|
|
62
|
+
const valueObjects = contract.valueObjects as Record<string, ContractValueObject> | undefined;
|
|
63
|
+
const valueObjectTypeAliases = generateValueObjectTypeAliases(valueObjects);
|
|
64
|
+
const valueObjectsDescriptor = generateValueObjectsDescriptorType(valueObjects);
|
|
65
|
+
|
|
66
|
+
const executionClause =
|
|
67
|
+
contract.execution !== undefined
|
|
68
|
+
? `\n readonly execution: ${serializeExecutionType(contract.execution)};`
|
|
69
|
+
: '';
|
|
70
|
+
|
|
71
|
+
const fieldOutputTypesMap = generateFieldOutputTypesMap(
|
|
72
|
+
contract.models as Record<string, ContractModel> | undefined,
|
|
73
|
+
codecLookup,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const contractWrapper = emitter.getContractWrapper('ContractBase', 'TypeMaps');
|
|
77
|
+
|
|
78
|
+
return `// ⚠️ GENERATED FILE - DO NOT EDIT
|
|
79
|
+
// This file is automatically generated by 'prisma-next contract emit'.
|
|
80
|
+
// To regenerate, run: prisma-next contract emit
|
|
81
|
+
${importLines.join('\n')}
|
|
82
|
+
|
|
83
|
+
${familyImportLines.join('\n')}
|
|
84
|
+
import type {
|
|
85
|
+
Contract as ContractType,
|
|
86
|
+
ExecutionHashBase,
|
|
87
|
+
ProfileHashBase,
|
|
88
|
+
StorageHashBase,
|
|
89
|
+
} from '@prisma-next/contract/types';
|
|
90
|
+
|
|
91
|
+
${hashAliases}
|
|
92
|
+
|
|
93
|
+
export type CodecTypes = ${codecTypes};
|
|
94
|
+
export type OperationTypes = ${operationTypes};
|
|
95
|
+
${familyTypeAliases}
|
|
96
|
+
${valueObjectTypeAliases}
|
|
97
|
+
export type FieldOutputTypes = ${fieldOutputTypesMap};
|
|
98
|
+
export type TypeMaps = ${typeMapsExpr};
|
|
99
|
+
|
|
100
|
+
type ContractBase = ContractType<
|
|
101
|
+
${storageType},
|
|
102
|
+
${modelsType}
|
|
103
|
+
> & {
|
|
104
|
+
readonly target: ${serializeValue(contract.target)};
|
|
105
|
+
readonly targetFamily: ${serializeValue(contract.targetFamily)};
|
|
106
|
+
readonly roots: ${rootsType};
|
|
107
|
+
readonly capabilities: ${serializeValue(contract.capabilities)};
|
|
108
|
+
readonly extensionPacks: ${serializeValue(contract.extensionPacks)};${executionClause}
|
|
109
|
+
readonly meta: ${serializeValue(contract.meta)};
|
|
110
|
+
${valueObjects ? `readonly valueObjects: ${valueObjectsDescriptor};` : ''}
|
|
111
|
+
readonly profileHash: ProfileHash;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
${contractWrapper}
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { canonicalizeContract } from '@prisma-next/
|
|
1
|
+
import { canonicalizeContract } from '@prisma-next/contract/hashing';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import { createTestContract } from './utils';
|
|
4
4
|
|
|
5
5
|
describe('canonicalization', () => {
|
|
6
6
|
it('orders top-level sections correctly', () => {
|
|
7
|
-
const ir =
|
|
7
|
+
const ir = createTestContract({
|
|
8
8
|
capabilities: { postgres: { jsonAgg: true } },
|
|
9
9
|
meta: { emitterVersion: 'test' },
|
|
10
10
|
});
|
|
@@ -29,8 +29,8 @@ describe('canonicalization', () => {
|
|
|
29
29
|
expect(capabilitiesIndex).toBeLessThan(metaIndex);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('
|
|
33
|
-
const ir =
|
|
32
|
+
it('preserves nullable false on columns', () => {
|
|
33
|
+
const ir = createTestContract({
|
|
34
34
|
storage: {
|
|
35
35
|
tables: {
|
|
36
36
|
user: {
|
|
@@ -51,15 +51,12 @@ describe('canonicalization', () => {
|
|
|
51
51
|
const columns = user['columns'] as Record<string, unknown>;
|
|
52
52
|
const id = columns['id'] as Record<string, unknown>;
|
|
53
53
|
const email = columns['email'] as Record<string, unknown>;
|
|
54
|
-
expect(id['nullable']).
|
|
54
|
+
expect(id['nullable']).toBe(false);
|
|
55
55
|
expect(email['nullable']).toBe(true);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it
|
|
59
|
-
|
|
60
|
-
{ nullable: undefined },
|
|
61
|
-
])('omits nullable:false for columns with defaults (nullable=$nullable)', ({ nullable }) => {
|
|
62
|
-
const ir = createContractIR({
|
|
58
|
+
it('preserves nullable:false for columns with defaults', () => {
|
|
59
|
+
const ir = createTestContract({
|
|
63
60
|
storage: {
|
|
64
61
|
tables: {
|
|
65
62
|
user: {
|
|
@@ -67,7 +64,7 @@ describe('canonicalization', () => {
|
|
|
67
64
|
created_at: {
|
|
68
65
|
codecId: 'pg/timestamptz@1',
|
|
69
66
|
nativeType: 'timestamptz',
|
|
70
|
-
nullable,
|
|
67
|
+
nullable: false,
|
|
71
68
|
default: { kind: 'function', expression: 'now()' },
|
|
72
69
|
},
|
|
73
70
|
updated_at: {
|
|
@@ -89,12 +86,12 @@ describe('canonicalization', () => {
|
|
|
89
86
|
const columns = user['columns'] as Record<string, unknown>;
|
|
90
87
|
const createdAt = columns['created_at'] as Record<string, unknown>;
|
|
91
88
|
const updatedAt = columns['updated_at'] as Record<string, unknown>;
|
|
92
|
-
expect(createdAt['nullable']).
|
|
89
|
+
expect(createdAt['nullable']).toBe(false);
|
|
93
90
|
expect(updatedAt['nullable']).toBe(true);
|
|
94
91
|
});
|
|
95
92
|
|
|
96
93
|
it('preserves nullable:true for columns with defaults', () => {
|
|
97
|
-
const ir =
|
|
94
|
+
const ir = createTestContract({
|
|
98
95
|
storage: {
|
|
99
96
|
tables: {
|
|
100
97
|
user: {
|
|
@@ -123,7 +120,7 @@ describe('canonicalization', () => {
|
|
|
123
120
|
});
|
|
124
121
|
|
|
125
122
|
it('omits empty arrays and objects except required ones', () => {
|
|
126
|
-
const ir =
|
|
123
|
+
const ir = createTestContract();
|
|
127
124
|
|
|
128
125
|
const result = canonicalizeContract(ir);
|
|
129
126
|
const parsed = JSON.parse(result);
|
|
@@ -143,7 +140,7 @@ describe('canonicalization', () => {
|
|
|
143
140
|
});
|
|
144
141
|
|
|
145
142
|
it('preserves semantic array order for column lists', () => {
|
|
146
|
-
const ir =
|
|
143
|
+
const ir = createTestContract({
|
|
147
144
|
storage: {
|
|
148
145
|
tables: {
|
|
149
146
|
user: {
|
|
@@ -161,7 +158,7 @@ describe('canonicalization', () => {
|
|
|
161
158
|
|
|
162
159
|
const result1 = canonicalizeContract(ir);
|
|
163
160
|
|
|
164
|
-
const ir2 =
|
|
161
|
+
const ir2 = createTestContract({
|
|
165
162
|
storage: {
|
|
166
163
|
tables: {
|
|
167
164
|
user: {
|
|
@@ -183,7 +180,7 @@ describe('canonicalization', () => {
|
|
|
183
180
|
});
|
|
184
181
|
|
|
185
182
|
it('sorts indexes by canonical name', () => {
|
|
186
|
-
const ir =
|
|
183
|
+
const ir = createTestContract({
|
|
187
184
|
storage: {
|
|
188
185
|
tables: {
|
|
189
186
|
user: {
|
|
@@ -210,7 +207,7 @@ describe('canonicalization', () => {
|
|
|
210
207
|
});
|
|
211
208
|
|
|
212
209
|
it('sorts uniques by canonical name', () => {
|
|
213
|
-
const ir =
|
|
210
|
+
const ir = createTestContract({
|
|
214
211
|
storage: {
|
|
215
212
|
tables: {
|
|
216
213
|
user: {
|
|
@@ -239,7 +236,7 @@ describe('canonicalization', () => {
|
|
|
239
236
|
});
|
|
240
237
|
|
|
241
238
|
it('preserves column order in composite unique constraints', () => {
|
|
242
|
-
const ir =
|
|
239
|
+
const ir = createTestContract({
|
|
243
240
|
storage: {
|
|
244
241
|
tables: {
|
|
245
242
|
user: {
|
|
@@ -264,7 +261,7 @@ describe('canonicalization', () => {
|
|
|
264
261
|
});
|
|
265
262
|
|
|
266
263
|
it('sorts nested object keys lexicographically', () => {
|
|
267
|
-
const ir =
|
|
264
|
+
const ir = createTestContract({
|
|
268
265
|
storage: {
|
|
269
266
|
tables: {
|
|
270
267
|
user: {
|
|
@@ -290,7 +287,7 @@ describe('canonicalization', () => {
|
|
|
290
287
|
|
|
291
288
|
describe('Mongo storage.collections preservation', () => {
|
|
292
289
|
it('preserves empty storage.collections container', () => {
|
|
293
|
-
const ir =
|
|
290
|
+
const ir = createTestContract({
|
|
294
291
|
targetFamily: 'mongo',
|
|
295
292
|
target: 'mongo',
|
|
296
293
|
storage: { collections: {} },
|
|
@@ -303,7 +300,7 @@ describe('canonicalization', () => {
|
|
|
303
300
|
});
|
|
304
301
|
|
|
305
302
|
it('preserves collection entries with empty payloads', () => {
|
|
306
|
-
const ir =
|
|
303
|
+
const ir = createTestContract({
|
|
307
304
|
targetFamily: 'mongo',
|
|
308
305
|
target: 'mongo',
|
|
309
306
|
storage: { collections: { users: {}, posts: {} } },
|
|
@@ -318,7 +315,7 @@ describe('canonicalization', () => {
|
|
|
318
315
|
});
|
|
319
316
|
|
|
320
317
|
it('sorts collection names lexicographically', () => {
|
|
321
|
-
const ir =
|
|
318
|
+
const ir = createTestContract({
|
|
322
319
|
targetFamily: 'mongo',
|
|
323
320
|
target: 'mongo',
|
|
324
321
|
storage: { collections: { zebras: {}, apples: {}, mangoes: {} } },
|
|
@@ -332,12 +329,12 @@ describe('canonicalization', () => {
|
|
|
332
329
|
});
|
|
333
330
|
|
|
334
331
|
it('produces different hashes when collections differ', () => {
|
|
335
|
-
const ir1 =
|
|
332
|
+
const ir1 = createTestContract({
|
|
336
333
|
targetFamily: 'mongo',
|
|
337
334
|
target: 'mongo',
|
|
338
335
|
storage: { collections: { users: {} } },
|
|
339
336
|
});
|
|
340
|
-
const ir2 =
|
|
337
|
+
const ir2 = createTestContract({
|
|
341
338
|
targetFamily: 'mongo',
|
|
342
339
|
target: 'mongo',
|
|
343
340
|
storage: { collections: { users: {}, posts: {} } },
|
|
@@ -350,7 +347,7 @@ describe('canonicalization', () => {
|
|
|
350
347
|
});
|
|
351
348
|
|
|
352
349
|
it('sorts extension namespaces lexicographically', () => {
|
|
353
|
-
const ir =
|
|
350
|
+
const ir = createTestContract({
|
|
354
351
|
extensionPacks: {
|
|
355
352
|
pgvector: { version: '0.0.1' },
|
|
356
353
|
postgres: { version: '0.0.1' },
|
|
@@ -366,7 +363,7 @@ describe('canonicalization', () => {
|
|
|
366
363
|
});
|
|
367
364
|
|
|
368
365
|
it('omits generated false', () => {
|
|
369
|
-
const ir =
|
|
366
|
+
const ir = createTestContract({
|
|
370
367
|
storage: {
|
|
371
368
|
tables: {
|
|
372
369
|
user: {
|