@kizunajs/fetch 2.0.0-beta.1 → 2.0.0-beta.11
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/server.cjs +71 -29
- package/dist/server.d.cts +28 -13
- package/dist/server.d.mts +28 -13
- package/dist/server.mjs +71 -32
- package/package.json +4 -4
package/dist/server.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let kizunajs_schemas = require("kizunajs/schemas");
|
|
3
3
|
let kizunajs_generator = require("kizunajs/generator");
|
|
4
|
+
let zod = require("zod");
|
|
4
5
|
//#region src/zod-to-typescript.ts
|
|
5
6
|
/**
|
|
6
7
|
* Collects the named types a walk produces, so a model shared by many routes is
|
|
@@ -27,6 +28,15 @@ var TypeCollector = class {
|
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
30
|
const quote = (value) => JSON.stringify(value);
|
|
31
|
+
/**
|
|
32
|
+
* A literal as the type the JSON carries, so `z.literal(true)` is `true`.
|
|
33
|
+
*/
|
|
34
|
+
const literalType = (value) => {
|
|
35
|
+
if (typeof value === "string") return quote(value);
|
|
36
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
37
|
+
if (value === void 0) return "undefined";
|
|
38
|
+
return String(value);
|
|
39
|
+
};
|
|
30
40
|
const indent$1 = (text, depth = 1) => text.split("\n").map((line) => line.length > 0 ? " ".repeat(depth) + line : line).join("\n");
|
|
31
41
|
/**
|
|
32
42
|
* A property name, quoted only when it is not a plain identifier.
|
|
@@ -159,24 +169,27 @@ const typeOf = (schema, collector, hint) => {
|
|
|
159
169
|
const def = (0, kizunajs_generator.readDef)(schema);
|
|
160
170
|
if (def.type === "nullable" && def.innerType) return `${typeOf(def.innerType, collector, hint)} | null`;
|
|
161
171
|
if (def.type === "nullish" && def.innerType) return `${typeOf(def.innerType, collector, hint)} | null`;
|
|
162
|
-
if (def.innerType && def.type !== "object")
|
|
163
|
-
const { inner } = (0, kizunajs_generator.unwrapOptionalWrappers)(schema);
|
|
164
|
-
if (inner !== schema) return typeOf(inner, collector, hint);
|
|
165
|
-
return typeOf(def.innerType, collector, hint);
|
|
166
|
-
}
|
|
172
|
+
if (def.innerType && def.type !== "object") return typeOf(def.innerType, collector, hint);
|
|
167
173
|
if (def.type === "pipe") {
|
|
168
174
|
const side = def.out ?? def.in;
|
|
169
175
|
return side ? typeOf(side, collector, hint) : "unknown";
|
|
170
176
|
}
|
|
171
177
|
const modelName = (0, kizunajs_generator.readMetaId)(schema);
|
|
172
|
-
if (modelName !== void 0
|
|
178
|
+
if (modelName !== void 0) {
|
|
173
179
|
if (collector.claim(modelName)) collector.add({
|
|
174
180
|
name: modelName,
|
|
175
181
|
description: (0, kizunajs_generator.readMetaDescription)(schema),
|
|
176
|
-
body:
|
|
182
|
+
body: structuralType(schema, collector, modelName)
|
|
177
183
|
});
|
|
178
184
|
return modelName;
|
|
179
185
|
}
|
|
186
|
+
return structuralType(schema, collector, hint);
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* The type a schema describes by its shape, its name already spent by the caller.
|
|
190
|
+
*/
|
|
191
|
+
const structuralType = (schema, collector, hint) => {
|
|
192
|
+
const def = (0, kizunajs_generator.readDef)(schema);
|
|
180
193
|
switch (def.type) {
|
|
181
194
|
case "string": return "string";
|
|
182
195
|
case "number":
|
|
@@ -192,11 +205,11 @@ const typeOf = (schema, collector, hint) => {
|
|
|
192
205
|
case "undefined": return "undefined";
|
|
193
206
|
case "literal": {
|
|
194
207
|
const values = def.values ?? [];
|
|
195
|
-
return values.length > 0 ? values.map(
|
|
208
|
+
return values.length > 0 ? values.map(literalType).join(" | ") : "never";
|
|
196
209
|
}
|
|
197
210
|
case "enum": {
|
|
198
211
|
const values = Object.values(def.entries ?? {});
|
|
199
|
-
return values.length > 0 ? values.map(
|
|
212
|
+
return values.length > 0 ? values.map(literalType).join(" | ") : "never";
|
|
200
213
|
}
|
|
201
214
|
case "array": return def.element ? `Array<${typeOf(def.element, collector, `${hint}Item`)}>` : "unknown[]";
|
|
202
215
|
case "object": return objectBody(schema, collector, hint);
|
|
@@ -247,7 +260,7 @@ const exampleCall = (routeKey, route) => {
|
|
|
247
260
|
entries.push(`params: {\n${indent(fields.join("\n"))}\n},`);
|
|
248
261
|
}
|
|
249
262
|
if (route.query && requiresArgument(route.query)) entries.push(`query: ${sampleObject(route.query)},`);
|
|
250
|
-
if (route.body && requiresArgument(route.body)) entries.push(`body: ${sampleValue(route.body)},`);
|
|
263
|
+
if (route.body && !(0, kizunajs_generator.isVoidSchema)(route.body) && requiresArgument(route.body)) entries.push(`body: ${sampleValue(route.body)},`);
|
|
251
264
|
return `const result = await client.${routeKey}(${entries.length > 0 ? `{\n${indent(entries.join("\n"))}\n}` : ""});`;
|
|
252
265
|
};
|
|
253
266
|
/**
|
|
@@ -279,7 +292,7 @@ const routeDoc = (routeKey, route) => {
|
|
|
279
292
|
lines.push("@example", exampleCall(routeKey, route));
|
|
280
293
|
return docComment(lines);
|
|
281
294
|
};
|
|
282
|
-
const emitRoute = (routeKey, key, route, collector) => {
|
|
295
|
+
const emitRoute = (routeKey, key, route, collector, namespaceName) => {
|
|
283
296
|
const namespace = routeKey.split(".").map(kizunajs_generator.toPascalCase).join("");
|
|
284
297
|
const members = [];
|
|
285
298
|
const args = [];
|
|
@@ -287,19 +300,19 @@ const emitRoute = (routeKey, key, route, collector) => {
|
|
|
287
300
|
if (params.length > 0) {
|
|
288
301
|
const body = route.pathParams ? typeOf(route.pathParams, collector, `${namespace}Params`) : `{\n${indent(params.map((name) => `${name}: string;`).join("\n"))}\n}`;
|
|
289
302
|
members.push(`export type Params = ${body};`);
|
|
290
|
-
args.push(`params:
|
|
303
|
+
args.push(`params: ${namespaceName}.${namespace}.Params`);
|
|
291
304
|
}
|
|
292
305
|
if (route.query) {
|
|
293
306
|
members.push(`export type Query = ${typeOf(route.query, collector, `${namespace}Query`)};`);
|
|
294
|
-
args.push(`query?:
|
|
307
|
+
args.push(`query?: ${namespaceName}.${namespace}.Query`);
|
|
295
308
|
}
|
|
296
|
-
if (route.body) {
|
|
309
|
+
if (route.body && !(0, kizunajs_generator.isVoidSchema)(route.body)) {
|
|
297
310
|
members.push(`export type Body = ${typeOf(route.body, collector, `${namespace}Body`)};`);
|
|
298
|
-
args.push(`body:
|
|
311
|
+
args.push(`body: ${namespaceName}.${namespace}.Body`);
|
|
299
312
|
}
|
|
300
313
|
if (route.headers) {
|
|
301
314
|
members.push(`export type Headers = ${typeOf(route.headers, collector, `${namespace}Headers`)};`);
|
|
302
|
-
args.push(`headers?:
|
|
315
|
+
args.push(`headers?: ${namespaceName}.${namespace}.Headers`);
|
|
303
316
|
} else args.push("headers?: Record<string, string>");
|
|
304
317
|
args.push("fetchOptions?: RequestInit");
|
|
305
318
|
const validates = route.body !== void 0 || route.query !== void 0;
|
|
@@ -323,13 +336,13 @@ const emitRoute = (routeKey, key, route, collector) => {
|
|
|
323
336
|
const streams = Object.values(route.responses).some((response) => (0, kizunajs_generator.isStreamResponse)(response));
|
|
324
337
|
return {
|
|
325
338
|
namespace,
|
|
326
|
-
signature: `${routeDoc(routeKey, route)}${key}: ClientMethod<'${route.method}', ${streams}, ${argsType},
|
|
339
|
+
signature: `${routeDoc(routeKey, route)}${key}: ClientMethod<'${route.method}', ${streams}, ${argsType}, ${namespaceName}.${namespace}.Result>;`,
|
|
327
340
|
declaration: `export namespace ${namespace} {\n${indent(members.join("\n\n"))}\n}`,
|
|
328
341
|
table: `${key}: {\n${indent(tableEntries.map((entry) => `${entry},`).join("\n"))}\n},`
|
|
329
342
|
};
|
|
330
343
|
};
|
|
331
344
|
const isRoute = (value) => !!value && typeof value === "object" && typeof value.method === "string";
|
|
332
|
-
const emitTree = (routes, prefix, collector) => {
|
|
345
|
+
const emitTree = (routes, prefix, collector, namespaceName) => {
|
|
333
346
|
const signatures = [];
|
|
334
347
|
const tables = [];
|
|
335
348
|
const declarations = [];
|
|
@@ -337,14 +350,14 @@ const emitTree = (routes, prefix, collector) => {
|
|
|
337
350
|
const node = routes[key];
|
|
338
351
|
const routeKey = prefix ? `${prefix}.${key}` : key;
|
|
339
352
|
if (isRoute(node)) {
|
|
340
|
-
const emitted = emitRoute(routeKey, key, node, collector);
|
|
353
|
+
const emitted = emitRoute(routeKey, key, node, collector, namespaceName);
|
|
341
354
|
signatures.push(emitted.signature);
|
|
342
355
|
tables.push(emitted.table);
|
|
343
356
|
declarations.push(emitted.declaration);
|
|
344
357
|
continue;
|
|
345
358
|
}
|
|
346
359
|
if (node && typeof node === "object") {
|
|
347
|
-
const group = emitTree(node, routeKey, collector);
|
|
360
|
+
const group = emitTree(node, routeKey, collector, namespaceName);
|
|
348
361
|
signatures.push(`${key}: {\n${indent(group.signatures)}\n};`);
|
|
349
362
|
tables.push(`${key}: {\n${indent(group.table)}\n},`);
|
|
350
363
|
declarations.push(...group.declarations);
|
|
@@ -376,9 +389,9 @@ const emitRequestContext = (contract, collector) => {
|
|
|
376
389
|
return fields.length > 0 ? `export interface RequestContext {\n${indent(fields.join("\n"))}\n}` : void 0;
|
|
377
390
|
};
|
|
378
391
|
const generateFetchClient = (contract, options = {}) => {
|
|
379
|
-
const { runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
392
|
+
const { namespace: namespaceName = "API", runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
380
393
|
const collector = new TypeCollector();
|
|
381
|
-
const tree = emitTree(contract
|
|
394
|
+
const tree = emitTree((0, kizunajs_generator.listedRoutes)(contract), "", collector, namespaceName);
|
|
382
395
|
const api = [...collector.all().map((model) => {
|
|
383
396
|
return `${model.description ? `/**\n * ${model.description}\n */\n` : ""}export type ${model.name} = ${model.body};`;
|
|
384
397
|
}), ...tree.declarations].join("\n\n");
|
|
@@ -394,7 +407,7 @@ ${[
|
|
|
394
407
|
*/
|
|
395
408
|
import { createGeneratedClient, type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
396
409
|
|
|
397
|
-
export namespace
|
|
410
|
+
export namespace ${namespaceName} {
|
|
398
411
|
${indent(api)}
|
|
399
412
|
}
|
|
400
413
|
|
|
@@ -421,16 +434,45 @@ export const createClient = (config: ClientConfig & { requestContext?: RequestCo
|
|
|
421
434
|
};
|
|
422
435
|
//#endregion
|
|
423
436
|
//#region src/client-target.ts
|
|
437
|
+
const FetchClientOptionsSchema = zod.z.object({
|
|
438
|
+
/**
|
|
439
|
+
* Name of the generated namespace holding every model and every route's
|
|
440
|
+
* `Params`, `Query`, `Body`, `Headers` and `Result`.
|
|
441
|
+
*
|
|
442
|
+
* @default 'API'
|
|
443
|
+
*/
|
|
444
|
+
namespace: zod.z.string().optional(),
|
|
445
|
+
/**
|
|
446
|
+
* Module the generated file imports its runtime from.
|
|
447
|
+
*
|
|
448
|
+
* @default '@kizunajs/fetch'
|
|
449
|
+
*/
|
|
450
|
+
runtimeModule: zod.z.string().optional(),
|
|
451
|
+
/**
|
|
452
|
+
* Command the file's header tells a reader to run.
|
|
453
|
+
*
|
|
454
|
+
* @default 'kizuna generate'
|
|
455
|
+
*/
|
|
456
|
+
regenerateCommand: zod.z.string().optional(),
|
|
457
|
+
/**
|
|
458
|
+
* Path to the api this was generated from, relative to the generated
|
|
459
|
+
* file. Named in the header so tooling can tell when the client has fallen
|
|
460
|
+
* behind, and so a reader knows where the API is declared.
|
|
461
|
+
*/
|
|
462
|
+
source: zod.z.string().optional()
|
|
463
|
+
});
|
|
424
464
|
/**
|
|
425
|
-
* A TypeScript client
|
|
465
|
+
* A TypeScript client for `kizuna.config.ts`.
|
|
426
466
|
*
|
|
427
467
|
* @example
|
|
428
|
-
* fetchClient({
|
|
468
|
+
* fetchClient({
|
|
469
|
+
* output: './packages/api-client/src/generated.ts',
|
|
470
|
+
* });
|
|
429
471
|
*/
|
|
430
|
-
const fetchClient = (
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
generate: (
|
|
472
|
+
const fetchClient = (0, kizunajs_generator.defineClient)({
|
|
473
|
+
target: "fetch",
|
|
474
|
+
options: FetchClientOptionsSchema,
|
|
475
|
+
generate: ({ options, api }) => ({ finalize: () => generateFetchClient(api, options) })
|
|
434
476
|
});
|
|
435
477
|
//#endregion
|
|
436
478
|
exports.fetchClient = fetchClient;
|
package/dist/server.d.cts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as _$kizunajs from "kizunajs";
|
|
3
|
+
import { ApiDefinition } from "kizunajs";
|
|
4
|
+
import { GeneratedFileOptions } from "kizunajs/generator";
|
|
2
5
|
|
|
3
6
|
//#region src/generator.d.ts
|
|
4
7
|
/**
|
|
5
8
|
* What a generated client is called and where its runtime comes from.
|
|
6
9
|
*/
|
|
7
10
|
interface FetchClientOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Name of the generated namespace holding every model and every route's
|
|
13
|
+
* `Params`, `Query`, `Body`, `Headers` and `Result`.
|
|
14
|
+
*
|
|
15
|
+
* @default 'API'
|
|
16
|
+
*/
|
|
17
|
+
namespace?: string;
|
|
8
18
|
/**
|
|
9
19
|
* Module the generated file imports its runtime from.
|
|
10
20
|
*
|
|
@@ -27,24 +37,29 @@ interface FetchClientOptions {
|
|
|
27
37
|
declare const generateFetchClient: (contract: ApiDefinition, options?: FetchClientOptions) => string;
|
|
28
38
|
//#endregion
|
|
29
39
|
//#region src/client-target.d.ts
|
|
40
|
+
declare const FetchClientOptionsSchema: z.ZodObject<{
|
|
41
|
+
namespace: z.ZodOptional<z.ZodString>;
|
|
42
|
+
runtimeModule: z.ZodOptional<z.ZodString>;
|
|
43
|
+
regenerateCommand: z.ZodOptional<z.ZodString>;
|
|
44
|
+
source: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>;
|
|
30
46
|
/**
|
|
31
47
|
* Where a generated TypeScript client is written.
|
|
32
48
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Path the generated file is written to.
|
|
36
|
-
*/
|
|
37
|
-
output: string;
|
|
38
|
-
}
|
|
49
|
+
type FetchClientTargetOptions = z.input<typeof FetchClientOptionsSchema> & GeneratedFileOptions;
|
|
39
50
|
/**
|
|
40
|
-
* A TypeScript client
|
|
51
|
+
* A TypeScript client for `kizuna.config.ts`.
|
|
41
52
|
*
|
|
42
53
|
* @example
|
|
43
|
-
* fetchClient({
|
|
54
|
+
* fetchClient({
|
|
55
|
+
* output: './packages/api-client/src/generated.ts',
|
|
56
|
+
* });
|
|
44
57
|
*/
|
|
45
|
-
declare const fetchClient: ({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
declare const fetchClient: (options: {
|
|
59
|
+
namespace?: string | undefined;
|
|
60
|
+
runtimeModule?: string | undefined;
|
|
61
|
+
regenerateCommand?: string | undefined;
|
|
62
|
+
source?: string | undefined;
|
|
63
|
+
} & GeneratedFileOptions) => _$kizunajs.GeneratedFile;
|
|
49
64
|
//#endregion
|
|
50
65
|
export { type FetchClientOptions, type FetchClientTargetOptions, fetchClient, generateFetchClient };
|
package/dist/server.d.mts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _$kizunajs from "kizunajs";
|
|
2
|
+
import { ApiDefinition } from "kizunajs";
|
|
3
|
+
import { GeneratedFileOptions } from "kizunajs/generator";
|
|
4
|
+
import { z } from "zod";
|
|
2
5
|
|
|
3
6
|
//#region src/generator.d.ts
|
|
4
7
|
/**
|
|
5
8
|
* What a generated client is called and where its runtime comes from.
|
|
6
9
|
*/
|
|
7
10
|
interface FetchClientOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Name of the generated namespace holding every model and every route's
|
|
13
|
+
* `Params`, `Query`, `Body`, `Headers` and `Result`.
|
|
14
|
+
*
|
|
15
|
+
* @default 'API'
|
|
16
|
+
*/
|
|
17
|
+
namespace?: string;
|
|
8
18
|
/**
|
|
9
19
|
* Module the generated file imports its runtime from.
|
|
10
20
|
*
|
|
@@ -27,24 +37,29 @@ interface FetchClientOptions {
|
|
|
27
37
|
declare const generateFetchClient: (contract: ApiDefinition, options?: FetchClientOptions) => string;
|
|
28
38
|
//#endregion
|
|
29
39
|
//#region src/client-target.d.ts
|
|
40
|
+
declare const FetchClientOptionsSchema: z.ZodObject<{
|
|
41
|
+
namespace: z.ZodOptional<z.ZodString>;
|
|
42
|
+
runtimeModule: z.ZodOptional<z.ZodString>;
|
|
43
|
+
regenerateCommand: z.ZodOptional<z.ZodString>;
|
|
44
|
+
source: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>;
|
|
30
46
|
/**
|
|
31
47
|
* Where a generated TypeScript client is written.
|
|
32
48
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Path the generated file is written to.
|
|
36
|
-
*/
|
|
37
|
-
output: string;
|
|
38
|
-
}
|
|
49
|
+
type FetchClientTargetOptions = z.input<typeof FetchClientOptionsSchema> & GeneratedFileOptions;
|
|
39
50
|
/**
|
|
40
|
-
* A TypeScript client
|
|
51
|
+
* A TypeScript client for `kizuna.config.ts`.
|
|
41
52
|
*
|
|
42
53
|
* @example
|
|
43
|
-
* fetchClient({
|
|
54
|
+
* fetchClient({
|
|
55
|
+
* output: './packages/api-client/src/generated.ts',
|
|
56
|
+
* });
|
|
44
57
|
*/
|
|
45
|
-
declare const fetchClient: ({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
declare const fetchClient: (options: {
|
|
59
|
+
namespace?: string | undefined;
|
|
60
|
+
runtimeModule?: string | undefined;
|
|
61
|
+
regenerateCommand?: string | undefined;
|
|
62
|
+
source?: string | undefined;
|
|
63
|
+
} & GeneratedFileOptions) => _$kizunajs.GeneratedFile;
|
|
49
64
|
//#endregion
|
|
50
65
|
export { type FetchClientOptions, type FetchClientTargetOptions, fetchClient, generateFetchClient };
|
package/dist/server.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ValidationErrorSchema } from "kizunajs/schemas";
|
|
2
|
-
import { isBinarySchema, isFileSchema, isStreamResponse, readDef, readDeprecation, readDiscriminatedUnion, readMetaDescription, readMetaExamples, readMetaId, readObjectShape, resolveResponseBody, resolveResponseHeaders, streamContentType, toPascalCase, unwrapOptionalWrappers } from "kizunajs/generator";
|
|
2
|
+
import { defineClient, isBinarySchema, isFileSchema, isStreamResponse, isVoidSchema, listedRoutes, readDef, readDeprecation, readDiscriminatedUnion, readMetaDescription, readMetaExamples, readMetaId, readObjectShape, resolveResponseBody, resolveResponseHeaders, streamContentType, toPascalCase, unwrapOptionalWrappers } from "kizunajs/generator";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
//#region src/zod-to-typescript.ts
|
|
4
5
|
/**
|
|
5
6
|
* Collects the named types a walk produces, so a model shared by many routes is
|
|
@@ -26,6 +27,15 @@ var TypeCollector = class {
|
|
|
26
27
|
}
|
|
27
28
|
};
|
|
28
29
|
const quote = (value) => JSON.stringify(value);
|
|
30
|
+
/**
|
|
31
|
+
* A literal as the type the JSON carries, so `z.literal(true)` is `true`.
|
|
32
|
+
*/
|
|
33
|
+
const literalType = (value) => {
|
|
34
|
+
if (typeof value === "string") return quote(value);
|
|
35
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
36
|
+
if (value === void 0) return "undefined";
|
|
37
|
+
return String(value);
|
|
38
|
+
};
|
|
29
39
|
const indent$1 = (text, depth = 1) => text.split("\n").map((line) => line.length > 0 ? " ".repeat(depth) + line : line).join("\n");
|
|
30
40
|
/**
|
|
31
41
|
* A property name, quoted only when it is not a plain identifier.
|
|
@@ -158,24 +168,27 @@ const typeOf = (schema, collector, hint) => {
|
|
|
158
168
|
const def = readDef(schema);
|
|
159
169
|
if (def.type === "nullable" && def.innerType) return `${typeOf(def.innerType, collector, hint)} | null`;
|
|
160
170
|
if (def.type === "nullish" && def.innerType) return `${typeOf(def.innerType, collector, hint)} | null`;
|
|
161
|
-
if (def.innerType && def.type !== "object")
|
|
162
|
-
const { inner } = unwrapOptionalWrappers(schema);
|
|
163
|
-
if (inner !== schema) return typeOf(inner, collector, hint);
|
|
164
|
-
return typeOf(def.innerType, collector, hint);
|
|
165
|
-
}
|
|
171
|
+
if (def.innerType && def.type !== "object") return typeOf(def.innerType, collector, hint);
|
|
166
172
|
if (def.type === "pipe") {
|
|
167
173
|
const side = def.out ?? def.in;
|
|
168
174
|
return side ? typeOf(side, collector, hint) : "unknown";
|
|
169
175
|
}
|
|
170
176
|
const modelName = readMetaId(schema);
|
|
171
|
-
if (modelName !== void 0
|
|
177
|
+
if (modelName !== void 0) {
|
|
172
178
|
if (collector.claim(modelName)) collector.add({
|
|
173
179
|
name: modelName,
|
|
174
180
|
description: readMetaDescription(schema),
|
|
175
|
-
body:
|
|
181
|
+
body: structuralType(schema, collector, modelName)
|
|
176
182
|
});
|
|
177
183
|
return modelName;
|
|
178
184
|
}
|
|
185
|
+
return structuralType(schema, collector, hint);
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* The type a schema describes by its shape, its name already spent by the caller.
|
|
189
|
+
*/
|
|
190
|
+
const structuralType = (schema, collector, hint) => {
|
|
191
|
+
const def = readDef(schema);
|
|
179
192
|
switch (def.type) {
|
|
180
193
|
case "string": return "string";
|
|
181
194
|
case "number":
|
|
@@ -191,11 +204,11 @@ const typeOf = (schema, collector, hint) => {
|
|
|
191
204
|
case "undefined": return "undefined";
|
|
192
205
|
case "literal": {
|
|
193
206
|
const values = def.values ?? [];
|
|
194
|
-
return values.length > 0 ? values.map(
|
|
207
|
+
return values.length > 0 ? values.map(literalType).join(" | ") : "never";
|
|
195
208
|
}
|
|
196
209
|
case "enum": {
|
|
197
210
|
const values = Object.values(def.entries ?? {});
|
|
198
|
-
return values.length > 0 ? values.map(
|
|
211
|
+
return values.length > 0 ? values.map(literalType).join(" | ") : "never";
|
|
199
212
|
}
|
|
200
213
|
case "array": return def.element ? `Array<${typeOf(def.element, collector, `${hint}Item`)}>` : "unknown[]";
|
|
201
214
|
case "object": return objectBody(schema, collector, hint);
|
|
@@ -246,7 +259,7 @@ const exampleCall = (routeKey, route) => {
|
|
|
246
259
|
entries.push(`params: {\n${indent(fields.join("\n"))}\n},`);
|
|
247
260
|
}
|
|
248
261
|
if (route.query && requiresArgument(route.query)) entries.push(`query: ${sampleObject(route.query)},`);
|
|
249
|
-
if (route.body && requiresArgument(route.body)) entries.push(`body: ${sampleValue(route.body)},`);
|
|
262
|
+
if (route.body && !isVoidSchema(route.body) && requiresArgument(route.body)) entries.push(`body: ${sampleValue(route.body)},`);
|
|
250
263
|
return `const result = await client.${routeKey}(${entries.length > 0 ? `{\n${indent(entries.join("\n"))}\n}` : ""});`;
|
|
251
264
|
};
|
|
252
265
|
/**
|
|
@@ -278,7 +291,7 @@ const routeDoc = (routeKey, route) => {
|
|
|
278
291
|
lines.push("@example", exampleCall(routeKey, route));
|
|
279
292
|
return docComment(lines);
|
|
280
293
|
};
|
|
281
|
-
const emitRoute = (routeKey, key, route, collector) => {
|
|
294
|
+
const emitRoute = (routeKey, key, route, collector, namespaceName) => {
|
|
282
295
|
const namespace = routeKey.split(".").map(toPascalCase).join("");
|
|
283
296
|
const members = [];
|
|
284
297
|
const args = [];
|
|
@@ -286,19 +299,19 @@ const emitRoute = (routeKey, key, route, collector) => {
|
|
|
286
299
|
if (params.length > 0) {
|
|
287
300
|
const body = route.pathParams ? typeOf(route.pathParams, collector, `${namespace}Params`) : `{\n${indent(params.map((name) => `${name}: string;`).join("\n"))}\n}`;
|
|
288
301
|
members.push(`export type Params = ${body};`);
|
|
289
|
-
args.push(`params:
|
|
302
|
+
args.push(`params: ${namespaceName}.${namespace}.Params`);
|
|
290
303
|
}
|
|
291
304
|
if (route.query) {
|
|
292
305
|
members.push(`export type Query = ${typeOf(route.query, collector, `${namespace}Query`)};`);
|
|
293
|
-
args.push(`query?:
|
|
306
|
+
args.push(`query?: ${namespaceName}.${namespace}.Query`);
|
|
294
307
|
}
|
|
295
|
-
if (route.body) {
|
|
308
|
+
if (route.body && !isVoidSchema(route.body)) {
|
|
296
309
|
members.push(`export type Body = ${typeOf(route.body, collector, `${namespace}Body`)};`);
|
|
297
|
-
args.push(`body:
|
|
310
|
+
args.push(`body: ${namespaceName}.${namespace}.Body`);
|
|
298
311
|
}
|
|
299
312
|
if (route.headers) {
|
|
300
313
|
members.push(`export type Headers = ${typeOf(route.headers, collector, `${namespace}Headers`)};`);
|
|
301
|
-
args.push(`headers?:
|
|
314
|
+
args.push(`headers?: ${namespaceName}.${namespace}.Headers`);
|
|
302
315
|
} else args.push("headers?: Record<string, string>");
|
|
303
316
|
args.push("fetchOptions?: RequestInit");
|
|
304
317
|
const validates = route.body !== void 0 || route.query !== void 0;
|
|
@@ -322,13 +335,13 @@ const emitRoute = (routeKey, key, route, collector) => {
|
|
|
322
335
|
const streams = Object.values(route.responses).some((response) => isStreamResponse(response));
|
|
323
336
|
return {
|
|
324
337
|
namespace,
|
|
325
|
-
signature: `${routeDoc(routeKey, route)}${key}: ClientMethod<'${route.method}', ${streams}, ${argsType},
|
|
338
|
+
signature: `${routeDoc(routeKey, route)}${key}: ClientMethod<'${route.method}', ${streams}, ${argsType}, ${namespaceName}.${namespace}.Result>;`,
|
|
326
339
|
declaration: `export namespace ${namespace} {\n${indent(members.join("\n\n"))}\n}`,
|
|
327
340
|
table: `${key}: {\n${indent(tableEntries.map((entry) => `${entry},`).join("\n"))}\n},`
|
|
328
341
|
};
|
|
329
342
|
};
|
|
330
343
|
const isRoute = (value) => !!value && typeof value === "object" && typeof value.method === "string";
|
|
331
|
-
const emitTree = (routes, prefix, collector) => {
|
|
344
|
+
const emitTree = (routes, prefix, collector, namespaceName) => {
|
|
332
345
|
const signatures = [];
|
|
333
346
|
const tables = [];
|
|
334
347
|
const declarations = [];
|
|
@@ -336,14 +349,14 @@ const emitTree = (routes, prefix, collector) => {
|
|
|
336
349
|
const node = routes[key];
|
|
337
350
|
const routeKey = prefix ? `${prefix}.${key}` : key;
|
|
338
351
|
if (isRoute(node)) {
|
|
339
|
-
const emitted = emitRoute(routeKey, key, node, collector);
|
|
352
|
+
const emitted = emitRoute(routeKey, key, node, collector, namespaceName);
|
|
340
353
|
signatures.push(emitted.signature);
|
|
341
354
|
tables.push(emitted.table);
|
|
342
355
|
declarations.push(emitted.declaration);
|
|
343
356
|
continue;
|
|
344
357
|
}
|
|
345
358
|
if (node && typeof node === "object") {
|
|
346
|
-
const group = emitTree(node, routeKey, collector);
|
|
359
|
+
const group = emitTree(node, routeKey, collector, namespaceName);
|
|
347
360
|
signatures.push(`${key}: {\n${indent(group.signatures)}\n};`);
|
|
348
361
|
tables.push(`${key}: {\n${indent(group.table)}\n},`);
|
|
349
362
|
declarations.push(...group.declarations);
|
|
@@ -375,9 +388,9 @@ const emitRequestContext = (contract, collector) => {
|
|
|
375
388
|
return fields.length > 0 ? `export interface RequestContext {\n${indent(fields.join("\n"))}\n}` : void 0;
|
|
376
389
|
};
|
|
377
390
|
const generateFetchClient = (contract, options = {}) => {
|
|
378
|
-
const { runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
391
|
+
const { namespace: namespaceName = "API", runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
379
392
|
const collector = new TypeCollector();
|
|
380
|
-
const tree = emitTree(contract
|
|
393
|
+
const tree = emitTree(listedRoutes(contract), "", collector, namespaceName);
|
|
381
394
|
const api = [...collector.all().map((model) => {
|
|
382
395
|
return `${model.description ? `/**\n * ${model.description}\n */\n` : ""}export type ${model.name} = ${model.body};`;
|
|
383
396
|
}), ...tree.declarations].join("\n\n");
|
|
@@ -393,7 +406,7 @@ ${[
|
|
|
393
406
|
*/
|
|
394
407
|
import { createGeneratedClient, type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
395
408
|
|
|
396
|
-
export namespace
|
|
409
|
+
export namespace ${namespaceName} {
|
|
397
410
|
${indent(api)}
|
|
398
411
|
}
|
|
399
412
|
|
|
@@ -418,18 +431,44 @@ export const createClient = (config: ClientConfig & { requestContext?: RequestCo
|
|
|
418
431
|
createGeneratedClient(routes, config) as unknown as Client;`}
|
|
419
432
|
`;
|
|
420
433
|
};
|
|
421
|
-
//#endregion
|
|
422
|
-
//#region src/client-target.ts
|
|
423
434
|
/**
|
|
424
|
-
* A TypeScript client
|
|
435
|
+
* A TypeScript client for `kizuna.config.ts`.
|
|
425
436
|
*
|
|
426
437
|
* @example
|
|
427
|
-
* fetchClient({
|
|
438
|
+
* fetchClient({
|
|
439
|
+
* output: './packages/api-client/src/generated.ts',
|
|
440
|
+
* });
|
|
428
441
|
*/
|
|
429
|
-
const fetchClient = ({
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
442
|
+
const fetchClient = defineClient({
|
|
443
|
+
target: "fetch",
|
|
444
|
+
options: z.object({
|
|
445
|
+
/**
|
|
446
|
+
* Name of the generated namespace holding every model and every route's
|
|
447
|
+
* `Params`, `Query`, `Body`, `Headers` and `Result`.
|
|
448
|
+
*
|
|
449
|
+
* @default 'API'
|
|
450
|
+
*/
|
|
451
|
+
namespace: z.string().optional(),
|
|
452
|
+
/**
|
|
453
|
+
* Module the generated file imports its runtime from.
|
|
454
|
+
*
|
|
455
|
+
* @default '@kizunajs/fetch'
|
|
456
|
+
*/
|
|
457
|
+
runtimeModule: z.string().optional(),
|
|
458
|
+
/**
|
|
459
|
+
* Command the file's header tells a reader to run.
|
|
460
|
+
*
|
|
461
|
+
* @default 'kizuna generate'
|
|
462
|
+
*/
|
|
463
|
+
regenerateCommand: z.string().optional(),
|
|
464
|
+
/**
|
|
465
|
+
* Path to the api this was generated from, relative to the generated
|
|
466
|
+
* file. Named in the header so tooling can tell when the client has fallen
|
|
467
|
+
* behind, and so a reader knows where the API is declared.
|
|
468
|
+
*/
|
|
469
|
+
source: z.string().optional()
|
|
470
|
+
}),
|
|
471
|
+
generate: ({ options, api }) => ({ finalize: () => generateFetchClient(api, options) })
|
|
433
472
|
});
|
|
434
473
|
//#endregion
|
|
435
474
|
export { fetchClient, generateFetchClient };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kizunajs/fetch",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.11",
|
|
4
4
|
"description": "Typed fetch client for Kizuna.js APIs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kizunajs",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"zod": "^4.0.0",
|
|
71
|
-
"kizunajs": "2.0.0-beta.
|
|
71
|
+
"kizunajs": "2.0.0-beta.11"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/express": "^5.0.0",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"tsdown": "^0.21.0",
|
|
77
77
|
"typescript": "^6.0.3",
|
|
78
78
|
"zod": "^4.0.0",
|
|
79
|
-
"kizunajs": "2.0.0-beta.
|
|
80
|
-
"@kizunajs/express": "2.0.0-beta.
|
|
79
|
+
"kizunajs": "2.0.0-beta.11",
|
|
80
|
+
"@kizunajs/express": "2.0.0-beta.11"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "tsdown src/index.ts src/server.ts --format esm,cjs --dts --clean --external kizunajs --external zod",
|