@prisma-next/emitter 0.3.0-pr.93.5 → 0.3.0-pr.94.2

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.
@@ -1,650 +0,0 @@
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';
10
- import { timeouts } from '@prisma-next/test-utils';
11
- import { describe, expect, it } from 'vitest';
12
- import { createContractIR } from './utils';
13
-
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, { 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
- };
60
-
61
- describe('emitter', () => {
62
- it(
63
- 'emits contract.json and contract.d.ts',
64
- async () => {
65
- const ir = createContractIR({
66
- models: {
67
- User: {
68
- storage: { table: 'user' },
69
- fields: {
70
- id: { column: 'id' },
71
- email: { column: 'email' },
72
- },
73
- relations: {},
74
- },
75
- },
76
- storage: {
77
- tables: {
78
- user: {
79
- columns: {
80
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
81
- email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
82
- },
83
- primaryKey: { columns: ['id'] },
84
- uniques: [],
85
- indexes: [],
86
- foreignKeys: [],
87
- },
88
- },
89
- },
90
- extensionPacks: {
91
- postgres: {
92
- version: '0.0.1',
93
- },
94
- pg: {},
95
- },
96
- });
97
-
98
- // Create empty registry and minimal test data (emitter tests don't load packs)
99
- const operationRegistry = createOperationRegistry();
100
- const codecTypeImports: TypesImportSpec[] = [];
101
- const operationTypeImports: TypesImportSpec[] = [];
102
- const extensionIds = ['postgres', 'pg'];
103
- const options: EmitOptions = {
104
- outputDir: '',
105
- operationRegistry,
106
- codecTypeImports,
107
- operationTypeImports,
108
- extensionIds,
109
- };
110
-
111
- const result = await emit(ir, options, mockSqlHook);
112
- expect(result.coreHash).toMatch(/^sha256:[a-f0-9]{64}$/);
113
- expect(result.contractDts).toContain('export type Contract');
114
- expect(result.contractDts).toContain('CodecTypes');
115
-
116
- const contractJson = JSON.parse(result.contractJson) as Record<string, unknown>;
117
- const storage = contractJson['storage'] as Record<string, unknown>;
118
- const tables = storage['tables'] as Record<string, unknown>;
119
- expect(tables).toBeDefined();
120
- },
121
- timeouts.typeScriptCompilation,
122
- );
123
-
124
- it('does not validate codec namespaces against extensions', async () => {
125
- // Namespace validation removed - codecs can use any namespace
126
- const ir = createContractIR({
127
- storage: {
128
- tables: {
129
- user: {
130
- columns: {
131
- id: { codecId: 'unknown/type@1', nativeType: 'unknown_type', nullable: false },
132
- },
133
- primaryKey: { columns: ['id'] },
134
- uniques: [],
135
- indexes: [],
136
- foreignKeys: [],
137
- },
138
- },
139
- },
140
- });
141
-
142
- const operationRegistry = createOperationRegistry();
143
- const options: EmitOptions = {
144
- outputDir: '',
145
- operationRegistry,
146
- codecTypeImports: [],
147
- operationTypeImports: [],
148
- extensionIds: [],
149
- };
150
-
151
- // Should succeed - namespace validation removed
152
- const result = await emit(ir, options, mockSqlHook);
153
- expect(result.contractJson).toBeDefined();
154
- expect(result.contractDts).toBeDefined();
155
- });
156
-
157
- it('validates type ID format', async () => {
158
- const ir = createContractIR({
159
- storage: {
160
- tables: {
161
- user: {
162
- columns: {
163
- id: { codecId: 'invalid-format', nativeType: 'int4', nullable: false },
164
- },
165
- primaryKey: { columns: ['id'] },
166
- uniques: [],
167
- indexes: [],
168
- foreignKeys: [],
169
- },
170
- },
171
- },
172
- });
173
-
174
- const operationRegistry = createOperationRegistry();
175
- const options: EmitOptions = {
176
- outputDir: '',
177
- operationRegistry,
178
- codecTypeImports: [],
179
- operationTypeImports: [],
180
- extensionIds: [],
181
- };
182
-
183
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('invalid codecId format');
184
- });
185
-
186
- it('throws error when targetFamily is missing', async () => {
187
- const ir = createContractIR({
188
- targetFamily: undefined as unknown as string,
189
- storage: {
190
- tables: {
191
- user: {
192
- columns: {
193
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
194
- },
195
- uniques: [],
196
- indexes: [],
197
- foreignKeys: [],
198
- },
199
- },
200
- },
201
- }) as ContractIR;
202
-
203
- const operationRegistry = createOperationRegistry();
204
- const options: EmitOptions = {
205
- outputDir: '',
206
- operationRegistry,
207
- codecTypeImports: [],
208
- operationTypeImports: [],
209
- extensionIds: [],
210
- };
211
-
212
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
213
- 'ContractIR must have targetFamily',
214
- );
215
- });
216
-
217
- it('throws error when target is missing', async () => {
218
- const ir = createContractIR({
219
- target: undefined as unknown as string,
220
- storage: {
221
- tables: {
222
- user: {
223
- columns: {
224
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
225
- },
226
- uniques: [],
227
- indexes: [],
228
- foreignKeys: [],
229
- },
230
- },
231
- },
232
- }) as ContractIR;
233
-
234
- const operationRegistry = createOperationRegistry();
235
- const options: EmitOptions = {
236
- outputDir: '',
237
- operationRegistry,
238
- codecTypeImports: [],
239
- operationTypeImports: [],
240
- extensionIds: [],
241
- };
242
-
243
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have target');
244
- });
245
-
246
- it('emits contract even when extension pack namespace does not match extensionIds', async () => {
247
- // Adapter-provided codecs (pg/int4@1) don't need to be in contract.extensionPacks
248
- const ir = createContractIR({
249
- storage: {
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
- },
260
- },
261
- });
262
-
263
- const operationRegistry = createOperationRegistry();
264
- const options: EmitOptions = {
265
- outputDir: '',
266
- operationRegistry,
267
- codecTypeImports: [],
268
- operationTypeImports: [],
269
- extensionIds: [], // No extensions, but codec still works
270
- };
271
-
272
- // Should succeed - adapter-provided codecs don't need to be in contract.extensionPacks
273
- const result = await emit(ir, options, mockSqlHook);
274
- expect(result.contractJson).toBeDefined();
275
- expect(result.contractDts).toBeDefined();
276
- });
277
-
278
- it('handles missing extensionPacks field', async () => {
279
- // Namespace validation removed - codecs can use any namespace
280
- const ir = createContractIR({
281
- storage: {
282
- tables: {
283
- user: {
284
- columns: {
285
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
286
- },
287
- uniques: [],
288
- indexes: [],
289
- foreignKeys: [],
290
- },
291
- },
292
- },
293
- });
294
-
295
- const operationRegistry = createOperationRegistry();
296
- const options: EmitOptions = {
297
- outputDir: '',
298
- operationRegistry,
299
- codecTypeImports: [],
300
- operationTypeImports: [],
301
- extensionIds: [],
302
- };
303
-
304
- // Should succeed - namespace validation removed
305
- const result = await emit(ir, options, mockSqlHook);
306
- expect(result.contractJson).toBeDefined();
307
- expect(result.contractDts).toBeDefined();
308
- });
309
-
310
- it('handles empty packs array', async () => {
311
- // Namespace validation removed - codecs can use any namespace
312
- const ir = createContractIR({
313
- storage: {
314
- tables: {
315
- user: {
316
- columns: {
317
- id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
318
- },
319
- uniques: [],
320
- indexes: [],
321
- foreignKeys: [],
322
- },
323
- },
324
- },
325
- });
326
-
327
- const operationRegistry = createOperationRegistry();
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,
351
- codecTypeImports: [],
352
- operationTypeImports: [],
353
- extensionIds: [],
354
- };
355
-
356
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow(
357
- 'ContractIR must have schemaVersion',
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
- );
518
- });
519
-
520
- it('throws error when capabilities is not an object', async () => {
521
- const ir = createContractIR({
522
- capabilities: 'not-an-object' as unknown as Record<string, Record<string, boolean>>,
523
- }) as ContractIR;
524
-
525
- const operationRegistry = createOperationRegistry();
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
- });
538
-
539
- it('throws error when meta is missing', async () => {
540
- const ir = createContractIR({
541
- meta: undefined as unknown as Record<string, unknown>,
542
- }) as ContractIR;
543
-
544
- const operationRegistry = createOperationRegistry();
545
- const options: EmitOptions = {
546
- outputDir: '',
547
- operationRegistry,
548
- codecTypeImports: [],
549
- operationTypeImports: [],
550
- extensionIds: [],
551
- };
552
-
553
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have meta');
554
- });
555
-
556
- it('throws error when meta is not an object', async () => {
557
- const ir = createContractIR({
558
- meta: 'not-an-object' as unknown as Record<string, unknown>,
559
- }) as ContractIR;
560
-
561
- const operationRegistry = createOperationRegistry();
562
- const options: EmitOptions = {
563
- outputDir: '',
564
- operationRegistry,
565
- codecTypeImports: [],
566
- operationTypeImports: [],
567
- extensionIds: [],
568
- };
569
-
570
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have meta');
571
- });
572
-
573
- it('throws error when sources is missing', async () => {
574
- const ir = createContractIR({
575
- sources: undefined as unknown as Record<string, unknown>,
576
- }) as ContractIR;
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
- });
589
-
590
- it('throws error when sources is not an object', async () => {
591
- const ir = createContractIR({
592
- sources: 'not-an-object' as unknown as Record<string, unknown>,
593
- }) as ContractIR;
594
-
595
- const operationRegistry = createOperationRegistry();
596
- const options: EmitOptions = {
597
- outputDir: '',
598
- operationRegistry,
599
- codecTypeImports: [],
600
- operationTypeImports: [],
601
- extensionIds: [],
602
- };
603
-
604
- await expect(emit(ir, options, mockSqlHook)).rejects.toThrow('ContractIR must have sources');
605
- });
606
-
607
- it('emits contract even when extensionIds are not in contract.extensionPacks', async () => {
608
- // extensionIds includes adapters/targets which are not in contract.extensionPacks
609
- const ir = createContractIR({
610
- storage: {
611
- tables: {},
612
- },
613
- });
614
-
615
- // Use a mock hook that doesn't validate types to avoid type validation errors
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(),
640
- codecTypeImports: [],
641
- operationTypeImports: [],
642
- extensionIds: ['postgres'], // Adapter ID, not an extension
643
- };
644
-
645
- // Should succeed - extensionIds can include adapters/targets
646
- const result = await emit(ir, options, mockHookNoTypeValidation);
647
- expect(result.contractJson).toBeDefined();
648
- expect(result.contractDts).toBeDefined();
649
- });
650
- });