dixous 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/response-methods.d.ts +16 -0
- package/dist/response-methods.d.ts.map +1 -0
- package/dist/response-methods.js +23 -0
- package/dist/response-methods.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
- package/src/index.ts +219 -0
- package/src/response-methods.ts +36 -0
- package/src/types.ts +141 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dixous contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Dixous
|
|
2
|
+
|
|
3
|
+
A minimal, fully typed, extendable fetch client.
|
|
4
|
+
|
|
5
|
+
Start with a simple request, then add validation, middleware, and custom response handlers as you need them.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install dixous
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or from JSR: `deno add jsr:@asguho/dixous` / `npx jsr add @asguho/dixous`.
|
|
14
|
+
Requires a runtime with native Fetch (Node.js 22+, Deno, or a modern browser).
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createDixous } from "dixous";
|
|
20
|
+
const dixous = createDixous();
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
23
|
+
const image = await dixous.fetch("https://example.com/image.png").blob();
|
|
24
|
+
|
|
25
|
+
const User = z.object({
|
|
26
|
+
id: z.number(),
|
|
27
|
+
name: z.string(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const api = dixous({
|
|
31
|
+
baseUrl: "https://api.example.com/",
|
|
32
|
+
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
|
|
33
|
+
});
|
|
34
|
+
const user = await api.fetch("users/1").json(User);
|
|
35
|
+
|
|
36
|
+
console.log(user.name); // string, validated at runtime
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requests run when you call `.json(schema)`, `.text()`, `.blob()`, `.arrayBuffer()`, or `.response()`. JSON accepts any Standard Schema v1 validator.
|
|
40
|
+
|
|
41
|
+
## Fully extensible
|
|
42
|
+
|
|
43
|
+
Define your client once. Add response methods, middleware, and typed options with `defineExtension`.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// lib/dixous.ts
|
|
47
|
+
import { createDixous, defineExtension } from "dixous";
|
|
48
|
+
import { parseXml } from "schema-xml";
|
|
49
|
+
import { z } from "zod";
|
|
50
|
+
|
|
51
|
+
// Parse and validate XML with Schema XML.
|
|
52
|
+
const xml = defineExtension({
|
|
53
|
+
methods: {
|
|
54
|
+
xml: (fetchResponse) =>
|
|
55
|
+
async <S extends z.ZodType>(schema: S): Promise<z.output<S>> =>
|
|
56
|
+
parseXml(await (await fetchResponse()).text(), schema),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Retry GET requests up to three times in total on a 503 response.
|
|
61
|
+
const retry = defineExtension({
|
|
62
|
+
async request({ request }, next) {
|
|
63
|
+
for (let attempt = 1; ; attempt++) {
|
|
64
|
+
request.signal.throwIfAborted();
|
|
65
|
+
const response = await next();
|
|
66
|
+
if (request.method !== "GET" || response.status !== 503 || attempt === 3) {
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
await response.body?.cancel();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Add a typed query option.
|
|
75
|
+
const query = defineExtension<{ query?: Record<string, string> }>()({
|
|
76
|
+
async request(context, next) {
|
|
77
|
+
const url = new URL(context.request.url);
|
|
78
|
+
for (const [key, value] of Object.entries(context.options.query ?? {})) {
|
|
79
|
+
url.searchParams.append(key, value);
|
|
80
|
+
}
|
|
81
|
+
context.request = new Request(url, context.request);
|
|
82
|
+
return next();
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const dixous = createDixous({ extensions: [xml, query, retry] });
|
|
87
|
+
```
|
|
88
|
+
Use the extended client anywhere:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { dixous } from "./lib/dixous";
|
|
92
|
+
import { z } from "zod";
|
|
93
|
+
|
|
94
|
+
const Catalog = z.object({
|
|
95
|
+
catalog: z.object({ book: z.array(z.object({ title: z.string() })) }),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await dixous.fetch("https://example.com/catalog", {
|
|
99
|
+
query: { author: "Ursula K. Le Guin" },
|
|
100
|
+
}).xml(Catalog);
|
|
101
|
+
|
|
102
|
+
console.log(result.catalog.book); // { title: string }[]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The XML example uses [Schema XML](https://github.com/Asguho/schema-xml) and Zod (`npm install schema-xml zod`).
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm ci
|
|
111
|
+
npm test
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See [RELEASING.md](./RELEASING.md) for npm and JSR publishing.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DefaultResponseMethods } from "./response-methods.ts";
|
|
2
|
+
import type { ContextKey, Dixous, Extension, ExtensionClientOptions, ExtensionDefinition, ExtensionMeta, ExtensionMethods, ExtensionRequestOptions, NoClientOptionOverrides, NoRequestInitOverrides, ResponseMethods } from "./types.ts";
|
|
3
|
+
export { SchemaValidationError } from "./response-methods.ts";
|
|
4
|
+
export type { DefaultResponseMethods, InferOutput } from "./response-methods.ts";
|
|
5
|
+
export type { BaseClientOptions, ClientOptions, Context, ContextKey, Dixous, Extension, Fetcher, FetchResponse, Middleware, Next, RequestContext, RequestOptions, } from "./types.ts";
|
|
6
|
+
export declare function createContextKey<T>(): ContextKey<T>;
|
|
7
|
+
export declare function defineExtension<const Methods extends ResponseMethods = {}>(extension: ExtensionDefinition<{}, {}, Methods>): Extension<{}, {}, Methods>;
|
|
8
|
+
export declare function defineExtension<RequestExtra extends object & NoRequestInitOverrides = {}, ClientExtra extends object & NoClientOptionOverrides = {}>(): <const Methods extends ResponseMethods = {}>(extension: ExtensionDefinition<RequestExtra, ClientExtra, Methods>) => Extension<RequestExtra, ClientExtra, Methods>;
|
|
9
|
+
export declare class HttpError extends Error {
|
|
10
|
+
readonly request: Request;
|
|
11
|
+
readonly response: Response;
|
|
12
|
+
readonly status: number;
|
|
13
|
+
constructor(request: Request, response: Response);
|
|
14
|
+
}
|
|
15
|
+
export declare function createDixous<const Extensions extends readonly ExtensionMeta[] = []>(options?: {
|
|
16
|
+
extensions?: Extensions;
|
|
17
|
+
fetch?: typeof globalThis.fetch;
|
|
18
|
+
}): Dixous<ExtensionRequestOptions<Extensions>, ExtensionClientOptions<Extensions>, DefaultResponseMethods & ExtensionMethods<Extensions>>;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,KAAK,EAGV,UAAU,EACV,MAAM,EACN,SAAS,EACT,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EAIvB,uBAAuB,EACvB,sBAAsB,EAGtB,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEjF,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,OAAO,EACP,UAAU,EACV,MAAM,EACN,SAAS,EACT,OAAO,EACP,aAAa,EACb,UAAU,EACV,IAAI,EACJ,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,wBAAgB,gBAAgB,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAEnD;AAcD,wBAAgB,eAAe,CAAC,KAAK,CAAC,OAAO,SAAS,eAAe,GAAG,EAAE,EACxE,SAAS,EAAE,mBAAmB,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAC9C,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;AAC9B,wBAAgB,eAAe,CAC7B,YAAY,SAAS,MAAM,GAAG,sBAAsB,GAAG,EAAE,EACzD,WAAW,SAAS,MAAM,GAAG,uBAAuB,GAAG,EAAE,KACtD,CAAC,KAAK,CAAC,OAAO,SAAS,eAAe,GAAG,EAAE,EAC9C,SAAS,EAAE,mBAAmB,CAAC,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,KAC/D,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAMnD,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YAAY,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAM/C;CACF;AAoFD,wBAAgB,YAAY,CAC1B,KAAK,CAAC,UAAU,SAAS,SAAS,aAAa,EAAE,GAAG,EAAE,EACtD,OAAO,CAAC,EAAE;IACV,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,GAAG,MAAM,CACR,uBAAuB,CAAC,UAAU,CAAC,EACnC,sBAAsB,CAAC,UAAU,CAAC,EAClC,sBAAsB,GAAG,gBAAgB,CAAC,UAAU,CAAC,CACtD,CA2CA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { defaultResponseMethods } from "./response-methods.js";
|
|
2
|
+
export { SchemaValidationError } from "./response-methods.js";
|
|
3
|
+
export function createContextKey() {
|
|
4
|
+
return Symbol();
|
|
5
|
+
}
|
|
6
|
+
function createContext() {
|
|
7
|
+
const values = new Map();
|
|
8
|
+
return {
|
|
9
|
+
get(key) {
|
|
10
|
+
return values.get(key);
|
|
11
|
+
},
|
|
12
|
+
set(key, value) {
|
|
13
|
+
values.set(key, value);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function defineExtension(extension) {
|
|
18
|
+
// Extension metadata is a type-only brand; composition uses the captured values.
|
|
19
|
+
return extension === undefined ? (definition) => definition : extension;
|
|
20
|
+
}
|
|
21
|
+
export class HttpError extends Error {
|
|
22
|
+
request;
|
|
23
|
+
response;
|
|
24
|
+
status;
|
|
25
|
+
constructor(request, response) {
|
|
26
|
+
super(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`);
|
|
27
|
+
this.name = "HttpError";
|
|
28
|
+
this.request = request;
|
|
29
|
+
this.response = response;
|
|
30
|
+
this.status = response.status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function snapshotClient(options = {}) {
|
|
34
|
+
const client = { ...options };
|
|
35
|
+
if (client.baseUrl !== undefined)
|
|
36
|
+
client.baseUrl = client.baseUrl.toString();
|
|
37
|
+
if (client.headers !== undefined)
|
|
38
|
+
client.headers = new Headers(client.headers);
|
|
39
|
+
return Object.freeze(client);
|
|
40
|
+
}
|
|
41
|
+
function snapshotOptions(options = {}) {
|
|
42
|
+
const snapshot = { ...options };
|
|
43
|
+
if (snapshot.headers !== undefined)
|
|
44
|
+
snapshot.headers = new Headers(snapshot.headers);
|
|
45
|
+
return Object.freeze(snapshot);
|
|
46
|
+
}
|
|
47
|
+
function createTemplate(input, options, client) {
|
|
48
|
+
const headers = new Headers(client.headers);
|
|
49
|
+
if (input instanceof Request) {
|
|
50
|
+
input.headers.forEach((value, name) => headers.set(name, value));
|
|
51
|
+
}
|
|
52
|
+
if (options.headers !== undefined) {
|
|
53
|
+
new Headers(options.headers).forEach((value, name) => headers.set(name, value));
|
|
54
|
+
}
|
|
55
|
+
const source = !(input instanceof Request) && client.baseUrl !== undefined
|
|
56
|
+
? new URL(input.toString(), client.baseUrl)
|
|
57
|
+
: input;
|
|
58
|
+
return new Request(source, { ...options, headers });
|
|
59
|
+
}
|
|
60
|
+
function runMiddleware(middleware, context, fetchImpl) {
|
|
61
|
+
async function dispatch(index) {
|
|
62
|
+
const current = middleware[index];
|
|
63
|
+
if (current === undefined)
|
|
64
|
+
return fetchImpl(context.request.clone());
|
|
65
|
+
// Each middleware invocation owns its guard. Sequential retries re-enter
|
|
66
|
+
// the downstream chain with the same context and new downstream guards.
|
|
67
|
+
let running = false;
|
|
68
|
+
return current(context, async () => {
|
|
69
|
+
if (running)
|
|
70
|
+
throw new Error("Overlapping next() calls are not allowed");
|
|
71
|
+
running = true;
|
|
72
|
+
try {
|
|
73
|
+
return await dispatch(index + 1);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
running = false;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return dispatch(0);
|
|
81
|
+
}
|
|
82
|
+
function createFetchResponse(template, options, client, middleware, fetchImpl) {
|
|
83
|
+
let execution;
|
|
84
|
+
return () => {
|
|
85
|
+
// Defer execution until after storing the promise, including when a
|
|
86
|
+
// synchronous middleware re-enters its operation's FetchResponse.
|
|
87
|
+
execution ??= Promise.resolve().then(async () => {
|
|
88
|
+
const context = {
|
|
89
|
+
request: template.clone(),
|
|
90
|
+
options,
|
|
91
|
+
client,
|
|
92
|
+
state: createContext(),
|
|
93
|
+
};
|
|
94
|
+
const response = await runMiddleware(middleware, context, fetchImpl);
|
|
95
|
+
if (!response.ok)
|
|
96
|
+
throw new HttpError(context.request, response);
|
|
97
|
+
return response;
|
|
98
|
+
});
|
|
99
|
+
return execution;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function createDixous(options) {
|
|
103
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
104
|
+
const middleware = [];
|
|
105
|
+
const methods = new Map(Object.entries(defaultResponseMethods));
|
|
106
|
+
for (const entry of options?.extensions ?? []) {
|
|
107
|
+
// Contributions are erased only inside the kernel; the public signature
|
|
108
|
+
// intersects their exact types when constructing the resulting client.
|
|
109
|
+
const extension = entry;
|
|
110
|
+
if (extension.request !== undefined)
|
|
111
|
+
middleware.push(extension.request);
|
|
112
|
+
for (const [name, factory] of Object.entries(extension.methods ?? {})) {
|
|
113
|
+
if (methods.has(name))
|
|
114
|
+
throw new Error(`Duplicate response method: ${name}`);
|
|
115
|
+
methods.set(name, factory);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function configured(clientOptions) {
|
|
119
|
+
const client = snapshotClient(clientOptions);
|
|
120
|
+
return {
|
|
121
|
+
fetch(input, requestOptions) {
|
|
122
|
+
const snapshot = snapshotOptions(requestOptions);
|
|
123
|
+
const template = createTemplate(input, snapshot, client);
|
|
124
|
+
const pending = Object.create(null);
|
|
125
|
+
for (const [name, factory] of methods) {
|
|
126
|
+
pending[name] = (...args) => {
|
|
127
|
+
const fetchResponse = createFetchResponse(template, snapshot, client, middleware, fetchImpl);
|
|
128
|
+
// Factories and method work are lazy too, and run once per call.
|
|
129
|
+
return factory(fetchResponse)(...args);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return Object.freeze(pending);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return Object.assign(configured, { fetch: configured().fetch });
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import type { FetchResponse } from "./types.ts";
|
|
3
|
+
export type InferOutput<Schema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<Schema>;
|
|
4
|
+
export declare class SchemaValidationError extends Error {
|
|
5
|
+
readonly issues: readonly StandardSchemaV1.Issue[];
|
|
6
|
+
constructor(issues: readonly StandardSchemaV1.Issue[]);
|
|
7
|
+
}
|
|
8
|
+
export declare const defaultResponseMethods: {
|
|
9
|
+
json: (fetchResponse: FetchResponse) => <Schema extends StandardSchemaV1>(schema: Schema) => Promise<InferOutput<Schema>>;
|
|
10
|
+
text: (fetchResponse: FetchResponse) => () => Promise<string>;
|
|
11
|
+
blob: (fetchResponse: FetchResponse) => () => Promise<Blob>;
|
|
12
|
+
arrayBuffer: (fetchResponse: FetchResponse) => () => Promise<ArrayBuffer>;
|
|
13
|
+
response: (fetchResponse: FetchResponse) => () => Promise<Response>;
|
|
14
|
+
};
|
|
15
|
+
export type DefaultResponseMethods = typeof defaultResponseMethods;
|
|
16
|
+
//# sourceMappingURL=response-methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-methods.d.ts","sourceRoot":"","sources":["../src/response-methods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,YAAY,CAAC;AAEjE,MAAM,MAAM,WAAW,CAAC,MAAM,SAAS,gBAAgB,IACrD,gBAAgB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AAEvC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAEnD,YAAY,MAAM,EAAE,SAAS,gBAAgB,CAAC,KAAK,EAAE,EAIpD;CACF;AAGD,eAAO,MAAM,sBAAsB;0BACX,aAAa,MAC1B,MAAM,SAAS,gBAAgB,UAAU,MAAM,KAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;0BAMjE,aAAa,WAAe,OAAO,CAAC,MAAM,CAAC;0BAE3C,aAAa,WAAe,OAAO,CAAC,IAAI,CAAC;iCAElC,aAAa,WAAe,OAAO,CAAC,WAAW,CAAC;8BAEnD,aAAa,WAAS,OAAO,CAAC,QAAQ,CAAC;CAExC,CAAC;AAE5B,MAAM,MAAM,sBAAsB,GAAG,OAAO,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class SchemaValidationError extends Error {
|
|
2
|
+
issues;
|
|
3
|
+
constructor(issues) {
|
|
4
|
+
super("Response failed schema validation");
|
|
5
|
+
this.name = "SchemaValidationError";
|
|
6
|
+
this.issues = issues;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
// Built-ins use the same per-operation factories as extension methods.
|
|
10
|
+
export const defaultResponseMethods = {
|
|
11
|
+
json: (fetchResponse) => async (schema) => {
|
|
12
|
+
const response = await fetchResponse();
|
|
13
|
+
const result = await schema["~standard"].validate(await response.json());
|
|
14
|
+
if (result.issues)
|
|
15
|
+
throw new SchemaValidationError(result.issues);
|
|
16
|
+
return result.value;
|
|
17
|
+
},
|
|
18
|
+
text: (fetchResponse) => async () => (await fetchResponse()).text(),
|
|
19
|
+
blob: (fetchResponse) => async () => (await fetchResponse()).blob(),
|
|
20
|
+
arrayBuffer: (fetchResponse) => async () => (await fetchResponse()).arrayBuffer(),
|
|
21
|
+
response: (fetchResponse) => () => fetchResponse(),
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=response-methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-methods.js","sourceRoot":"","sources":["../src/response-methods.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,MAAM,CAAoC;IAEnD,YAAY,MAAyC;QACnD,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,CAAC,aAA4B,EAAE,EAAE,CACrC,KAAK,EAAmC,MAAc,EAAgC,EAAE;QACtF,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACzE,IAAI,MAAM,CAAC,MAAM;YAAE,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClE,OAAO,MAAM,CAAC,KAA4B,CAAC;IAC7C,CAAC;IACH,IAAI,EAAE,CAAC,aAA4B,EAAE,EAAE,CAAC,KAAK,IAAqB,EAAE,CAClE,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE;IAChC,IAAI,EAAE,CAAC,aAA4B,EAAE,EAAE,CAAC,KAAK,IAAmB,EAAE,CAChE,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE;IAChC,WAAW,EAAE,CAAC,aAA4B,EAAE,EAAE,CAAC,KAAK,IAA0B,EAAE,CAC9E,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,WAAW,EAAE;IACvC,QAAQ,EAAE,CAAC,aAA4B,EAAE,EAAE,CAAC,GAAsB,EAAE,CAClE,aAAa,EAAE;CACQ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
declare const contextKeyType: unique symbol;
|
|
2
|
+
export type ContextKey<T> = symbol & {
|
|
3
|
+
readonly [contextKeyType]: (value: T) => T;
|
|
4
|
+
};
|
|
5
|
+
export interface Context {
|
|
6
|
+
get<T>(key: ContextKey<T>): T | undefined;
|
|
7
|
+
set<T>(key: ContextKey<T>, value: T): void;
|
|
8
|
+
}
|
|
9
|
+
export type RequestOptions<Extra extends object = {}> = RequestInit & Extra;
|
|
10
|
+
export interface BaseClientOptions {
|
|
11
|
+
baseUrl?: string | URL;
|
|
12
|
+
headers?: HeadersInit;
|
|
13
|
+
}
|
|
14
|
+
export type ClientOptions<Extra extends object = {}> = BaseClientOptions & Extra;
|
|
15
|
+
export interface RequestContext<RequestExtra extends object = {}, ClientExtra extends object = {}> {
|
|
16
|
+
request: Request;
|
|
17
|
+
readonly options: Readonly<RequestOptions<RequestExtra>>;
|
|
18
|
+
readonly client: Readonly<ClientOptions<ClientExtra>>;
|
|
19
|
+
readonly state: Context;
|
|
20
|
+
}
|
|
21
|
+
export type Next = () => Promise<Response>;
|
|
22
|
+
export type Middleware<RequestExtra extends object = {}, ClientExtra extends object = {}> = (context: RequestContext<RequestExtra, ClientExtra>, next: Next) => Promise<Response>;
|
|
23
|
+
export type FetchResponse = () => Promise<Response>;
|
|
24
|
+
export type ResponseMethod = (fetchResponse: FetchResponse) => (...args: never[]) => Promise<unknown>;
|
|
25
|
+
export type ResponseMethods = Record<string, ResponseMethod>;
|
|
26
|
+
type InstantiateMethods<Methods extends ResponseMethods> = {
|
|
27
|
+
readonly [K in keyof Methods]: ReturnType<Methods[K]>;
|
|
28
|
+
};
|
|
29
|
+
declare const extensionType: unique symbol;
|
|
30
|
+
export interface ExtensionMeta {
|
|
31
|
+
readonly [extensionType]: {
|
|
32
|
+
request: object;
|
|
33
|
+
client: object;
|
|
34
|
+
methods: ResponseMethods;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface Extension<RequestExtra extends object = {}, ClientExtra extends object = {}, Methods extends ResponseMethods = {}> {
|
|
38
|
+
readonly [extensionType]: {
|
|
39
|
+
request: RequestExtra;
|
|
40
|
+
client: ClientExtra;
|
|
41
|
+
methods: Methods;
|
|
42
|
+
};
|
|
43
|
+
request?: Middleware<RequestExtra, ClientExtra>;
|
|
44
|
+
methods?: Methods;
|
|
45
|
+
}
|
|
46
|
+
export type ExtensionDefinition<RequestExtra extends object, ClientExtra extends object, Methods extends ResponseMethods> = Pick<Extension<RequestExtra, ClientExtra, Methods>, "request"> & {
|
|
47
|
+
methods?: Methods & Record<string, (fetchResponse: FetchResponse) => unknown>;
|
|
48
|
+
};
|
|
49
|
+
export type NoRequestInitOverrides = {
|
|
50
|
+
[K in keyof RequestInit]?: never;
|
|
51
|
+
};
|
|
52
|
+
export type NoClientOptionOverrides = {
|
|
53
|
+
[K in keyof BaseClientOptions]?: never;
|
|
54
|
+
};
|
|
55
|
+
type UnionToIntersection<T> = [T] extends [never] ? {} : (T extends unknown ? (value: T) => void : never) extends (value: infer Result) => void ? Result : never;
|
|
56
|
+
export type ExtensionRequestOptions<Extensions extends readonly ExtensionMeta[]> = UnionToIntersection<Extensions[number][typeof extensionType]["request"]> extends infer Result extends object ? Result : never;
|
|
57
|
+
export type ExtensionClientOptions<Extensions extends readonly ExtensionMeta[]> = UnionToIntersection<Extensions[number][typeof extensionType]["client"]> extends infer Result extends object ? Result : never;
|
|
58
|
+
export type ExtensionMethods<Extensions extends readonly ExtensionMeta[]> = UnionToIntersection<Extensions[number][typeof extensionType]["methods"]> extends infer Result extends ResponseMethods ? Result : never;
|
|
59
|
+
export interface Fetcher<RequestExtra extends object, Methods extends ResponseMethods> {
|
|
60
|
+
fetch(input: string | URL | Request, options?: RequestOptions<RequestExtra>): InstantiateMethods<Methods>;
|
|
61
|
+
}
|
|
62
|
+
export interface Dixous<RequestExtra extends object, ClientExtra extends object, Methods extends ResponseMethods> extends Fetcher<RequestExtra, Methods> {
|
|
63
|
+
(options?: ClientOptions<ClientExtra>): Fetcher<RequestExtra, Methods>;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dixous",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A minimal, extension-driven query client built on native Fetch.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/*.js",
|
|
8
|
+
"dist/*.js.map",
|
|
9
|
+
"dist/*.d.ts",
|
|
10
|
+
"dist/*.d.ts.map",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
22
|
+
"test": "npm run build && npm run typecheck && node --test test/*.test.mjs",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"test:package": "node scripts/test-package.mjs",
|
|
25
|
+
"check:release": "node scripts/check-release.mjs"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^7.0.2"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@standard-schema/spec": "^1.1.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Asguho/Dixous.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/Asguho/Dixous#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/Asguho/Dixous/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22"
|
|
44
|
+
},
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { defaultResponseMethods, type DefaultResponseMethods } from "./response-methods.ts";
|
|
2
|
+
import type {
|
|
3
|
+
ClientOptions,
|
|
4
|
+
Context,
|
|
5
|
+
ContextKey,
|
|
6
|
+
Dixous,
|
|
7
|
+
Extension,
|
|
8
|
+
ExtensionClientOptions,
|
|
9
|
+
ExtensionDefinition,
|
|
10
|
+
ExtensionMeta,
|
|
11
|
+
ExtensionMethods,
|
|
12
|
+
ExtensionRequestOptions,
|
|
13
|
+
Fetcher,
|
|
14
|
+
FetchResponse,
|
|
15
|
+
Middleware,
|
|
16
|
+
NoClientOptionOverrides,
|
|
17
|
+
NoRequestInitOverrides,
|
|
18
|
+
RequestContext,
|
|
19
|
+
RequestOptions,
|
|
20
|
+
ResponseMethods,
|
|
21
|
+
} from "./types.ts";
|
|
22
|
+
|
|
23
|
+
export { SchemaValidationError } from "./response-methods.ts";
|
|
24
|
+
export type { DefaultResponseMethods, InferOutput } from "./response-methods.ts";
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
BaseClientOptions,
|
|
28
|
+
ClientOptions,
|
|
29
|
+
Context,
|
|
30
|
+
ContextKey,
|
|
31
|
+
Dixous,
|
|
32
|
+
Extension,
|
|
33
|
+
Fetcher,
|
|
34
|
+
FetchResponse,
|
|
35
|
+
Middleware,
|
|
36
|
+
Next,
|
|
37
|
+
RequestContext,
|
|
38
|
+
RequestOptions,
|
|
39
|
+
} from "./types.ts";
|
|
40
|
+
|
|
41
|
+
export function createContextKey<T>(): ContextKey<T> {
|
|
42
|
+
return Symbol() as ContextKey<T>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createContext(): Context {
|
|
46
|
+
const values = new Map<symbol, unknown>();
|
|
47
|
+
return {
|
|
48
|
+
get<T>(key: ContextKey<T>) {
|
|
49
|
+
return values.get(key) as T | undefined;
|
|
50
|
+
},
|
|
51
|
+
set<T>(key: ContextKey<T>, value: T) {
|
|
52
|
+
values.set(key, value);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function defineExtension<const Methods extends ResponseMethods = {}>(
|
|
58
|
+
extension: ExtensionDefinition<{}, {}, Methods>,
|
|
59
|
+
): Extension<{}, {}, Methods>;
|
|
60
|
+
export function defineExtension<
|
|
61
|
+
RequestExtra extends object & NoRequestInitOverrides = {},
|
|
62
|
+
ClientExtra extends object & NoClientOptionOverrides = {},
|
|
63
|
+
>(): <const Methods extends ResponseMethods = {}>(
|
|
64
|
+
extension: ExtensionDefinition<RequestExtra, ClientExtra, Methods>,
|
|
65
|
+
) => Extension<RequestExtra, ClientExtra, Methods>;
|
|
66
|
+
export function defineExtension(extension?: object): unknown {
|
|
67
|
+
// Extension metadata is a type-only brand; composition uses the captured values.
|
|
68
|
+
return extension === undefined ? (definition: object) => definition : extension;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class HttpError extends Error {
|
|
72
|
+
readonly request: Request;
|
|
73
|
+
readonly response: Response;
|
|
74
|
+
readonly status: number;
|
|
75
|
+
|
|
76
|
+
constructor(request: Request, response: Response) {
|
|
77
|
+
super(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`);
|
|
78
|
+
this.name = "HttpError";
|
|
79
|
+
this.request = request;
|
|
80
|
+
this.response = response;
|
|
81
|
+
this.status = response.status;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function snapshotClient(options: ClientOptions = {}): Readonly<ClientOptions> {
|
|
86
|
+
const client = { ...options };
|
|
87
|
+
if (client.baseUrl !== undefined) client.baseUrl = client.baseUrl.toString();
|
|
88
|
+
if (client.headers !== undefined) client.headers = new Headers(client.headers);
|
|
89
|
+
return Object.freeze(client);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function snapshotOptions(options: RequestOptions = {}): Readonly<RequestOptions> {
|
|
93
|
+
const snapshot = { ...options };
|
|
94
|
+
if (snapshot.headers !== undefined) snapshot.headers = new Headers(snapshot.headers);
|
|
95
|
+
return Object.freeze(snapshot);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createTemplate(
|
|
99
|
+
input: string | URL | Request,
|
|
100
|
+
options: Readonly<RequestOptions>,
|
|
101
|
+
client: Readonly<ClientOptions>,
|
|
102
|
+
): Request {
|
|
103
|
+
const headers = new Headers(client.headers);
|
|
104
|
+
if (input instanceof Request) {
|
|
105
|
+
input.headers.forEach((value, name) => headers.set(name, value));
|
|
106
|
+
}
|
|
107
|
+
if (options.headers !== undefined) {
|
|
108
|
+
new Headers(options.headers).forEach((value, name) => headers.set(name, value));
|
|
109
|
+
}
|
|
110
|
+
const source = !(input instanceof Request) && client.baseUrl !== undefined
|
|
111
|
+
? new URL(input.toString(), client.baseUrl)
|
|
112
|
+
: input;
|
|
113
|
+
return new Request(source, { ...options, headers });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runMiddleware(
|
|
117
|
+
middleware: readonly Middleware[],
|
|
118
|
+
context: RequestContext,
|
|
119
|
+
fetchImpl: typeof globalThis.fetch,
|
|
120
|
+
): Promise<Response> {
|
|
121
|
+
async function dispatch(index: number): Promise<Response> {
|
|
122
|
+
const current = middleware[index];
|
|
123
|
+
if (current === undefined) return fetchImpl(context.request.clone());
|
|
124
|
+
|
|
125
|
+
// Each middleware invocation owns its guard. Sequential retries re-enter
|
|
126
|
+
// the downstream chain with the same context and new downstream guards.
|
|
127
|
+
let running = false;
|
|
128
|
+
return current(context, async () => {
|
|
129
|
+
if (running) throw new Error("Overlapping next() calls are not allowed");
|
|
130
|
+
running = true;
|
|
131
|
+
try {
|
|
132
|
+
return await dispatch(index + 1);
|
|
133
|
+
} finally {
|
|
134
|
+
running = false;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return dispatch(0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createFetchResponse(
|
|
142
|
+
template: Request,
|
|
143
|
+
options: Readonly<RequestOptions>,
|
|
144
|
+
client: Readonly<ClientOptions>,
|
|
145
|
+
middleware: readonly Middleware[],
|
|
146
|
+
fetchImpl: typeof globalThis.fetch,
|
|
147
|
+
): FetchResponse {
|
|
148
|
+
let execution: Promise<Response> | undefined;
|
|
149
|
+
return () => {
|
|
150
|
+
// Defer execution until after storing the promise, including when a
|
|
151
|
+
// synchronous middleware re-enters its operation's FetchResponse.
|
|
152
|
+
execution ??= Promise.resolve().then(async () => {
|
|
153
|
+
const context: RequestContext = {
|
|
154
|
+
request: template.clone(),
|
|
155
|
+
options,
|
|
156
|
+
client,
|
|
157
|
+
state: createContext(),
|
|
158
|
+
};
|
|
159
|
+
const response = await runMiddleware(middleware, context, fetchImpl);
|
|
160
|
+
if (!response.ok) throw new HttpError(context.request, response);
|
|
161
|
+
return response;
|
|
162
|
+
});
|
|
163
|
+
return execution;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createDixous<
|
|
168
|
+
const Extensions extends readonly ExtensionMeta[] = [],
|
|
169
|
+
>(options?: {
|
|
170
|
+
extensions?: Extensions;
|
|
171
|
+
fetch?: typeof globalThis.fetch;
|
|
172
|
+
}): Dixous<
|
|
173
|
+
ExtensionRequestOptions<Extensions>,
|
|
174
|
+
ExtensionClientOptions<Extensions>,
|
|
175
|
+
DefaultResponseMethods & ExtensionMethods<Extensions>
|
|
176
|
+
> {
|
|
177
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
178
|
+
const middleware: Middleware[] = [];
|
|
179
|
+
const methods = new Map<string, ResponseMethods[string]>(Object.entries(defaultResponseMethods));
|
|
180
|
+
|
|
181
|
+
for (const entry of options?.extensions ?? []) {
|
|
182
|
+
// Contributions are erased only inside the kernel; the public signature
|
|
183
|
+
// intersects their exact types when constructing the resulting client.
|
|
184
|
+
const extension = entry as Extension<{}, {}, ResponseMethods>;
|
|
185
|
+
if (extension.request !== undefined) middleware.push(extension.request);
|
|
186
|
+
for (const [name, factory] of Object.entries(extension.methods ?? {})) {
|
|
187
|
+
if (methods.has(name)) throw new Error(`Duplicate response method: ${name}`);
|
|
188
|
+
methods.set(name, factory);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function configured(clientOptions?: ClientOptions): Fetcher<{}, ResponseMethods> {
|
|
193
|
+
const client = snapshotClient(clientOptions);
|
|
194
|
+
return {
|
|
195
|
+
fetch(input, requestOptions) {
|
|
196
|
+
const snapshot = snapshotOptions(requestOptions);
|
|
197
|
+
const template = createTemplate(input, snapshot, client);
|
|
198
|
+
const pending: Record<string, (...args: never[]) => Promise<unknown>> =
|
|
199
|
+
Object.create(null);
|
|
200
|
+
for (const [name, factory] of methods) {
|
|
201
|
+
pending[name] = (...args) => {
|
|
202
|
+
const fetchResponse = createFetchResponse(
|
|
203
|
+
template, snapshot, client, middleware, fetchImpl,
|
|
204
|
+
);
|
|
205
|
+
// Factories and method work are lazy too, and run once per call.
|
|
206
|
+
return factory(fetchResponse)(...args);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return Object.freeze(pending);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return Object.assign(configured, { fetch: configured().fetch }) as Dixous<
|
|
215
|
+
ExtensionRequestOptions<Extensions>,
|
|
216
|
+
ExtensionClientOptions<Extensions>,
|
|
217
|
+
DefaultResponseMethods & ExtensionMethods<Extensions>
|
|
218
|
+
>;
|
|
219
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import type { FetchResponse, ResponseMethods } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export type InferOutput<Schema extends StandardSchemaV1> =
|
|
5
|
+
StandardSchemaV1.InferOutput<Schema>;
|
|
6
|
+
|
|
7
|
+
export class SchemaValidationError extends Error {
|
|
8
|
+
readonly issues: readonly StandardSchemaV1.Issue[];
|
|
9
|
+
|
|
10
|
+
constructor(issues: readonly StandardSchemaV1.Issue[]) {
|
|
11
|
+
super("Response failed schema validation");
|
|
12
|
+
this.name = "SchemaValidationError";
|
|
13
|
+
this.issues = issues;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Built-ins use the same per-operation factories as extension methods.
|
|
18
|
+
export const defaultResponseMethods = {
|
|
19
|
+
json: (fetchResponse: FetchResponse) =>
|
|
20
|
+
async <Schema extends StandardSchemaV1>(schema: Schema): Promise<InferOutput<Schema>> => {
|
|
21
|
+
const response = await fetchResponse();
|
|
22
|
+
const result = await schema["~standard"].validate(await response.json());
|
|
23
|
+
if (result.issues) throw new SchemaValidationError(result.issues);
|
|
24
|
+
return result.value as InferOutput<Schema>;
|
|
25
|
+
},
|
|
26
|
+
text: (fetchResponse: FetchResponse) => async (): Promise<string> =>
|
|
27
|
+
(await fetchResponse()).text(),
|
|
28
|
+
blob: (fetchResponse: FetchResponse) => async (): Promise<Blob> =>
|
|
29
|
+
(await fetchResponse()).blob(),
|
|
30
|
+
arrayBuffer: (fetchResponse: FetchResponse) => async (): Promise<ArrayBuffer> =>
|
|
31
|
+
(await fetchResponse()).arrayBuffer(),
|
|
32
|
+
response: (fetchResponse: FetchResponse) => (): Promise<Response> =>
|
|
33
|
+
fetchResponse(),
|
|
34
|
+
} satisfies ResponseMethods;
|
|
35
|
+
|
|
36
|
+
export type DefaultResponseMethods = typeof defaultResponseMethods;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
declare const contextKeyType: unique symbol;
|
|
2
|
+
|
|
3
|
+
export type ContextKey<T> = symbol & {
|
|
4
|
+
readonly [contextKeyType]: (value: T) => T;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export interface Context {
|
|
8
|
+
get<T>(key: ContextKey<T>): T | undefined;
|
|
9
|
+
set<T>(key: ContextKey<T>, value: T): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type RequestOptions<Extra extends object = {}> = RequestInit & Extra;
|
|
13
|
+
|
|
14
|
+
export interface BaseClientOptions {
|
|
15
|
+
baseUrl?: string | URL;
|
|
16
|
+
headers?: HeadersInit;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ClientOptions<Extra extends object = {}> = BaseClientOptions & Extra;
|
|
20
|
+
|
|
21
|
+
export interface RequestContext<
|
|
22
|
+
RequestExtra extends object = {},
|
|
23
|
+
ClientExtra extends object = {},
|
|
24
|
+
> {
|
|
25
|
+
request: Request;
|
|
26
|
+
readonly options: Readonly<RequestOptions<RequestExtra>>;
|
|
27
|
+
readonly client: Readonly<ClientOptions<ClientExtra>>;
|
|
28
|
+
readonly state: Context;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type Next = () => Promise<Response>;
|
|
32
|
+
|
|
33
|
+
export type Middleware<
|
|
34
|
+
RequestExtra extends object = {},
|
|
35
|
+
ClientExtra extends object = {},
|
|
36
|
+
> = (
|
|
37
|
+
context: RequestContext<RequestExtra, ClientExtra>,
|
|
38
|
+
next: Next,
|
|
39
|
+
) => Promise<Response>;
|
|
40
|
+
|
|
41
|
+
export type FetchResponse = () => Promise<Response>;
|
|
42
|
+
|
|
43
|
+
export type ResponseMethod = (
|
|
44
|
+
fetchResponse: FetchResponse,
|
|
45
|
+
) => (...args: never[]) => Promise<unknown>;
|
|
46
|
+
|
|
47
|
+
export type ResponseMethods = Record<string, ResponseMethod>;
|
|
48
|
+
|
|
49
|
+
type InstantiateMethods<Methods extends ResponseMethods> = {
|
|
50
|
+
readonly [K in keyof Methods]: ReturnType<Methods[K]>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
declare const extensionType: unique symbol;
|
|
54
|
+
|
|
55
|
+
export interface ExtensionMeta {
|
|
56
|
+
readonly [extensionType]: {
|
|
57
|
+
request: object;
|
|
58
|
+
client: object;
|
|
59
|
+
methods: ResponseMethods;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Extension<
|
|
64
|
+
RequestExtra extends object = {},
|
|
65
|
+
ClientExtra extends object = {},
|
|
66
|
+
Methods extends ResponseMethods = {},
|
|
67
|
+
> {
|
|
68
|
+
readonly [extensionType]: {
|
|
69
|
+
request: RequestExtra;
|
|
70
|
+
client: ClientExtra;
|
|
71
|
+
methods: Methods;
|
|
72
|
+
};
|
|
73
|
+
request?: Middleware<RequestExtra, ClientExtra>;
|
|
74
|
+
methods?: Methods;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type ExtensionDefinition<
|
|
78
|
+
RequestExtra extends object,
|
|
79
|
+
ClientExtra extends object,
|
|
80
|
+
Methods extends ResponseMethods,
|
|
81
|
+
> = Pick<Extension<RequestExtra, ClientExtra, Methods>, "request"> & {
|
|
82
|
+
// Contextually type factory parameters while preserving each method's signature.
|
|
83
|
+
methods?: Methods & Record<string, (fetchResponse: FetchResponse) => unknown>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type NoRequestInitOverrides = {
|
|
87
|
+
[K in keyof RequestInit]?: never;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type NoClientOptionOverrides = {
|
|
91
|
+
[K in keyof BaseClientOptions]?: never;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type UnionToIntersection<T> = [T] extends [never]
|
|
95
|
+
? {}
|
|
96
|
+
: (T extends unknown ? (value: T) => void : never) extends (
|
|
97
|
+
value: infer Result,
|
|
98
|
+
) => void
|
|
99
|
+
? Result
|
|
100
|
+
: never;
|
|
101
|
+
|
|
102
|
+
export type ExtensionRequestOptions<
|
|
103
|
+
Extensions extends readonly ExtensionMeta[],
|
|
104
|
+
> = UnionToIntersection<
|
|
105
|
+
Extensions[number][typeof extensionType]["request"]
|
|
106
|
+
> extends infer Result extends object
|
|
107
|
+
? Result
|
|
108
|
+
: never;
|
|
109
|
+
|
|
110
|
+
export type ExtensionClientOptions<
|
|
111
|
+
Extensions extends readonly ExtensionMeta[],
|
|
112
|
+
> = UnionToIntersection<
|
|
113
|
+
Extensions[number][typeof extensionType]["client"]
|
|
114
|
+
> extends infer Result extends object
|
|
115
|
+
? Result
|
|
116
|
+
: never;
|
|
117
|
+
|
|
118
|
+
export type ExtensionMethods<Extensions extends readonly ExtensionMeta[]> =
|
|
119
|
+
UnionToIntersection<
|
|
120
|
+
Extensions[number][typeof extensionType]["methods"]
|
|
121
|
+
> extends infer Result extends ResponseMethods
|
|
122
|
+
? Result
|
|
123
|
+
: never;
|
|
124
|
+
|
|
125
|
+
export interface Fetcher<
|
|
126
|
+
RequestExtra extends object,
|
|
127
|
+
Methods extends ResponseMethods,
|
|
128
|
+
> {
|
|
129
|
+
fetch(
|
|
130
|
+
input: string | URL | Request,
|
|
131
|
+
options?: RequestOptions<RequestExtra>,
|
|
132
|
+
): InstantiateMethods<Methods>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface Dixous<
|
|
136
|
+
RequestExtra extends object,
|
|
137
|
+
ClientExtra extends object,
|
|
138
|
+
Methods extends ResponseMethods,
|
|
139
|
+
> extends Fetcher<RequestExtra, Methods> {
|
|
140
|
+
(options?: ClientOptions<ClientExtra>): Fetcher<RequestExtra, Methods>;
|
|
141
|
+
}
|