@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
@@ -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,
@@ -138,8 +72,10 @@ describe('emitter integration', () => {
138
72
  schemaVersion: '1',
139
73
  targetFamily: 'sql',
140
74
  target: 'postgres',
141
- storageHash: result.storageHash,
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,69 +85,80 @@ 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.storageHash).toBe(result2.storageHash);
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
146
  it(
206
147
  'round-trip: IR → JSON → parse JSON → compare',
207
148
  async () => {
208
- const ir = createContractIR({
149
+ const ir = createTestContract({
209
150
  models: {
210
151
  User: {
211
- storage: { table: 'user' },
152
+ storage: {
153
+ table: 'user',
154
+ fields: {
155
+ id: { column: 'id' },
156
+ email: { column: 'email' },
157
+ },
158
+ },
212
159
  fields: {
213
- id: { column: 'id' },
214
- email: { column: 'email' },
160
+ id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
161
+ email: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
215
162
  },
216
163
  relations: {},
217
164
  },
@@ -238,14 +185,10 @@ describe('emitter integration', () => {
238
185
  },
239
186
  });
240
187
 
241
- // Create minimal test data (emitter tests don't load packs)
242
- const operationRegistry = createOperationRegistry();
243
188
  const codecTypeImports: TypesImportSpec[] = [];
244
189
  const operationTypeImports: TypesImportSpec[] = [];
245
190
  const extensionIds = ['postgres', 'pg'];
246
- const options: EmitOptions = {
247
- outputDir: '',
248
- operationRegistry,
191
+ const options: EmitStackInput = {
249
192
  codecTypeImports,
250
193
  operationTypeImports,
251
194
  extensionIds,
@@ -254,17 +197,16 @@ describe('emitter integration', () => {
254
197
  const result1 = await emit(ir, options, mockSqlHook);
255
198
  const contractJson1 = JSON.parse(result1.contractJson) as Record<string, unknown>;
256
199
 
257
- const ir2 = createContractIR({
258
- schemaVersion: contractJson1['schemaVersion'] as string,
200
+ const ir2 = createTestContract({
259
201
  targetFamily: contractJson1['targetFamily'] as string,
260
202
  target: contractJson1['target'] as string,
203
+ roots: contractJson1['roots'] as Record<string, string>,
261
204
  models: contractJson1['models'] as Record<string, unknown>,
262
205
  storage: contractJson1['storage'] as Record<string, unknown>,
263
206
  extensionPacks: contractJson1['extensionPacks'] as Record<string, unknown>,
264
207
  capabilities:
265
208
  (contractJson1['capabilities'] as Record<string, Record<string, boolean>>) || {},
266
209
  meta: (contractJson1['meta'] as Record<string, unknown>) || {},
267
- sources: (contractJson1['sources'] as Record<string, unknown>) || {},
268
210
  });
269
211
 
270
212
  const result2 = await emit(ir2, options, mockSqlHook);