@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 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 && def.type === "object") {
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: objectBody(schema, collector, modelName)
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((value) => quote(String(value))).join(" | ") : "never";
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((value) => quote(String(value))).join(" | ") : "never";
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: API.${namespace}.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?: API.${namespace}.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: API.${namespace}.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?: API.${namespace}.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}, API.${namespace}.Result>;`,
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.routes, "", collector);
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 API {
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 target for `kizuna.config.ts`.
465
+ * A TypeScript client for `kizuna.config.ts`.
426
466
  *
427
467
  * @example
428
- * fetchClient({ output: './packages/api-client/src/generated.ts' });
468
+ * fetchClient({
469
+ * output: './packages/api-client/src/generated.ts',
470
+ * });
429
471
  */
430
- const fetchClient = ({ output, ...options }) => ({
431
- kind: "fetch",
432
- output,
433
- generate: (contract) => generateFetchClient(contract, options)
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 { ApiDefinition, ClientTarget } from "kizunajs";
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
- interface FetchClientTargetOptions extends FetchClientOptions {
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 target for `kizuna.config.ts`.
51
+ * A TypeScript client for `kizuna.config.ts`.
41
52
  *
42
53
  * @example
43
- * fetchClient({ output: './packages/api-client/src/generated.ts' });
54
+ * fetchClient({
55
+ * output: './packages/api-client/src/generated.ts',
56
+ * });
44
57
  */
45
- declare const fetchClient: ({
46
- output,
47
- ...options
48
- }: FetchClientTargetOptions) => ClientTarget;
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 { ApiDefinition, ClientTarget } from "kizunajs";
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
- interface FetchClientTargetOptions extends FetchClientOptions {
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 target for `kizuna.config.ts`.
51
+ * A TypeScript client for `kizuna.config.ts`.
41
52
  *
42
53
  * @example
43
- * fetchClient({ output: './packages/api-client/src/generated.ts' });
54
+ * fetchClient({
55
+ * output: './packages/api-client/src/generated.ts',
56
+ * });
44
57
  */
45
- declare const fetchClient: ({
46
- output,
47
- ...options
48
- }: FetchClientTargetOptions) => ClientTarget;
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 && def.type === "object") {
177
+ if (modelName !== void 0) {
172
178
  if (collector.claim(modelName)) collector.add({
173
179
  name: modelName,
174
180
  description: readMetaDescription(schema),
175
- body: objectBody(schema, collector, modelName)
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((value) => quote(String(value))).join(" | ") : "never";
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((value) => quote(String(value))).join(" | ") : "never";
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: API.${namespace}.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?: API.${namespace}.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: API.${namespace}.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?: API.${namespace}.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}, API.${namespace}.Result>;`,
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.routes, "", collector);
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 API {
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 target for `kizuna.config.ts`.
435
+ * A TypeScript client for `kizuna.config.ts`.
425
436
  *
426
437
  * @example
427
- * fetchClient({ output: './packages/api-client/src/generated.ts' });
438
+ * fetchClient({
439
+ * output: './packages/api-client/src/generated.ts',
440
+ * });
428
441
  */
429
- const fetchClient = ({ output, ...options }) => ({
430
- kind: "fetch",
431
- output,
432
- generate: (contract) => generateFetchClient(contract, options)
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.1",
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.1"
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.1",
80
- "@kizunajs/express": "2.0.0-beta.1"
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",