@prisma-next/emitter 0.3.0-pr.99.5 → 0.3.0
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 +27 -11
- 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,12 +1,12 @@
|
|
|
1
|
-
import { canonicalizeContract } from '@prisma-next/
|
|
1
|
+
import { canonicalizeContract } from '@prisma-next/contract/hashing';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import { createTestContract } from './utils';
|
|
4
4
|
|
|
5
5
|
describe('canonicalization', () => {
|
|
6
6
|
it('orders top-level sections correctly', () => {
|
|
7
|
-
const ir =
|
|
7
|
+
const ir = createTestContract({
|
|
8
8
|
capabilities: { postgres: { jsonAgg: true } },
|
|
9
|
-
meta: {
|
|
9
|
+
meta: { emitterVersion: 'test' },
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
const result = canonicalizeContract(ir);
|
|
@@ -29,8 +29,8 @@ describe('canonicalization', () => {
|
|
|
29
29
|
expect(capabilitiesIndex).toBeLessThan(metaIndex);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('
|
|
33
|
-
const ir =
|
|
32
|
+
it('preserves nullable false on columns', () => {
|
|
33
|
+
const ir = createTestContract({
|
|
34
34
|
storage: {
|
|
35
35
|
tables: {
|
|
36
36
|
user: {
|
|
@@ -51,12 +51,76 @@ describe('canonicalization', () => {
|
|
|
51
51
|
const columns = user['columns'] as Record<string, unknown>;
|
|
52
52
|
const id = columns['id'] as Record<string, unknown>;
|
|
53
53
|
const email = columns['email'] as Record<string, unknown>;
|
|
54
|
-
expect(id['nullable']).
|
|
54
|
+
expect(id['nullable']).toBe(false);
|
|
55
55
|
expect(email['nullable']).toBe(true);
|
|
56
56
|
});
|
|
57
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
|
+
|
|
58
122
|
it('omits empty arrays and objects except required ones', () => {
|
|
59
|
-
const ir =
|
|
123
|
+
const ir = createTestContract();
|
|
60
124
|
|
|
61
125
|
const result = canonicalizeContract(ir);
|
|
62
126
|
const parsed = JSON.parse(result);
|
|
@@ -66,19 +130,17 @@ describe('canonicalization', () => {
|
|
|
66
130
|
tables: expect.anything(),
|
|
67
131
|
},
|
|
68
132
|
});
|
|
69
|
-
// Required top-level fields (capabilities, extensionPacks, meta
|
|
70
|
-
// because they are required by ContractIR and needed for round-trip tests
|
|
133
|
+
// Required top-level fields (capabilities, extensionPacks, meta) are preserved even when empty.
|
|
71
134
|
expect(parsed).toMatchObject({
|
|
72
135
|
capabilities: expect.anything(),
|
|
73
136
|
extensionPacks: expect.anything(),
|
|
74
137
|
meta: expect.anything(),
|
|
75
|
-
relations: expect.anything(),
|
|
76
|
-
sources: expect.anything(),
|
|
77
138
|
});
|
|
139
|
+
expect(parsed).not.toHaveProperty('relations');
|
|
78
140
|
});
|
|
79
141
|
|
|
80
142
|
it('preserves semantic array order for column lists', () => {
|
|
81
|
-
const ir =
|
|
143
|
+
const ir = createTestContract({
|
|
82
144
|
storage: {
|
|
83
145
|
tables: {
|
|
84
146
|
user: {
|
|
@@ -96,7 +158,7 @@ describe('canonicalization', () => {
|
|
|
96
158
|
|
|
97
159
|
const result1 = canonicalizeContract(ir);
|
|
98
160
|
|
|
99
|
-
const ir2 =
|
|
161
|
+
const ir2 = createTestContract({
|
|
100
162
|
storage: {
|
|
101
163
|
tables: {
|
|
102
164
|
user: {
|
|
@@ -117,8 +179,8 @@ describe('canonicalization', () => {
|
|
|
117
179
|
expect(result1).not.toBe(result2);
|
|
118
180
|
});
|
|
119
181
|
|
|
120
|
-
it('sorts
|
|
121
|
-
const ir =
|
|
182
|
+
it('sorts indexes by canonical name', () => {
|
|
183
|
+
const ir = createTestContract({
|
|
122
184
|
storage: {
|
|
123
185
|
tables: {
|
|
124
186
|
user: {
|
|
@@ -144,8 +206,62 @@ describe('canonicalization', () => {
|
|
|
144
206
|
expect(indexNames).toEqual(['user_email_idx', 'user_name_idx']);
|
|
145
207
|
});
|
|
146
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
|
+
|
|
147
263
|
it('sorts nested object keys lexicographically', () => {
|
|
148
|
-
const ir =
|
|
264
|
+
const ir = createTestContract({
|
|
149
265
|
storage: {
|
|
150
266
|
tables: {
|
|
151
267
|
user: {
|
|
@@ -169,8 +285,69 @@ describe('canonicalization', () => {
|
|
|
169
285
|
expect(columnKeys).toEqual(['a_field', 'm_field', 'z_field']);
|
|
170
286
|
});
|
|
171
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
|
+
|
|
172
349
|
it('sorts extension namespaces lexicographically', () => {
|
|
173
|
-
const ir =
|
|
350
|
+
const ir = createTestContract({
|
|
174
351
|
extensionPacks: {
|
|
175
352
|
pgvector: { version: '0.0.1' },
|
|
176
353
|
postgres: { version: '0.0.1' },
|
|
@@ -186,7 +363,7 @@ describe('canonicalization', () => {
|
|
|
186
363
|
});
|
|
187
364
|
|
|
188
365
|
it('omits generated false', () => {
|
|
189
|
-
const ir =
|
|
366
|
+
const ir = createTestContract({
|
|
190
367
|
storage: {
|
|
191
368
|
tables: {
|
|
192
369
|
user: {
|