@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.
- package/README.md +33 -30
- package/dist/domain-type-generation.d.mts +14 -2
- package/dist/domain-type-generation.d.mts.map +1 -1
- package/dist/domain-type-generation.mjs +139 -1
- package/dist/domain-type-generation.mjs.map +1 -1
- package/dist/exports/index.d.mts +39 -4
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +104 -3
- package/dist/exports/index.mjs.map +1 -0
- package/dist/test/utils.d.mts +16 -14
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +12 -51
- package/dist/test/utils.mjs.map +1 -1
- 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 +12 -6
- package/src/domain-type-generation.ts +227 -1
- package/src/emit-types.ts +23 -0
- package/src/emit.ts +68 -0
- package/src/exports/index.ts +4 -9
- package/src/generate-contract-dts.ts +116 -0
- package/src/type-expression-safety.ts +3 -0
- package/test/canonicalization.test.ts +25 -28
- package/test/domain-type-generation.test.ts +509 -2
- package/test/emitter.integration.test.ts +81 -139
- package/test/emitter.roundtrip.test.ts +114 -184
- package/test/emitter.test.ts +82 -467
- package/test/hashing.test.ts +8 -30
- package/test/mock-spi.ts +18 -0
- package/test/type-expression-safety.test.ts +34 -0
- package/test/utils.ts +30 -156
- 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,
|
|
@@ -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,69 +85,80 @@ 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
146
|
it(
|
|
206
147
|
'round-trip: IR → JSON → parse JSON → compare',
|
|
207
148
|
async () => {
|
|
208
|
-
const ir =
|
|
149
|
+
const ir = createTestContract({
|
|
209
150
|
models: {
|
|
210
151
|
User: {
|
|
211
|
-
storage: {
|
|
152
|
+
storage: {
|
|
153
|
+
table: 'user',
|
|
154
|
+
fields: {
|
|
155
|
+
id: { column: 'id' },
|
|
156
|
+
email: { column: 'email' },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
212
159
|
fields: {
|
|
213
|
-
id: {
|
|
214
|
-
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:
|
|
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 =
|
|
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);
|