@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.
Files changed (36) hide show
  1. package/README.md +33 -30
  2. package/dist/domain-type-generation.d.mts +14 -2
  3. package/dist/domain-type-generation.d.mts.map +1 -1
  4. package/dist/domain-type-generation.mjs +139 -1
  5. package/dist/domain-type-generation.mjs.map +1 -1
  6. package/dist/exports/index.d.mts +39 -4
  7. package/dist/exports/index.d.mts.map +1 -0
  8. package/dist/exports/index.mjs +104 -3
  9. package/dist/exports/index.mjs.map +1 -0
  10. package/dist/test/utils.d.mts +16 -14
  11. package/dist/test/utils.d.mts.map +1 -1
  12. package/dist/test/utils.mjs +12 -51
  13. package/dist/test/utils.mjs.map +1 -1
  14. package/dist/type-expression-safety-7_1tfJXA.mjs +8 -0
  15. package/dist/type-expression-safety-7_1tfJXA.mjs.map +1 -0
  16. package/dist/type-expression-safety.d.mts +5 -0
  17. package/dist/type-expression-safety.d.mts.map +1 -0
  18. package/dist/type-expression-safety.mjs +3 -0
  19. package/package.json +12 -6
  20. package/src/domain-type-generation.ts +227 -1
  21. package/src/emit-types.ts +23 -0
  22. package/src/emit.ts +68 -0
  23. package/src/exports/index.ts +4 -9
  24. package/src/generate-contract-dts.ts +116 -0
  25. package/src/type-expression-safety.ts +3 -0
  26. package/test/canonicalization.test.ts +25 -28
  27. package/test/domain-type-generation.test.ts +509 -2
  28. package/test/emitter.integration.test.ts +81 -139
  29. package/test/emitter.roundtrip.test.ts +114 -184
  30. package/test/emitter.test.ts +82 -467
  31. package/test/hashing.test.ts +8 -30
  32. package/test/mock-spi.ts +18 -0
  33. package/test/type-expression-safety.test.ts +34 -0
  34. package/test/utils.ts +30 -156
  35. package/src/target-family.ts +0 -7
  36. 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
+ }
@@ -0,0 +1,3 @@
1
+ export function isSafeTypeExpression(expr: string): boolean {
2
+ return !/import\s*\(|require\s*\(|declare\s|export\s|eval\s*\(/.test(expr);
3
+ }
@@ -1,10 +1,10 @@
1
- import { canonicalizeContract } from '@prisma-next/core-control-plane/emission';
1
+ import { canonicalizeContract } from '@prisma-next/contract/hashing';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { createContractIR } from './utils';
3
+ import { createTestContract } from './utils';
4
4
 
5
5
  describe('canonicalization', () => {
6
6
  it('orders top-level sections correctly', () => {
7
- const ir = createContractIR({
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('omits nullable false from columns', () => {
33
- const ir = createContractIR({
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']).toBeUndefined();
54
+ expect(id['nullable']).toBe(false);
55
55
  expect(email['nullable']).toBe(true);
56
56
  });
57
57
 
58
- it.each([
59
- { nullable: false },
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']).toBeUndefined();
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 = createContractIR({
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 = createContractIR();
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
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 = createContractIR({
332
+ const ir1 = createTestContract({
336
333
  targetFamily: 'mongo',
337
334
  target: 'mongo',
338
335
  storage: { collections: { users: {} } },
339
336
  });
340
- const ir2 = createContractIR({
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 = createContractIR({
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 = createContractIR({
366
+ const ir = createTestContract({
370
367
  storage: {
371
368
  tables: {
372
369
  user: {