@kizunajs/fetch 2.0.0-beta.12 → 2.0.0-beta.13
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/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/server.cjs +68 -5
- package/dist/server.mjs +69 -6
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
1
|
+
import { $brand, z } from "zod";
|
|
2
2
|
import { AUTO_GUARD_BRAND, AUTO_RESPONSES_BRAND, ExtractPathParams, HasPathParams, Method, ProblemDetails, RequestContextHeaderInputs, RouteDefinition, RoutePath, Routes, StreamMessageOf, StreamResponseDefinition, ValidationError, ValidationError as ValidationError$1, ValidationErrorFor, isProblemDetails, isValidationError } from "kizunajs";
|
|
3
3
|
|
|
4
4
|
//#region src/client.d.ts
|
|
@@ -163,4 +163,4 @@ interface GeneratedRoutes {
|
|
|
163
163
|
*/
|
|
164
164
|
declare const createGeneratedClient: (routes: GeneratedRoutes, config: ClientConfig) => Record<string, unknown>;
|
|
165
165
|
//#endregion
|
|
166
|
-
export { CLIENT_ROUTE, Client, ClientArgs, ClientConfig, ClientMethod, ClientResponse, ContextHeaderInputs, GeneratedResponse, GeneratedRoute, GeneratedRoutes, OutgoingRequest, type ValidationError, createGeneratedClient, isProblemDetails, isValidationError, routeOf };
|
|
166
|
+
export { type $brand, CLIENT_ROUTE, Client, ClientArgs, ClientConfig, ClientMethod, ClientResponse, ContextHeaderInputs, GeneratedResponse, GeneratedRoute, GeneratedRoutes, OutgoingRequest, type ValidationError, createGeneratedClient, isProblemDetails, isValidationError, routeOf };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTO_GUARD_BRAND, AUTO_RESPONSES_BRAND, ExtractPathParams, HasPathParams, Method, ProblemDetails, RequestContextHeaderInputs, RouteDefinition, RoutePath, Routes, StreamMessageOf, StreamResponseDefinition, ValidationError, ValidationError as ValidationError$1, ValidationErrorFor, isProblemDetails, isValidationError } from "kizunajs";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import { $brand, z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/client.d.ts
|
|
5
5
|
type ResponseUnion<R extends RouteDefinition> = { [S in keyof R['responses']]: {
|
|
@@ -163,4 +163,4 @@ interface GeneratedRoutes {
|
|
|
163
163
|
*/
|
|
164
164
|
declare const createGeneratedClient: (routes: GeneratedRoutes, config: ClientConfig) => Record<string, unknown>;
|
|
165
165
|
//#endregion
|
|
166
|
-
export { CLIENT_ROUTE, Client, ClientArgs, ClientConfig, ClientMethod, ClientResponse, ContextHeaderInputs, GeneratedResponse, GeneratedRoute, GeneratedRoutes, OutgoingRequest, type ValidationError, createGeneratedClient, isProblemDetails, isValidationError, routeOf };
|
|
166
|
+
export { type $brand, CLIENT_ROUTE, Client, ClientArgs, ClientConfig, ClientMethod, ClientResponse, ContextHeaderInputs, GeneratedResponse, GeneratedRoute, GeneratedRoutes, OutgoingRequest, type ValidationError, createGeneratedClient, isProblemDetails, isValidationError, routeOf };
|
package/dist/server.cjs
CHANGED
|
@@ -10,6 +10,7 @@ let zod = require("zod");
|
|
|
10
10
|
var TypeCollector = class {
|
|
11
11
|
named = /* @__PURE__ */ new Map();
|
|
12
12
|
claimed = /* @__PURE__ */ new Set();
|
|
13
|
+
brands = /* @__PURE__ */ new Map();
|
|
13
14
|
/**
|
|
14
15
|
* Claims a name before its body is walked, so a model that refers to itself
|
|
15
16
|
* resolves to its own name instead of recursing. Answers whether the caller
|
|
@@ -23,12 +24,55 @@ var TypeCollector = class {
|
|
|
23
24
|
add(type) {
|
|
24
25
|
this.named.set(type.name, type);
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Declares a brand as a named type, `UserId = string & $brand<"UserId">`,
|
|
29
|
+
* and answers the name.
|
|
30
|
+
*/
|
|
31
|
+
addBrand(brand, base, example) {
|
|
32
|
+
const name = (0, kizunajs_generator.sanitizeIdentifier)(brand);
|
|
33
|
+
const existing = this.brands.get(name);
|
|
34
|
+
if (existing !== void 0 && (existing.brand !== brand || existing.base !== base)) throw new Error(`@kizunajs/fetch: brand ${quote(brand)} wraps both ${existing.base} and ${base}. Give each its own name.`);
|
|
35
|
+
if (existing === void 0) this.brands.set(name, {
|
|
36
|
+
brand,
|
|
37
|
+
base,
|
|
38
|
+
example
|
|
39
|
+
});
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
usesBrand() {
|
|
43
|
+
return this.brands.size > 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Each brand's name, the type it wraps, and a value for its `@example`, for
|
|
47
|
+
* the constructors the file exports.
|
|
48
|
+
*/
|
|
49
|
+
allBrands() {
|
|
50
|
+
return [...this.brands].map(([name, { base, example }]) => ({
|
|
51
|
+
name,
|
|
52
|
+
base,
|
|
53
|
+
example
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
26
56
|
all() {
|
|
27
|
-
|
|
57
|
+
for (const name of this.brands.keys()) if (this.named.has(name)) throw new Error(`@kizunajs/fetch: brand ${quote(name)} has the same name as a model. Give each its own name.`);
|
|
58
|
+
const brands = [...this.brands].map(([name, { brand, base }]) => ({
|
|
59
|
+
name,
|
|
60
|
+
description: `A branded \`${base}\`. Responses hand one back, and \`to${name}\` makes one from a plain value.`,
|
|
61
|
+
body: `${base} & $brand<${quote(brand)}>`
|
|
62
|
+
}));
|
|
63
|
+
return [...this.named.values(), ...brands].sort((left, right) => left.name.localeCompare(right.name));
|
|
28
64
|
}
|
|
29
65
|
};
|
|
30
66
|
const quote = (value) => JSON.stringify(value);
|
|
31
67
|
/**
|
|
68
|
+
* What a brand wraps: the scalars, as the JSON carries them.
|
|
69
|
+
*/
|
|
70
|
+
const BRANDABLE_BASES = new Set([
|
|
71
|
+
"string",
|
|
72
|
+
"number",
|
|
73
|
+
"boolean"
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
32
76
|
* A literal as the type the JSON carries, so `z.literal(true)` is `true`.
|
|
33
77
|
*/
|
|
34
78
|
const literalType = (value) => {
|
|
@@ -161,9 +205,15 @@ const objectBody = (schema, collector, hint) => {
|
|
|
161
205
|
};
|
|
162
206
|
/**
|
|
163
207
|
* The TypeScript type a schema describes. Named models are collected and
|
|
164
|
-
* referenced; anonymous shapes are inlined.
|
|
208
|
+
* referenced; anonymous shapes are inlined. A brand is named like a model.
|
|
165
209
|
*/
|
|
166
210
|
const typeOf = (schema, collector, hint) => {
|
|
211
|
+
const type = unbrandedTypeOf(schema, collector, hint);
|
|
212
|
+
const brand = (0, kizunajs_generator.readMetaBrand)(schema);
|
|
213
|
+
if (brand === void 0 || !BRANDABLE_BASES.has(type)) return type;
|
|
214
|
+
return collector.addBrand(brand, type, sampleValue(schema, 0, brand));
|
|
215
|
+
};
|
|
216
|
+
const unbrandedTypeOf = (schema, collector, hint) => {
|
|
167
217
|
if ((0, kizunajs_generator.isFileSchema)(schema)) return "File | Blob";
|
|
168
218
|
if ((0, kizunajs_generator.isBinarySchema)(schema)) return "Uint8Array";
|
|
169
219
|
const def = (0, kizunajs_generator.readDef)(schema);
|
|
@@ -388,14 +438,27 @@ const emitRequestContext = (contract, collector) => {
|
|
|
388
438
|
}
|
|
389
439
|
return fields.length > 0 ? `export interface RequestContext {\n${indent(fields.join("\n"))}\n}` : void 0;
|
|
390
440
|
};
|
|
441
|
+
/**
|
|
442
|
+
* A constructor per brand, so a value from outside the API becomes one with
|
|
443
|
+
* `toUserId('usr_k7f3q9')` rather than a cast.
|
|
444
|
+
*/
|
|
445
|
+
const emitBrandConstructors = (collector, namespaceName) => collector.allBrands().map(({ name, base, example }) => `
|
|
446
|
+
${docComment([
|
|
447
|
+
`Brands a value from outside the API, like a URL segment, as \`${namespaceName}.${name}\`. A value from a response is branded already.`,
|
|
448
|
+
"",
|
|
449
|
+
"@example",
|
|
450
|
+
`const id = to${name}(${example});`
|
|
451
|
+
])}export const to${name} = (value: ${base}): ${namespaceName}.${name} => value as ${namespaceName}.${name};
|
|
452
|
+
`).join("");
|
|
391
453
|
const generateFetchClient = (contract, options = {}) => {
|
|
392
454
|
const { namespace: namespaceName = "API", runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
393
455
|
const collector = new TypeCollector();
|
|
394
456
|
const tree = emitTree((0, kizunajs_generator.listedRoutes)(contract), "", collector, namespaceName);
|
|
457
|
+
const requestContext = emitRequestContext(contract, collector);
|
|
395
458
|
const api = [...collector.all().map((model) => {
|
|
396
459
|
return `${model.description ? `/**\n * ${model.description}\n */\n` : ""}export type ${model.name} = ${model.body};`;
|
|
397
460
|
}), ...tree.declarations].join("\n\n");
|
|
398
|
-
const
|
|
461
|
+
const brands = emitBrandConstructors(collector, namespaceName);
|
|
399
462
|
return `/* eslint-disable */
|
|
400
463
|
/**
|
|
401
464
|
${[
|
|
@@ -405,7 +468,7 @@ ${[
|
|
|
405
468
|
` * Regenerate: ${regenerateCommand}`
|
|
406
469
|
].join("\n")}
|
|
407
470
|
*/
|
|
408
|
-
import { createGeneratedClient, type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
471
|
+
import { createGeneratedClient, ${collector.usesBrand() ? "type $brand, " : ""}type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
409
472
|
|
|
410
473
|
export namespace ${namespaceName} {
|
|
411
474
|
${indent(api)}
|
|
@@ -418,7 +481,7 @@ ${indent(tree.signatures)}
|
|
|
418
481
|
const routes: GeneratedRoutes = {
|
|
419
482
|
${indent(tree.table)}
|
|
420
483
|
};
|
|
421
|
-
|
|
484
|
+
${brands}
|
|
422
485
|
${requestContext === void 0 ? `/**
|
|
423
486
|
* A client for this API. Pass the base URL and anything else the runtime takes.
|
|
424
487
|
*/
|
package/dist/server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValidationErrorSchema } from "kizunajs/schemas";
|
|
2
|
-
import { defineClient, isBinarySchema, isFileSchema, isStreamResponse, isVoidSchema, listedRoutes, 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, readMetaBrand, readMetaDescription, readMetaExamples, readMetaId, readObjectShape, resolveResponseBody, resolveResponseHeaders, sanitizeIdentifier, streamContentType, toPascalCase, unwrapOptionalWrappers } from "kizunajs/generator";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/zod-to-typescript.ts
|
|
5
5
|
/**
|
|
@@ -9,6 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
var TypeCollector = class {
|
|
10
10
|
named = /* @__PURE__ */ new Map();
|
|
11
11
|
claimed = /* @__PURE__ */ new Set();
|
|
12
|
+
brands = /* @__PURE__ */ new Map();
|
|
12
13
|
/**
|
|
13
14
|
* Claims a name before its body is walked, so a model that refers to itself
|
|
14
15
|
* resolves to its own name instead of recursing. Answers whether the caller
|
|
@@ -22,12 +23,55 @@ var TypeCollector = class {
|
|
|
22
23
|
add(type) {
|
|
23
24
|
this.named.set(type.name, type);
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Declares a brand as a named type, `UserId = string & $brand<"UserId">`,
|
|
28
|
+
* and answers the name.
|
|
29
|
+
*/
|
|
30
|
+
addBrand(brand, base, example) {
|
|
31
|
+
const name = sanitizeIdentifier(brand);
|
|
32
|
+
const existing = this.brands.get(name);
|
|
33
|
+
if (existing !== void 0 && (existing.brand !== brand || existing.base !== base)) throw new Error(`@kizunajs/fetch: brand ${quote(brand)} wraps both ${existing.base} and ${base}. Give each its own name.`);
|
|
34
|
+
if (existing === void 0) this.brands.set(name, {
|
|
35
|
+
brand,
|
|
36
|
+
base,
|
|
37
|
+
example
|
|
38
|
+
});
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
usesBrand() {
|
|
42
|
+
return this.brands.size > 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Each brand's name, the type it wraps, and a value for its `@example`, for
|
|
46
|
+
* the constructors the file exports.
|
|
47
|
+
*/
|
|
48
|
+
allBrands() {
|
|
49
|
+
return [...this.brands].map(([name, { base, example }]) => ({
|
|
50
|
+
name,
|
|
51
|
+
base,
|
|
52
|
+
example
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
25
55
|
all() {
|
|
26
|
-
|
|
56
|
+
for (const name of this.brands.keys()) if (this.named.has(name)) throw new Error(`@kizunajs/fetch: brand ${quote(name)} has the same name as a model. Give each its own name.`);
|
|
57
|
+
const brands = [...this.brands].map(([name, { brand, base }]) => ({
|
|
58
|
+
name,
|
|
59
|
+
description: `A branded \`${base}\`. Responses hand one back, and \`to${name}\` makes one from a plain value.`,
|
|
60
|
+
body: `${base} & $brand<${quote(brand)}>`
|
|
61
|
+
}));
|
|
62
|
+
return [...this.named.values(), ...brands].sort((left, right) => left.name.localeCompare(right.name));
|
|
27
63
|
}
|
|
28
64
|
};
|
|
29
65
|
const quote = (value) => JSON.stringify(value);
|
|
30
66
|
/**
|
|
67
|
+
* What a brand wraps: the scalars, as the JSON carries them.
|
|
68
|
+
*/
|
|
69
|
+
const BRANDABLE_BASES = new Set([
|
|
70
|
+
"string",
|
|
71
|
+
"number",
|
|
72
|
+
"boolean"
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
31
75
|
* A literal as the type the JSON carries, so `z.literal(true)` is `true`.
|
|
32
76
|
*/
|
|
33
77
|
const literalType = (value) => {
|
|
@@ -160,9 +204,15 @@ const objectBody = (schema, collector, hint) => {
|
|
|
160
204
|
};
|
|
161
205
|
/**
|
|
162
206
|
* The TypeScript type a schema describes. Named models are collected and
|
|
163
|
-
* referenced; anonymous shapes are inlined.
|
|
207
|
+
* referenced; anonymous shapes are inlined. A brand is named like a model.
|
|
164
208
|
*/
|
|
165
209
|
const typeOf = (schema, collector, hint) => {
|
|
210
|
+
const type = unbrandedTypeOf(schema, collector, hint);
|
|
211
|
+
const brand = readMetaBrand(schema);
|
|
212
|
+
if (brand === void 0 || !BRANDABLE_BASES.has(type)) return type;
|
|
213
|
+
return collector.addBrand(brand, type, sampleValue(schema, 0, brand));
|
|
214
|
+
};
|
|
215
|
+
const unbrandedTypeOf = (schema, collector, hint) => {
|
|
166
216
|
if (isFileSchema(schema)) return "File | Blob";
|
|
167
217
|
if (isBinarySchema(schema)) return "Uint8Array";
|
|
168
218
|
const def = readDef(schema);
|
|
@@ -387,14 +437,27 @@ const emitRequestContext = (contract, collector) => {
|
|
|
387
437
|
}
|
|
388
438
|
return fields.length > 0 ? `export interface RequestContext {\n${indent(fields.join("\n"))}\n}` : void 0;
|
|
389
439
|
};
|
|
440
|
+
/**
|
|
441
|
+
* A constructor per brand, so a value from outside the API becomes one with
|
|
442
|
+
* `toUserId('usr_k7f3q9')` rather than a cast.
|
|
443
|
+
*/
|
|
444
|
+
const emitBrandConstructors = (collector, namespaceName) => collector.allBrands().map(({ name, base, example }) => `
|
|
445
|
+
${docComment([
|
|
446
|
+
`Brands a value from outside the API, like a URL segment, as \`${namespaceName}.${name}\`. A value from a response is branded already.`,
|
|
447
|
+
"",
|
|
448
|
+
"@example",
|
|
449
|
+
`const id = to${name}(${example});`
|
|
450
|
+
])}export const to${name} = (value: ${base}): ${namespaceName}.${name} => value as ${namespaceName}.${name};
|
|
451
|
+
`).join("");
|
|
390
452
|
const generateFetchClient = (contract, options = {}) => {
|
|
391
453
|
const { namespace: namespaceName = "API", runtimeModule = "@kizunajs/fetch", regenerateCommand = "kizuna generate", source } = options;
|
|
392
454
|
const collector = new TypeCollector();
|
|
393
455
|
const tree = emitTree(listedRoutes(contract), "", collector, namespaceName);
|
|
456
|
+
const requestContext = emitRequestContext(contract, collector);
|
|
394
457
|
const api = [...collector.all().map((model) => {
|
|
395
458
|
return `${model.description ? `/**\n * ${model.description}\n */\n` : ""}export type ${model.name} = ${model.body};`;
|
|
396
459
|
}), ...tree.declarations].join("\n\n");
|
|
397
|
-
const
|
|
460
|
+
const brands = emitBrandConstructors(collector, namespaceName);
|
|
398
461
|
return `/* eslint-disable */
|
|
399
462
|
/**
|
|
400
463
|
${[
|
|
@@ -404,7 +467,7 @@ ${[
|
|
|
404
467
|
` * Regenerate: ${regenerateCommand}`
|
|
405
468
|
].join("\n")}
|
|
406
469
|
*/
|
|
407
|
-
import { createGeneratedClient, type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
470
|
+
import { createGeneratedClient, ${collector.usesBrand() ? "type $brand, " : ""}type ClientConfig, type ClientMethod, type GeneratedRoutes } from '${runtimeModule}';
|
|
408
471
|
|
|
409
472
|
export namespace ${namespaceName} {
|
|
410
473
|
${indent(api)}
|
|
@@ -417,7 +480,7 @@ ${indent(tree.signatures)}
|
|
|
417
480
|
const routes: GeneratedRoutes = {
|
|
418
481
|
${indent(tree.table)}
|
|
419
482
|
};
|
|
420
|
-
|
|
483
|
+
${brands}
|
|
421
484
|
${requestContext === void 0 ? `/**
|
|
422
485
|
* A client for this API. Pass the base URL and anything else the runtime takes.
|
|
423
486
|
*/
|
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.13",
|
|
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.13"
|
|
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.13",
|
|
80
|
+
"@kizunajs/express": "2.0.0-beta.13"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "tsdown src/index.ts src/server.ts --format esm,cjs --dts --clean --external kizunajs --external zod",
|