@prisma-next/emitter 0.3.0-pr.99.5 → 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.
Files changed (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +45 -35
  3. package/dist/domain-type-generation.d.mts +38 -0
  4. package/dist/domain-type-generation.d.mts.map +1 -0
  5. package/dist/domain-type-generation.mjs +255 -0
  6. package/dist/domain-type-generation.mjs.map +1 -0
  7. package/dist/exports/index.d.mts +39 -0
  8. package/dist/exports/index.d.mts.map +1 -0
  9. package/dist/exports/index.mjs +106 -0
  10. package/dist/exports/index.mjs.map +1 -0
  11. package/dist/test/utils.d.mts +21 -0
  12. package/dist/test/utils.d.mts.map +1 -0
  13. package/dist/test/utils.mjs +18 -0
  14. package/dist/test/utils.mjs.map +1 -0
  15. package/dist/type-expression-safety-7_1tfJXA.mjs +8 -0
  16. package/dist/type-expression-safety-7_1tfJXA.mjs.map +1 -0
  17. package/dist/type-expression-safety.d.mts +5 -0
  18. package/dist/type-expression-safety.d.mts.map +1 -0
  19. package/dist/type-expression-safety.mjs +3 -0
  20. package/package.json +27 -11
  21. package/src/domain-type-generation.ts +429 -0
  22. package/src/emit-types.ts +23 -0
  23. package/src/emit.ts +68 -0
  24. package/src/exports/index.ts +14 -9
  25. package/src/generate-contract-dts.ts +117 -0
  26. package/src/type-expression-safety.ts +3 -0
  27. package/test/canonicalization.test.ts +196 -19
  28. package/test/domain-type-generation.test.ts +997 -0
  29. package/test/emitter.integration.test.ts +132 -187
  30. package/test/emitter.roundtrip.test.ts +117 -191
  31. package/test/emitter.test.ts +123 -494
  32. package/test/hashing.test.ts +9 -34
  33. package/test/mock-spi.ts +18 -0
  34. package/test/type-expression-safety.test.ts +34 -0
  35. package/test/utils.ts +30 -165
  36. package/dist/exports/index.js +0 -6
  37. package/dist/exports/index.js.map +0 -1
  38. package/dist/src/exports/index.d.ts +0 -4
  39. package/dist/src/exports/index.d.ts.map +0 -1
  40. package/dist/src/target-family.d.ts +0 -2
  41. package/dist/src/target-family.d.ts.map +0 -1
  42. package/dist/test/utils.d.ts +0 -14
  43. package/dist/test/utils.d.ts.map +0 -1
  44. package/dist/test/utils.js +0 -78
  45. package/dist/test/utils.js.map +0 -1
  46. package/src/target-family.ts +0 -7
  47. package/test/factories.test.ts +0 -274
@@ -1,86 +1,18 @@
1
- import type { ContractIR } from '@prisma-next/contract/ir';
2
- import type {
3
- TargetFamilyHook,
4
- TypesImportSpec,
5
- ValidationContext,
6
- } from '@prisma-next/contract/types';
7
- import type { EmitOptions } from '@prisma-next/core-control-plane/emission';
8
- import { emit } from '@prisma-next/core-control-plane/emission';
9
- import { createOperationRegistry } from '@prisma-next/operations';
1
+ import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
10
2
  import { timeouts } from '@prisma-next/test-utils';
11
3
  import { describe, expect, it } from 'vitest';
12
- import { createContractIR } from './utils';
4
+ import type { EmitStackInput } from '../src/exports';
5
+ import { emit } from '../src/exports';
6
+ import { createMockSpi } from './mock-spi';
7
+ import { createTestContract } from './utils';
13
8
 
14
- const mockSqlHook: TargetFamilyHook = {
15
- id: 'sql',
16
- validateTypes: (ir: ContractIR, _ctx: ValidationContext) => {
17
- const storage = ir.storage as
18
- | { tables?: Record<string, { columns?: Record<string, { codecId?: string }> }> }
19
- | undefined;
20
- if (!storage?.tables) {
21
- return;
22
- }
23
-
24
- const referencedNamespaces = new Set<string>();
25
- const extensionPacks = ir.extensionPacks as Record<string, unknown> | undefined;
26
- if (extensionPacks) {
27
- for (const namespace of Object.keys(extensionPacks)) {
28
- referencedNamespaces.add(namespace);
29
- }
30
- }
31
-
32
- const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/;
33
-
34
- for (const [tableName, table] of Object.entries(storage.tables)) {
35
- if (!table.columns) continue;
36
- for (const [colName, col] of Object.entries(table.columns)) {
37
- const column = col as { codecId?: string };
38
- if (!column.codecId) {
39
- throw new Error(`Column "${colName}" in table "${tableName}" is missing codecId`);
40
- }
41
-
42
- if (!typeIdRegex.test(column.codecId)) {
43
- throw new Error(
44
- `Column "${colName}" in table "${tableName}" has invalid codecId format "${column.codecId}". Expected format: ns/name@version`,
45
- );
46
- }
47
-
48
- const match = column.codecId.match(typeIdRegex);
49
- if (match?.[1]) {
50
- const namespace = match[1];
51
- if (!referencedNamespaces.has(namespace)) {
52
- if (namespace === 'pg' && referencedNamespaces.has('postgres')) {
53
- continue;
54
- }
55
- throw new Error(
56
- `Column "${colName}" in table "${tableName}" uses codecId "${column.codecId}" from namespace "${namespace}" which is not referenced in contract.extensionPacks`,
57
- );
58
- }
59
- }
60
- }
61
- }
62
- },
63
- validateStructure: (ir: ContractIR) => {
64
- if (ir.targetFamily !== 'sql') {
65
- throw new Error(`Expected targetFamily "sql", got "${ir.targetFamily}"`);
66
- }
67
- },
68
- generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports) => {
69
- void _codecTypeImports;
70
- void _operationTypeImports;
71
- return `// Generated contract types
72
- export type CodecTypes = Record<string, never>;
73
- export type LaneCodecTypes = CodecTypes;
74
- export type Contract = unknown;
75
- `;
76
- },
77
- };
9
+ const mockSqlHook = createMockSpi();
78
10
 
