@prisma-next/emitter 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.
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,92 +1,30 @@
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, { type?: 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 integration', () => {
80
12
  it(
81
13
  'emits complete contract from IR to artifacts',
82
14
  async () => {
83
- const ir = createContractIR({
15
+ const ir = createTestContract({
84
16
  models: {
85
17
  User: {
86
- storage: { table: 'user' },
18
+ storage: {
19
+ table: 'user',
20
+ fields: {
21
+ id: { column: 'id' },
22
+ email: { column: 'email' },
23
+ },
24
+ },
87
25
  fields: {
88
- id: { column: 'id' },
89
- email: { column: 'email' },
26
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
27
+ email: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
90
28
  },
91
29
  relations: {},
92
30
  },
@@ -113,14 +51,10 @@ describe('emitter integration', () => {
113
51
  },
114
52
  });
115
53
 
116
- // Create minimal test data (emitter tests don't load packs)
117
- const operationRegistry = createOperationRegistry();
118
54
  const codecTypeImports: TypesImportSpec[] = [];
119
55
  const operationTypeImports: TypesImportSpec[] = [];
120
56
  const extensionIds = ['postgres', 'pg'];
121
- const options: EmitOptions = {
122
- outputDir: '',
123
- operationRegistry,
57
+ const options: EmitStackInput = {
124
58
  codecTypeImports,
125
59
  operationTypeImports,
126
60
  extensionIds,
@@ -128,7 +62,7 @@ describe('emitter integration', () => {
128
62
 
129
63
  const result = await emit(ir, options, mockSqlHook);
130
64
 
131
- expect(result.coreHash).toMatch(/^sha256:[a-f0-9]{64}$/);
65
+ expect(result.storageHash).toMatch(/^sha256:[a-f0-9]{64}$/);
132
66
  expect(result.contractDts).toContain('export type Contract');
133
67
  expect(result.contractDts).toContain('CodecTypes');
134
68
  expect(result.contractDts).toContain('LaneCodecTypes');
@@ -138,8 +72,10 @@ describe('emitter integration', () => {
138
72
  schemaVersion: '1',
139
73
  targetFamily: 'sql',
140
74
  target: 'postgres',
141
- coreHash: result.coreHash,
75
+ profileHash: expect.stringMatching(/^sha256:/),
76
+ roots: {},
142
77
  storage: {
78
+ storageHash: result.storageHash,
143
79
  tables: {
144
80
  user: expect.anything(),
145
81
  },
@@ -149,126 +85,135 @@ describe('emitter integration', () => {
149
85
  timeouts.typeScriptCompilation,
150
86
  );
151
87
 
152
- it('produces stable hashes for identical input', async () => {
153
- const ir = createContractIR({
154
- models: {
155
- User: {
156
- storage: { table: 'user' },
157
- fields: {
158
- id: { column: 'id' },
88
+ it(
89
+ 'produces stable hashes for identical input',
90
+ async () => {
91
+ const ir = createTestContract({
92
+ models: {
93
+ User: {
94
+ storage: {
95
+ table: 'user',
96
+ fields: {
97
+ id: { column: 'id' },
98
+ },
99
+ },
100
+ fields: {
101
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
102
+ },
103
+ relations: {},
159
104
  },
160
- relations: {},
161
105
  },
162
- },
163
- storage: {
164
- tables: {
165
- user: {
166
- columns: {
167
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
106
+ storage: {
107
+ tables: {
108
+ user: {
109
+ columns: {
110
+ id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
111
+ },
112
+ primaryKey: { columns: ['id'] },
113
+ uniques: [],
114
+ indexes: [],
115
+ foreignKeys: [],
168
116
  },
169
- primaryKey: { columns: ['id'] },
170
- uniques: [],
171
- indexes: [],
172
- foreignKeys: [],
173
117
  },
174
118
  },
175
- },
176
- extensionPacks: {
177
- postgres: {
178
- version: '0.0.1',
119
+ extensionPacks: {
120
+ postgres: {
121
+ version: '0.0.1',
122
+ },
123
+ pg: {},
179
124
  },
180
- pg: {},
181
- },
182
- });
125
+ });
183
126
 
184
- // Create minimal test data (emitter tests don't load packs)
185
- const operationRegistry = createOperationRegistry();
186
- const codecTypeImports: TypesImportSpec[] = [];
187
- const operationTypeImports: TypesImportSpec[] = [];
188
- const extensionIds = ['postgres', 'pg'];
189
- const options: EmitOptions = {
190
- outputDir: '',
191
- operationRegistry,
192
- codecTypeImports,
193
- operationTypeImports,
194
- extensionIds,
195
- };
127
+ const codecTypeImports: TypesImportSpec[] = [];
128
+ const operationTypeImports: TypesImportSpec[] = [];
129
+ const extensionIds = ['postgres', 'pg'];
130
+ const options: EmitStackInput = {
131
+ codecTypeImports,
132
+ operationTypeImports,
133
+ extensionIds,
134
+ };
196
135
 
197
- const result1 = await emit(ir, options, mockSqlHook);
198
- const result2 = await emit(ir, options, mockSqlHook);
136
+ const result1 = await emit(ir, options, mockSqlHook);
137
+ const result2 = await emit(ir, options, mockSqlHook);
199
138
 
200
- expect(result1.coreHash).toBe(result2.coreHash);
201
- expect(result1.contractDts).toBe(result2.contractDts);
202
- expect(result1.contractJson).toBe(result2.contractJson);
203
- });
139
+ expect(result1.storageHash).toBe(result2.storageHash);
140
+ expect(result1.contractDts).toBe(result2.contractDts);
141
+ expect(result1.contractJson).toBe(result2.contractJson);
142
+ },
143
+ timeouts.typeScriptCompilation,
144
+ );
204
145
 
205
- it('round-trip: IR → JSON → parse JSON → compare', async () => {
206
- const ir = createContractIR({
207
- models: {
208
- User: {
209
- storage: { table: 'user' },
210
- fields: {
211
- id: { column: 'id' },
212
- email: { column: 'email' },
146
+ it(
147
+ 'round-trip: IR JSON → parse JSON → compare',
148
+ async () => {
149
+ const ir = createTestContract({
150
+ models: {
151
+ User: {
152
+ storage: {
153
+ table: 'user',
154
+ fields: {
155
+ id: { column: 'id' },
156
+ email: { column: 'email' },
157
+ },
158
+ },
159
+ fields: {
160
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
161
+ email: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
162
+ },
163
+ relations: {},
213
164
  },
214
- relations: {},
215
165
  },
216
- },
217
- storage: {
218
- tables: {
219
- user: {
220
- columns: {
221
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
222
- email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
166
+ storage: {
167
+ tables: {
168
+ user: {
169
+ columns: {
170
+ id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
171
+ email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
172
+ },
173
+ primaryKey: { columns: ['id'] },
174
+ uniques: [],
175
+ indexes: [],
176
+ foreignKeys: [],
223
177
  },
224
- primaryKey: { columns: ['id'] },
225
- uniques: [],
226
- indexes: [],
227
- foreignKeys: [],
228
178
  },
229
179
  },
230
- },
231
- extensionPacks: {
232
- postgres: {
233
- version: '0.0.1',
180
+ extensionPacks: {
181
+ postgres: {
182
+ version: '0.0.1',
183
+ },
184
+ pg: {},
234
185
  },
235
- pg: {},
236
- },
237
- });
238
-
239
- // Create minimal test data (emitter tests don't load packs)
240
- const operationRegistry = createOperationRegistry();
241
- const codecTypeImports: TypesImportSpec[] = [];
242
- const operationTypeImports: TypesImportSpec[] = [];
243
- const extensionIds = ['postgres', 'pg'];
244
- const options: EmitOptions = {
245
- outputDir: '',
246
- operationRegistry,
247
- codecTypeImports,
248
- operationTypeImports,
249
- extensionIds,
250
- };
186
+ });
251
187
 
252
- const result1 = await emit(ir, options, mockSqlHook);
253
- const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
188
+ const codecTypeImports: TypesImportSpec[] = [];
189
+ const operationTypeImports: TypesImportSpec[] = [];
190
+ const extensionIds = ['postgres', 'pg'];
191
+ const options: EmitStackInput = {
192
+ codecTypeImports,
193
+ operationTypeImports,
194
+ extensionIds,
195
+ };
254
196
 
255
- const ir2 = createContractIR({
256
- schemaVersion: contractJson1['schemaVersion'] as string,
257
- targetFamily: contractJson1['targetFamily'] as string,
258
- target: contractJson1['target'] as string,
259
- models: contractJson1['models'] as Record<string, unknown>,
260
- relations: (contractJson1['relations'] as Record<string, unknown>) || {},
261
- storage: contractJson1['storage'] as Record<string, unknown>,
262
- extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
263
- capabilities:
264
- (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
265
- meta: (contractJson1['meta'] as Record<string, unknown>) || {},
266
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
267
- });
197
+ const result1 = await emit(ir, options, mockSqlHook);
198
+ const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
199
+
200
+ const ir2 = createTestContract({
201
+ targetFamily: contractJson1['targetFamily'] as string,
202
+ target: contractJson1['target'] as string,
203
+ roots: contractJson1['roots'] as Record<string, string>,
204
+ models: contractJson1['models'] as Record<string, unknown>,
205
+ storage: contractJson1['storage'] as Record<string, unknown>,
206
+ extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
207
+ capabilities:
208
+ (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
209
+ meta: (contractJson1['meta'] as Record<string, unknown>) || {},
210
+ });
268
211
 
269
- const result2 = await emit(ir2, options, mockSqlHook);
212
+ const result2 = await emit(ir2, options, mockSqlHook);
270
213
 
271
- expect(result1.contractJson).toBe(result2.contractJson);
272
- expect(result1.coreHash).toBe(result2.coreHash);
273
- });
214
+ expect(result1.contractJson).toBe(result2.contractJson);
215
+ expect(result1.storageHash).toBe(result2.storageHash);
216
+ },
217
+ timeouts.typeScriptCompilation,
218
+ );
274
219
  });