@prisma-next/emitter 0.3.0-pr.99.6 → 0.4.0-dev.1
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/LICENSE +201 -0
- package/README.md +45 -35
- package/dist/domain-type-generation.d.mts +38 -0
- package/dist/domain-type-generation.d.mts.map +1 -0
- package/dist/domain-type-generation.mjs +255 -0
- package/dist/domain-type-generation.mjs.map +1 -0
- package/dist/exports/index.d.mts +39 -0
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +106 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/test/utils.d.mts +21 -0
- package/dist/test/utils.d.mts.map +1 -0
- package/dist/test/utils.mjs +18 -0
- package/dist/test/utils.mjs.map +1 -0
- 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 +28 -12
- package/src/domain-type-generation.ts +429 -0
- package/src/emit-types.ts +23 -0
- package/src/emit.ts +68 -0
- package/src/exports/index.ts +14 -9
- package/src/generate-contract-dts.ts +117 -0
- package/src/type-expression-safety.ts +3 -0
- package/test/canonicalization.test.ts +196 -19
- package/test/domain-type-generation.test.ts +997 -0
- package/test/emitter.integration.test.ts +132 -187
- package/test/emitter.roundtrip.test.ts +117 -191
- package/test/emitter.test.ts +123 -494
- package/test/hashing.test.ts +9 -34
- package/test/mock-spi.ts +18 -0
- package/test/type-expression-safety.test.ts +34 -0
- package/test/utils.ts +30 -165
- package/dist/exports/index.js +0 -6
- package/dist/exports/index.js.map +0 -1
- package/dist/src/exports/index.d.ts +0 -4
- package/dist/src/exports/index.d.ts.map +0 -1
- package/dist/src/target-family.d.ts +0 -2
- package/dist/src/target-family.d.ts.map +0 -1
- package/dist/test/utils.d.ts +0 -14
- package/dist/test/utils.d.ts.map +0 -1
- package/dist/test/utils.js +0 -78
- package/dist/test/utils.js.map +0 -1
- package/src/target-family.ts +0 -7
- package/test/factories.test.ts +0 -274
|
@@ -1,92 +1,30 @@
|
|
|
1
|
-
import type {
|
|
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 {
|
|
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
|
|
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 =
|
|
15
|
+
const ir = createTestContract({
|
|
84
16
|
models: {
|
|
85
17
|
User: {
|
|
86
|
-
storage: {
|
|
18
|
+
storage: {
|
|
19
|
+
table: 'user',
|
|
20
|
+
fields: {
|
|
21
|
+
id: { column: 'id' },
|
|
22
|
+
email: { column: 'email' },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
87
25
|
fields: {
|
|
88
|
-
id: {
|
|
89
|
-
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:
|
|
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.
|
|
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
|
-
|
|
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(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
119
|
+
extensionPacks: {
|
|
120
|
+
postgres: {
|
|
121
|
+
version: '0.0.1',
|
|
122
|
+
},
|
|
123
|
+
pg: {},
|
|
179
124
|
},
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
});
|
|
125
|
+
});
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
198
|
-
|
|
136
|
+
const result1 = await emit(ir, options, mockSqlHook);
|
|
137
|
+
const result2 = await emit(ir, options, mockSqlHook);
|
|
199
138
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
180
|
+
extensionPacks: {
|
|
181
|
+
postgres: {
|
|
182
|
+
version: '0.0.1',
|
|
183
|
+
},
|
|
184
|
+
pg: {},
|
|
234
185
|
},
|
|
235
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
212
|
+
const result2 = await emit(ir2, options, mockSqlHook);
|
|
270
213
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
214
|
+
expect(result1.contractJson).toBe(result2.contractJson);
|
|
215
|
+
expect(result1.storageHash).toBe(result2.storageHash);
|
|
216
|
+
},
|
|
217
|
+
timeouts.typeScriptCompilation,
|
|
218
|
+
);
|
|
274
219
|
});
|