@contractkit/plugin-typescript 0.16.1
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/.turbo/turbo-build$colon$ci.log +35 -0
- package/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-test$colon$ci.log +81 -0
- package/.turbo/turbo-test.log +19 -0
- package/CHANGELOG.md +151 -0
- package/README.md +153 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +1882 -0
- package/coverage/coverage-final.json +9 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/codegen-contract.ts.html +3331 -0
- package/coverage/src/codegen-operation.ts.html +2530 -0
- package/coverage/src/codegen-plain-types.ts.html +901 -0
- package/coverage/src/codegen-sdk.ts.html +2797 -0
- package/coverage/src/index.html +206 -0
- package/coverage/src/index.ts.html +1360 -0
- package/coverage/src/path-utils.ts.html +649 -0
- package/coverage/src/ts-render.ts.html +592 -0
- package/coverage/tests/helpers.ts.html +826 -0
- package/coverage/tests/index.html +116 -0
- package/dist/codegen-contract.d.ts +56 -0
- package/dist/codegen-contract.d.ts.map +1 -0
- package/dist/codegen-operation.d.ts +25 -0
- package/dist/codegen-operation.d.ts.map +1 -0
- package/dist/codegen-plain-types.d.ts +10 -0
- package/dist/codegen-plain-types.d.ts.map +1 -0
- package/dist/codegen-sdk.d.ts +38 -0
- package/dist/codegen-sdk.d.ts.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3162 -0
- package/dist/index.js.map +1 -0
- package/dist/path-utils.d.ts +15 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/ts-render.d.ts +20 -0
- package/dist/ts-render.d.ts.map +1 -0
- package/eslint.config.js +6 -0
- package/package.json +43 -0
- package/src/codegen-contract.ts +1082 -0
- package/src/codegen-operation.ts +815 -0
- package/src/codegen-plain-types.ts +272 -0
- package/src/codegen-sdk.ts +904 -0
- package/src/index.ts +425 -0
- package/src/path-utils.ts +188 -0
- package/src/ts-render.ts +169 -0
- package/tests/codegen-contract.test.ts +1004 -0
- package/tests/codegen-operation.test.ts +939 -0
- package/tests/codegen-plain-types.test.ts +636 -0
- package/tests/codegen-sdk.test.ts +1500 -0
- package/tests/codegen-server.test.ts +192 -0
- package/tests/helpers.ts +247 -0
- package/tests/pipeline.test.ts +372 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { parseCk, decomposeCk, validateOp, validateRefs, applyOptionsDefaults, DiagnosticCollector } from '@contractkit/core';
|
|
2
|
+
import { generateContract } from '../src/codegen-contract.js';
|
|
3
|
+
import { generateOp } from '../src/codegen-operation.js';
|
|
4
|
+
import { generateSdk } from '../src/codegen-sdk.js';
|
|
5
|
+
import { SIMPLE_USER_CONTRACT, VISIBILITY_CONTRACT, INHERITANCE_CONTRACT, SIMPLE_USERS_OP, PARAMETERIZED_OP } from './helpers.js';
|
|
6
|
+
|
|
7
|
+
function compileContractSource(source: string) {
|
|
8
|
+
const diag = new DiagnosticCollector();
|
|
9
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
10
|
+
const { contract } = decomposeCk(ck);
|
|
11
|
+
const output = generateContract(contract);
|
|
12
|
+
return { root: contract, output, diag };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function compileOpSource(source: string, file = 'users.ck') {
|
|
16
|
+
const diag = new DiagnosticCollector();
|
|
17
|
+
const ck = parseCk(source, file, diag);
|
|
18
|
+
const { op } = decomposeCk(ck);
|
|
19
|
+
const output = generateOp(op);
|
|
20
|
+
return { root: op, output, diag };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('Contract pipeline (source -> parse -> codegen)', () => {
|
|
24
|
+
it('compiles a simple contract to valid Zod schema code', () => {
|
|
25
|
+
const { output, diag } = compileContractSource(SIMPLE_USER_CONTRACT);
|
|
26
|
+
expect(diag.hasErrors()).toBe(false);
|
|
27
|
+
expect(output).toContain("import { z } from 'zod';");
|
|
28
|
+
expect(output).toContain('id: z.uuid()');
|
|
29
|
+
expect(output).toContain('name: z.string()');
|
|
30
|
+
expect(output).toContain('email: z.email()');
|
|
31
|
+
expect(output).toContain('age: z.coerce.number().optional()');
|
|
32
|
+
expect(output).toContain(`active: z.preprocess((v) => v === 'true' ? true : v === 'false' ? false : v, z.boolean()).default(true)`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('compiles a contract with visibility to three-schema pattern', () => {
|
|
36
|
+
const { output, diag } = compileContractSource(VISIBILITY_CONTRACT);
|
|
37
|
+
expect(diag.hasErrors()).toBe(false);
|
|
38
|
+
expect(output).toContain('const UserBase = z.strictObject({');
|
|
39
|
+
expect(output).toContain('export const User = z.strictObject({');
|
|
40
|
+
expect(output).toContain('export const UserInput = z.strictObject({');
|
|
41
|
+
|
|
42
|
+
// Read schema (User) should not contain writeonly 'password'
|
|
43
|
+
const readSection = output.split('export const User =')[1]!.split('});')[0]!;
|
|
44
|
+
expect(readSection).not.toContain('password');
|
|
45
|
+
|
|
46
|
+
// Write schema (UserInput) should not contain readonly 'id'
|
|
47
|
+
const writeSection = output.split('export const UserInput =')[1]!.split('});')[0]!;
|
|
48
|
+
expect(writeSection).not.toContain('id:');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('compiles a contract with inheritance', () => {
|
|
52
|
+
const { output, diag } = compileContractSource(INHERITANCE_CONTRACT);
|
|
53
|
+
expect(diag.hasErrors()).toBe(false);
|
|
54
|
+
expect(output).toContain('User.extend({');
|
|
55
|
+
expect(output).toContain('z.enum(["admin", "superadmin"])');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('compiles a contract with all type kinds', () => {
|
|
59
|
+
const source = `\
|
|
60
|
+
contract Kitchen: {
|
|
61
|
+
tags: array(string)
|
|
62
|
+
coords: tuple(number, number)
|
|
63
|
+
meta: record(string, unknown)
|
|
64
|
+
status: enum(open, closed)
|
|
65
|
+
kind: literal("kitchen")
|
|
66
|
+
value: string | number
|
|
67
|
+
ref: Address
|
|
68
|
+
children: lazy(Kitchen)
|
|
69
|
+
}`;
|
|
70
|
+
const { output, diag } = compileContractSource(source);
|
|
71
|
+
expect(diag.hasErrors()).toBe(false);
|
|
72
|
+
expect(output).toContain('z.array(z.string())');
|
|
73
|
+
expect(output).toContain('z.tuple([z.coerce.number(), z.coerce.number()])');
|
|
74
|
+
expect(output).toContain('z.record(z.string(), z.unknown())');
|
|
75
|
+
expect(output).toContain('z.enum(["open", "closed"])');
|
|
76
|
+
expect(output).toContain('z.literal("kitchen")');
|
|
77
|
+
expect(output).toContain('z.union([z.string(), z.coerce.number()])');
|
|
78
|
+
expect(output).toContain('Address');
|
|
79
|
+
expect(output).toContain('z.lazy(() => Kitchen)');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('includes DateTime import when date fields are used', () => {
|
|
83
|
+
const source = `\
|
|
84
|
+
contract Event: {
|
|
85
|
+
startDate: date
|
|
86
|
+
createdAt: datetime
|
|
87
|
+
}`;
|
|
88
|
+
const { output } = compileContractSource(source);
|
|
89
|
+
expect(output).toContain("import { DateTime } from 'luxon';");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('OP pipeline (source -> parse -> codegen)', () => {
|
|
94
|
+
it('compiles a simple operation to Koa router code', () => {
|
|
95
|
+
const { output, diag } = compileOpSource(SIMPLE_USERS_OP);
|
|
96
|
+
expect(diag.hasErrors()).toBe(false);
|
|
97
|
+
expect(output).not.toContain("import { z } from 'zod';");
|
|
98
|
+
expect(output).toContain('ServerKitRouter');
|
|
99
|
+
expect(output).toContain("UsersRouter.get('/users'");
|
|
100
|
+
expect(output).toContain("UsersRouter.post('/users'");
|
|
101
|
+
expect(output).toContain("bodyParserMiddleware(['json'])");
|
|
102
|
+
expect(output).toContain('ctx.status = 201');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('compiles an operation with params, request, and response', () => {
|
|
106
|
+
const { output, diag } = compileOpSource(PARAMETERIZED_OP);
|
|
107
|
+
expect(diag.hasErrors()).toBe(false);
|
|
108
|
+
expect(output).toContain("UsersRouter.get('/users/:id'");
|
|
109
|
+
expect(output).toContain("UsersRouter.delete('/users/:id'");
|
|
110
|
+
expect(output).toContain('parseAndValidate(');
|
|
111
|
+
expect(output).toContain('id: z.uuid()');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('uses correct router name for dotted file names', () => {
|
|
115
|
+
const source = `operation /items: { get: {} }`;
|
|
116
|
+
const { output } = compileOpSource(source, 'ledger.items.ck');
|
|
117
|
+
expect(output).toContain('LedgerItemsRouter');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('undeclared path param warnings', () => {
|
|
122
|
+
it('warns when a route has path params but no params block', () => {
|
|
123
|
+
const source = `operation /users/{id}: { get: {} }`;
|
|
124
|
+
const diag = new DiagnosticCollector();
|
|
125
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
126
|
+
const { op } = decomposeCk(ck);
|
|
127
|
+
validateOp(op, diag);
|
|
128
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
129
|
+
expect(warnings).toHaveLength(1);
|
|
130
|
+
expect(warnings[0]!.message).toContain('{id}');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('warns for each undeclared param', () => {
|
|
134
|
+
const source = `operation /users/{userId}/posts/{postId}: { get: {} }`;
|
|
135
|
+
const diag = new DiagnosticCollector();
|
|
136
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
137
|
+
const { op } = decomposeCk(ck);
|
|
138
|
+
validateOp(op, diag);
|
|
139
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
140
|
+
expect(warnings).toHaveLength(2);
|
|
141
|
+
expect(warnings[0]!.message).toContain('{userId}');
|
|
142
|
+
expect(warnings[1]!.message).toContain('{postId}');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('does not warn when all path params are declared', () => {
|
|
146
|
+
const source = `operation /users/{id}: {\n params: {\n id: uuid\n }\n get: {}\n}`;
|
|
147
|
+
const diag = new DiagnosticCollector();
|
|
148
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
149
|
+
const { op } = decomposeCk(ck);
|
|
150
|
+
validateOp(op, diag);
|
|
151
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
152
|
+
expect(warnings).toHaveLength(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('warns only for the subset of undeclared params', () => {
|
|
156
|
+
const source = `operation /accounts/{accountId}/entries/{entryId}: {\n params: {\n accountId: uuid\n }\n get: {}\n}`;
|
|
157
|
+
const diag = new DiagnosticCollector();
|
|
158
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
159
|
+
const { op } = decomposeCk(ck);
|
|
160
|
+
validateOp(op, diag);
|
|
161
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
162
|
+
expect(warnings).toHaveLength(1);
|
|
163
|
+
expect(warnings[0]!.message).toContain('{entryId}');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('does not warn when params uses a type reference', () => {
|
|
167
|
+
const source = `operation /users/{id}: {\n params: UserParams\n get: {}\n}`;
|
|
168
|
+
const diag = new DiagnosticCollector();
|
|
169
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
170
|
+
const { op } = decomposeCk(ck);
|
|
171
|
+
validateOp(op, diag);
|
|
172
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
173
|
+
expect(warnings).toHaveLength(0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('does not warn for routes without path params', () => {
|
|
177
|
+
const source = `operation /users: { get: {} }`;
|
|
178
|
+
const diag = new DiagnosticCollector();
|
|
179
|
+
const ck = parseCk(source, 'test.ck', diag);
|
|
180
|
+
const { op } = decomposeCk(ck);
|
|
181
|
+
validateOp(op, diag);
|
|
182
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
183
|
+
expect(warnings).toHaveLength(0);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('param type warnings', () => {
|
|
188
|
+
it('does not warn when param types are specified', () => {
|
|
189
|
+
const diag = new DiagnosticCollector();
|
|
190
|
+
const ck = parseCk(PARAMETERIZED_OP, 'test.ck', diag);
|
|
191
|
+
const { op } = decomposeCk(ck);
|
|
192
|
+
validateOp(op, diag);
|
|
193
|
+
const warnings = diag.getAll().filter(d => d.message.includes('no explicit type'));
|
|
194
|
+
expect(warnings).toHaveLength(0);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('error handling pipeline', () => {
|
|
199
|
+
it('reports diagnostics for invalid contract source', () => {
|
|
200
|
+
const { diag } = compileContractSource('Bad name: string');
|
|
201
|
+
expect(diag.hasErrors()).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('reports diagnostics for invalid OP source', () => {
|
|
205
|
+
const { diag } = compileOpSource('no-slash { get: {} }');
|
|
206
|
+
expect(diag.hasErrors()).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('options-level header globals parity', () => {
|
|
211
|
+
function compileOp(source: string) {
|
|
212
|
+
const diag = new DiagnosticCollector();
|
|
213
|
+
const ck = parseCk(source, 'widgets.ck', diag);
|
|
214
|
+
applyOptionsDefaults(ck, diag);
|
|
215
|
+
const { op } = decomposeCk(ck);
|
|
216
|
+
return { server: generateOp(op), sdk: generateSdk(op), diag };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Strip source-line refs (e.g. `widgets.ck#L7`) so we can compare two equivalent
|
|
220
|
+
// shapes whose operation sits on different lines in the source.
|
|
221
|
+
const stripLineRefs = (s: string) => s.replace(/widgets\.ck#L\d+/g, 'widgets.ck#L?');
|
|
222
|
+
|
|
223
|
+
it('options-level request headers produce the same server and SDK output as inlined headers', () => {
|
|
224
|
+
const globalsForm = `
|
|
225
|
+
options { request: { headers: {
|
|
226
|
+
x-request-id: uuid
|
|
227
|
+
authorization: string
|
|
228
|
+
} } }
|
|
229
|
+
|
|
230
|
+
operation /widgets: {
|
|
231
|
+
get: {
|
|
232
|
+
response: { 200: { application/json: Widget } }
|
|
233
|
+
}
|
|
234
|
+
}`;
|
|
235
|
+
const inlinedForm = `
|
|
236
|
+
operation /widgets: {
|
|
237
|
+
get: {
|
|
238
|
+
headers: {
|
|
239
|
+
x-request-id: uuid
|
|
240
|
+
authorization: string
|
|
241
|
+
}
|
|
242
|
+
response: { 200: { application/json: Widget } }
|
|
243
|
+
}
|
|
244
|
+
}`;
|
|
245
|
+
const a = compileOp(globalsForm);
|
|
246
|
+
const b = compileOp(inlinedForm);
|
|
247
|
+
expect(a.diag.hasErrors()).toBe(false);
|
|
248
|
+
expect(b.diag.hasErrors()).toBe(false);
|
|
249
|
+
expect(stripLineRefs(a.server)).toBe(stripLineRefs(b.server));
|
|
250
|
+
expect(stripLineRefs(a.sdk)).toBe(stripLineRefs(b.sdk));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('options-level response headers on primary status produce the same server and SDK output as inlined headers', () => {
|
|
254
|
+
const globalsForm = `
|
|
255
|
+
options { response: { headers: {
|
|
256
|
+
x-request-id: uuid
|
|
257
|
+
} } }
|
|
258
|
+
|
|
259
|
+
operation /widgets: {
|
|
260
|
+
get: {
|
|
261
|
+
response: { 200: { application/json: Widget } }
|
|
262
|
+
}
|
|
263
|
+
}`;
|
|
264
|
+
const inlinedForm = `
|
|
265
|
+
operation /widgets: {
|
|
266
|
+
get: {
|
|
267
|
+
response: {
|
|
268
|
+
200: {
|
|
269
|
+
application/json: Widget
|
|
270
|
+
headers: { x-request-id: uuid }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}`;
|
|
275
|
+
const a = compileOp(globalsForm);
|
|
276
|
+
const b = compileOp(inlinedForm);
|
|
277
|
+
expect(a.diag.hasErrors()).toBe(false);
|
|
278
|
+
expect(b.diag.hasErrors()).toBe(false);
|
|
279
|
+
expect(stripLineRefs(a.server)).toBe(stripLineRefs(b.server));
|
|
280
|
+
expect(stripLineRefs(a.sdk)).toBe(stripLineRefs(b.sdk));
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('headers: none on an operation suppresses the global request header merge', () => {
|
|
284
|
+
const source = `
|
|
285
|
+
options { request: { headers: { x-request-id: uuid } } }
|
|
286
|
+
operation /widgets: {
|
|
287
|
+
get: {
|
|
288
|
+
headers: none
|
|
289
|
+
response: { 200: { application/json: Widget } }
|
|
290
|
+
}
|
|
291
|
+
}`;
|
|
292
|
+
const { server, sdk } = compileOp(source);
|
|
293
|
+
// the request header should not appear in either output
|
|
294
|
+
expect(server).not.toContain("'x-request-id'");
|
|
295
|
+
expect(sdk).not.toContain("'x-request-id'");
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('cross-file type reference validation', () => {
|
|
300
|
+
it('warns when a contract references an undefined model', () => {
|
|
301
|
+
const diag = new DiagnosticCollector();
|
|
302
|
+
const ck = parseCk('contract Order: { customer: NonExistentModel }', 'order.ck', diag);
|
|
303
|
+
const { contract } = decomposeCk(ck);
|
|
304
|
+
validateRefs([contract], [], diag);
|
|
305
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
306
|
+
expect(warnings.some(w => w.message.includes('NonExistentModel'))).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('does not warn when referenced model exists in another file', () => {
|
|
310
|
+
const diag = new DiagnosticCollector();
|
|
311
|
+
const ck1 = parseCk('contract User: { name: string }', 'user.ck', diag);
|
|
312
|
+
const ck2 = parseCk('contract Order: { customer: User }', 'order.ck', diag);
|
|
313
|
+
const { contract: contract1 } = decomposeCk(ck1);
|
|
314
|
+
const { contract: contract2 } = decomposeCk(ck2);
|
|
315
|
+
validateRefs([contract1, contract2], [], diag);
|
|
316
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning' && d.message.includes('User'));
|
|
317
|
+
expect(warnings).toHaveLength(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('warns when base model is undefined', () => {
|
|
321
|
+
const diag = new DiagnosticCollector();
|
|
322
|
+
const ck = parseCk('contract Admin: MissingBase & { role: string }', 'admin.ck', diag);
|
|
323
|
+
const { contract } = decomposeCk(ck);
|
|
324
|
+
validateRefs([contract], [], diag);
|
|
325
|
+
const warnings = diag.getAll().filter(d => d.severity === 'warning');
|
|
326
|
+
expect(warnings.some(w => w.message.includes('MissingBase'))).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('warns when an operation references an undefined body type', () => {
|
|
330
|
+
const diag = new DiagnosticCollector();
|
|
331
|
+
const ck = parseCk(
|
|
332
|
+
`\
|
|
333
|
+
operation /users: {
|
|
334
|
+
get: {
|
|
335
|
+
response: {
|
|
336
|
+
200: {
|
|
337
|
+
application/json: MissingType
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}`,
|
|
342
|
+
'users.ck',
|
|
343
|
+
diag,
|
|
344
|
+
);
|
|
345
|
+
const { op } = decomposeCk(ck);
|
|
346
|
+
const diagAll = new DiagnosticCollector();
|
|
347
|
+
validateRefs([], [op], diagAll);
|
|
348
|
+
const warnings = diagAll.getAll().filter(d => d.severity === 'warning');
|
|
349
|
+
expect(warnings.some(w => w.message.includes('MissingType'))).toBe(true);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('does not warn for scalar type names in ops', () => {
|
|
353
|
+
const diag = new DiagnosticCollector();
|
|
354
|
+
const ck = parseCk(
|
|
355
|
+
`\
|
|
356
|
+
operation /users: {
|
|
357
|
+
get: {
|
|
358
|
+
query: {
|
|
359
|
+
page: int
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}`,
|
|
363
|
+
'users.ck',
|
|
364
|
+
diag,
|
|
365
|
+
);
|
|
366
|
+
const { op } = decomposeCk(ck);
|
|
367
|
+
const diagAll = new DiagnosticCollector();
|
|
368
|
+
validateRefs([], [op], diagAll);
|
|
369
|
+
const warnings = diagAll.getAll().filter(d => d.severity === 'warning');
|
|
370
|
+
expect(warnings).toHaveLength(0);
|
|
371
|
+
});
|
|
372
|
+
});
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineProject } from 'vitest/config';
|
|
2
|
+
import swc from 'unplugin-swc';
|
|
3
|
+
|
|
4
|
+
export default defineProject({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
include: ['./tests/**/*.test.ts'],
|
|
8
|
+
environment: 'node',
|
|
9
|
+
testTimeout: 50000,
|
|
10
|
+
hookTimeout: 30000,
|
|
11
|
+
fileParallelism: true,
|
|
12
|
+
},
|
|
13
|
+
plugins: [swc.vite()],
|
|
14
|
+
});
|