dixous 0.1.1 → 0.2.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/README.md +196 -64
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -113
- package/dist/index.js.map +1 -1
- package/dist/response-methods.d.ts +2 -15
- package/dist/response-methods.d.ts.map +1 -1
- package/dist/response-methods.js +22 -21
- package/dist/response-methods.js.map +1 -1
- package/dist/standard-schema.d.ts +51 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +28 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/types.d.ts +88 -54
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -5
- package/src/errors.ts +26 -0
- package/src/index.ts +114 -184
- package/src/response-methods.ts +21 -32
- package/src/standard-schema.ts +51 -0
- package/src/types.ts +118 -117
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Dixous
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A small, fully typed HTTP client built on Fetch.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Dixous gives you runtime-validated responses, composable clients, and an extension system that can change how requests execute and what APIs are available on them.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -10,101 +10,233 @@ Start with a simple request, then add validation, middleware, and custom respons
|
|
|
10
10
|
npm install dixous
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Quick start
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
17
|
-
import { z } from "zod"
|
|
16
|
+
import { Dixous } from "dixous"
|
|
17
|
+
import { z } from "zod"
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const api = Dixous.create({
|
|
20
|
+
baseUrl: "https://api.example.com/",
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: "Bearer YOUR_API_TOKEN",
|
|
23
|
+
},
|
|
24
|
+
})
|
|
20
25
|
|
|
21
26
|
const User = z.object({
|
|
22
27
|
id: z.number(),
|
|
23
28
|
name: z.string(),
|
|
24
|
-
})
|
|
29
|
+
})
|
|
25
30
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
const user = await api.fetch("users/1").json(User);
|
|
31
|
+
const user = await api
|
|
32
|
+
.request("users/1")
|
|
33
|
+
.json(User)
|
|
31
34
|
|
|
32
|
-
console.log(user.name)
|
|
35
|
+
console.log(user.name)
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
The response is validated at runtime and inferred automatically from the schema.
|
|
39
|
+
|
|
40
|
+
Dixous works with any [Standard Schema](https://standardschema.dev/) validator.
|
|
41
|
+
|
|
42
|
+
## Why Dixous?
|
|
43
|
+
|
|
44
|
+
Dixous tries to stay small without becoming limiting.
|
|
45
|
+
|
|
46
|
+
* Built around native `Request` and `Response`
|
|
47
|
+
* Runtime validation with full TypeScript inference
|
|
48
|
+
* Immutable clients that compose and specialize naturally
|
|
49
|
+
* Extensions can add options, wrap request execution, and add request APIs
|
|
50
|
+
* Features such as retries, caching, logging, authentication, and custom formats stay outside the core
|
|
51
|
+
* Drop down to the native `Response` whenever you need to
|
|
52
|
+
|
|
53
|
+
## Extend the request API
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
Extensions can add entirely new request methods.
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
For example, [Schema XML](https://github.com/Asguho/schema-xml) can make XML feel like a native Dixous response format:
|
|
40
58
|
|
|
41
59
|
```ts
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
import {
|
|
61
|
+
Dixous,
|
|
62
|
+
defineExtension,
|
|
63
|
+
} from "dixous"
|
|
64
|
+
import { parseXml } from "schema-xml"
|
|
65
|
+
import { z } from "zod"
|
|
46
66
|
|
|
47
|
-
// Parse and validate XML with Schema XML.
|
|
48
67
|
const xml = defineExtension({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
async <
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const response = await next();
|
|
62
|
-
if (request.method !== "GET" || response.status !== 503 || attempt === 3) {
|
|
63
|
-
return response;
|
|
64
|
-
}
|
|
65
|
-
await response.body?.cancel();
|
|
68
|
+
operation(operation) {
|
|
69
|
+
return {
|
|
70
|
+
async xml<Schema extends z.ZodType>(
|
|
71
|
+
schema: Schema,
|
|
72
|
+
): Promise<z.output<Schema>> {
|
|
73
|
+
const response = await operation.response()
|
|
74
|
+
|
|
75
|
+
return parseXml(
|
|
76
|
+
await response.text(),
|
|
77
|
+
schema,
|
|
78
|
+
)
|
|
79
|
+
},
|
|
66
80
|
}
|
|
67
81
|
},
|
|
68
|
-
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const api = Dixous.create({
|
|
85
|
+
extensions: [xml],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const Catalog = z.object({
|
|
89
|
+
catalog: z.object({
|
|
90
|
+
book: z.array(
|
|
91
|
+
z.object({
|
|
92
|
+
title: z.string(),
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const catalog = await api
|
|
99
|
+
.request("https://example.com/catalog.xml")
|
|
100
|
+
.xml(Catalog)
|
|
101
|
+
|
|
102
|
+
console.log(catalog.catalog.book)
|
|
103
|
+
// { title: string }[]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Dixous itself knows nothing about XML. Installing the extension adds `.xml(schema)` directly to the request type with full inference.
|
|
107
|
+
|
|
108
|
+
Inside an extension, `operation.response()` is the response the operation reads. Dixous rejects non-OK responses before the extension sees them, so extensions only decode bodies.
|
|
109
|
+
|
|
110
|
+
## Branch on status
|
|
111
|
+
|
|
112
|
+
Use `.match()` when different statuses carry different bodies. Each handler receives the full operation API, including extension methods, bound to the matched response:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const result = await api
|
|
116
|
+
.request("users/1")
|
|
117
|
+
.match({
|
|
118
|
+
200: operation => operation.json(User),
|
|
119
|
+
404: operation => operation.text(),
|
|
120
|
+
422: operation => operation.json(ValidationError),
|
|
121
|
+
})
|
|
122
|
+
// User | string | ValidationError
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Statuses match exactly. An unmatched status rejects with `UnexpectedResponseError`.
|
|
126
|
+
|
|
127
|
+
`match` is a default method like `json`. An extension can replace it, using `operation.execute()` for the raw response and `operation.api(response)` to rebuild the operation API over it.
|
|
128
|
+
|
|
129
|
+
## Composable by design
|
|
130
|
+
|
|
131
|
+
Create one shared Dixous client for your application, then import and specialize it where needed.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// lib/dixous.ts
|
|
135
|
+
|
|
136
|
+
import { Dixous } from "dixous"
|
|
69
137
|
|
|
70
|
-
|
|
71
|
-
|
|
138
|
+
export const dixous = Dixous.create({
|
|
139
|
+
baseUrl: "https://api.example.com/",
|
|
140
|
+
extensions: [
|
|
141
|
+
retry(),
|
|
142
|
+
query(),
|
|
143
|
+
],
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Elsewhere:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { dixous } from "./lib/dixous"
|
|
151
|
+
|
|
152
|
+
const github = dixous.create({
|
|
153
|
+
baseUrl: "https://api.github.com/",
|
|
154
|
+
headers: {
|
|
155
|
+
Authorization: `Bearer ${token}`,
|
|
156
|
+
},
|
|
157
|
+
retryAttempts: 5,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const users = await github
|
|
161
|
+
.request("users", {
|
|
162
|
+
query: {
|
|
163
|
+
since: "100",
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
.json(Users)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Derived clients inherit their parent's configuration, extensions, and types, while more specific configuration overrides shared defaults. The parent client is never changed.
|
|
170
|
+
|
|
171
|
+
## Extend request behavior
|
|
172
|
+
|
|
173
|
+
Extensions can also change how requests execute and contribute their own typed options.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const query = defineExtension<{
|
|
177
|
+
query?: Record<string, string>
|
|
178
|
+
}>()({
|
|
72
179
|
async request(context, next) {
|
|
73
|
-
const url = new URL(context.request.url)
|
|
180
|
+
const url = new URL(context.request.url)
|
|
181
|
+
|
|
74
182
|
for (const [key, value] of Object.entries(context.options.query ?? {})) {
|
|
75
|
-
url.searchParams.append(key, value)
|
|
183
|
+
url.searchParams.append(key, value)
|
|
76
184
|
}
|
|
77
|
-
|
|
78
|
-
|
|
185
|
+
|
|
186
|
+
context.request = new Request(
|
|
187
|
+
url,
|
|
188
|
+
context.request,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return next()
|
|
79
192
|
},
|
|
80
|
-
})
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Install it:
|
|
81
197
|
|
|
82
|
-
|
|
198
|
+
```ts
|
|
199
|
+
const api = Dixous.create({
|
|
200
|
+
extensions: [query],
|
|
201
|
+
})
|
|
83
202
|
```
|
|
84
|
-
|
|
203
|
+
|
|
204
|
+
And the option becomes part of the client:
|
|
85
205
|
|
|
86
206
|
```ts
|
|
87
|
-
|
|
88
|
-
|
|
207
|
+
const books = await api
|
|
208
|
+
.request("books", {
|
|
209
|
+
query: {
|
|
210
|
+
author: "Ursula K. Le Guin",
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
.json(Books)
|
|
214
|
+
```
|
|
89
215
|
|
|
90
|
-
|
|
91
|
-
catalog: z.object({ book: z.array(z.object({ title: z.string() })) }),
|
|
92
|
-
});
|
|
216
|
+
Without the extension, `query` is not part of the request options.
|
|
93
217
|
|
|
94
|
-
|
|
95
|
-
query: { author: "Ursula K. Le Guin" },
|
|
96
|
-
}).xml(Catalog);
|
|
218
|
+
The same mechanism can power retries, authentication, caching, logging, tracing, rate limiting, and more.
|
|
97
219
|
|
|
98
|
-
|
|
99
|
-
```
|
|
220
|
+
## Native when you need it
|
|
100
221
|
|
|
101
|
-
|
|
222
|
+
Use `.response()` whenever the HTTP response itself is part of your application logic:
|
|
102
223
|
|
|
103
|
-
|
|
224
|
+
```ts
|
|
225
|
+
const response = await api
|
|
226
|
+
.request("users/1")
|
|
227
|
+
.response()
|
|
104
228
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
229
|
+
if (response.status === 404) {
|
|
230
|
+
// Handle an expected missing user.
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (response.ok) {
|
|
234
|
+
const body = await response.json()
|
|
235
|
+
}
|
|
108
236
|
```
|
|
109
237
|
|
|
110
|
-
|
|
238
|
+
It returns the final native `Response` without applying a status policy.
|
|
239
|
+
|
|
240
|
+
Requests are lazy and memoized per `request()` call: `.response()`, `.match()`, and the body readers share one execution, while response bodies keep their normal native consumption semantics.
|
|
241
|
+
|
|
242
|
+
See [Extensions](./docs/extensions.md) for middleware ordering, retries, caching, logging, custom formats, extension state, and advanced composition.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StandardSchemaIssue } from "./standard-schema.ts";
|
|
2
|
+
export declare class UnexpectedResponseError extends Error {
|
|
3
|
+
readonly request: Request;
|
|
4
|
+
readonly response: Response;
|
|
5
|
+
constructor(request: Request, response: Response);
|
|
6
|
+
}
|
|
7
|
+
export declare class ResponseValidationError extends Error {
|
|
8
|
+
readonly request: Request;
|
|
9
|
+
readonly response: Response;
|
|
10
|
+
readonly issues: readonly StandardSchemaIssue[];
|
|
11
|
+
constructor(request: Request, response: Response, issues: readonly StandardSchemaIssue[]);
|
|
12
|
+
}
|
|
13
|
+
export declare class ConcurrentNextError extends Error {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO;IAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ;IAAlE,YAAqB,OAAO,EAAE,OAAO,EAAW,QAAQ,EAAE,QAAQ,EAGjE;CACF;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,OAAO,EAAE,OAAO;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE;IAHjD,YACW,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,SAAS,mBAAmB,EAAE,EAIhD;CACF;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,cAGC;CACF"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class UnexpectedResponseError extends Error {
|
|
2
|
+
request;
|
|
3
|
+
response;
|
|
4
|
+
constructor(request, response) {
|
|
5
|
+
super(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`);
|
|
6
|
+
this.request = request;
|
|
7
|
+
this.response = response;
|
|
8
|
+
this.name = "UnexpectedResponseError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class ResponseValidationError extends Error {
|
|
12
|
+
request;
|
|
13
|
+
response;
|
|
14
|
+
issues;
|
|
15
|
+
constructor(request, response, issues) {
|
|
16
|
+
super("Response failed schema validation");
|
|
17
|
+
this.request = request;
|
|
18
|
+
this.response = response;
|
|
19
|
+
this.issues = issues;
|
|
20
|
+
this.name = "ResponseValidationError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class ConcurrentNextError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super("Concurrent calls to the same next() are not allowed");
|
|
26
|
+
this.name = "ConcurrentNextError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAC3B,OAAO;IAAoB,QAAQ;IAAxD,YAAqB,OAAgB,EAAW,QAAkB;QAChE,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;uBADrE,OAAO;wBAAoB,QAAQ;QAEtD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAErC,OAAO;IACP,QAAQ;IACR,MAAM;IAHjB,YACW,OAAgB,EAChB,QAAkB,EAClB,MAAsC;QAE/C,KAAK,CAAC,mCAAmC,CAAC,CAAC;uBAJlC,OAAO;wBACP,QAAQ;sBACR,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C;QACE,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
5
|
-
export
|
|
6
|
-
export declare function
|
|
7
|
-
export
|
|
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);
|
|
1
|
+
import type { AnyExtension, ApplyExtensionApi, ApplyExtensionOptions, CreateOptions, DefaultOperationApi, Dixous as DixousClient, Extension, ExtensionDefinition } from "./types.ts";
|
|
2
|
+
export { ConcurrentNextError, ResponseValidationError, UnexpectedResponseError } from "./errors.ts";
|
|
3
|
+
export type { InferOutput, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1 } from "./standard-schema.ts";
|
|
4
|
+
export type { AnyExtension, CoreOptions, CreateOptions, DefaultOperationApi, Extension, ExtensionDefinition, Match, MatchedOperation, MatchResult, Next, OperationContext, RequestContext, RequestInput, RequestMiddleware, RequestOperation, RequestOptions, ReservedOperationKey, StatusHandlers, } from "./types.ts";
|
|
5
|
+
export declare function defineExtension<const OperationApi extends object = {}>(definition: ExtensionDefinition<{}, OperationApi>): Extension<{}, OperationApi>;
|
|
6
|
+
export declare function defineExtension<Options extends object>(): <const OperationApi extends object = {}>(definition: ExtensionDefinition<Options, OperationApi>) => Extension<Options, OperationApi>;
|
|
7
|
+
export interface Dixous<Options extends object = {}, OperationApi extends object = DefaultOperationApi> extends DixousClient<Options, OperationApi> {
|
|
14
8
|
}
|
|
15
|
-
export declare
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}): Dixous<ExtensionRequestOptions<Extensions>, ExtensionClientOptions<Extensions>, DefaultResponseMethods & ExtensionMethods<Extensions>>;
|
|
9
|
+
export declare const Dixous: {
|
|
10
|
+
create<const Extensions extends readonly AnyExtension[] = []>(options?: CreateOptions<{}, Extensions>): Dixous<ApplyExtensionOptions<{}, Extensions>, ApplyExtensionApi<DefaultOperationApi, Extensions>>;
|
|
11
|
+
};
|
|
19
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAe,aAAa,EAClF,mBAAmB,EAAE,MAAM,IAAI,YAAY,EAAE,SAAS,EACtD,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACpG,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACrH,YAAY,EACV,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,SAAS,EACxE,mBAAmB,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EACjF,cAAc,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EACjF,oBAAoB,EAAE,cAAc,GACrC,MAAM,YAAY,CAAC;AAEpB,wBAAgB,eAAe,CAAC,KAAK,CAAC,YAAY,SAAS,MAAM,GAAG,EAAE,EACpE,UAAU,EAAE,mBAAmB,CAAC,EAAE,EAAE,YAAY,CAAC,GAChD,SAAS,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;AAC/B,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,KAAK,CAAC,KAAK,CAAC,YAAY,SAAS,MAAM,GAAG,EAAE,EAChG,UAAU,EAAE,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,KACnD,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAoHtC,MAAM,WAAW,MAAM,CAAC,OAAO,SAAS,MAAM,GAAG,EAAE,EAAE,YAAY,SAAS,MAAM,GAAG,mBAAmB,CACpG,SAAQ,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC;CAAG;AAEhD,eAAO,MAAM,MAAM,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,SAAS,YAAY,EAAE,GAAG,EAAE,EAC1D,OAAO,CAAC,EAAE,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,GACtC,MAAM,CAAC,qBAAqB,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,iBAAiB,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC,CAAC;CAGrG,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,73 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
1
|
+
import { ConcurrentNextError, UnexpectedResponseError } from "./errors.js";
|
|
2
|
+
import { defaultOperationApi } from "./response-methods.js";
|
|
3
|
+
export { ConcurrentNextError, ResponseValidationError, UnexpectedResponseError } from "./errors.js";
|
|
4
|
+
export function defineExtension(definition) {
|
|
5
|
+
return definition === undefined ? (entry) => entry : definition;
|
|
5
6
|
}
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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;
|
|
7
|
+
function mergeHeaders(...sources) {
|
|
8
|
+
const headers = new Headers();
|
|
9
|
+
for (const source of sources) {
|
|
10
|
+
if (source !== undefined)
|
|
11
|
+
new Headers(source).forEach((value, name) => headers.set(name, value));
|
|
31
12
|
}
|
|
13
|
+
return headers;
|
|
32
14
|
}
|
|
33
|
-
function
|
|
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) {
|
|
15
|
+
function runMiddleware(middleware, context, transport) {
|
|
61
16
|
async function dispatch(index) {
|
|
62
17
|
const current = middleware[index];
|
|
63
18
|
if (current === undefined)
|
|
64
|
-
return
|
|
65
|
-
// Each middleware invocation owns its guard. Sequential retries re-enter
|
|
66
|
-
// the downstream chain with the same context and new downstream guards.
|
|
19
|
+
return transport(context.request);
|
|
67
20
|
let running = false;
|
|
68
21
|
return current(context, async () => {
|
|
69
22
|
if (running)
|
|
70
|
-
throw new
|
|
23
|
+
throw new ConcurrentNextError();
|
|
71
24
|
running = true;
|
|
72
25
|
try {
|
|
73
26
|
return await dispatch(index + 1);
|
|
@@ -79,60 +32,76 @@ function runMiddleware(middleware, context, fetchImpl) {
|
|
|
79
32
|
}
|
|
80
33
|
return dispatch(0);
|
|
81
34
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
35
|
+
const reservedOperationKeys = ["response", "then"];
|
|
36
|
+
function createOperation(context, execute, extensions) {
|
|
37
|
+
const build = (response) => {
|
|
38
|
+
const scoped = Object.create(context, {
|
|
39
|
+
response: { value: response, enumerable: true },
|
|
40
|
+
execute: { value: execute, enumerable: true },
|
|
41
|
+
api: { value: (matched) => build(async () => matched), enumerable: true },
|
|
42
|
+
});
|
|
43
|
+
const operation = Object.assign(Object.create(null), defaultOperationApi(scoped));
|
|
44
|
+
for (const extension of extensions) {
|
|
45
|
+
const contribution = extension.operation?.(scoped);
|
|
46
|
+
if (contribution !== undefined) {
|
|
47
|
+
for (const key of reservedOperationKeys) {
|
|
48
|
+
if (key in contribution)
|
|
49
|
+
throw new TypeError(`Extension operation cannot replace ${key}`);
|
|
50
|
+
}
|
|
51
|
+
Object.assign(operation, contribution);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Object.defineProperties(operation, {
|
|
55
|
+
response: { value: execute, enumerable: true },
|
|
56
|
+
then: { value: undefined },
|
|
98
57
|
});
|
|
99
|
-
return execution;
|
|
100
58
|
};
|
|
59
|
+
return build(async () => {
|
|
60
|
+
const response = await execute();
|
|
61
|
+
if (!response.ok)
|
|
62
|
+
throw new UnexpectedResponseError(context.request, response);
|
|
63
|
+
return response;
|
|
64
|
+
});
|
|
101
65
|
}
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
66
|
+
function createClient(parent = {}, supplied = {}) {
|
|
67
|
+
const { extensions: inherited = [], ...defaults } = parent;
|
|
68
|
+
const { extensions: appended = [], ...overrides } = supplied;
|
|
69
|
+
const configuration = Object.freeze({
|
|
70
|
+
...defaults,
|
|
71
|
+
...overrides,
|
|
72
|
+
...(overrides.baseUrl !== undefined ? { baseUrl: overrides.baseUrl.toString() } : {}),
|
|
73
|
+
headers: mergeHeaders(defaults.headers, overrides.headers),
|
|
74
|
+
// Capture contributions so later mutations cannot alter an immutable client.
|
|
75
|
+
extensions: Object.freeze([...inherited, ...appended].map(entry => Object.freeze({ ...entry }))),
|
|
76
|
+
});
|
|
77
|
+
const { extensions, ...clientOptions } = configuration;
|
|
78
|
+
const middleware = extensions.flatMap(entry => entry.request ? [entry.request] : []);
|
|
79
|
+
const transport = configuration.fetch ?? globalThis.fetch;
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
create(options) { return createClient(configuration, options); },
|
|
82
|
+
request(input, suppliedOptions = {}) {
|
|
83
|
+
const options = Object.freeze({
|
|
84
|
+
...clientOptions,
|
|
85
|
+
...suppliedOptions,
|
|
86
|
+
headers: mergeHeaders(configuration.headers, input instanceof Request ? input.headers : undefined, suppliedOptions.headers),
|
|
87
|
+
});
|
|
88
|
+
const source = !(input instanceof Request) && configuration.baseUrl !== undefined
|
|
89
|
+
? new URL(input.toString(), configuration.baseUrl)
|
|
90
|
+
: input;
|
|
91
|
+
const request = new Request(source, options);
|
|
92
|
+
let execution;
|
|
93
|
+
const execute = () => {
|
|
94
|
+
// Store the promise before middleware can synchronously re-enter execute().
|
|
95
|
+
execution ??= Promise.resolve().then(() => runMiddleware(middleware, context, transport));
|
|
96
|
+
return execution;
|
|
97
|
+
};
|
|
98
|
+
const context = { input, request, options };
|
|
99
|
+
Object.defineProperties(context, { input: { writable: false }, options: { writable: false } });
|
|
100
|
+
return createOperation(context, execute, extensions);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
137
103
|
}
|
|
104
|
+
export const Dixous = Object.freeze({
|
|
105
|
+
create: createClient.bind(undefined, {}),
|
|
106
|
+
});
|
|
138
107
|
//# sourceMappingURL=index.js.map
|