@prisma-next/emitter 0.3.0-dev.15 → 0.3.0-dev.151
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 +30 -0
- package/dist/domain-type-generation.d.mts.map +1 -0
- package/dist/domain-type-generation.mjs +213 -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 +105 -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 +30 -14
- package/src/domain-type-generation.ts +357 -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 +116 -0
- package/src/type-expression-safety.ts +3 -0
- package/test/canonicalization.test.ts +196 -19
- package/test/domain-type-generation.test.ts +824 -0
- package/test/emitter.integration.test.ts +132 -187
- package/test/emitter.roundtrip.test.ts +117 -191
- package/test/emitter.test.ts +124 -442
- 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
package/test/emitter.test.ts
CHANGED
|
@@ -1,74 +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 {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| { tables?: Record<string, { columns?: Record<string, { codecId?: string }> }> }
|
|
19
|
-
| undefined;
|
|
20
|
-
if (!storage?.tables) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Only validate codec ID format (ns/name@version)
|
|
25
|
-
// Namespace validation removed - codecs can use any namespace
|
|
26
|
-
const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/;
|
|
27
|
-
|
|
28
|
-
for (const [tableName, table] of Object.entries(storage.tables)) {
|
|
29
|
-
if (!table.columns) continue;
|
|
30
|
-
for (const [colName, col] of Object.entries(table.columns)) {
|
|
31
|
-
if (!col.codecId) {
|
|
32
|
-
throw new Error(`Column "${colName}" in table "${tableName}" is missing codecId`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!typeIdRegex.test(col.codecId)) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`Column "${colName}" in table "${tableName}" has invalid codecId format "${col.codecId}". Expected format: ns/name@version`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
validateStructure: (ir: ContractIR) => {
|
|
44
|
-
if (ir.targetFamily !== 'sql') {
|
|
45
|
-
throw new Error(`Expected targetFamily "sql", got "${ir.targetFamily}"`);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
generateContractTypes: (ir: ContractIR, _codecTypeImports, _operationTypeImports) => {
|
|
49
|
-
// Access ir properties to satisfy lint rules, but we don't use them in the mock
|
|
50
|
-
void ir;
|
|
51
|
-
void _codecTypeImports;
|
|
52
|
-
void _operationTypeImports;
|
|
53
|
-
return `// Generated contract types
|
|
54
|
-
export type CodecTypes = Record<string, never>;
|
|
55
|
-
export type LaneCodecTypes = CodecTypes;
|
|
56
|
-
export type Contract = unknown;
|
|
57
|
-
`;
|
|
58
|
-
},
|
|
59
|
-
};
|
|
4
|
+
import type { EmitStackInput } from '../src/exports';
|
|
5
|
+
import { emit } from '../src/exports';
|
|
6
|
+
import { createMockSpi } from './mock-spi';
|
|
7
|
+
import { createTestContract } from './utils';
|
|
8
|
+
|
|
9
|
+
const mockSqlHook = createMockSpi();
|
|
60
10
|
|
|
61
11
|
describe('emitter', () => {
|
|
62
12
|
it(
|
|
63
13
|
'emits contract.json and contract.d.ts',
|
|
64
14
|
async () => {
|
|
65
|
-
const ir =
|
|
15
|
+
const ir = createTestContract({
|
|
66
16
|
models: {
|
|
67
17
|
User: {
|
|
68
|
-
storage: {
|
|
18
|
+
storage: {
|
|
19
|
+
table: 'user',
|
|
20
|
+
fields: {
|
|
21
|
+
id: { column: 'id' },
|
|
22
|
+
email: { column: 'email' },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
69
25
|
fields: {
|
|
70
|
-
id: {
|
|
71
|
-
email: {
|
|
26
|
+
id: { type: { kind: 'scalar', codecId: 'pg/int4@1' }, nullable: false },
|
|
27
|
+
email: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false },
|
|
72
28
|
},
|
|
73
29
|
relations: {},
|
|
74
30
|
},
|
|
@@ -95,21 +51,17 @@ describe('emitter', () => {
|
|
|
95
51
|
},
|
|
96
52
|
});
|
|
97
53
|
|
|
98
|
-
// Create empty registry and minimal test data (emitter tests don't load packs)
|
|
99
|
-
const operationRegistry = createOperationRegistry();
|
|
100
54
|
const codecTypeImports: TypesImportSpec[] = [];
|
|
101
55
|
const operationTypeImports: TypesImportSpec[] = [];
|
|
102
56
|
const extensionIds = ['postgres', 'pg'];
|
|
103
|
-
const options:
|
|
104
|
-
outputDir: '',
|
|
105
|
-
operationRegistry,
|
|
57
|
+
const options: EmitStackInput = {
|
|
106
58
|
codecTypeImports,
|
|
107
59
|
operationTypeImports,
|
|
108
60
|
extensionIds,
|
|
109
61
|
};
|
|
110
62
|
|
|
111
63
|
const result = await emit(ir, options, mockSqlHook);
|
|
112
|
-
expect(result.
|
|
64
|
+
expect(result.storageHash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
113
65
|
expect(result.contractDts).toContain('export type Contract');
|
|
114
66
|
expect(result.contractDts).toContain('CodecTypes');
|
|
115
67
|
|
|
@@ -121,16 +73,14 @@ describe('emitter', () => {
|
|
|
121
73
|
timeouts.typeScriptCompilation,
|
|
122
74
|
);
|
|
123
75
|
|
|
124
|
-
it('
|
|
125
|
-
|
|
126
|
-
const ir = createContractIR({
|
|
76
|
+
it('emits contract even when extension pack namespace does not match extensionIds', async () => {
|
|
77
|
+
const ir = createTestContract({
|
|
127
78
|
storage: {
|
|
128
79
|
tables: {
|
|
129
80
|
user: {
|
|
130
81
|
columns: {
|
|
131
|
-
id: { codecId: '
|
|
82
|
+
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
132
83
|
},
|
|
133
|
-
primaryKey: { columns: ['id'] },
|
|
134
84
|
uniques: [],
|
|
135
85
|
indexes: [],
|
|
136
86
|
foreignKeys: [],
|
|
@@ -139,30 +89,26 @@ describe('emitter', () => {
|
|
|
139
89
|
},
|
|
140
90
|
});
|
|
141
91
|
|
|
142
|
-
const
|
|
143
|
-
const options: EmitOptions = {
|
|
144
|
-
outputDir: '',
|
|
145
|
-
operationRegistry,
|
|
92
|
+
const options: EmitStackInput = {
|
|
146
93
|
codecTypeImports: [],
|
|
147
94
|
operationTypeImports: [],
|
|
148
95
|
extensionIds: [],
|
|
149
96
|
};
|
|
150
97
|
|
|
151
|
-
// Should succeed - namespace validation removed
|
|
152
98
|
const result = await emit(ir, options, mockSqlHook);
|
|
153
99
|
expect(result.contractJson).toBeDefined();
|
|
154
100
|
expect(result.contractDts).toBeDefined();
|
|
155
101
|
});
|
|
156
102
|
|
|
157
|
-
it('
|
|
158
|
-
const ir =
|
|
103
|
+
it('tolerates codec namespaces not registered in extensionIds', async () => {
|
|
104
|
+
const ir = createTestContract({
|
|
159
105
|
storage: {
|
|
160
106
|
tables: {
|
|
161
|
-
|
|
107
|
+
data: {
|
|
162
108
|
columns: {
|
|
163
|
-
id: { codecId: '
|
|
109
|
+
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
110
|
+
value: { codecId: 'unknown/type@1', nativeType: 'custom', nullable: false },
|
|
164
111
|
},
|
|
165
|
-
primaryKey: { columns: ['id'] },
|
|
166
112
|
uniques: [],
|
|
167
113
|
indexes: [],
|
|
168
114
|
foreignKeys: [],
|
|
@@ -171,21 +117,19 @@ describe('emitter', () => {
|
|
|
171
117
|
},
|
|
172
118
|
});
|
|
173
119
|
|
|
174
|
-
const
|
|
175
|
-
const options: EmitOptions = {
|
|
176
|
-
outputDir: '',
|
|
177
|
-
operationRegistry,
|
|
120
|
+
const options: EmitStackInput = {
|
|
178
121
|
codecTypeImports: [],
|
|
179
122
|
operationTypeImports: [],
|
|
180
|
-
extensionIds: [],
|
|
123
|
+
extensionIds: ['some-other-extension'],
|
|
181
124
|
};
|
|
182
125
|
|
|
183
|
-
await
|
|
126
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
127
|
+
expect(result.contractJson).toBeDefined();
|
|
128
|
+
expect(result.contractDts).toBeDefined();
|
|
184
129
|
});
|
|
185
130
|
|
|
186
|
-
it('
|
|
187
|
-
const ir =
|
|
188
|
-
targetFamily: undefined as unknown as string,
|
|
131
|
+
it('handles missing extensionPacks field', async () => {
|
|
132
|
+
const ir = createTestContract({
|
|
189
133
|
storage: {
|
|
190
134
|
tables: {
|
|
191
135
|
user: {
|
|
@@ -198,25 +142,21 @@ describe('emitter', () => {
|
|
|
198
142
|
},
|
|
199
143
|
},
|
|
200
144
|
},
|
|
201
|
-
})
|
|
145
|
+
});
|
|
202
146
|
|
|
203
|
-
const
|
|
204
|
-
const options: EmitOptions = {
|
|
205
|
-
outputDir: '',
|
|
206
|
-
operationRegistry,
|
|
147
|
+
const options: EmitStackInput = {
|
|
207
148
|
codecTypeImports: [],
|
|
208
149
|
operationTypeImports: [],
|
|
209
150
|
extensionIds: [],
|
|
210
151
|
};
|
|
211
152
|
|
|
212
|
-
await
|
|
213
|
-
|
|
214
|
-
);
|
|
153
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
154
|
+
expect(result.contractJson).toBeDefined();
|
|
155
|
+
expect(result.contractDts).toBeDefined();
|
|
215
156
|
});
|
|
216
157
|
|
|
217
|
-
it('
|
|
218
|
-
const ir =
|
|
219
|
-
target: undefined as unknown as string,
|
|
158
|
+
it('handles empty packs array', async () => {
|
|
159
|
+
const ir = createTestContract({
|
|
220
160
|
storage: {
|
|
221
161
|
tables: {
|
|
222
162
|
user: {
|
|
@@ -229,422 +169,164 @@ describe('emitter', () => {
|
|
|
229
169
|
},
|
|
230
170
|
},
|
|
231
171
|
},
|
|
232
|
-
})
|
|
172
|
+
});
|
|
233
173
|
|
|
234
|
-
const
|
|
235
|
-
const options: EmitOptions = {
|
|
236
|
-
outputDir: '',
|
|
237
|
-
operationRegistry,
|
|
174
|
+
const options: EmitStackInput = {
|
|
238
175
|
codecTypeImports: [],
|
|
239
176
|
operationTypeImports: [],
|
|
240
177
|
extensionIds: [],
|
|
241
178
|
};
|
|
242
179
|
|
|
243
|
-
await
|
|
180
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
181
|
+
expect(result.contractJson).toBeDefined();
|
|
182
|
+
expect(result.contractDts).toBeDefined();
|
|
244
183
|
});
|
|
245
184
|
|
|
246
|
-
it('
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
tables: {
|
|
251
|
-
user: {
|
|
252
|
-
columns: {
|
|
253
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
254
|
-
},
|
|
255
|
-
uniques: [],
|
|
256
|
-
indexes: [],
|
|
257
|
-
foreignKeys: [],
|
|
258
|
-
},
|
|
259
|
-
},
|
|
185
|
+
it('omits sources from emitted contract artifact', async () => {
|
|
186
|
+
const ir = createTestContract({
|
|
187
|
+
sources: {
|
|
188
|
+
schema: { sourceId: 'schema.prisma' },
|
|
260
189
|
},
|
|
261
190
|
});
|
|
262
191
|
|
|
263
|
-
const
|
|
264
|
-
const options: EmitOptions = {
|
|
265
|
-
outputDir: '',
|
|
266
|
-
operationRegistry,
|
|
192
|
+
const options: EmitStackInput = {
|
|
267
193
|
codecTypeImports: [],
|
|
268
194
|
operationTypeImports: [],
|
|
269
|
-
extensionIds: [],
|
|
195
|
+
extensionIds: [],
|
|
270
196
|
};
|
|
271
197
|
|
|
272
|
-
// Should succeed - adapter-provided codecs don't need to be in contract.extensionPacks
|
|
273
198
|
const result = await emit(ir, options, mockSqlHook);
|
|
274
|
-
|
|
275
|
-
expect(
|
|
199
|
+
const contractJson = JSON.parse(result.contractJson) as Record<string, unknown>;
|
|
200
|
+
expect(contractJson).not.toHaveProperty('sources');
|
|
276
201
|
});
|
|
277
202
|
|
|
278
|
-
it('
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
columns: {
|
|
285
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
286
|
-
},
|
|
287
|
-
uniques: [],
|
|
288
|
-
indexes: [],
|
|
289
|
-
foreignKeys: [],
|
|
290
|
-
},
|
|
291
|
-
},
|
|
203
|
+
it('accepts meta keys when family validation allows them', async () => {
|
|
204
|
+
const ir = createTestContract({
|
|
205
|
+
meta: {
|
|
206
|
+
sourceId: 'schema.prisma',
|
|
207
|
+
schemaPath: '/tmp/schema.prisma',
|
|
208
|
+
source: 'psl',
|
|
292
209
|
},
|
|
293
210
|
});
|
|
294
211
|
|
|
295
|
-
const
|
|
296
|
-
const options: EmitOptions = {
|
|
297
|
-
outputDir: '',
|
|
298
|
-
operationRegistry,
|
|
212
|
+
const options: EmitStackInput = {
|
|
299
213
|
codecTypeImports: [],
|
|
300
214
|
operationTypeImports: [],
|
|
301
215
|
extensionIds: [],
|
|
302
216
|
};
|
|
303
217
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
218
|
+
await expect(emit(ir, options, mockSqlHook)).resolves.toMatchObject({
|
|
219
|
+
contractJson: expect.any(String),
|
|
220
|
+
contractDts: expect.any(String),
|
|
221
|
+
});
|
|
308
222
|
});
|
|
309
223
|
|
|
310
|
-
it('
|
|
311
|
-
|
|
312
|
-
const ir = createContractIR({
|
|
224
|
+
it('accepts canonical section keys when family validation allows them', async () => {
|
|
225
|
+
const ir = createTestContract({
|
|
313
226
|
storage: {
|
|
314
227
|
tables: {
|
|
315
228
|
user: {
|
|
316
229
|
columns: {
|
|
317
|
-
id: {
|
|
230
|
+
id: {
|
|
231
|
+
codecId: 'pg/int4@1',
|
|
232
|
+
nativeType: 'int4',
|
|
233
|
+
nullable: false,
|
|
234
|
+
sourceId: 'schema.prisma',
|
|
235
|
+
},
|
|
318
236
|
},
|
|
237
|
+
primaryKey: { columns: ['id'] },
|
|
319
238
|
uniques: [],
|
|
320
239
|
indexes: [],
|
|
321
240
|
foreignKeys: [],
|
|
322
241
|
},
|
|
323
242
|
},
|
|
324
|
-
},
|
|
243
|
+
} as unknown as Record<string, unknown>,
|
|
325
244
|
});
|
|
326
245
|
|
|
327
|
-
const
|
|
328
|
-
const options: EmitOptions = {
|
|
329
|
-
outputDir: '',
|
|
330
|
-
operationRegistry,
|
|
331
|
-
codecTypeImports: [],
|
|
332
|
-
operationTypeImports: [],
|
|
333
|
-
extensionIds: [],
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Should succeed - namespace validation removed
|
|
337
|
-
const result = await emit(ir, options, mockSqlHook);
|
|
338
|
-
expect(result.contractJson).toBeDefined();
|
|
339
|
-
expect(result.contractDts).toBeDefined();
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('throws error when schemaVersion is missing', async () => {
|
|
343
|
-
const ir = createContractIR({
|
|
344
|
-
schemaVersion: undefined as unknown as string,
|
|
345
|
-
}) as ContractIR;
|
|
346
|
-
|
|
347
|
-
const operationRegistry = createOperationRegistry();
|
|
348
|
-
const options: EmitOptions = {
|
|
349
|
-
outputDir: '',
|
|
350
|
-
operationRegistry,
|
|
246
|
+
const options: EmitStackInput = {
|
|
351
247
|
codecTypeImports: [],
|
|
352
248
|
operationTypeImports: [],
|
|
353
249
|
extensionIds: [],
|
|
354
250
|
};
|
|
355
251
|
|
|
356
|
-
await expect(emit(ir, options, mockSqlHook)).
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
it('throws error when models is missing', async () => {
|
|
362
|
-
const ir = createContractIR({
|
|
363
|
-
models: undefined as unknown as Record<string, unknown>,
|
|
364
|
-
}) as ContractIR;
|
|
365
|
-
|
|
366
|
-
const operationRegistry = createOperationRegistry();
|
|
367
|
-
const options: EmitOptions = {
|
|
368
|
-
outputDir: '',
|
|
369
|
-
operationRegistry,
|
|
370
|
-
codecTypeImports: [],
|
|
371
|
-
operationTypeImports: [],
|
|
372
|
-
extensionIds: [],
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have models');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('throws error when models is not an object', async () => {
|
|
379
|
-
const ir = createContractIR({
|
|
380
|
-
models: 'not-an-object' as unknown as Record<string, unknown>,
|
|
381
|
-
}) as ContractIR;
|
|
382
|
-
|
|
383
|
-
const operationRegistry = createOperationRegistry();
|
|
384
|
-
const options: EmitOptions = {
|
|
385
|
-
outputDir: '',
|
|
386
|
-
operationRegistry,
|
|
387
|
-
codecTypeImports: [],
|
|
388
|
-
operationTypeImports: [],
|
|
389
|
-
extensionIds: [],
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have models');
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it('throws error when storage is missing', async () => {
|
|
396
|
-
const ir = createContractIR({
|
|
397
|
-
storage: undefined as unknown as Record<string, unknown>,
|
|
398
|
-
}) as ContractIR;
|
|
399
|
-
|
|
400
|
-
const operationRegistry = createOperationRegistry();
|
|
401
|
-
const options: EmitOptions = {
|
|
402
|
-
outputDir: '',
|
|
403
|
-
operationRegistry,
|
|
404
|
-
codecTypeImports: [],
|
|
405
|
-
operationTypeImports: [],
|
|
406
|
-
extensionIds: [],
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have storage');
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
it('throws error when storage is not an object', async () => {
|
|
413
|
-
const ir = createContractIR({
|
|
414
|
-
storage: 'not-an-object' as unknown as Record<string, unknown>,
|
|
415
|
-
}) as ContractIR;
|
|
416
|
-
|
|
417
|
-
const operationRegistry = createOperationRegistry();
|
|
418
|
-
const options: EmitOptions = {
|
|
419
|
-
outputDir: '',
|
|
420
|
-
operationRegistry,
|
|
421
|
-
codecTypeImports: [],
|
|
422
|
-
operationTypeImports: [],
|
|
423
|
-
extensionIds: [],
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have storage');
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it('throws error when relations is missing', async () => {
|
|
430
|
-
const ir = createContractIR({
|
|
431
|
-
relations: undefined as unknown as Record<string, unknown>,
|
|
432
|
-
}) as ContractIR;
|
|
433
|
-
|
|
434
|
-
const operationRegistry = createOperationRegistry();
|
|
435
|
-
const options: EmitOptions = {
|
|
436
|
-
outputDir: '',
|
|
437
|
-
operationRegistry,
|
|
438
|
-
codecTypeImports: [],
|
|
439
|
-
operationTypeImports: [],
|
|
440
|
-
extensionIds: [],
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have relations');
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('throws error when relations is not an object', async () => {
|
|
447
|
-
const ir = createContractIR({
|
|
448
|
-
relations: 'not-an-object' as unknown as Record<string, unknown>,
|
|
449
|
-
}) as ContractIR;
|
|
450
|
-
|
|
451
|
-
const operationRegistry = createOperationRegistry();
|
|
452
|
-
const options: EmitOptions = {
|
|
453
|
-
outputDir: '',
|
|
454
|
-
operationRegistry,
|
|
455
|
-
codecTypeImports: [],
|
|
456
|
-
operationTypeImports: [],
|
|
457
|
-
extensionIds: [],
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have relations');
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it('throws error when extension packs are missing', async () => {
|
|
464
|
-
const ir = createContractIR({
|
|
465
|
-
extensionPacks: undefined as unknown as Record<string, unknown>,
|
|
466
|
-
}) as ContractIR;
|
|
467
|
-
|
|
468
|
-
const operationRegistry = createOperationRegistry();
|
|
469
|
-
const options: EmitOptions = {
|
|
470
|
-
outputDir: '',
|
|
471
|
-
operationRegistry,
|
|
472
|
-
codecTypeImports: [],
|
|
473
|
-
operationTypeImports: [],
|
|
474
|
-
extensionIds: [],
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
|
|
478
|
-
'ContractIR must have extensionPacks',
|
|
479
|
-
);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it('throws error when extension packs are not an object', async () => {
|
|
483
|
-
const ir = createContractIR({
|
|
484
|
-
extensionPacks: 'not-an-object' as unknown as Record<string, unknown>,
|
|
485
|
-
}) as ContractIR;
|
|
486
|
-
|
|
487
|
-
const operationRegistry = createOperationRegistry();
|
|
488
|
-
const options: EmitOptions = {
|
|
489
|
-
outputDir: '',
|
|
490
|
-
operationRegistry,
|
|
491
|
-
codecTypeImports: [],
|
|
492
|
-
operationTypeImports: [],
|
|
493
|
-
extensionIds: [],
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
|
|
497
|
-
'ContractIR must have extensionPacks',
|
|
498
|
-
);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('throws error when capabilities is missing', async () => {
|
|
502
|
-
const ir = createContractIR({
|
|
503
|
-
capabilities: undefined as unknown as Record<string, Record<string, boolean>>,
|
|
504
|
-
}) as ContractIR;
|
|
505
|
-
|
|
506
|
-
const operationRegistry = createOperationRegistry();
|
|
507
|
-
const options: EmitOptions = {
|
|
508
|
-
outputDir: '',
|
|
509
|
-
operationRegistry,
|
|
510
|
-
codecTypeImports: [],
|
|
511
|
-
operationTypeImports: [],
|
|
512
|
-
extensionIds: [],
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
|
|
516
|
-
'ContractIR must have capabilities',
|
|
517
|
-
);
|
|
252
|
+
await expect(emit(ir, options, mockSqlHook)).resolves.toMatchObject({
|
|
253
|
+
contractJson: expect.any(String),
|
|
254
|
+
contractDts: expect.any(String),
|
|
255
|
+
});
|
|
518
256
|
});
|
|
519
257
|
|
|
520
|
-
it('
|
|
521
|
-
const ir =
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const options: EmitOptions = {
|
|
527
|
-
outputDir: '',
|
|
528
|
-
operationRegistry,
|
|
529
|
-
codecTypeImports: [],
|
|
530
|
-
operationTypeImports: [],
|
|
531
|
-
extensionIds: [],
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
|
|
535
|
-
'ContractIR must have capabilities',
|
|
536
|
-
);
|
|
537
|
-
});
|
|
258
|
+
it('emits contract even when extensionIds are not in contract.extensionPacks', async () => {
|
|
259
|
+
const ir = createTestContract({
|
|
260
|
+
storage: {
|
|
261
|
+
tables: {},
|
|
262
|
+
},
|
|
263
|
+
});
|
|
538
264
|
|
|
539
|
-
|
|
540
|
-
const ir = createContractIR({
|
|
541
|
-
meta: undefined as unknown as Record<string, unknown>,
|
|
542
|
-
}) as ContractIR;
|
|
265
|
+
const mockHookNoTypeValidation = createMockSpi();
|
|
543
266
|
|
|
544
|
-
const
|
|
545
|
-
const options: EmitOptions = {
|
|
546
|
-
outputDir: '',
|
|
547
|
-
operationRegistry,
|
|
267
|
+
const options: EmitStackInput = {
|
|
548
268
|
codecTypeImports: [],
|
|
549
269
|
operationTypeImports: [],
|
|
550
|
-
extensionIds: [],
|
|
270
|
+
extensionIds: ['postgres'],
|
|
551
271
|
};
|
|
552
272
|
|
|
553
|
-
await
|
|
273
|
+
const result = await emit(ir, options, mockHookNoTypeValidation);
|
|
274
|
+
expect(result.contractJson).toBeDefined();
|
|
275
|
+
expect(result.contractDts).toBeDefined();
|
|
554
276
|
});
|
|
555
277
|
|
|
556
|
-
it('
|
|
557
|
-
const ir =
|
|
558
|
-
|
|
559
|
-
})
|
|
278
|
+
it('defaults codecTypeImports and operationTypeImports to empty arrays when omitted', async () => {
|
|
279
|
+
const ir = createTestContract({
|
|
280
|
+
storage: { tables: {} },
|
|
281
|
+
});
|
|
560
282
|
|
|
561
|
-
const
|
|
562
|
-
const options: EmitOptions = {
|
|
563
|
-
outputDir: '',
|
|
564
|
-
operationRegistry,
|
|
565
|
-
codecTypeImports: [],
|
|
566
|
-
operationTypeImports: [],
|
|
283
|
+
const options: EmitStackInput = {
|
|
567
284
|
extensionIds: [],
|
|
568
285
|
};
|
|
569
286
|
|
|
570
|
-
await
|
|
287
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
288
|
+
expect(result.contractDts).toContain('export type CodecTypes');
|
|
289
|
+
expect(result.contractDts).toContain('export type OperationTypes');
|
|
571
290
|
});
|
|
572
291
|
|
|
573
|
-
it('
|
|
574
|
-
const ir =
|
|
575
|
-
|
|
576
|
-
})
|
|
577
|
-
|
|
578
|
-
const operationRegistry = createOperationRegistry();
|
|
579
|
-
const options: EmitOptions = {
|
|
580
|
-
outputDir: '',
|
|
581
|
-
operationRegistry,
|
|
582
|
-
codecTypeImports: [],
|
|
583
|
-
operationTypeImports: [],
|
|
584
|
-
extensionIds: [],
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have sources');
|
|
588
|
-
});
|
|
292
|
+
it('passes parameterizedTypeImports and queryOperationTypeImports to generateContractDts', async () => {
|
|
293
|
+
const ir = createTestContract({
|
|
294
|
+
storage: { tables: {} },
|
|
295
|
+
});
|
|
589
296
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}) as ContractIR;
|
|
297
|
+
const queryOperationTypeImports: TypesImportSpec[] = [
|
|
298
|
+
{ package: '@ext/query', named: 'QueryOperationTypes', alias: 'ExtQueryOpTypes' },
|
|
299
|
+
];
|
|
594
300
|
|
|
595
|
-
const
|
|
596
|
-
const options: EmitOptions = {
|
|
597
|
-
outputDir: '',
|
|
598
|
-
operationRegistry,
|
|
301
|
+
const options: EmitStackInput = {
|
|
599
302
|
codecTypeImports: [],
|
|
600
303
|
operationTypeImports: [],
|
|
601
304
|
extensionIds: [],
|
|
305
|
+
queryOperationTypeImports,
|
|
602
306
|
};
|
|
603
307
|
|
|
604
|
-
await
|
|
308
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
309
|
+
expect(result.contractDts).toContain("from '@ext/query'");
|
|
605
310
|
});
|
|
606
311
|
|
|
607
|
-
it('emits
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
312
|
+
it('emits execution clause when contract has execution section', async () => {
|
|
313
|
+
const ir = createTestContract({
|
|
314
|
+
storage: { tables: {} },
|
|
315
|
+
execution: {
|
|
316
|
+
executionHash: 'sha256:abc123',
|
|
317
|
+
operations: {},
|
|
612
318
|
},
|
|
613
319
|
});
|
|
614
320
|
|
|
615
|
-
|
|
616
|
-
const mockHookNoTypeValidation: TargetFamilyHook = {
|
|
617
|
-
id: 'sql',
|
|
618
|
-
validateTypes: () => {
|
|
619
|
-
// Skip type validation
|
|
620
|
-
},
|
|
621
|
-
validateStructure: (ir: ContractIR) => {
|
|
622
|
-
if (ir.targetFamily !== 'sql') {
|
|
623
|
-
throw new Error(`Expected targetFamily "sql", got "${ir.targetFamily}"`);
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
generateContractTypes: (_ir, _codecTypeImports, _operationTypeImports) => {
|
|
627
|
-
void _codecTypeImports;
|
|
628
|
-
void _operationTypeImports;
|
|
629
|
-
return `// Generated contract types
|
|
630
|
-
export type CodecTypes = Record<string, never>;
|
|
631
|
-
export type LaneCodecTypes = CodecTypes;
|
|
632
|
-
export type Contract = unknown;
|
|
633
|
-
`;
|
|
634
|
-
},
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
const options: EmitOptions = {
|
|
638
|
-
outputDir: '',
|
|
639
|
-
operationRegistry: createOperationRegistry(),
|
|
321
|
+
const options: EmitStackInput = {
|
|
640
322
|
codecTypeImports: [],
|
|
641
323
|
operationTypeImports: [],
|
|
642
|
-
extensionIds: [
|
|
324
|
+
extensionIds: [],
|
|
643
325
|
};
|
|
644
326
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
expect(result.
|
|
648
|
-
expect(result.
|
|
327
|
+
const result = await emit(ir, options, mockSqlHook);
|
|
328
|
+
expect(result.contractDts).toContain('readonly execution:');
|
|
329
|
+
expect(result.contractDts).toContain('readonly executionHash: ExecutionHash');
|
|
330
|
+
expect(result.executionHash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
649
331
|
});
|
|
650
332
|
});
|