79
11
  describe('emitter round-trip', () => {
80
12
  it(
81
13
  'round-trip with minimal IR',
82
14
  async () => {
83
- const ir = createContractIR({
15
+ const ir = createTestContract({
84
16
  storage: {
85
17
  tables: {
86
18
  user: {
@@ -100,14 +32,10 @@ describe('emitter round-trip', () => {
100
32
  },
101
33
  });
102
34
 
103
- // Create minimal test data (emitter tests don't load packs)
104
- const operationRegistry = createOperationRegistry();
105
35
  const codecTypeImports: TypesImportSpec[] = [];
106
36
  const operationTypeImports: TypesImportSpec[] = [];
107
37
  const extensionIds = ['postgres', 'pg'];
108
- const options: EmitOptions = {
109
- outputDir: '',
110
- operationRegistry,
38
+ const options: EmitStackInput = {
111
39
  codecTypeImports,
112
40
  operationTypeImports,
113
41
  extensionIds,
@@ -116,125 +44,135 @@ describe('emitter round-trip', () => {
116
44
  const result1 = await emit(ir, options, mockSqlHook);
117
45
  const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
118
46
 
119
- const ir2 = createContractIR({
120
- schemaVersion: contractJson1['schemaVersion'] as string,
47
+ const ir2 = createTestContract({
121
48
  targetFamily: contractJson1['targetFamily'] as string,
122
49
  target: contractJson1['target'] as string,
50
+ roots: contractJson1['roots'] as Record<string, string>,
123
51
  models: contractJson1['models'] as Record<string, unknown>,
124
- relations: (contractJson1['relations'] as Record<string, unknown>) || {},
125
52
  storage: contractJson1['storage'] as Record<string, unknown>,
126
53
  extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
127
54
  capabilities:
128
55
  (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
129
56
  meta: (contractJson1['meta'] as Record<string, unknown>) || {},
130
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
131
57
  });
132
58
 
133
59
  const result2 = await emit(ir2, options, mockSqlHook);
134
60
 
135
61
  expect(result1.contractJson).toBe(result2.contractJson);
136
- expect(result1.coreHash).toBe(result2.coreHash);
62
+ expect(result1.storageHash).toBe(result2.storageHash);
137
63
  },
138
64
  timeouts.typeScriptCompilation,
139
65
  );
140
66
 
141
- it('round-trip with complex IR', async () => {
142
- const ir = createContractIR({
143
- models: {
144
- User: {
145
- storage: { table: 'user' },
146
- fields: {
147
- id: { column: 'id' },
148
- email: { column: 'email' },
149
- name: { column: 'name' },
150
- },
151
- relations: {},
152
- },
153
- Post: {
154
- storage: { table: 'post' },
155
- fields: {
156
- id: { column: 'id' },
157
- title: { column: 'title' },
158
- userId: { column: 'user_id' },
67
+ it(
68
+ 'round-trip with complex IR',
69
+ async () => {
70
+ const ir = createTestContract({
71
+ models: {
72
+ User: {
73
+ storage: {
74
+ table: 'user',
75
+ fields: {
76
+ id: { column: 'id' },
77
+ email: { column: 'email' },
78
+ name: { column: 'name' },
79
+ },
80
+ },
81
+ fields: {
82
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
83
+ email: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
84
+ name: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: true },
85
+ },
86
+ relations: {},
159
87
  },
160
- relations: {},
161
- },
162
- },
163
- storage: {
164
- tables: {
165
- user: {
166
- columns: {
167
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
168
- email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
169
- name: { codecId: 'pg/text@1', nativeType: 'text', nullable: true },
88
+ Post: {
89
+ storage: {
90
+ table: 'post',
91
+ fields: {
92
+ id: { column: 'id' },
93
+ title: { column: 'title' },
94
+ userId: { column: 'user_id' },
95
+ },
170
96
  },
171
- primaryKey: { columns: ['id'] },
172
- uniques: [{ columns: ['email'], name: 'user_email_key' }],
173
- indexes: [{ columns: ['name'], name: 'user_name_idx' }],
174
- foreignKeys: [],
97
+ fields: {
98
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
99
+ title: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
100
+ userId: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
101
+ },
102
+ relations: {},
175
103
  },
176
- post: {
177
- columns: {
178
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
179
- title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
180
- user_id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
104
+ },
105
+ storage: {
106
+ tables: {
107
+ user: {
108
+ columns: {
109
+ id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
110
+ email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
111
+ name: { codecId: 'pg/text@1', nativeType: 'text', nullable: true },
112
+ },
113
+ primaryKey: { columns: ['id'] },
114
+ uniques: [{ columns: ['email'], name: 'user_email_key' }],
115
+ indexes: [{ columns: ['name'], name: 'user_name_idx' }],
116
+ foreignKeys: [],
181
117
  },
182
- primaryKey: { columns: ['id'] },
183
- uniques: [],
184
- indexes: [],
185
- foreignKeys: [
186
- {
187
- columns: ['user_id'],
188
- references: { table: 'user', columns: ['id'] },
189
- name: 'post_user_id_fkey',
118
+ post: {
119
+ columns: {
120
+ id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
121
+ title: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
122
+ user_id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
190
123
  },
191
- ],
124
+ primaryKey: { columns: ['id'] },
125
+ uniques: [],
126
+ indexes: [],
127
+ foreignKeys: [
128
+ {
129
+ columns: ['user_id'],
130
+ references: { table: 'user', columns: ['id'] },
131
+ name: 'post_user_id_fkey',
132
+ },
133
+ ],
134
+ },
192
135
  },
193
136
  },
194
- },
195
- extensionPacks: {
196
- postgres: { version: '0.0.1' },
197
- },
198
- });
137
+ extensionPacks: {
138
+ postgres: { version: '0.0.1' },
139
+ },
140
+ });
199
141
 
200
- // Create minimal test data (emitter tests don't load packs)
201
- const operationRegistry = createOperationRegistry();
202
- const codecTypeImports: TypesImportSpec[] = [];
203
- const operationTypeImports: TypesImportSpec[] = [];
204
- const extensionIds = ['postgres'];
205
- const options: EmitOptions = {
206
- outputDir: '',
207
- operationRegistry,
208
- codecTypeImports,
209
- operationTypeImports,
210
- extensionIds,
211
- };
142
+ const codecTypeImports: TypesImportSpec[] = [];
143
+ const operationTypeImports: TypesImportSpec[] = [];
144
+ const extensionIds = ['postgres'];
145
+ const options: EmitStackInput = {
146
+ codecTypeImports,
147
+ operationTypeImports,
148
+ extensionIds,
149
+ };
212
150
 
213
- const result1 = await emit(ir, options, mockSqlHook);
214
- const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
151
+ const result1 = await emit(ir, options, mockSqlHook);
152
+ const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
215
153
 
216
- const ir2 = createContractIR({
217
- schemaVersion: contractJson1['schemaVersion'] as string,
218
- targetFamily: contractJson1['targetFamily'] as string,
219
- target: contractJson1['target'] as string,
220
- models: contractJson1['models'] as Record<string, unknown>,
221
- relations: (contractJson1['relations'] as Record<string, unknown>) || {},
222
- storage: contractJson1['storage'] as Record<string, unknown>,
223
- extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
224
- capabilities:
225
- (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
226
- meta: (contractJson1['meta'] as Record<string, unknown>) || {},
227
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
228
- });
154
+ const ir2 = createTestContract({
155
+ targetFamily: contractJson1['targetFamily'] as string,
156
+ target: contractJson1['target'] as string,
157
+ roots: contractJson1['roots'] as Record<string, string>,
158
+ models: contractJson1['models'] as Record<string, unknown>,
159
+ storage: contractJson1['storage'] as Record<string, unknown>,
160
+ extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
161
+ capabilities:
162
+ (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
163
+ meta: (contractJson1['meta'] as Record<string, unknown>) || {},
164
+ });
229
165
 
230
- const result2 = await emit(ir2, options, mockSqlHook);
166
+ const result2 = await emit(ir2, options, mockSqlHook);
231
167
 
232
- expect(result1.contractJson).toBe(result2.contractJson);
233
- expect(result1.coreHash).toBe(result2.coreHash);
234
- });
168
+ expect(result1.contractJson).toBe(result2.contractJson);
169
+ expect(result1.storageHash).toBe(result2.storageHash);
170
+ },
171
+ timeouts.typeScriptCompilation,
172
+ );
235
173
 
236
174
  it('round-trip with nullable fields', async () => {
237
- const ir = createContractIR({
175
+ const ir = createTestContract({
238
176
  storage: {
239
177
  tables: {
240
178
  user: {
@@ -256,14 +194,10 @@ describe('emitter round-trip', () => {
256
194
  },
257
195
  });
258
196
 
259
- // Create minimal test data (emitter tests don't load packs)
260
- const operationRegistry = createOperationRegistry();
261
197
  const codecTypeImports: TypesImportSpec[] = [];
262
198
  const operationTypeImports: TypesImportSpec[] = [];
263
199
  const extensionIds = ['postgres', 'pg'];
264
- const options: EmitOptions = {
265
- outputDir: '',
266
- operationRegistry,
200
+ const options: EmitStackInput = {
267
201
  codecTypeImports,
268
202
  operationTypeImports,
269
203
  extensionIds,
@@ -272,24 +206,22 @@ describe('emitter round-trip', () => {
272
206
  const result1 = await emit(ir, options, mockSqlHook);
273
207
  const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
274
208
 
275
- const ir2 = createContractIR({
276
- schemaVersion: contractJson1['schemaVersion'] as string,
209
+ const ir2 = createTestContract({
277
210
  targetFamily: contractJson1['targetFamily'] as string,
278
211
  target: contractJson1['target'] as string,
212
+ roots: contractJson1['roots'] as Record<string, string>,
279
213
  models: contractJson1['models'] as Record<string, unknown>,
280
- relations: (contractJson1['relations'] as Record<string, unknown>) || {},
281
214
  storage: contractJson1['storage'] as Record<string, unknown>,
282
215
  extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
283
216
  capabilities:
284
217
  (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
285
218
  meta: (contractJson1['meta'] as Record<string, unknown>) || {},
286
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
287
219
  });
288
220
 
289
221
  const result2 = await emit(ir2, options, mockSqlHook);
290
222
 
291
223
  expect(result1.contractJson).toBe(result2.contractJson);
292
- expect(result1.coreHash).toBe(result2.coreHash);
224
+ expect(result1.storageHash).toBe(result2.storageHash);
293
225
 
294
226
  const parsed2 = JSON.parse(result2.contractJson) as Record<string, unknown>;
295
227
  const storage = parsed2['storage'] as Record<string, unknown>;
@@ -299,13 +231,13 @@ describe('emitter round-trip', () => {
299
231
  const id = columns['id'] as Record<string, unknown>;
300
232
  const email = columns['email'] as Record<string, unknown>;
301
233
  const name = columns['name'] as Record<string, unknown>;
302
- expect(id['nullable']).toBeUndefined();
234
+ expect(id['nullable']).toBe(false);
303
235
  expect(email['nullable']).toBe(true);
304
- expect(name['nullable']).toBeUndefined();
236
+ expect(name['nullable']).toBe(false);
305
237
  });
306
238
 
307
239
  it('round-trip with capabilities', async () => {
308
- const ir = createContractIR({
240
+ const ir = createTestContract({
309
241
  storage: {
310
242
  tables: {
311
243
  user: {
@@ -331,14 +263,10 @@ describe('emitter round-trip', () => {
331
263
  },
332
264
  });
333
265
 
334
- // Create minimal test data (emitter tests don't load packs)
335
- const operationRegistry = createOperationRegistry();
336
266
  const codecTypeImports: TypesImportSpec[] = [];
337
267
  const operationTypeImports: TypesImportSpec[] = [];
338
268
  const extensionIds = ['postgres', 'pg'];
339
- const options: EmitOptions = {
340
- outputDir: '',
341
- operationRegistry,
269
+ const options: EmitStackInput = {
342
270
  codecTypeImports,
343
271
  operationTypeImports,
344
272
  extensionIds,
@@ -347,24 +275,22 @@ describe('emitter round-trip', () => {
347
275
  const result1 = await emit(ir, options, mockSqlHook);
348
276
  const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
349
277
 
350
- const ir2 = createContractIR({
351
- schemaVersion: contractJson1['schemaVersion'] as string,
278
+ const ir2 = createTestContract({
352
279
  targetFamily: contractJson1['targetFamily'] as string,
353
280
  target: contractJson1['target'] as string,
281
+ roots: contractJson1['roots'] as Record<string, string>,
354
282
  models: contractJson1['models'] as Record<string, unknown>,
355
- relations: (contractJson1['relations'] as Record<string, unknown>) || {},
356
283
  storage: contractJson1['storage'] as Record<string, unknown>,
357
284
  extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
358
285
  capabilities:
359
286
  (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
360
287
  meta: (contractJson1['meta'] as Record<string, unknown>) || {},
361
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
362
288
  });
363
289
 
364
290
  const result2 = await emit(ir2, options, mockSqlHook);
365
291
 
366
292
  expect(result1.contractJson).toBe(result2.contractJson);
367
- expect(result1.coreHash).toBe(result2.coreHash);
293
+ expect(result1.storageHash).toBe(result2.storageHash);
368
294
  expect(result1.profileHash).toBe(result2.profileHash);
369
295
  });
370
296
  });