@prisma-next/emitter 0.5.0-dev.4 → 0.5.0-dev.40
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/dist/domain-type-generation.d.mts +18 -5
- package/dist/domain-type-generation.d.mts.map +1 -1
- package/dist/domain-type-generation.mjs +10 -9
- package/dist/domain-type-generation.mjs.map +1 -1
- package/dist/exports/index.mjs +6 -1
- package/dist/exports/index.mjs.map +1 -1
- package/package.json +8 -9
- package/src/domain-type-generation.ts +36 -5
- package/src/generate-contract-dts.ts +9 -0
- package/test/canonicalization.test.ts +0 -387
- package/test/domain-type-generation.test.ts +0 -997
- package/test/emitter.integration.test.ts +0 -219
- package/test/emitter.roundtrip.test.ts +0 -296
- package/test/emitter.test.ts +0 -367
- package/test/hashing.test.ts +0 -34
- package/test/mock-spi.ts +0 -18
- package/test/type-expression-safety.test.ts +0 -34
- package/test/utils.ts +0 -31
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { canonicalizeContract } from '@prisma-next/contract/hashing';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { createTestContract } from './utils';
|
|
4
|
-
|
|
5
|
-
describe('canonicalization', () => {
|
|
6
|
-
it('orders top-level sections correctly', () => {
|
|
7
|
-
const ir = createTestContract({
|
|
8
|
-
capabilities: { postgres: { jsonAgg: true } },
|
|
9
|
-
meta: { emitterVersion: 'test' },
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const result = canonicalizeContract(ir);
|
|
13
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
14
|
-
|
|
15
|
-
const keys = Object.keys(parsed);
|
|
16
|
-
const schemaVersionIndex = keys.indexOf('schemaVersion');
|
|
17
|
-
const targetFamilyIndex = keys.indexOf('targetFamily');
|
|
18
|
-
const targetIndex = keys.indexOf('target');
|
|
19
|
-
const modelsIndex = keys.indexOf('models');
|
|
20
|
-
const storageIndex = keys.indexOf('storage');
|
|
21
|
-
const capabilitiesIndex = keys.indexOf('capabilities');
|
|
22
|
-
const metaIndex = keys.indexOf('meta');
|
|
23
|
-
|
|
24
|
-
expect(schemaVersionIndex).toBeLessThan(targetFamilyIndex);
|
|
25
|
-
expect(targetFamilyIndex).toBeLessThan(targetIndex);
|
|
26
|
-
expect(targetIndex).toBeLessThan(modelsIndex);
|
|
27
|
-
expect(modelsIndex).toBeLessThan(storageIndex);
|
|
28
|
-
expect(storageIndex).toBeLessThan(capabilitiesIndex);
|
|
29
|
-
expect(capabilitiesIndex).toBeLessThan(metaIndex);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('preserves nullable false on columns', () => {
|
|
33
|
-
const ir = createTestContract({
|
|
34
|
-
storage: {
|
|
35
|
-
tables: {
|
|
36
|
-
user: {
|
|
37
|
-
columns: {
|
|
38
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
39
|
-
email: { codecId: 'pg/text@1', nativeType: 'text', nullable: true },
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const result = canonicalizeContract(ir);
|
|
47
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
48
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
49
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
50
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
51
|
-
const columns = user['columns'] as Record<string, unknown>;
|
|
52
|
-
const id = columns['id'] as Record<string, unknown>;
|
|
53
|
-
const email = columns['email'] as Record<string, unknown>;
|
|
54
|
-
expect(id['nullable']).toBe(false);
|
|
55
|
-
expect(email['nullable']).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('preserves nullable:false for columns with defaults', () => {
|
|
59
|
-
const ir = createTestContract({
|
|
60
|
-
storage: {
|
|
61
|
-
tables: {
|
|
62
|
-
user: {
|
|
63
|
-
columns: {
|
|
64
|
-
created_at: {
|
|
65
|
-
codecId: 'pg/timestamptz@1',
|
|
66
|
-
nativeType: 'timestamptz',
|
|
67
|
-
nullable: false,
|
|
68
|
-
default: { kind: 'function', expression: 'now()' },
|
|
69
|
-
},
|
|
70
|
-
updated_at: {
|
|
71
|
-
codecId: 'pg/timestamptz@1',
|
|
72
|
-
nativeType: 'timestamptz',
|
|
73
|
-
nullable: true,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const result = canonicalizeContract(ir);
|
|
82
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
83
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
84
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
85
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
86
|
-
const columns = user['columns'] as Record<string, unknown>;
|
|
87
|
-
const createdAt = columns['created_at'] as Record<string, unknown>;
|
|
88
|
-
const updatedAt = columns['updated_at'] as Record<string, unknown>;
|
|
89
|
-
expect(createdAt['nullable']).toBe(false);
|
|
90
|
-
expect(updatedAt['nullable']).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('preserves nullable:true for columns with defaults', () => {
|
|
94
|
-
const ir = createTestContract({
|
|
95
|
-
storage: {
|
|
96
|
-
tables: {
|
|
97
|
-
user: {
|
|
98
|
-
columns: {
|
|
99
|
-
bio: {
|
|
100
|
-
codecId: 'pg/text@1',
|
|
101
|
-
nativeType: 'text',
|
|
102
|
-
nullable: true,
|
|
103
|
-
default: { kind: 'literal', value: '' },
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const result = canonicalizeContract(ir);
|
|
112
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
113
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
114
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
115
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
116
|
-
const columns = user['columns'] as Record<string, unknown>;
|
|
117
|
-
const bio = columns['bio'] as Record<string, unknown>;
|
|
118
|
-
expect(bio['nullable']).toBe(true);
|
|
119
|
-
expect(bio['default']).toEqual({ kind: 'literal', value: '' });
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('omits empty arrays and objects except required ones', () => {
|
|
123
|
-
const ir = createTestContract();
|
|
124
|
-
|
|
125
|
-
const result = canonicalizeContract(ir);
|
|
126
|
-
const parsed = JSON.parse(result);
|
|
127
|
-
expect(parsed).toMatchObject({
|
|
128
|
-
models: expect.anything(),
|
|
129
|
-
storage: {
|
|
130
|
-
tables: expect.anything(),
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
// Required top-level fields (capabilities, extensionPacks, meta) are preserved even when empty.
|
|
134
|
-
expect(parsed).toMatchObject({
|
|
135
|
-
capabilities: expect.anything(),
|
|
136
|
-
extensionPacks: expect.anything(),
|
|
137
|
-
meta: expect.anything(),
|
|
138
|
-
});
|
|
139
|
-
expect(parsed).not.toHaveProperty('relations');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('preserves semantic array order for column lists', () => {
|
|
143
|
-
const ir = createTestContract({
|
|
144
|
-
storage: {
|
|
145
|
-
tables: {
|
|
146
|
-
user: {
|
|
147
|
-
columns: {
|
|
148
|
-
first: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
149
|
-
second: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
150
|
-
},
|
|
151
|
-
primaryKey: {
|
|
152
|
-
columns: ['second', 'first'],
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const result1 = canonicalizeContract(ir);
|
|
160
|
-
|
|
161
|
-
const ir2 = createTestContract({
|
|
162
|
-
storage: {
|
|
163
|
-
tables: {
|
|
164
|
-
user: {
|
|
165
|
-
columns: {
|
|
166
|
-
first: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
167
|
-
second: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
168
|
-
},
|
|
169
|
-
primaryKey: {
|
|
170
|
-
columns: ['first', 'second'],
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const result2 = canonicalizeContract(ir2);
|
|
178
|
-
|
|
179
|
-
expect(result1).not.toBe(result2);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('sorts indexes by canonical name', () => {
|
|
183
|
-
const ir = createTestContract({
|
|
184
|
-
storage: {
|
|
185
|
-
tables: {
|
|
186
|
-
user: {
|
|
187
|
-
columns: {
|
|
188
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
189
|
-
},
|
|
190
|
-
indexes: [
|
|
191
|
-
{ columns: ['id'], name: 'user_email_idx' },
|
|
192
|
-
{ columns: ['id'], name: 'user_name_idx' },
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const result = canonicalizeContract(ir);
|
|
200
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
201
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
202
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
203
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
204
|
-
const indexes = user['indexes'] as Array<{ name: string }>;
|
|
205
|
-
const indexNames = indexes.map((idx) => idx.name);
|
|
206
|
-
expect(indexNames).toEqual(['user_email_idx', 'user_name_idx']);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('sorts uniques by canonical name', () => {
|
|
210
|
-
const ir = createTestContract({
|
|
211
|
-
storage: {
|
|
212
|
-
tables: {
|
|
213
|
-
user: {
|
|
214
|
-
columns: {
|
|
215
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
216
|
-
email: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
217
|
-
username: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
218
|
-
},
|
|
219
|
-
uniques: [
|
|
220
|
-
{ columns: ['username'], name: 'user_username_key' },
|
|
221
|
-
{ columns: ['email'], name: 'user_email_key' },
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const result = canonicalizeContract(ir);
|
|
229
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
230
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
231
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
232
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
233
|
-
const uniques = user['uniques'] as Array<{ name: string }>;
|
|
234
|
-
const uniqueNames = uniques.map((u) => u.name);
|
|
235
|
-
expect(uniqueNames).toEqual(['user_email_key', 'user_username_key']);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('preserves column order in composite unique constraints', () => {
|
|
239
|
-
const ir = createTestContract({
|
|
240
|
-
storage: {
|
|
241
|
-
tables: {
|
|
242
|
-
user: {
|
|
243
|
-
columns: {
|
|
244
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false },
|
|
245
|
-
first_name: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
246
|
-
last_name: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
247
|
-
},
|
|
248
|
-
uniques: [{ columns: ['last_name', 'first_name'], name: 'user_name_key' }],
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const result = canonicalizeContract(ir);
|
|
255
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
256
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
257
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
258
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
259
|
-
const uniques = user['uniques'] as Array<{ columns: string[] }>;
|
|
260
|
-
expect(uniques[0]!.columns).toEqual(['last_name', 'first_name']);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('sorts nested object keys lexicographically', () => {
|
|
264
|
-
const ir = createTestContract({
|
|
265
|
-
storage: {
|
|
266
|
-
tables: {
|
|
267
|
-
user: {
|
|
268
|
-
columns: {
|
|
269
|
-
z_field: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
270
|
-
a_field: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
271
|
-
m_field: { codecId: 'pg/text@1', nativeType: 'text', nullable: false },
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const result = canonicalizeContract(ir);
|
|
279
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
280
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
281
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
282
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
283
|
-
const columns = user['columns'] as Record<string, unknown>;
|
|
284
|
-
const columnKeys = Object.keys(columns);
|
|
285
|
-
expect(columnKeys).toEqual(['a_field', 'm_field', 'z_field']);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
describe('Mongo storage.collections preservation', () => {
|
|
289
|
-
it('preserves empty storage.collections container', () => {
|
|
290
|
-
const ir = createTestContract({
|
|
291
|
-
targetFamily: 'mongo',
|
|
292
|
-
target: 'mongo',
|
|
293
|
-
storage: { collections: {} },
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const result = canonicalizeContract(ir);
|
|
297
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
298
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
299
|
-
expect(storage['collections']).toEqual({});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('preserves collection entries with empty payloads', () => {
|
|
303
|
-
const ir = createTestContract({
|
|
304
|
-
targetFamily: 'mongo',
|
|
305
|
-
target: 'mongo',
|
|
306
|
-
storage: { collections: { users: {}, posts: {} } },
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
const result = canonicalizeContract(ir);
|
|
310
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
311
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
312
|
-
const collections = storage['collections'] as Record<string, unknown>;
|
|
313
|
-
expect(collections['users']).toEqual({});
|
|
314
|
-
expect(collections['posts']).toEqual({});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('sorts collection names lexicographically', () => {
|
|
318
|
-
const ir = createTestContract({
|
|
319
|
-
targetFamily: 'mongo',
|
|
320
|
-
target: 'mongo',
|
|
321
|
-
storage: { collections: { zebras: {}, apples: {}, mangoes: {} } },
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const result = canonicalizeContract(ir);
|
|
325
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
326
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
327
|
-
const collections = storage['collections'] as Record<string, unknown>;
|
|
328
|
-
expect(Object.keys(collections)).toEqual(['apples', 'mangoes', 'zebras']);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('produces different hashes when collections differ', () => {
|
|
332
|
-
const ir1 = createTestContract({
|
|
333
|
-
targetFamily: 'mongo',
|
|
334
|
-
target: 'mongo',
|
|
335
|
-
storage: { collections: { users: {} } },
|
|
336
|
-
});
|
|
337
|
-
const ir2 = createTestContract({
|
|
338
|
-
targetFamily: 'mongo',
|
|
339
|
-
target: 'mongo',
|
|
340
|
-
storage: { collections: { users: {}, posts: {} } },
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const result1 = canonicalizeContract(ir1);
|
|
344
|
-
const result2 = canonicalizeContract(ir2);
|
|
345
|
-
expect(result1).not.toBe(result2);
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('sorts extension namespaces lexicographically', () => {
|
|
350
|
-
const ir = createTestContract({
|
|
351
|
-
extensionPacks: {
|
|
352
|
-
pgvector: { version: '0.0.1' },
|
|
353
|
-
postgres: { version: '0.0.1' },
|
|
354
|
-
another: { version: '0.0.1' },
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
const result = canonicalizeContract(ir);
|
|
359
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
360
|
-
const extensionPacks = parsed['extensionPacks'] as Record<string, unknown>;
|
|
361
|
-
const extensionKeys = Object.keys(extensionPacks);
|
|
362
|
-
expect(extensionKeys).toEqual(['another', 'pgvector', 'postgres']);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('omits generated false', () => {
|
|
366
|
-
const ir = createTestContract({
|
|
367
|
-
storage: {
|
|
368
|
-
tables: {
|
|
369
|
-
user: {
|
|
370
|
-
columns: {
|
|
371
|
-
id: { codecId: 'pg/int4@1', nativeType: 'int4', nullable: false, generated: false },
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const result = canonicalizeContract(ir);
|
|
379
|
-
const parsed = JSON.parse(result) as Record<string, unknown>;
|
|
380
|
-
const storage = parsed['storage'] as Record<string, unknown>;
|
|
381
|
-
const tables = storage['tables'] as Record<string, unknown>;
|
|
382
|
-
const user = tables['user'] as Record<string, unknown>;
|
|
383
|
-
const columns = user['columns'] as Record<string, unknown>;
|
|
384
|
-
const id = columns['id'] as Record<string, unknown>;
|
|
385
|
-
expect(id['generated']).toBeUndefined();
|
|
386
|
-
});
|
|
387
|
-
});
|