@c9up/aurora 0.1.7 → 0.1.8
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/http.d.ts +45 -1
- package/dist/http.js +70 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/http.ts +93 -2
- package/src/index.ts +12 -2
package/dist/http.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export interface HttpClientOptions {
|
|
|
28
28
|
token?: string | null | (() => string | null | undefined);
|
|
29
29
|
/** Default `credentials` mode (e.g. `"include"` to send cookies). */
|
|
30
30
|
credentials?: RequestCredentials;
|
|
31
|
+
/**
|
|
32
|
+
* Default timeout in ms — the request is aborted (rejecting with a
|
|
33
|
+
* `TimeoutError`) if it doesn't settle in time. Combined with a per-request
|
|
34
|
+
* `signal`, whichever fires first wins.
|
|
35
|
+
*/
|
|
36
|
+
timeout?: number;
|
|
31
37
|
}
|
|
32
38
|
export interface HttpRequestOptions<T = unknown> {
|
|
33
39
|
/** Query params appended to the URL. `null`/`undefined` values are skipped. */
|
|
@@ -36,8 +42,10 @@ export interface HttpRequestOptions<T = unknown> {
|
|
|
36
42
|
headers?: Record<string, string>;
|
|
37
43
|
/** Per-request bearer token override (`null` to force-omit). */
|
|
38
44
|
token?: string | null;
|
|
39
|
-
/** Abort signal. */
|
|
45
|
+
/** Abort signal — abort it to cancel the request (e.g. on unmount / new keystroke). */
|
|
40
46
|
signal?: AbortSignal;
|
|
47
|
+
/** Per-request timeout in ms (overrides the client default). Aborts with a `TimeoutError`. */
|
|
48
|
+
timeout?: number;
|
|
41
49
|
/** `credentials` mode for this request. */
|
|
42
50
|
credentials?: RequestCredentials;
|
|
43
51
|
/**
|
|
@@ -54,6 +62,28 @@ export declare class HttpError extends Error {
|
|
|
54
62
|
readonly data: unknown;
|
|
55
63
|
constructor(response: Response, data: unknown);
|
|
56
64
|
}
|
|
65
|
+
/** Type guard for {@link HttpError} — clean `catch` narrowing without a cast. */
|
|
66
|
+
export declare function isHttpError(value: unknown): value is HttpError;
|
|
67
|
+
/**
|
|
68
|
+
* Whether `value` is an aborted/timed-out request error — an `AbortError`
|
|
69
|
+
* (cancelled via a `signal`) or a `TimeoutError` (the `timeout` option fired).
|
|
70
|
+
* Use it to silently ignore cancellations (e.g. a superseded search request).
|
|
71
|
+
*/
|
|
72
|
+
export declare function isAbortError(value: unknown): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Outcome of a non-throwing request via {@link HttpClient.attempt}: a
|
|
75
|
+
* discriminated union a form submit can branch on (`if (r.ok)`) instead of
|
|
76
|
+
* wrapping every call in try/catch.
|
|
77
|
+
*/
|
|
78
|
+
export type HttpResult<T> = {
|
|
79
|
+
ok: true;
|
|
80
|
+
data: T;
|
|
81
|
+
error: null;
|
|
82
|
+
} | {
|
|
83
|
+
ok: false;
|
|
84
|
+
data: null;
|
|
85
|
+
error: HttpError;
|
|
86
|
+
};
|
|
57
87
|
export declare class HttpClient {
|
|
58
88
|
#private;
|
|
59
89
|
constructor(options?: HttpClientOptions);
|
|
@@ -72,6 +102,20 @@ export declare class HttpClient {
|
|
|
72
102
|
patch<T>(url: string, body?: unknown, options?: HttpRequestOptions<T>): Promise<T>;
|
|
73
103
|
/** Send a request and return the raw `Response` (no parsing, no throw on non-2xx). */
|
|
74
104
|
raw(method: string, url: string, body?: unknown, options?: HttpRequestOptions): Promise<Response>;
|
|
105
|
+
/**
|
|
106
|
+
* Run a request without throwing on a non-2xx response: returns a
|
|
107
|
+
* discriminated {@link HttpResult} so a form submit can branch
|
|
108
|
+
* (`if (r.ok) … else r.error.data`) instead of wrapping each call in
|
|
109
|
+
* try/catch. Genuine transport failures (offline, DNS) still reject —
|
|
110
|
+
* they're exceptional, not an HTTP error.
|
|
111
|
+
*
|
|
112
|
+
* ```js
|
|
113
|
+
* const r = await api.attempt(api.post('/auth/login', creds))
|
|
114
|
+
* if (r.ok) user(r.data)
|
|
115
|
+
* else fieldErrors(r.error.data) // HttpError.data = parsed 4xx body
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
attempt<T>(request: Promise<T>): Promise<HttpResult<T>>;
|
|
75
119
|
/** Derive a new client with merged defaults (e.g. a scope that adds a token). */
|
|
76
120
|
extend(options: HttpClientOptions): HttpClient;
|
|
77
121
|
}
|
package/dist/http.js
CHANGED
|
@@ -28,6 +28,23 @@ export class HttpError extends Error {
|
|
|
28
28
|
this.data = data;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
/** Type guard for {@link HttpError} — clean `catch` narrowing without a cast. */
|
|
32
|
+
export function isHttpError(value) {
|
|
33
|
+
return value instanceof HttpError;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether `value` is an aborted/timed-out request error — an `AbortError`
|
|
37
|
+
* (cancelled via a `signal`) or a `TimeoutError` (the `timeout` option fired).
|
|
38
|
+
* Use it to silently ignore cancellations (e.g. a superseded search request).
|
|
39
|
+
*/
|
|
40
|
+
export function isAbortError(value) {
|
|
41
|
+
const name = value instanceof Error
|
|
42
|
+
? value.name
|
|
43
|
+
: typeof DOMException !== "undefined" && value instanceof DOMException
|
|
44
|
+
? value.name
|
|
45
|
+
: undefined;
|
|
46
|
+
return name === "AbortError" || name === "TimeoutError";
|
|
47
|
+
}
|
|
31
48
|
/** Whether `body` should be JSON-encoded (vs. passed to `fetch` untouched). */
|
|
32
49
|
function shouldJsonEncode(body) {
|
|
33
50
|
if (body === null || typeof body !== "object") {
|
|
@@ -54,6 +71,26 @@ function hasHeader(headers, name) {
|
|
|
54
71
|
}
|
|
55
72
|
return false;
|
|
56
73
|
}
|
|
74
|
+
/** Merge abort signals into one (whichever fires first wins). `undefined` if none. */
|
|
75
|
+
function combineSignals(signals) {
|
|
76
|
+
const present = signals.filter((s) => s !== undefined);
|
|
77
|
+
if (present.length <= 1)
|
|
78
|
+
return present[0];
|
|
79
|
+
if (typeof AbortSignal.any === "function")
|
|
80
|
+
return AbortSignal.any(present);
|
|
81
|
+
// Fallback for runtimes without AbortSignal.any.
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
for (const signal of present) {
|
|
84
|
+
if (signal.aborted) {
|
|
85
|
+
controller.abort(signal.reason);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), {
|
|
89
|
+
once: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return controller.signal;
|
|
93
|
+
}
|
|
57
94
|
/** Parse a response by content-type; `null` for empty / no-content bodies. */
|
|
58
95
|
async function parseBody(response) {
|
|
59
96
|
if (response.status === 204 || response.status === 205)
|
|
@@ -71,11 +108,13 @@ export class HttpClient {
|
|
|
71
108
|
#headers;
|
|
72
109
|
#token;
|
|
73
110
|
#credentials;
|
|
111
|
+
#timeout;
|
|
74
112
|
constructor(options = {}) {
|
|
75
113
|
this.#baseURL = options.baseURL ?? "";
|
|
76
114
|
this.#headers = { ...options.headers };
|
|
77
115
|
this.#token = options.token;
|
|
78
116
|
this.#credentials = options.credentials;
|
|
117
|
+
this.#timeout = options.timeout;
|
|
79
118
|
}
|
|
80
119
|
/** Set a default header for every subsequent request (case-insensitive replace). Chainable. */
|
|
81
120
|
setHeader(name, value) {
|
|
@@ -125,6 +164,30 @@ export class HttpClient {
|
|
|
125
164
|
raw(method, url, body, options = {}) {
|
|
126
165
|
return this.#send(method, url, body, options);
|
|
127
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Run a request without throwing on a non-2xx response: returns a
|
|
169
|
+
* discriminated {@link HttpResult} so a form submit can branch
|
|
170
|
+
* (`if (r.ok) … else r.error.data`) instead of wrapping each call in
|
|
171
|
+
* try/catch. Genuine transport failures (offline, DNS) still reject —
|
|
172
|
+
* they're exceptional, not an HTTP error.
|
|
173
|
+
*
|
|
174
|
+
* ```js
|
|
175
|
+
* const r = await api.attempt(api.post('/auth/login', creds))
|
|
176
|
+
* if (r.ok) user(r.data)
|
|
177
|
+
* else fieldErrors(r.error.data) // HttpError.data = parsed 4xx body
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
async attempt(request) {
|
|
181
|
+
try {
|
|
182
|
+
return { ok: true, data: await request, error: null };
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof HttpError) {
|
|
186
|
+
return { ok: false, data: null, error };
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
128
191
|
/** Derive a new client with merged defaults (e.g. a scope that adds a token). */
|
|
129
192
|
extend(options) {
|
|
130
193
|
return new HttpClient({
|
|
@@ -132,6 +195,7 @@ export class HttpClient {
|
|
|
132
195
|
headers: { ...this.#headers, ...options.headers },
|
|
133
196
|
token: options.token ?? this.#token,
|
|
134
197
|
credentials: options.credentials ?? this.#credentials,
|
|
198
|
+
timeout: options.timeout ?? this.#timeout,
|
|
135
199
|
});
|
|
136
200
|
}
|
|
137
201
|
#resolveToken(override) {
|
|
@@ -177,11 +241,16 @@ export class HttpClient {
|
|
|
177
241
|
payload = body;
|
|
178
242
|
}
|
|
179
243
|
}
|
|
244
|
+
const timeout = options.timeout ?? this.#timeout;
|
|
245
|
+
const signal = combineSignals([
|
|
246
|
+
options.signal,
|
|
247
|
+
timeout !== undefined ? AbortSignal.timeout(timeout) : undefined,
|
|
248
|
+
]);
|
|
180
249
|
return fetch(this.#buildUrl(url, options.query), {
|
|
181
250
|
method,
|
|
182
251
|
headers,
|
|
183
252
|
body: payload,
|
|
184
|
-
signal
|
|
253
|
+
signal,
|
|
185
254
|
credentials: options.credentials ?? this.#credentials,
|
|
186
255
|
});
|
|
187
256
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export type { CookieOptions, PersistedSignalOptions, ShareData, StorageArea, Web
|
|
|
2
2
|
export { back, clipboard, cookie, forward, hash, mediaQuery, navigate, online, persistedSignal, prefersDark, queryParam, redirect, reload, replace, session, share, storage, visibility, WebStorage, windowSize, } from "./browser.js";
|
|
3
3
|
export { component, onMount, onUnmount } from "./component.js";
|
|
4
4
|
export { html, isTemplateResult } from "./html.js";
|
|
5
|
-
export type { HttpClientOptions, HttpRequestOptions } from "./http.js";
|
|
6
|
-
export { HttpClient, HttpError, http } from "./http.js";
|
|
5
|
+
export type { HttpClientOptions, HttpRequestOptions, HttpResult, } from "./http.js";
|
|
6
|
+
export { HttpClient, HttpError, http, isAbortError, isHttpError, } from "./http.js";
|
|
7
7
|
export { hydrate } from "./hydrate.js";
|
|
8
8
|
export { batch, effect, isSignal, memo, onCleanup, type ReadSignal, type Signal, signal, untrack, } from "./reactive.js";
|
|
9
9
|
export { type Disposer, render } from "./render.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { back, clipboard, cookie, forward, hash, mediaQuery, navigate, online, persistedSignal, prefersDark, queryParam, redirect, reload, replace, session, share, storage, visibility, WebStorage, windowSize, } from "./browser.js";
|
|
2
2
|
export { component, onMount, onUnmount } from "./component.js";
|
|
3
3
|
export { html, isTemplateResult } from "./html.js";
|
|
4
|
-
export { HttpClient, HttpError, http } from "./http.js";
|
|
4
|
+
export { HttpClient, HttpError, http, isAbortError, isHttpError, } from "./http.js";
|
|
5
5
|
export { hydrate } from "./hydrate.js";
|
|
6
6
|
export { batch, effect, isSignal, memo, onCleanup, signal, untrack, } from "./reactive.js";
|
|
7
7
|
export { render } from "./render.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/aurora",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Aurora — reactive UI runtime for the Ream framework. Tagged-template DOM, signal-based state, isomorphic SSR + hydration, zero build step.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/http.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface HttpClientOptions {
|
|
|
29
29
|
token?: string | null | (() => string | null | undefined);
|
|
30
30
|
/** Default `credentials` mode (e.g. `"include"` to send cookies). */
|
|
31
31
|
credentials?: RequestCredentials;
|
|
32
|
+
/**
|
|
33
|
+
* Default timeout in ms — the request is aborted (rejecting with a
|
|
34
|
+
* `TimeoutError`) if it doesn't settle in time. Combined with a per-request
|
|
35
|
+
* `signal`, whichever fires first wins.
|
|
36
|
+
*/
|
|
37
|
+
timeout?: number;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export interface HttpRequestOptions<T = unknown> {
|
|
@@ -38,8 +44,10 @@ export interface HttpRequestOptions<T = unknown> {
|
|
|
38
44
|
headers?: Record<string, string>;
|
|
39
45
|
/** Per-request bearer token override (`null` to force-omit). */
|
|
40
46
|
token?: string | null;
|
|
41
|
-
/** Abort signal. */
|
|
47
|
+
/** Abort signal — abort it to cancel the request (e.g. on unmount / new keystroke). */
|
|
42
48
|
signal?: AbortSignal;
|
|
49
|
+
/** Per-request timeout in ms (overrides the client default). Aborts with a `TimeoutError`. */
|
|
50
|
+
timeout?: number;
|
|
43
51
|
/** `credentials` mode for this request. */
|
|
44
52
|
credentials?: RequestCredentials;
|
|
45
53
|
/**
|
|
@@ -65,6 +73,35 @@ export class HttpError extends Error {
|
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
/** Type guard for {@link HttpError} — clean `catch` narrowing without a cast. */
|
|
77
|
+
export function isHttpError(value: unknown): value is HttpError {
|
|
78
|
+
return value instanceof HttpError;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether `value` is an aborted/timed-out request error — an `AbortError`
|
|
83
|
+
* (cancelled via a `signal`) or a `TimeoutError` (the `timeout` option fired).
|
|
84
|
+
* Use it to silently ignore cancellations (e.g. a superseded search request).
|
|
85
|
+
*/
|
|
86
|
+
export function isAbortError(value: unknown): boolean {
|
|
87
|
+
const name =
|
|
88
|
+
value instanceof Error
|
|
89
|
+
? value.name
|
|
90
|
+
: typeof DOMException !== "undefined" && value instanceof DOMException
|
|
91
|
+
? value.name
|
|
92
|
+
: undefined;
|
|
93
|
+
return name === "AbortError" || name === "TimeoutError";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Outcome of a non-throwing request via {@link HttpClient.attempt}: a
|
|
98
|
+
* discriminated union a form submit can branch on (`if (r.ok)`) instead of
|
|
99
|
+
* wrapping every call in try/catch.
|
|
100
|
+
*/
|
|
101
|
+
export type HttpResult<T> =
|
|
102
|
+
| { ok: true; data: T; error: null }
|
|
103
|
+
| { ok: false; data: null; error: HttpError };
|
|
104
|
+
|
|
68
105
|
/** Whether `body` should be JSON-encoded (vs. passed to `fetch` untouched). */
|
|
69
106
|
function shouldJsonEncode(body: unknown): boolean {
|
|
70
107
|
if (body === null || typeof body !== "object") {
|
|
@@ -94,6 +131,27 @@ function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
|
94
131
|
return false;
|
|
95
132
|
}
|
|
96
133
|
|
|
134
|
+
/** Merge abort signals into one (whichever fires first wins). `undefined` if none. */
|
|
135
|
+
function combineSignals(
|
|
136
|
+
signals: ReadonlyArray<AbortSignal | undefined>,
|
|
137
|
+
): AbortSignal | undefined {
|
|
138
|
+
const present = signals.filter((s): s is AbortSignal => s !== undefined);
|
|
139
|
+
if (present.length <= 1) return present[0];
|
|
140
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(present);
|
|
141
|
+
// Fallback for runtimes without AbortSignal.any.
|
|
142
|
+
const controller = new AbortController();
|
|
143
|
+
for (const signal of present) {
|
|
144
|
+
if (signal.aborted) {
|
|
145
|
+
controller.abort(signal.reason);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), {
|
|
149
|
+
once: true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return controller.signal;
|
|
153
|
+
}
|
|
154
|
+
|
|
97
155
|
/** Parse a response by content-type; `null` for empty / no-content bodies. */
|
|
98
156
|
async function parseBody(response: Response): Promise<unknown> {
|
|
99
157
|
if (response.status === 204 || response.status === 205) return null;
|
|
@@ -109,12 +167,14 @@ export class HttpClient {
|
|
|
109
167
|
readonly #headers: Record<string, string>;
|
|
110
168
|
readonly #token?: string | null | (() => string | null | undefined);
|
|
111
169
|
readonly #credentials?: RequestCredentials;
|
|
170
|
+
readonly #timeout?: number;
|
|
112
171
|
|
|
113
172
|
constructor(options: HttpClientOptions = {}) {
|
|
114
173
|
this.#baseURL = options.baseURL ?? "";
|
|
115
174
|
this.#headers = { ...options.headers };
|
|
116
175
|
this.#token = options.token;
|
|
117
176
|
this.#credentials = options.credentials;
|
|
177
|
+
this.#timeout = options.timeout;
|
|
118
178
|
}
|
|
119
179
|
|
|
120
180
|
/** Set a default header for every subsequent request (case-insensitive replace). Chainable. */
|
|
@@ -192,6 +252,30 @@ export class HttpClient {
|
|
|
192
252
|
return this.#send(method, url, body, options);
|
|
193
253
|
}
|
|
194
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Run a request without throwing on a non-2xx response: returns a
|
|
257
|
+
* discriminated {@link HttpResult} so a form submit can branch
|
|
258
|
+
* (`if (r.ok) … else r.error.data`) instead of wrapping each call in
|
|
259
|
+
* try/catch. Genuine transport failures (offline, DNS) still reject —
|
|
260
|
+
* they're exceptional, not an HTTP error.
|
|
261
|
+
*
|
|
262
|
+
* ```js
|
|
263
|
+
* const r = await api.attempt(api.post('/auth/login', creds))
|
|
264
|
+
* if (r.ok) user(r.data)
|
|
265
|
+
* else fieldErrors(r.error.data) // HttpError.data = parsed 4xx body
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
async attempt<T>(request: Promise<T>): Promise<HttpResult<T>> {
|
|
269
|
+
try {
|
|
270
|
+
return { ok: true, data: await request, error: null };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof HttpError) {
|
|
273
|
+
return { ok: false, data: null, error };
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
195
279
|
/** Derive a new client with merged defaults (e.g. a scope that adds a token). */
|
|
196
280
|
extend(options: HttpClientOptions): HttpClient {
|
|
197
281
|
return new HttpClient({
|
|
@@ -199,6 +283,7 @@ export class HttpClient {
|
|
|
199
283
|
headers: { ...this.#headers, ...options.headers },
|
|
200
284
|
token: options.token ?? this.#token,
|
|
201
285
|
credentials: options.credentials ?? this.#credentials,
|
|
286
|
+
timeout: options.timeout ?? this.#timeout,
|
|
202
287
|
});
|
|
203
288
|
}
|
|
204
289
|
|
|
@@ -250,11 +335,17 @@ export class HttpClient {
|
|
|
250
335
|
}
|
|
251
336
|
}
|
|
252
337
|
|
|
338
|
+
const timeout = options.timeout ?? this.#timeout;
|
|
339
|
+
const signal = combineSignals([
|
|
340
|
+
options.signal,
|
|
341
|
+
timeout !== undefined ? AbortSignal.timeout(timeout) : undefined,
|
|
342
|
+
]);
|
|
343
|
+
|
|
253
344
|
return fetch(this.#buildUrl(url, options.query), {
|
|
254
345
|
method,
|
|
255
346
|
headers,
|
|
256
347
|
body: payload,
|
|
257
|
-
signal
|
|
348
|
+
signal,
|
|
258
349
|
credentials: options.credentials ?? this.#credentials,
|
|
259
350
|
});
|
|
260
351
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,8 +36,18 @@ export {
|
|
|
36
36
|
} from "./browser.js";
|
|
37
37
|
export { component, onMount, onUnmount } from "./component.js";
|
|
38
38
|
export { html, isTemplateResult } from "./html.js";
|
|
39
|
-
export type {
|
|
40
|
-
|
|
39
|
+
export type {
|
|
40
|
+
HttpClientOptions,
|
|
41
|
+
HttpRequestOptions,
|
|
42
|
+
HttpResult,
|
|
43
|
+
} from "./http.js";
|
|
44
|
+
export {
|
|
45
|
+
HttpClient,
|
|
46
|
+
HttpError,
|
|
47
|
+
http,
|
|
48
|
+
isAbortError,
|
|
49
|
+
isHttpError,
|
|
50
|
+
} from "./http.js";
|
|
41
51
|
export { hydrate } from "./hydrate.js";
|
|
42
52
|
export {
|
|
43
53
|
batch,
|