@fragno-dev/api-fragment 0.0.0-stage → 0.0.1
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.md +16 -0
- package/README.md +249 -2
- package/dist/browser/client/client-3ZV9sw3i.js +2580 -0
- package/dist/browser/client/client-3ZV9sw3i.js.map +1 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts +711 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts.map +1 -0
- package/dist/browser/client/client.d.ts +1645 -0
- package/dist/browser/client/client.d.ts.map +1 -0
- package/dist/browser/client/client.js +2 -0
- package/dist/browser/client/react.d.ts +1665 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +194 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +1668 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +129 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +1668 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +131 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +1695 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +159 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +1668 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +127 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-D4NnY-zV.d.ts +713 -0
- package/dist/browser/client-D4NnY-zV.d.ts.map +1 -0
- package/dist/browser/client-DQE0c7GI.d.ts +711 -0
- package/dist/browser/client-DQE0c7GI.d.ts.map +1 -0
- package/dist/browser/index.d.ts +3130 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/src-BrYPVbAN.js +2404 -0
- package/dist/browser/src-BrYPVbAN.js.map +1 -0
- package/dist/browser/src-t-4wp9-f.js +2400 -0
- package/dist/browser/src-t-4wp9-f.js.map +1 -0
- package/dist/node/api-types.d.ts +731 -0
- package/dist/node/api-types.d.ts.map +1 -0
- package/dist/node/api-types.js +281 -0
- package/dist/node/api-types.js.map +1 -0
- package/dist/node/crypto.d.ts +6 -0
- package/dist/node/crypto.d.ts.map +1 -0
- package/dist/node/crypto.js +103 -0
- package/dist/node/crypto.js.map +1 -0
- package/dist/node/definition.d.ts +638 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +315 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/oauth.js +124 -0
- package/dist/node/oauth.js.map +1 -0
- package/dist/node/routes.d.ts +1585 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +1100 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +99 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +16 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/server.d.ts +1585 -0
- package/dist/node/server.d.ts.map +1 -0
- package/dist/node/server.js +12 -0
- package/dist/node/server.js.map +1 -0
- package/dist/node/services.d.ts +10 -0
- package/dist/node/services.d.ts.map +1 -0
- package/dist/node/services.js +304 -0
- package/dist/node/services.js.map +1 -0
- package/dist/node/webhooks/auth.d.ts +133 -0
- package/dist/node/webhooks/auth.d.ts.map +1 -0
- package/dist/node/webhooks/auth.js +268 -0
- package/dist/node/webhooks/auth.js.map +1 -0
- package/dist/node/webhooks/verification.d.ts +102 -0
- package/dist/node/webhooks/verification.d.ts.map +1 -0
- package/dist/node/webhooks/verification.js +119 -0
- package/dist/node/webhooks/verification.js.map +1 -0
- package/package.json +136 -3
|
@@ -0,0 +1,2580 @@
|
|
|
1
|
+
import { defineFragment, defineRoutes } from "@fragno-dev/core";
|
|
2
|
+
import "@fragno-dev/db";
|
|
3
|
+
import { column, idColumn, referenceColumn, schema } from "@fragno-dev/db/schema";
|
|
4
|
+
import "@fragno-dev/db/cursor";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region ../fragno/dist/api/internal/path.js
|
|
7
|
+
/**
|
|
8
|
+
* Extract parameter names from a path pattern at runtime.
|
|
9
|
+
* Examples:
|
|
10
|
+
* - "/users/:id" => ["id"]
|
|
11
|
+
* - "/files/**" => ["**"]
|
|
12
|
+
* - "/files/**:rest" => ["rest"]
|
|
13
|
+
*/
|
|
14
|
+
function extractPathParams(pathPattern) {
|
|
15
|
+
const segments = pathPattern.split("/").filter((s) => s.length > 0);
|
|
16
|
+
const names = [];
|
|
17
|
+
for (const segment of segments) {
|
|
18
|
+
if (segment.startsWith(":")) {
|
|
19
|
+
names.push(segment.slice(1));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (segment === "**") {
|
|
23
|
+
names.push("**");
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (segment.startsWith("**:")) {
|
|
27
|
+
names.push(segment.slice(3));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return names;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a concrete path by replacing placeholders in a path pattern with values.
|
|
35
|
+
*
|
|
36
|
+
* Supports the same placeholder syntax as the matcher:
|
|
37
|
+
* - Named parameter ":name" is URL-encoded as a single segment
|
|
38
|
+
* - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
|
|
39
|
+
* - Named wildcard "**:name" inserts the remainder from the named key
|
|
40
|
+
*
|
|
41
|
+
* Examples:
|
|
42
|
+
* - buildPath("/users/:id", { id: "123" }) => "/users/123"
|
|
43
|
+
* - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
|
|
44
|
+
* - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
|
|
45
|
+
*/
|
|
46
|
+
function buildPath(pathPattern, params) {
|
|
47
|
+
const patternSegments = pathPattern.split("/");
|
|
48
|
+
const builtSegments = [];
|
|
49
|
+
for (const segment of patternSegments) {
|
|
50
|
+
if (segment.length === 0) {
|
|
51
|
+
builtSegments.push("");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (segment.startsWith(":")) {
|
|
55
|
+
const name = segment.slice(1);
|
|
56
|
+
const value = params[name];
|
|
57
|
+
if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
|
|
58
|
+
builtSegments.push(encodeURIComponent(value));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (segment === "**") {
|
|
62
|
+
const value = params["**"];
|
|
63
|
+
if (value === void 0) throw new Error("Missing value for path wildcard **");
|
|
64
|
+
builtSegments.push(value);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (segment.startsWith("**:")) {
|
|
68
|
+
const name = segment.slice(3);
|
|
69
|
+
const value = params[name];
|
|
70
|
+
if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
|
|
71
|
+
builtSegments.push(value);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
builtSegments.push(segment);
|
|
75
|
+
}
|
|
76
|
+
return builtSegments.join("/");
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region ../fragno/dist/api/request-body.js
|
|
80
|
+
const isBinaryRequestBody = (body) => body instanceof ReadableStream || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
|
|
81
|
+
const containsFileBody = (value) => {
|
|
82
|
+
if (value instanceof File || value instanceof Blob || value instanceof FormData) return true;
|
|
83
|
+
return typeof value === "object" && value !== null && Object.values(value).some((entry) => entry instanceof File || entry instanceof Blob || entry instanceof FormData);
|
|
84
|
+
};
|
|
85
|
+
const appendFormDataValue = (formData, key, value) => {
|
|
86
|
+
if (value instanceof File) formData.append(key, value, value.name);
|
|
87
|
+
else if (value instanceof Blob) formData.append(key, value);
|
|
88
|
+
else if (value !== void 0 && value !== null) formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
89
|
+
};
|
|
90
|
+
const prepareMultipartBody = (body) => {
|
|
91
|
+
if (body instanceof FormData) return body;
|
|
92
|
+
const formData = new FormData();
|
|
93
|
+
if (body instanceof File) {
|
|
94
|
+
formData.append("file", body, body.name);
|
|
95
|
+
return formData;
|
|
96
|
+
}
|
|
97
|
+
if (body instanceof Blob) {
|
|
98
|
+
formData.append("file", body);
|
|
99
|
+
return formData;
|
|
100
|
+
}
|
|
101
|
+
if (typeof body === "object" && body !== null) {
|
|
102
|
+
for (const [key, value] of Object.entries(body)) appendFormDataValue(formData, key, value);
|
|
103
|
+
return formData;
|
|
104
|
+
}
|
|
105
|
+
throw new TypeError("Multipart routes require FormData, File, Blob, or an object body.");
|
|
106
|
+
};
|
|
107
|
+
const prepareClientRequestBody = (body, contentType) => {
|
|
108
|
+
if (body === void 0) return {
|
|
109
|
+
body: void 0,
|
|
110
|
+
contentType: void 0
|
|
111
|
+
};
|
|
112
|
+
if (contentType === "application/octet-stream") {
|
|
113
|
+
if (!isBinaryRequestBody(body)) throw new TypeError("Octet-stream routes require Blob, ArrayBuffer, ArrayBufferView, or ReadableStream bodies.");
|
|
114
|
+
return {
|
|
115
|
+
body,
|
|
116
|
+
contentType
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (body instanceof FormData || body instanceof File || body instanceof Blob) return {
|
|
120
|
+
body: prepareMultipartBody(body),
|
|
121
|
+
contentType: null
|
|
122
|
+
};
|
|
123
|
+
if (containsFileBody(body)) return {
|
|
124
|
+
body: prepareMultipartBody(body),
|
|
125
|
+
contentType: null
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
body: JSON.stringify(body),
|
|
129
|
+
contentType: "application/json"
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
const createRequestInitWithBody = (method, headers, body, options = {}) => {
|
|
133
|
+
const requestInit = {
|
|
134
|
+
...options,
|
|
135
|
+
method,
|
|
136
|
+
headers,
|
|
137
|
+
body
|
|
138
|
+
};
|
|
139
|
+
if (body instanceof ReadableStream) requestInit.duplex = "half";
|
|
140
|
+
return requestInit;
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region ../fragno/dist/api/internal/route.js
|
|
144
|
+
function getMountRoute(opts) {
|
|
145
|
+
const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
|
|
146
|
+
if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
|
|
147
|
+
return mountRoute;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region ../fragno/dist/api/error.js
|
|
151
|
+
var FragnoApiError = class extends Error {
|
|
152
|
+
#status;
|
|
153
|
+
#code;
|
|
154
|
+
constructor({ message, code }, status) {
|
|
155
|
+
super(message);
|
|
156
|
+
this.name = "FragnoApiError";
|
|
157
|
+
this.#status = status;
|
|
158
|
+
this.#code = code;
|
|
159
|
+
}
|
|
160
|
+
get status() {
|
|
161
|
+
return this.#status;
|
|
162
|
+
}
|
|
163
|
+
get code() {
|
|
164
|
+
return this.#code;
|
|
165
|
+
}
|
|
166
|
+
toResponse() {
|
|
167
|
+
return Response.json({
|
|
168
|
+
message: this.message,
|
|
169
|
+
code: this.code
|
|
170
|
+
}, { status: this.status });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var FragnoApiValidationError = class extends FragnoApiError {
|
|
174
|
+
issues;
|
|
175
|
+
constructor(message, issues) {
|
|
176
|
+
super({
|
|
177
|
+
message,
|
|
178
|
+
code: "FRAGNO_VALIDATION_ERROR"
|
|
179
|
+
}, 400);
|
|
180
|
+
this.name = "FragnoApiValidationError";
|
|
181
|
+
this.issues = issues;
|
|
182
|
+
}
|
|
183
|
+
toResponse() {
|
|
184
|
+
return Response.json({
|
|
185
|
+
message: this.message,
|
|
186
|
+
issues: this.issues,
|
|
187
|
+
code: this.code
|
|
188
|
+
}, { status: this.status });
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region ../fragno/dist/api/request-input-context.js
|
|
193
|
+
var RequestInputContext = class RequestInputContext {
|
|
194
|
+
#path;
|
|
195
|
+
#method;
|
|
196
|
+
#pathParams;
|
|
197
|
+
#searchParams;
|
|
198
|
+
#headers;
|
|
199
|
+
#body;
|
|
200
|
+
#request;
|
|
201
|
+
#parsedBody;
|
|
202
|
+
#inputSchema;
|
|
203
|
+
#shouldValidateInput;
|
|
204
|
+
constructor(config) {
|
|
205
|
+
this.#path = config.path;
|
|
206
|
+
this.#method = config.method;
|
|
207
|
+
this.#pathParams = config.pathParams;
|
|
208
|
+
this.#searchParams = config.searchParams;
|
|
209
|
+
this.#headers = config.headers;
|
|
210
|
+
this.#body = config.rawBody;
|
|
211
|
+
this.#request = config.request;
|
|
212
|
+
this.#parsedBody = config.parsedBody;
|
|
213
|
+
this.#inputSchema = config.inputSchema;
|
|
214
|
+
this.#shouldValidateInput = config.shouldValidateInput ?? true;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Create a RequestContext from a Request object for server-side handling
|
|
218
|
+
*/
|
|
219
|
+
static async fromRequest(config) {
|
|
220
|
+
return new RequestInputContext({
|
|
221
|
+
method: config.method,
|
|
222
|
+
path: config.path,
|
|
223
|
+
pathParams: config.state.pathParams,
|
|
224
|
+
searchParams: config.state.searchParams,
|
|
225
|
+
headers: config.state.headers,
|
|
226
|
+
request: config.request,
|
|
227
|
+
parsedBody: config.state.body,
|
|
228
|
+
rawBody: config.rawBody,
|
|
229
|
+
inputSchema: config.inputSchema,
|
|
230
|
+
shouldValidateInput: config.shouldValidateInput
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Create a RequestContext for server-side rendering contexts (no Request object)
|
|
235
|
+
*/
|
|
236
|
+
static fromSSRContext(config) {
|
|
237
|
+
return new RequestInputContext({
|
|
238
|
+
method: config.method,
|
|
239
|
+
path: config.path,
|
|
240
|
+
pathParams: config.pathParams,
|
|
241
|
+
searchParams: config.searchParams ?? new URLSearchParams(),
|
|
242
|
+
headers: config.headers ?? new Headers(),
|
|
243
|
+
parsedBody: "body" in config ? config.body : void 0,
|
|
244
|
+
inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
|
|
245
|
+
shouldValidateInput: false
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The HTTP method as string (e.g., `GET`, `POST`)
|
|
250
|
+
*/
|
|
251
|
+
get method() {
|
|
252
|
+
return this.#method;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The matched route path (e.g., `/users/:id`)
|
|
256
|
+
* @remarks `string`
|
|
257
|
+
*/
|
|
258
|
+
get path() {
|
|
259
|
+
return this.#path;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Extracted path parameters as object (e.g., `{ id: '123' }`)
|
|
263
|
+
* @remarks `Record<string, string>`
|
|
264
|
+
*/
|
|
265
|
+
get pathParams() {
|
|
266
|
+
return this.#pathParams;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
|
|
270
|
+
* @remarks `URLSearchParams`
|
|
271
|
+
*/
|
|
272
|
+
get query() {
|
|
273
|
+
return this.#searchParams;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers
|
|
277
|
+
* @remarks `Headers`
|
|
278
|
+
*/
|
|
279
|
+
get headers() {
|
|
280
|
+
return this.#headers;
|
|
281
|
+
}
|
|
282
|
+
get rawBody() {
|
|
283
|
+
return this.#body;
|
|
284
|
+
}
|
|
285
|
+
get request() {
|
|
286
|
+
return this.#request;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Access the request body as FormData.
|
|
290
|
+
*
|
|
291
|
+
* Use this method when handling file uploads or multipart form submissions.
|
|
292
|
+
* The request must have been sent with Content-Type: multipart/form-data.
|
|
293
|
+
*
|
|
294
|
+
* @throws Error if the request body is not FormData
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* defineRoute({
|
|
299
|
+
* method: "POST",
|
|
300
|
+
* path: "/upload",
|
|
301
|
+
* async handler(ctx, res) {
|
|
302
|
+
* const formData = ctx.formData();
|
|
303
|
+
* const file = formData.get("file") as File;
|
|
304
|
+
* const description = formData.get("description") as string;
|
|
305
|
+
* // ... process file
|
|
306
|
+
* }
|
|
307
|
+
* });
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
formData() {
|
|
311
|
+
if (!(this.#parsedBody instanceof FormData)) throw new Error("Request body is not FormData. Ensure the request was sent with Content-Type: multipart/form-data.");
|
|
312
|
+
return this.#parsedBody;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check if the request body is FormData.
|
|
316
|
+
*
|
|
317
|
+
* Useful for routes that accept both JSON and FormData payloads.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```typescript
|
|
321
|
+
* defineRoute({
|
|
322
|
+
* method: "POST",
|
|
323
|
+
* path: "/upload",
|
|
324
|
+
* async handler(ctx, res) {
|
|
325
|
+
* if (ctx.isFormData()) {
|
|
326
|
+
* const formData = ctx.formData();
|
|
327
|
+
* // handle file upload
|
|
328
|
+
* } else {
|
|
329
|
+
* const json = await ctx.input.valid();
|
|
330
|
+
* // handle JSON payload
|
|
331
|
+
* }
|
|
332
|
+
* }
|
|
333
|
+
* });
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
isFormData() {
|
|
337
|
+
return this.#parsedBody instanceof FormData;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Access the request body as a ReadableStream (application/octet-stream).
|
|
341
|
+
*
|
|
342
|
+
* @throws Error if the request body is not a ReadableStream
|
|
343
|
+
*/
|
|
344
|
+
bodyStream() {
|
|
345
|
+
if (!(this.#parsedBody instanceof ReadableStream)) throw new Error("Request body is not a ReadableStream. Ensure the request was sent with Content-Type: application/octet-stream.");
|
|
346
|
+
return this.#parsedBody;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check if the request body is a ReadableStream.
|
|
350
|
+
*/
|
|
351
|
+
isBodyStream() {
|
|
352
|
+
return this.#parsedBody instanceof ReadableStream;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Input validation context (only if inputSchema is defined)
|
|
356
|
+
* @remarks `InputContext`
|
|
357
|
+
*/
|
|
358
|
+
get input() {
|
|
359
|
+
if (!this.#inputSchema) return;
|
|
360
|
+
return {
|
|
361
|
+
schema: this.#inputSchema,
|
|
362
|
+
valid: async () => {
|
|
363
|
+
if (!this.#shouldValidateInput) return this.#parsedBody;
|
|
364
|
+
return this.#validateInput();
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async #validateInput() {
|
|
369
|
+
if (!this.#inputSchema) throw new Error("No input schema defined for this route");
|
|
370
|
+
if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
|
|
371
|
+
if (this.#parsedBody instanceof ReadableStream) throw new Error("Schema validation is only supported for JSON data, not FormData, Blob, or ReadableStream");
|
|
372
|
+
const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
|
|
373
|
+
if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
|
|
374
|
+
return result.value;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region ../fragno/dist/api/internal/response-stream.js
|
|
379
|
+
var ResponseStream = class {
|
|
380
|
+
#writer;
|
|
381
|
+
#encoder;
|
|
382
|
+
#abortSubscribers = [];
|
|
383
|
+
#responseReadable;
|
|
384
|
+
#aborted = false;
|
|
385
|
+
#abortPromise;
|
|
386
|
+
#closed = false;
|
|
387
|
+
/**
|
|
388
|
+
* Whether the stream has been aborted.
|
|
389
|
+
*/
|
|
390
|
+
get aborted() {
|
|
391
|
+
return this.#aborted;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Whether the stream has been closed normally.
|
|
395
|
+
*/
|
|
396
|
+
get closed() {
|
|
397
|
+
return this.#closed;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* The readable stream that the response is piped to.
|
|
401
|
+
*/
|
|
402
|
+
get responseReadable() {
|
|
403
|
+
return this.#responseReadable;
|
|
404
|
+
}
|
|
405
|
+
constructor(writable, readable) {
|
|
406
|
+
this.#writer = writable.getWriter();
|
|
407
|
+
this.#encoder = new TextEncoder();
|
|
408
|
+
const reader = readable.getReader();
|
|
409
|
+
this.#abortSubscribers.push(async () => {
|
|
410
|
+
await reader.cancel();
|
|
411
|
+
});
|
|
412
|
+
this.#responseReadable = new ReadableStream({
|
|
413
|
+
async pull(controller) {
|
|
414
|
+
const { done, value } = await reader.read();
|
|
415
|
+
if (done) controller.close();
|
|
416
|
+
else controller.enqueue(value);
|
|
417
|
+
},
|
|
418
|
+
cancel: () => this.abort()
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async writeRaw(input) {
|
|
422
|
+
try {
|
|
423
|
+
if (typeof input === "string") input = this.#encoder.encode(input);
|
|
424
|
+
await this.#writer.write(input);
|
|
425
|
+
return true;
|
|
426
|
+
} catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async write(input) {
|
|
431
|
+
await this.writeRaw(JSON.stringify(input) + "\n");
|
|
432
|
+
}
|
|
433
|
+
sleep(ms) {
|
|
434
|
+
return new Promise((resolve) => {
|
|
435
|
+
setTimeout(resolve, ms);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async close() {
|
|
439
|
+
try {
|
|
440
|
+
await this.#writer.close();
|
|
441
|
+
} catch {} finally {
|
|
442
|
+
this.#closed = true;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
onAbort(listener) {
|
|
446
|
+
this.#abortSubscribers.push(listener);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Abort the stream.
|
|
450
|
+
* You can call this method when stream is aborted by external event.
|
|
451
|
+
*/
|
|
452
|
+
abort() {
|
|
453
|
+
if (!this.#abortPromise) {
|
|
454
|
+
this.#aborted = true;
|
|
455
|
+
this.#abortPromise = Promise.allSettled(this.#abortSubscribers.map(async (subscriber) => subscriber())).then((results) => {
|
|
456
|
+
const errors = [];
|
|
457
|
+
for (const result of results) if (result.status === "rejected") errors.push(result.reason);
|
|
458
|
+
if (errors.length > 0) throw new AggregateError(errors, "Failed to abort response stream.");
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return this.#abortPromise;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region ../fragno/dist/api/request-output-context.js
|
|
466
|
+
/**
|
|
467
|
+
* Utility function to merge headers from multiple sources.
|
|
468
|
+
* Later headers override earlier ones.
|
|
469
|
+
*/
|
|
470
|
+
function mergeHeaders$1(...headerSources) {
|
|
471
|
+
const mergedHeaders = new Headers();
|
|
472
|
+
for (const headerSource of headerSources) {
|
|
473
|
+
if (!headerSource) continue;
|
|
474
|
+
if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
|
|
475
|
+
else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
|
|
476
|
+
else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
|
|
477
|
+
}
|
|
478
|
+
return mergedHeaders;
|
|
479
|
+
}
|
|
480
|
+
var OutputContext = class {
|
|
481
|
+
#runJsonStreamCallback;
|
|
482
|
+
constructor(options = {}) {
|
|
483
|
+
this.#runJsonStreamCallback = options.runJsonStreamCallback;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Creates an error response.
|
|
487
|
+
*
|
|
488
|
+
* Shortcut for `throw new FragnoApiError(...)`
|
|
489
|
+
*/
|
|
490
|
+
error = ({ message, code }, initOrStatus, headers) => {
|
|
491
|
+
if (typeof initOrStatus === "undefined") return Response.json({
|
|
492
|
+
message,
|
|
493
|
+
code
|
|
494
|
+
}, {
|
|
495
|
+
status: 500,
|
|
496
|
+
headers
|
|
497
|
+
});
|
|
498
|
+
if (typeof initOrStatus === "number") return Response.json({
|
|
499
|
+
message,
|
|
500
|
+
code
|
|
501
|
+
}, {
|
|
502
|
+
status: initOrStatus,
|
|
503
|
+
headers
|
|
504
|
+
});
|
|
505
|
+
const mergedHeaders = mergeHeaders$1(initOrStatus.headers, headers);
|
|
506
|
+
return Response.json({
|
|
507
|
+
message,
|
|
508
|
+
code
|
|
509
|
+
}, {
|
|
510
|
+
status: initOrStatus.status,
|
|
511
|
+
headers: mergedHeaders
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
empty = (initOrStatus, headers) => {
|
|
515
|
+
const defaultHeaders = {};
|
|
516
|
+
if (typeof initOrStatus === "undefined") {
|
|
517
|
+
const mergedHeaders = mergeHeaders$1(defaultHeaders, headers);
|
|
518
|
+
return new Response(null, {
|
|
519
|
+
status: 201,
|
|
520
|
+
headers: mergedHeaders
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
if (typeof initOrStatus === "number") {
|
|
524
|
+
const mergedHeaders = mergeHeaders$1(defaultHeaders, headers);
|
|
525
|
+
return new Response(null, {
|
|
526
|
+
status: initOrStatus,
|
|
527
|
+
headers: mergedHeaders
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const mergedHeaders = mergeHeaders$1(defaultHeaders, initOrStatus.headers, headers);
|
|
531
|
+
return new Response(null, {
|
|
532
|
+
status: initOrStatus.status,
|
|
533
|
+
headers: mergedHeaders
|
|
534
|
+
});
|
|
535
|
+
};
|
|
536
|
+
json = (object, initOrStatus, headers) => {
|
|
537
|
+
if (typeof initOrStatus === "undefined") return Response.json(object, {
|
|
538
|
+
status: 200,
|
|
539
|
+
headers
|
|
540
|
+
});
|
|
541
|
+
if (typeof initOrStatus === "number") return Response.json(object, {
|
|
542
|
+
status: initOrStatus,
|
|
543
|
+
headers
|
|
544
|
+
});
|
|
545
|
+
const mergedHeaders = mergeHeaders$1(initOrStatus.headers, headers);
|
|
546
|
+
return Response.json(object, {
|
|
547
|
+
status: initOrStatus.status,
|
|
548
|
+
headers: mergedHeaders
|
|
549
|
+
});
|
|
550
|
+
};
|
|
551
|
+
jsonStream = (cb, { onError, headers } = {}) => {
|
|
552
|
+
const defaultHeaders = {
|
|
553
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
554
|
+
"transfer-encoding": "chunked",
|
|
555
|
+
"cache-control": "no-cache"
|
|
556
|
+
};
|
|
557
|
+
const { readable, writable } = new TransformStream();
|
|
558
|
+
const stream = new ResponseStream(writable, readable);
|
|
559
|
+
const runStreamCallback = async () => {
|
|
560
|
+
try {
|
|
561
|
+
await cb(stream);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
|
|
564
|
+
else console.error(e);
|
|
565
|
+
} finally {
|
|
566
|
+
await stream.close();
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
try {
|
|
570
|
+
Promise.resolve(this.#runJsonStreamCallback ? this.#runJsonStreamCallback(runStreamCallback) : runStreamCallback()).catch(async (error) => {
|
|
571
|
+
console.error(error);
|
|
572
|
+
await stream.close();
|
|
573
|
+
});
|
|
574
|
+
} catch (error) {
|
|
575
|
+
console.error(error);
|
|
576
|
+
stream.close();
|
|
577
|
+
}
|
|
578
|
+
return new Response(stream.responseReadable, {
|
|
579
|
+
status: 200,
|
|
580
|
+
headers: mergeHeaders$1(defaultHeaders, headers)
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
};
|
|
584
|
+
var RequestOutputContext = class extends OutputContext {
|
|
585
|
+
#outputSchema;
|
|
586
|
+
constructor(outputSchema, options) {
|
|
587
|
+
super(options);
|
|
588
|
+
this.#outputSchema = outputSchema;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region ../fragno/dist/api/route.js
|
|
593
|
+
/**
|
|
594
|
+
* Helper to resolve route factories into routes
|
|
595
|
+
* @internal
|
|
596
|
+
*/
|
|
597
|
+
function resolveRouteFactories(context, routesOrFactories) {
|
|
598
|
+
const routes = [];
|
|
599
|
+
for (const item of routesOrFactories) if (typeof item === "function") {
|
|
600
|
+
const factoryRoutes = item(context);
|
|
601
|
+
routes.push(...factoryRoutes);
|
|
602
|
+
} else routes.push(item);
|
|
603
|
+
return routes;
|
|
604
|
+
}
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region ../fragno/dist/util/content-type.js
|
|
607
|
+
/**
|
|
608
|
+
* Parses a content-type header string into its components
|
|
609
|
+
*
|
|
610
|
+
* @param contentType - The content-type header value to parse
|
|
611
|
+
* @returns A ParsedContentType object or null if the input is invalid
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```ts
|
|
615
|
+
* const { type, subtype, mediaType, parameters }
|
|
616
|
+
* = parseContentType("application/json; charset=utf-8");
|
|
617
|
+
* console.assert(type === "application");
|
|
618
|
+
* console.assert(subtype === "json");
|
|
619
|
+
* console.assert(mediaType === "application/json");
|
|
620
|
+
* console.assert(parameters["charset"] === "utf-8");
|
|
621
|
+
*/
|
|
622
|
+
function parseContentType(contentType) {
|
|
623
|
+
if (!contentType || typeof contentType !== "string") return null;
|
|
624
|
+
const trimmed = contentType.trim();
|
|
625
|
+
if (!trimmed) return null;
|
|
626
|
+
const parts = trimmed.split(";").map((part) => part.trim());
|
|
627
|
+
const mediaType = parts[0];
|
|
628
|
+
if (!mediaType) return null;
|
|
629
|
+
const typeParts = mediaType.split("/");
|
|
630
|
+
if (typeParts.length !== 2) return null;
|
|
631
|
+
const [type, subtype] = typeParts.map((part) => part.trim().toLowerCase());
|
|
632
|
+
if (!type || !subtype) return null;
|
|
633
|
+
const parameters = {};
|
|
634
|
+
for (let i = 1; i < parts.length; i++) {
|
|
635
|
+
const param = parts[i];
|
|
636
|
+
const equalIndex = param.indexOf("=");
|
|
637
|
+
if (equalIndex > 0) {
|
|
638
|
+
const key = param.slice(0, equalIndex).trim().toLowerCase();
|
|
639
|
+
let value = param.slice(equalIndex + 1).trim();
|
|
640
|
+
if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
|
|
641
|
+
if (key) parameters[key] = value;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
type,
|
|
646
|
+
subtype,
|
|
647
|
+
mediaType: `${type}/${subtype}`,
|
|
648
|
+
parameters
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region ../fragno/dist/util/nanostores.js
|
|
653
|
+
/**
|
|
654
|
+
* Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
|
|
655
|
+
*/
|
|
656
|
+
function unwrapAtom(value) {
|
|
657
|
+
if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Normalizes an object where values can be plain values, Atoms, or Vue Refs.
|
|
662
|
+
* Returns a new object with all values normalized to plain values.
|
|
663
|
+
*/
|
|
664
|
+
function unwrapObject(params) {
|
|
665
|
+
if (!params) return;
|
|
666
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
|
|
667
|
+
}
|
|
668
|
+
function isReadableAtom(value) {
|
|
669
|
+
if (!value) return false;
|
|
670
|
+
if (typeof value !== "object" || value === null) return false;
|
|
671
|
+
if (!("get" in value) || typeof value.get !== "function") return false;
|
|
672
|
+
if (!("lc" in value) || typeof value.lc !== "number") return false;
|
|
673
|
+
if (!("notify" in value) || typeof value.notify !== "function") return false;
|
|
674
|
+
if (!("off" in value) || typeof value.off !== "function") return false;
|
|
675
|
+
if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
|
|
676
|
+
if (!("value" in value)) return false;
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/task/index.js
|
|
681
|
+
let tasks = 0;
|
|
682
|
+
let resolves = [];
|
|
683
|
+
function startTask() {
|
|
684
|
+
tasks += 1;
|
|
685
|
+
return () => {
|
|
686
|
+
tasks -= 1;
|
|
687
|
+
if (tasks === 0) {
|
|
688
|
+
let prevResolves = resolves;
|
|
689
|
+
resolves = [];
|
|
690
|
+
for (let i of prevResolves) i();
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function task(cb) {
|
|
695
|
+
let endTask = startTask();
|
|
696
|
+
let promise = cb().finally(endTask);
|
|
697
|
+
promise.t = true;
|
|
698
|
+
return promise;
|
|
699
|
+
}
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/clean-stores/index.js
|
|
702
|
+
const clean = Symbol("clean");
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/atom/index.js
|
|
705
|
+
let listenerQueue = [];
|
|
706
|
+
let lqIndex = 0;
|
|
707
|
+
const QUEUE_ITEMS_PER_LISTENER = 4;
|
|
708
|
+
const nanostoresGlobal = globalThis.nanostoresGlobal ||= { epoch: 0 };
|
|
709
|
+
const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
|
|
710
|
+
let listeners = [];
|
|
711
|
+
let $atom = {
|
|
712
|
+
get() {
|
|
713
|
+
if (!$atom.lc) $atom.listen(() => {})();
|
|
714
|
+
return $atom.value;
|
|
715
|
+
},
|
|
716
|
+
init: initialValue,
|
|
717
|
+
lc: 0,
|
|
718
|
+
listen(listener) {
|
|
719
|
+
$atom.lc = listeners.push(listener);
|
|
720
|
+
return () => {
|
|
721
|
+
for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length;) if (listenerQueue[i] === listener) listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER);
|
|
722
|
+
else i += QUEUE_ITEMS_PER_LISTENER;
|
|
723
|
+
let index = listeners.indexOf(listener);
|
|
724
|
+
if (~index) {
|
|
725
|
+
listeners.splice(index, 1);
|
|
726
|
+
if (!--$atom.lc) $atom.off();
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
},
|
|
730
|
+
notify(oldValue, changedKey) {
|
|
731
|
+
nanostoresGlobal.epoch++;
|
|
732
|
+
let runListenerQueue = !listenerQueue.length;
|
|
733
|
+
for (let listener of listeners) listenerQueue.push(listener, $atom.value, oldValue, changedKey);
|
|
734
|
+
if (runListenerQueue) {
|
|
735
|
+
for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) listenerQueue[lqIndex](listenerQueue[lqIndex + 1], listenerQueue[lqIndex + 2], listenerQueue[lqIndex + 3]);
|
|
736
|
+
listenerQueue.length = 0;
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
off() {},
|
|
740
|
+
set(newValue) {
|
|
741
|
+
let oldValue = $atom.value;
|
|
742
|
+
if (oldValue !== newValue) {
|
|
743
|
+
$atom.value = newValue;
|
|
744
|
+
$atom.notify(oldValue);
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
subscribe(listener) {
|
|
748
|
+
let unbind = $atom.listen(listener);
|
|
749
|
+
listener($atom.value);
|
|
750
|
+
return unbind;
|
|
751
|
+
},
|
|
752
|
+
value: initialValue
|
|
753
|
+
};
|
|
754
|
+
$atom[clean] = () => {
|
|
755
|
+
listeners = [];
|
|
756
|
+
$atom.lc = 0;
|
|
757
|
+
$atom.off();
|
|
758
|
+
};
|
|
759
|
+
return $atom;
|
|
760
|
+
};
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/lifecycle/index.js
|
|
763
|
+
const START = 0;
|
|
764
|
+
const STOP = 1;
|
|
765
|
+
const MOUNT = 5;
|
|
766
|
+
const UNMOUNT = 6;
|
|
767
|
+
const REVERT_MUTATION = 10;
|
|
768
|
+
let on = (object, listener, eventKey, mutateStore) => {
|
|
769
|
+
object.events = object.events || {};
|
|
770
|
+
if (!object.events[eventKey + REVERT_MUTATION]) object.events[eventKey + REVERT_MUTATION] = mutateStore((eventProps) => {
|
|
771
|
+
object.events[eventKey].reduceRight((event, l) => (l(event), event), {
|
|
772
|
+
shared: {},
|
|
773
|
+
...eventProps
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
object.events[eventKey] = object.events[eventKey] || [];
|
|
777
|
+
object.events[eventKey].push(listener);
|
|
778
|
+
return () => {
|
|
779
|
+
let currentListeners = object.events[eventKey];
|
|
780
|
+
let index = currentListeners.indexOf(listener);
|
|
781
|
+
currentListeners.splice(index, 1);
|
|
782
|
+
if (!currentListeners.length) {
|
|
783
|
+
delete object.events[eventKey];
|
|
784
|
+
object.events[eventKey + REVERT_MUTATION]();
|
|
785
|
+
delete object.events[eventKey + REVERT_MUTATION];
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
};
|
|
789
|
+
let onStart = ($store, listener) => on($store, listener, START, (runListeners) => {
|
|
790
|
+
let originListen = $store.listen;
|
|
791
|
+
$store.listen = (arg) => {
|
|
792
|
+
if (!$store.lc && !$store.starting) {
|
|
793
|
+
$store.starting = true;
|
|
794
|
+
runListeners();
|
|
795
|
+
delete $store.starting;
|
|
796
|
+
}
|
|
797
|
+
return originListen(arg);
|
|
798
|
+
};
|
|
799
|
+
return () => {
|
|
800
|
+
$store.listen = originListen;
|
|
801
|
+
};
|
|
802
|
+
});
|
|
803
|
+
let onStop = ($store, listener) => on($store, listener, STOP, (runListeners) => {
|
|
804
|
+
let originOff = $store.off;
|
|
805
|
+
$store.off = () => {
|
|
806
|
+
runListeners();
|
|
807
|
+
originOff();
|
|
808
|
+
};
|
|
809
|
+
return () => {
|
|
810
|
+
$store.off = originOff;
|
|
811
|
+
};
|
|
812
|
+
});
|
|
813
|
+
const STORE_UNMOUNT_DELAY = 1e3;
|
|
814
|
+
let onMount = ($store, initialize) => {
|
|
815
|
+
let listener = (payload) => {
|
|
816
|
+
let destroy = initialize(payload);
|
|
817
|
+
if (destroy) $store.events[UNMOUNT].push(destroy);
|
|
818
|
+
};
|
|
819
|
+
return on($store, listener, MOUNT, (runListeners) => {
|
|
820
|
+
let originListen = $store.listen;
|
|
821
|
+
$store.listen = (...args) => {
|
|
822
|
+
if (!$store.lc && !$store.active) {
|
|
823
|
+
$store.active = true;
|
|
824
|
+
runListeners();
|
|
825
|
+
}
|
|
826
|
+
return originListen(...args);
|
|
827
|
+
};
|
|
828
|
+
let originOff = $store.off;
|
|
829
|
+
$store.events[UNMOUNT] = [];
|
|
830
|
+
$store.off = () => {
|
|
831
|
+
originOff();
|
|
832
|
+
setTimeout(() => {
|
|
833
|
+
if ($store.active && !$store.lc) {
|
|
834
|
+
$store.active = false;
|
|
835
|
+
for (let destroy of $store.events[UNMOUNT]) destroy();
|
|
836
|
+
$store.events[UNMOUNT] = [];
|
|
837
|
+
}
|
|
838
|
+
}, STORE_UNMOUNT_DELAY);
|
|
839
|
+
};
|
|
840
|
+
{
|
|
841
|
+
let originClean = $store[clean];
|
|
842
|
+
$store[clean] = () => {
|
|
843
|
+
for (let destroy of $store.events[UNMOUNT]) destroy();
|
|
844
|
+
$store.events[UNMOUNT] = [];
|
|
845
|
+
$store.active = false;
|
|
846
|
+
originClean();
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return () => {
|
|
850
|
+
$store.listen = originListen;
|
|
851
|
+
$store.off = originOff;
|
|
852
|
+
};
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/warn/index.js
|
|
857
|
+
let warned = {};
|
|
858
|
+
function warn(text) {
|
|
859
|
+
if (!warned[text]) {
|
|
860
|
+
warned[text] = true;
|
|
861
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
862
|
+
console.groupCollapsed("Nano Stores: " + text);
|
|
863
|
+
console.trace("Source of deprecated call");
|
|
864
|
+
console.groupEnd();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/computed/index.js
|
|
870
|
+
let computedStore = (stores, cb, batched) => {
|
|
871
|
+
if (!Array.isArray(stores)) stores = [stores];
|
|
872
|
+
let previousArgs;
|
|
873
|
+
let currentEpoch;
|
|
874
|
+
let set = () => {
|
|
875
|
+
if (currentEpoch === nanostoresGlobal.epoch) return;
|
|
876
|
+
currentEpoch = nanostoresGlobal.epoch;
|
|
877
|
+
let args = stores.map(($store) => $store.get());
|
|
878
|
+
if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) {
|
|
879
|
+
previousArgs = args;
|
|
880
|
+
let value = cb(...args);
|
|
881
|
+
if (value && value.then && value.t) {
|
|
882
|
+
warn("Use @nanostores/async for async computed. We will remove Promise support in computed() in Nano Stores 2.0");
|
|
883
|
+
value.then((asyncValue) => {
|
|
884
|
+
if (previousArgs === args) $computed.set(asyncValue);
|
|
885
|
+
});
|
|
886
|
+
} else {
|
|
887
|
+
$computed.set(value);
|
|
888
|
+
currentEpoch = nanostoresGlobal.epoch;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
let $computed = /* @__PURE__ */ atom(void 0);
|
|
893
|
+
let get = $computed.get;
|
|
894
|
+
$computed.get = () => {
|
|
895
|
+
set();
|
|
896
|
+
return get();
|
|
897
|
+
};
|
|
898
|
+
let timer;
|
|
899
|
+
let run = batched ? () => {
|
|
900
|
+
clearTimeout(timer);
|
|
901
|
+
timer = setTimeout(set);
|
|
902
|
+
} : set;
|
|
903
|
+
onMount($computed, () => {
|
|
904
|
+
let unbinds = stores.map(($store) => $store.listen(run));
|
|
905
|
+
set();
|
|
906
|
+
return () => {
|
|
907
|
+
for (let unbind of unbinds) unbind();
|
|
908
|
+
};
|
|
909
|
+
});
|
|
910
|
+
return $computed;
|
|
911
|
+
};
|
|
912
|
+
const computed = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn);
|
|
913
|
+
const batched = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn, true);
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region ../../node_modules/.pnpm/nanostores@1.3.0/node_modules/nanostores/map/index.js
|
|
916
|
+
const map = /* @__NO_SIDE_EFFECTS__ */ (initial = {}) => {
|
|
917
|
+
let $map = /* @__PURE__ */ atom(initial);
|
|
918
|
+
$map.setKey = function(key, value) {
|
|
919
|
+
let oldMap = $map.value;
|
|
920
|
+
if (typeof value === "undefined" && key in $map.value) {
|
|
921
|
+
$map.value = { ...$map.value };
|
|
922
|
+
delete $map.value[key];
|
|
923
|
+
$map.notify(oldMap, key);
|
|
924
|
+
} else if ($map.value[key] !== value) {
|
|
925
|
+
$map.value = {
|
|
926
|
+
...$map.value,
|
|
927
|
+
[key]: value
|
|
928
|
+
};
|
|
929
|
+
$map.notify(oldMap, key);
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
return $map;
|
|
933
|
+
};
|
|
934
|
+
//#endregion
|
|
935
|
+
//#region ../fragno/dist/util/ssr.js
|
|
936
|
+
let stores = [];
|
|
937
|
+
function addStore(store) {
|
|
938
|
+
stores.push(store);
|
|
939
|
+
}
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region ../fragno/dist/client/client-error.js
|
|
942
|
+
/**
|
|
943
|
+
* Base error class for all Fragno client errors.
|
|
944
|
+
*/
|
|
945
|
+
var FragnoClientError = class extends Error {
|
|
946
|
+
#code;
|
|
947
|
+
constructor(message, code, options = {}) {
|
|
948
|
+
super(message, { cause: options.cause });
|
|
949
|
+
this.name = "FragnoClientError";
|
|
950
|
+
this.#code = code;
|
|
951
|
+
}
|
|
952
|
+
get code() {
|
|
953
|
+
return this.#code;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
var FragnoClientFetchError = class extends FragnoClientError {
|
|
957
|
+
constructor(message, code, options = {}) {
|
|
958
|
+
super(message, code, options);
|
|
959
|
+
this.name = "FragnoClientFetchError";
|
|
960
|
+
}
|
|
961
|
+
static fromUnknownFetchError(error) {
|
|
962
|
+
if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
963
|
+
if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
|
|
964
|
+
return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
/**
|
|
968
|
+
* Error thrown when a network request fails (e.g., no internet connection, DNS failure).
|
|
969
|
+
*/
|
|
970
|
+
var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
|
|
971
|
+
constructor(message = "Network request failed", options = {}) {
|
|
972
|
+
super(message, "NETWORK_ERROR", options);
|
|
973
|
+
this.name = "FragnoClientFetchNetworkError";
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
/**
|
|
977
|
+
* Error thrown when a request is aborted (e.g., user cancels request, timeout).
|
|
978
|
+
*/
|
|
979
|
+
var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
|
|
980
|
+
constructor(message = "Request was aborted", options = {}) {
|
|
981
|
+
super(message, "ABORT_ERROR", options);
|
|
982
|
+
this.name = "FragnoClientFetchAbortError";
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
/**
|
|
986
|
+
* Error thrown when the API result is unexpected, e.g. no json is returned.
|
|
987
|
+
*/
|
|
988
|
+
var FragnoClientUnknownApiError = class extends FragnoClientError {
|
|
989
|
+
#status;
|
|
990
|
+
constructor(message = "Unknown API error", status, options = {}) {
|
|
991
|
+
super(message, "UNKNOWN_API_ERROR", options);
|
|
992
|
+
this.name = "FragnoClientUnknownApiError";
|
|
993
|
+
this.#status = status;
|
|
994
|
+
}
|
|
995
|
+
get status() {
|
|
996
|
+
return this.#status;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
var FragnoClientApiError = class FragnoClientApiError extends FragnoClientError {
|
|
1000
|
+
#status;
|
|
1001
|
+
constructor({ message, code }, status, options = {}) {
|
|
1002
|
+
super(message, code, options);
|
|
1003
|
+
this.name = "FragnoClientApiError";
|
|
1004
|
+
this.#status = status;
|
|
1005
|
+
}
|
|
1006
|
+
get status() {
|
|
1007
|
+
return this.#status;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* The error code returned by the API.
|
|
1011
|
+
*
|
|
1012
|
+
* The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
|
|
1013
|
+
* for forward compatibility with future error codes.
|
|
1014
|
+
*/
|
|
1015
|
+
get code() {
|
|
1016
|
+
return super.code;
|
|
1017
|
+
}
|
|
1018
|
+
static async fromResponse(response) {
|
|
1019
|
+
const unknown = await response.json();
|
|
1020
|
+
const status = response.status;
|
|
1021
|
+
if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
1022
|
+
if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
1023
|
+
return new FragnoClientApiError({
|
|
1024
|
+
message: unknown.message,
|
|
1025
|
+
code: unknown.code
|
|
1026
|
+
}, status);
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
//#endregion
|
|
1030
|
+
//#region ../fragno/dist/client/internal/fetcher-merge.js
|
|
1031
|
+
/**
|
|
1032
|
+
* Merge two fetcher configurations, with user config taking precedence.
|
|
1033
|
+
* If user provides a custom function, it takes full precedence.
|
|
1034
|
+
* Otherwise, deep merge RequestInit options.
|
|
1035
|
+
*/
|
|
1036
|
+
function mergeFetcherConfigs(authorConfig, userConfig) {
|
|
1037
|
+
if (userConfig?.type === "function") return userConfig;
|
|
1038
|
+
if (!userConfig && authorConfig?.type === "function") return authorConfig;
|
|
1039
|
+
const authorOpts = authorConfig?.type === "options" ? authorConfig.options : {};
|
|
1040
|
+
const userOpts = userConfig?.type === "options" ? userConfig.options : {};
|
|
1041
|
+
if (Object.keys(authorOpts).length === 0 && Object.keys(userOpts).length === 0) return;
|
|
1042
|
+
return {
|
|
1043
|
+
type: "options",
|
|
1044
|
+
options: {
|
|
1045
|
+
...authorOpts,
|
|
1046
|
+
...userOpts,
|
|
1047
|
+
headers: mergeHeaders(authorOpts.headers, userOpts.headers)
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Merge headers from author and user configs.
|
|
1053
|
+
* User headers override author headers.
|
|
1054
|
+
*/
|
|
1055
|
+
function mergeHeaders(author, user) {
|
|
1056
|
+
if (!author && !user) return;
|
|
1057
|
+
const merged = new Headers(author);
|
|
1058
|
+
new Headers(user).forEach((value, key) => {
|
|
1059
|
+
merged.set(key, value);
|
|
1060
|
+
});
|
|
1061
|
+
if (merged.keys().next().done) return;
|
|
1062
|
+
return merged;
|
|
1063
|
+
}
|
|
1064
|
+
//#endregion
|
|
1065
|
+
//#region ../fragno/dist/client/internal/ndjson-streaming.js
|
|
1066
|
+
/**
|
|
1067
|
+
* Creates a promise that rejects when the abort signal is triggered
|
|
1068
|
+
*/
|
|
1069
|
+
function createAbortPromise(abortSignal) {
|
|
1070
|
+
return new Promise((_, reject) => {
|
|
1071
|
+
const abortHandler = () => {
|
|
1072
|
+
reject(new FragnoClientFetchAbortError("Operation was aborted"));
|
|
1073
|
+
};
|
|
1074
|
+
if (abortSignal.aborted) abortHandler();
|
|
1075
|
+
else abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
const parseNdjsonBuffer = (buffer, options) => {
|
|
1079
|
+
const lines = buffer.split("\n");
|
|
1080
|
+
const trailingBuffer = options.flush ? "" : lines.pop() ?? "";
|
|
1081
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1082
|
+
const line = lines[index];
|
|
1083
|
+
if (!line.trim()) continue;
|
|
1084
|
+
let jsonObject;
|
|
1085
|
+
try {
|
|
1086
|
+
jsonObject = JSON.parse(line);
|
|
1087
|
+
} catch (parseError) {
|
|
1088
|
+
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", options.parseErrorStatus, { cause: parseError });
|
|
1089
|
+
}
|
|
1090
|
+
if (options.onItem(jsonObject) === false) {
|
|
1091
|
+
const remainingLines = lines.slice(index + 1);
|
|
1092
|
+
return {
|
|
1093
|
+
stopped: true,
|
|
1094
|
+
buffer: remainingLines.length > 0 ? `${remainingLines.join("\n")}\n${trailingBuffer}` : trailingBuffer
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
stopped: false,
|
|
1100
|
+
buffer: trailingBuffer
|
|
1101
|
+
};
|
|
1102
|
+
};
|
|
1103
|
+
/**
|
|
1104
|
+
* Handles NDJSON streaming responses by returning the first item from the fetcher
|
|
1105
|
+
* and then continuing to stream updates via the store's mutate method.
|
|
1106
|
+
*
|
|
1107
|
+
* This makes it so that we can wait until the first chunk before updating the store, if we did
|
|
1108
|
+
* not do this, `loading` would briefly be false before the first item would be populated in the
|
|
1109
|
+
* result.
|
|
1110
|
+
*
|
|
1111
|
+
* @param response - The fetch Response object containing the NDJSON stream
|
|
1112
|
+
* @param store - The fetcher store to update with streaming data
|
|
1113
|
+
* @param abortSignal - Optional AbortSignal to cancel the streaming operation
|
|
1114
|
+
* @returns A promise that resolves to an object containing the first item and a streaming promise
|
|
1115
|
+
*/
|
|
1116
|
+
async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
|
|
1117
|
+
if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
|
|
1118
|
+
const { abortSignal } = options;
|
|
1119
|
+
if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1120
|
+
const decoder = new TextDecoder();
|
|
1121
|
+
const reader = response.body.getReader();
|
|
1122
|
+
let buffer = "";
|
|
1123
|
+
let firstItem = null;
|
|
1124
|
+
const items = [];
|
|
1125
|
+
try {
|
|
1126
|
+
while (firstItem === null) {
|
|
1127
|
+
if (abortSignal?.aborted) {
|
|
1128
|
+
reader.releaseLock();
|
|
1129
|
+
throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1130
|
+
}
|
|
1131
|
+
const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
|
|
1132
|
+
if (done) break;
|
|
1133
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1134
|
+
buffer = parseNdjsonBuffer(buffer, {
|
|
1135
|
+
parseErrorStatus: 500,
|
|
1136
|
+
onItem: (jsonObject) => {
|
|
1137
|
+
items.push(jsonObject);
|
|
1138
|
+
firstItem = jsonObject;
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
}).buffer;
|
|
1142
|
+
if (firstItem !== null) {
|
|
1143
|
+
const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
|
|
1144
|
+
return {
|
|
1145
|
+
firstItem,
|
|
1146
|
+
items,
|
|
1147
|
+
streamingPromise
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (firstItem === null) {
|
|
1152
|
+
reader.releaseLock();
|
|
1153
|
+
throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
|
|
1154
|
+
}
|
|
1155
|
+
reader.releaseLock();
|
|
1156
|
+
throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (error instanceof FragnoClientFetchError || error instanceof FragnoClientUnknownApiError) {
|
|
1159
|
+
store?.setError(error);
|
|
1160
|
+
throw error;
|
|
1161
|
+
}
|
|
1162
|
+
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
|
|
1163
|
+
store?.setError(clientError);
|
|
1164
|
+
throw clientError;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Continues streaming the remaining items in the background
|
|
1169
|
+
*/
|
|
1170
|
+
async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
|
|
1171
|
+
let buffer = initialBuffer;
|
|
1172
|
+
const appendItem = (jsonObject) => {
|
|
1173
|
+
items.push(jsonObject);
|
|
1174
|
+
store?.setData([...items]);
|
|
1175
|
+
};
|
|
1176
|
+
try {
|
|
1177
|
+
while (true) {
|
|
1178
|
+
buffer = parseNdjsonBuffer(buffer, {
|
|
1179
|
+
parseErrorStatus: 400,
|
|
1180
|
+
onItem: appendItem
|
|
1181
|
+
}).buffer;
|
|
1182
|
+
if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1183
|
+
const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
|
|
1184
|
+
if (done) {
|
|
1185
|
+
parseNdjsonBuffer(buffer, {
|
|
1186
|
+
flush: true,
|
|
1187
|
+
parseErrorStatus: 400,
|
|
1188
|
+
onItem: appendItem
|
|
1189
|
+
});
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1193
|
+
}
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
if (error instanceof FragnoClientFetchError || error instanceof FragnoClientUnknownApiError) {
|
|
1196
|
+
store?.setError(error);
|
|
1197
|
+
throw error;
|
|
1198
|
+
}
|
|
1199
|
+
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
|
|
1200
|
+
store?.setError(clientError);
|
|
1201
|
+
throw clientError;
|
|
1202
|
+
} finally {
|
|
1203
|
+
reader.releaseLock();
|
|
1204
|
+
}
|
|
1205
|
+
return items;
|
|
1206
|
+
}
|
|
1207
|
+
//#endregion
|
|
1208
|
+
//#region ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
|
|
1209
|
+
let createNanoEvents = () => ({
|
|
1210
|
+
emit(event, ...args) {
|
|
1211
|
+
for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) callbacks[i](...args);
|
|
1212
|
+
},
|
|
1213
|
+
events: {},
|
|
1214
|
+
on(event, cb) {
|
|
1215
|
+
(this.events[event] ||= []).push(cb);
|
|
1216
|
+
return () => {
|
|
1217
|
+
this.events[event] = this.events[event]?.filter((i) => cb !== i);
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
//#endregion
|
|
1222
|
+
//#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.3.0/node_modules/@nanostores/query/dist/nanoquery.js
|
|
1223
|
+
function defaultOnErrorRetry({ retryCount }) {
|
|
1224
|
+
return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
|
|
1225
|
+
}
|
|
1226
|
+
const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectChangeSubscribe]) => {
|
|
1227
|
+
const nanoquery = ({ cache = /* @__PURE__ */ new Map(), fetcher: globalFetcher, ...globalSettings } = {}) => {
|
|
1228
|
+
const events = createNanoEvents();
|
|
1229
|
+
let focus = true;
|
|
1230
|
+
visibilityChangeSubscribe(() => {
|
|
1231
|
+
focus = isAppVisible();
|
|
1232
|
+
focus && events.emit(FOCUS);
|
|
1233
|
+
});
|
|
1234
|
+
reconnectChangeSubscribe(() => events.emit(RECONNECT));
|
|
1235
|
+
const _revalidateOnInterval = /* @__PURE__ */ new Map(), _errorInvalidateTimeouts = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Map();
|
|
1236
|
+
let rewrittenSettings = {};
|
|
1237
|
+
const getCachedValueByKey = (key) => {
|
|
1238
|
+
const fromCache = cache.get(key);
|
|
1239
|
+
if (!fromCache) return [];
|
|
1240
|
+
return (fromCache.expires || 0) > getNow() ? [fromCache.data, fromCache.error] : [];
|
|
1241
|
+
};
|
|
1242
|
+
const runFetcher = async ([key, keyParts], store, settings) => {
|
|
1243
|
+
if (!focus) return;
|
|
1244
|
+
const set = (v) => {
|
|
1245
|
+
if (store.key === key) {
|
|
1246
|
+
store.set(v);
|
|
1247
|
+
events.emit(SET_CACHE, key, v, true);
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
const setAsLoading = (prev) => {
|
|
1251
|
+
set({
|
|
1252
|
+
...prev === void 0 ? {} : { data: prev },
|
|
1253
|
+
...loading,
|
|
1254
|
+
promise: _runningFetches.get(key)
|
|
1255
|
+
});
|
|
1256
|
+
};
|
|
1257
|
+
let { dedupeTime = 4e3, cacheLifetime = Infinity, fetcher, onErrorRetry = defaultOnErrorRetry } = {
|
|
1258
|
+
...settings,
|
|
1259
|
+
...rewrittenSettings
|
|
1260
|
+
};
|
|
1261
|
+
if (cacheLifetime < dedupeTime) cacheLifetime = dedupeTime;
|
|
1262
|
+
const now = getNow();
|
|
1263
|
+
if (_runningFetches.has(key)) {
|
|
1264
|
+
if (!store.value.loading) setAsLoading(getCachedValueByKey(key)[0]);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
let cachedValue, cachedError;
|
|
1268
|
+
const fromCache = cache.get(key);
|
|
1269
|
+
if (fromCache?.data !== void 0 || fromCache?.error) {
|
|
1270
|
+
[cachedValue, cachedError] = getCachedValueByKey(key);
|
|
1271
|
+
if ((fromCache.created || 0) + dedupeTime > now) {
|
|
1272
|
+
if (store.value.data != cachedValue || store.value.error != cachedError) set({
|
|
1273
|
+
...notLoading,
|
|
1274
|
+
data: cachedValue,
|
|
1275
|
+
error: cachedError
|
|
1276
|
+
});
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const finishTask = startTask();
|
|
1281
|
+
try {
|
|
1282
|
+
clearTimeout(_errorInvalidateTimeouts.get(key));
|
|
1283
|
+
const promise = fetcher(...keyParts);
|
|
1284
|
+
_runningFetches.set(key, promise);
|
|
1285
|
+
setAsLoading(cachedValue);
|
|
1286
|
+
const res = await promise;
|
|
1287
|
+
cache.set(key, {
|
|
1288
|
+
data: res,
|
|
1289
|
+
created: getNow(),
|
|
1290
|
+
expires: getNow() + cacheLifetime
|
|
1291
|
+
});
|
|
1292
|
+
set({
|
|
1293
|
+
data: res,
|
|
1294
|
+
...notLoading
|
|
1295
|
+
});
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
settings.onError?.(error);
|
|
1298
|
+
const retryCount = (cache.get(key)?.retryCount || 0) + 1;
|
|
1299
|
+
cache.set(key, {
|
|
1300
|
+
error,
|
|
1301
|
+
created: getNow(),
|
|
1302
|
+
expires: getNow() + cacheLifetime,
|
|
1303
|
+
retryCount
|
|
1304
|
+
});
|
|
1305
|
+
if (onErrorRetry) {
|
|
1306
|
+
const timer = onErrorRetry({
|
|
1307
|
+
error,
|
|
1308
|
+
key,
|
|
1309
|
+
retryCount
|
|
1310
|
+
});
|
|
1311
|
+
if (timer) _errorInvalidateTimeouts.set(key, setTimeout(() => {
|
|
1312
|
+
invalidateKeys(key);
|
|
1313
|
+
cache.set(key, { retryCount });
|
|
1314
|
+
}, timer));
|
|
1315
|
+
}
|
|
1316
|
+
set({
|
|
1317
|
+
data: store.value.data,
|
|
1318
|
+
error,
|
|
1319
|
+
...notLoading
|
|
1320
|
+
});
|
|
1321
|
+
} finally {
|
|
1322
|
+
finishTask();
|
|
1323
|
+
_runningFetches.delete(key);
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
const createFetcherStore = (keyInput, { fetcher = globalFetcher, ...fetcherSettings } = {}) => {
|
|
1327
|
+
if (!fetcher) throw new Error("You need to set up either global fetcher of fetcher in createFetcherStore");
|
|
1328
|
+
const fetcherStore = /* @__PURE__ */ map({ ...notLoading }), settings = {
|
|
1329
|
+
...globalSettings,
|
|
1330
|
+
...fetcherSettings,
|
|
1331
|
+
fetcher
|
|
1332
|
+
};
|
|
1333
|
+
fetcherStore._ = fetcherSymbol;
|
|
1334
|
+
fetcherStore.invalidate = () => {
|
|
1335
|
+
const { key } = fetcherStore;
|
|
1336
|
+
if (key) invalidateKeys(key);
|
|
1337
|
+
};
|
|
1338
|
+
fetcherStore.revalidate = () => {
|
|
1339
|
+
const { key } = fetcherStore;
|
|
1340
|
+
if (key) revalidateKeys(key);
|
|
1341
|
+
};
|
|
1342
|
+
fetcherStore.mutate = (data) => {
|
|
1343
|
+
const { key } = fetcherStore;
|
|
1344
|
+
if (key) mutateCache(key, data);
|
|
1345
|
+
};
|
|
1346
|
+
fetcherStore.fetch = async () => {
|
|
1347
|
+
let resolve;
|
|
1348
|
+
const promise = new Promise((r) => resolve = r);
|
|
1349
|
+
const unsub = fetcherStore.listen(({ error, data }) => {
|
|
1350
|
+
if (error !== void 0) resolve({ error });
|
|
1351
|
+
if (data !== void 0) resolve({ data });
|
|
1352
|
+
});
|
|
1353
|
+
return promise.finally(unsub);
|
|
1354
|
+
};
|
|
1355
|
+
let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore;
|
|
1356
|
+
let evtUnsubs = [];
|
|
1357
|
+
onStart(fetcherStore, () => {
|
|
1358
|
+
const firstRun = !keysInternalUnsub;
|
|
1359
|
+
[keyStore, keysInternalUnsub] = getKeyStore(keyInput);
|
|
1360
|
+
keyUnsub = keyStore.subscribe((currentKeys) => {
|
|
1361
|
+
if (currentKeys) {
|
|
1362
|
+
const [newKey, keyParts] = currentKeys;
|
|
1363
|
+
fetcherStore.key = newKey;
|
|
1364
|
+
runFetcher([newKey, keyParts], fetcherStore, settings);
|
|
1365
|
+
prevKey = newKey;
|
|
1366
|
+
prevKeyParts = keyParts;
|
|
1367
|
+
} else {
|
|
1368
|
+
fetcherStore.key = prevKey = prevKeyParts = void 0;
|
|
1369
|
+
fetcherStore.set({ ...notLoading });
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
const currentKeyValue = keyStore.get();
|
|
1373
|
+
if (currentKeyValue) {
|
|
1374
|
+
[prevKey, prevKeyParts] = currentKeyValue;
|
|
1375
|
+
if (firstRun) handleNewListener();
|
|
1376
|
+
}
|
|
1377
|
+
const { revalidateInterval = 0, revalidateOnFocus, revalidateOnReconnect } = settings;
|
|
1378
|
+
const runRefetcher = () => {
|
|
1379
|
+
if (prevKey) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
|
|
1380
|
+
};
|
|
1381
|
+
if (revalidateInterval > 0) _revalidateOnInterval.set(keyInput, setInterval(runRefetcher, revalidateInterval));
|
|
1382
|
+
if (revalidateOnFocus) evtUnsubs.push(events.on(FOCUS, runRefetcher));
|
|
1383
|
+
if (revalidateOnReconnect) evtUnsubs.push(events.on(RECONNECT, runRefetcher));
|
|
1384
|
+
const cacheKeyChangeHandler = (keySelector) => {
|
|
1385
|
+
if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
|
|
1386
|
+
};
|
|
1387
|
+
evtUnsubs.push(events.on(INVALIDATE_KEYS, cacheKeyChangeHandler), events.on(REVALIDATE_KEYS, cacheKeyChangeHandler), events.on(SET_CACHE, (keySelector, data, full) => {
|
|
1388
|
+
if (prevKey && testKeyAgainstSelector(prevKey, keySelector) && fetcherStore.value !== data && fetcherStore.value.data !== data) fetcherStore.set(full ? data : {
|
|
1389
|
+
data,
|
|
1390
|
+
...notLoading
|
|
1391
|
+
});
|
|
1392
|
+
}));
|
|
1393
|
+
});
|
|
1394
|
+
const handleNewListener = () => {
|
|
1395
|
+
if (prevKey && prevKeyParts) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
|
|
1396
|
+
};
|
|
1397
|
+
const originListen = fetcherStore.listen;
|
|
1398
|
+
fetcherStore.listen = (listener) => {
|
|
1399
|
+
const unsub = originListen(listener);
|
|
1400
|
+
listener(fetcherStore.value);
|
|
1401
|
+
handleNewListener();
|
|
1402
|
+
return unsub;
|
|
1403
|
+
};
|
|
1404
|
+
onStop(fetcherStore, () => {
|
|
1405
|
+
fetcherStore.value = { ...notLoading };
|
|
1406
|
+
keysInternalUnsub?.();
|
|
1407
|
+
evtUnsubs.forEach((fn) => fn());
|
|
1408
|
+
evtUnsubs = [];
|
|
1409
|
+
keyUnsub?.();
|
|
1410
|
+
clearInterval(_revalidateOnInterval.get(keyInput));
|
|
1411
|
+
});
|
|
1412
|
+
return fetcherStore;
|
|
1413
|
+
};
|
|
1414
|
+
const iterOverCache = (keySelector, cb) => {
|
|
1415
|
+
for (const key of cache.keys()) if (testKeyAgainstSelector(key, keySelector)) cb(key);
|
|
1416
|
+
};
|
|
1417
|
+
const invalidateKeys = (keySelector) => {
|
|
1418
|
+
iterOverCache(keySelector, (key) => {
|
|
1419
|
+
cache.delete(key);
|
|
1420
|
+
});
|
|
1421
|
+
events.emit(INVALIDATE_KEYS, keySelector);
|
|
1422
|
+
};
|
|
1423
|
+
const revalidateKeys = (keySelector) => {
|
|
1424
|
+
iterOverCache(keySelector, (key) => {
|
|
1425
|
+
const cached = cache.get(key);
|
|
1426
|
+
if (cached) cache.set(key, {
|
|
1427
|
+
...cached,
|
|
1428
|
+
created: -Infinity
|
|
1429
|
+
});
|
|
1430
|
+
});
|
|
1431
|
+
events.emit(REVALIDATE_KEYS, keySelector);
|
|
1432
|
+
};
|
|
1433
|
+
const mutateCache = (keySelector, data) => {
|
|
1434
|
+
iterOverCache(keySelector, (key) => {
|
|
1435
|
+
if (data === void 0) cache.delete(key);
|
|
1436
|
+
else cache.set(key, {
|
|
1437
|
+
data,
|
|
1438
|
+
created: getNow(),
|
|
1439
|
+
expires: getNow() + (globalSettings.cacheLifetime ?? 8e3)
|
|
1440
|
+
});
|
|
1441
|
+
});
|
|
1442
|
+
events.emit(SET_CACHE, keySelector, data);
|
|
1443
|
+
};
|
|
1444
|
+
function createMutatorStore(mutator, opts) {
|
|
1445
|
+
const { throttleCalls, onError } = opts ?? {
|
|
1446
|
+
throttleCalls: true,
|
|
1447
|
+
onError: globalSettings?.onError
|
|
1448
|
+
};
|
|
1449
|
+
const mutate = async (data) => {
|
|
1450
|
+
if (throttleCalls && store.value?.loading) return;
|
|
1451
|
+
const newMutator = rewrittenSettings.fetcher ?? mutator;
|
|
1452
|
+
const keysToInvalidate = [], keysToRevalidate = [];
|
|
1453
|
+
const safeKeySet = (k, v) => {
|
|
1454
|
+
if (store.lc) store.setKey(k, v);
|
|
1455
|
+
};
|
|
1456
|
+
try {
|
|
1457
|
+
store.set({
|
|
1458
|
+
error: void 0,
|
|
1459
|
+
data: void 0,
|
|
1460
|
+
mutate,
|
|
1461
|
+
...loading
|
|
1462
|
+
});
|
|
1463
|
+
const result = await newMutator({
|
|
1464
|
+
data,
|
|
1465
|
+
invalidate: (key) => {
|
|
1466
|
+
keysToInvalidate.push(key);
|
|
1467
|
+
},
|
|
1468
|
+
revalidate: (key) => {
|
|
1469
|
+
keysToRevalidate.push(key);
|
|
1470
|
+
},
|
|
1471
|
+
getCacheUpdater: (key, shouldRevalidate = true) => [(newVal) => {
|
|
1472
|
+
mutateCache(key, newVal);
|
|
1473
|
+
if (shouldRevalidate) keysToRevalidate.push(key);
|
|
1474
|
+
}, cache.get(key)?.data]
|
|
1475
|
+
});
|
|
1476
|
+
safeKeySet("data", result);
|
|
1477
|
+
return result;
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
onError?.(error);
|
|
1480
|
+
safeKeySet("error", error);
|
|
1481
|
+
store.setKey("error", error);
|
|
1482
|
+
} finally {
|
|
1483
|
+
safeKeySet("loading", false);
|
|
1484
|
+
keysToInvalidate.forEach(invalidateKeys);
|
|
1485
|
+
keysToRevalidate.forEach(revalidateKeys);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
const store = /* @__PURE__ */ map({
|
|
1489
|
+
mutate,
|
|
1490
|
+
...notLoading
|
|
1491
|
+
});
|
|
1492
|
+
onStop(store, () => store.set({
|
|
1493
|
+
mutate,
|
|
1494
|
+
...notLoading
|
|
1495
|
+
}));
|
|
1496
|
+
store.mutate = mutate;
|
|
1497
|
+
return store;
|
|
1498
|
+
}
|
|
1499
|
+
const __unsafeOverruleSettings = (data) => {
|
|
1500
|
+
console.warn(`You should only use __unsafeOverruleSettings in test environment`);
|
|
1501
|
+
rewrittenSettings = data;
|
|
1502
|
+
};
|
|
1503
|
+
return [
|
|
1504
|
+
createFetcherStore,
|
|
1505
|
+
createMutatorStore,
|
|
1506
|
+
{
|
|
1507
|
+
__unsafeOverruleSettings,
|
|
1508
|
+
invalidateKeys,
|
|
1509
|
+
revalidateKeys,
|
|
1510
|
+
mutateCache
|
|
1511
|
+
}
|
|
1512
|
+
];
|
|
1513
|
+
};
|
|
1514
|
+
function isSomeKey(key) {
|
|
1515
|
+
return typeof key === "string" || typeof key === "number" || key === true;
|
|
1516
|
+
}
|
|
1517
|
+
const getKeyStore = (keys) => {
|
|
1518
|
+
if (isSomeKey(keys)) return [/* @__PURE__ */ atom(["" + keys, [keys]]), () => {}];
|
|
1519
|
+
const keyParts = [];
|
|
1520
|
+
const $key = /* @__PURE__ */ atom(null);
|
|
1521
|
+
const keysAsStoresToIndexes = /* @__PURE__ */ new Map();
|
|
1522
|
+
const setKeyStoreValue = () => {
|
|
1523
|
+
if (keyParts.some((v) => v === null || v === void 0 || v === false)) $key.set(null);
|
|
1524
|
+
else $key.set([keyParts.join(""), keyParts]);
|
|
1525
|
+
};
|
|
1526
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1527
|
+
const keyOrStore = keys[i];
|
|
1528
|
+
if (isSomeKey(keyOrStore)) keyParts.push(keyOrStore);
|
|
1529
|
+
else {
|
|
1530
|
+
keyParts.push(null);
|
|
1531
|
+
keysAsStoresToIndexes.set(keyOrStore, i);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
const storesAsArray = [...keysAsStoresToIndexes.keys()];
|
|
1535
|
+
const $storeKeys = /* @__PURE__ */ batched(storesAsArray, (...storeValues) => {
|
|
1536
|
+
for (let i = 0; i < storeValues.length; i++) {
|
|
1537
|
+
const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store);
|
|
1538
|
+
keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i];
|
|
1539
|
+
}
|
|
1540
|
+
setKeyStoreValue();
|
|
1541
|
+
});
|
|
1542
|
+
setKeyStoreValue();
|
|
1543
|
+
return [$key, $storeKeys.subscribe(noop)];
|
|
1544
|
+
};
|
|
1545
|
+
function noop() {}
|
|
1546
|
+
const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, REVALIDATE_KEYS = 4, SET_CACHE = 5;
|
|
1547
|
+
const testKeyAgainstSelector = (key, selector) => {
|
|
1548
|
+
if (Array.isArray(selector)) return selector.includes(key);
|
|
1549
|
+
else if (typeof selector === "function") return selector(key);
|
|
1550
|
+
else return key === selector;
|
|
1551
|
+
};
|
|
1552
|
+
const getNow = () => (/* @__PURE__ */ new Date()).getTime();
|
|
1553
|
+
const fetcherSymbol = Symbol();
|
|
1554
|
+
const loading = { loading: true }, notLoading = { loading: false };
|
|
1555
|
+
return nanoquery;
|
|
1556
|
+
};
|
|
1557
|
+
const subscribe = (name, fn) => {
|
|
1558
|
+
if (!(typeof window === "undefined")) addEventListener(name, fn);
|
|
1559
|
+
};
|
|
1560
|
+
const nanoquery = nanoqueryFactory([
|
|
1561
|
+
() => !document.hidden,
|
|
1562
|
+
(cb) => subscribe("visibilitychange", cb),
|
|
1563
|
+
(cb) => subscribe("online", cb)
|
|
1564
|
+
]);
|
|
1565
|
+
//#endregion
|
|
1566
|
+
//#region ../fragno/dist/client/client.js
|
|
1567
|
+
/**
|
|
1568
|
+
* Symbols used to identify hook types
|
|
1569
|
+
*/
|
|
1570
|
+
const GET_HOOK_SYMBOL = Symbol("fragno-get-hook");
|
|
1571
|
+
const MUTATOR_HOOK_SYMBOL = Symbol("fragno-mutator-hook");
|
|
1572
|
+
const STORE_SYMBOL = Symbol("fragno-store");
|
|
1573
|
+
async function schemaAllowsUndefined(schema) {
|
|
1574
|
+
try {
|
|
1575
|
+
return !(await schema["~standard"].validate(void 0)).issues;
|
|
1576
|
+
} catch {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
async function assertBodyProvided(body, inputSchema, errorMessage) {
|
|
1581
|
+
if (typeof body !== "undefined" || inputSchema === void 0) return;
|
|
1582
|
+
if (await schemaAllowsUndefined(inputSchema)) return;
|
|
1583
|
+
throw new Error(errorMessage);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Merge request headers from multiple sources.
|
|
1587
|
+
* Returns undefined if there are no headers to merge.
|
|
1588
|
+
* @internal
|
|
1589
|
+
*/
|
|
1590
|
+
function mergeRequestHeaders(...headerSources) {
|
|
1591
|
+
const result = {};
|
|
1592
|
+
let hasHeaders = false;
|
|
1593
|
+
for (const source of headerSources) {
|
|
1594
|
+
if (!source) continue;
|
|
1595
|
+
if (source instanceof Headers) for (const [key, value] of source.entries()) {
|
|
1596
|
+
result[key] = value;
|
|
1597
|
+
hasHeaders = true;
|
|
1598
|
+
}
|
|
1599
|
+
else if (Array.isArray(source)) for (const [key, value] of source) {
|
|
1600
|
+
result[key] = value;
|
|
1601
|
+
hasHeaders = true;
|
|
1602
|
+
}
|
|
1603
|
+
else for (const [key, value] of Object.entries(source)) {
|
|
1604
|
+
result[key] = value;
|
|
1605
|
+
hasHeaders = true;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return hasHeaders ? result : void 0;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* @internal
|
|
1612
|
+
*/
|
|
1613
|
+
function buildUrl(config, params) {
|
|
1614
|
+
const { baseUrl = "", mountRoute, path } = config;
|
|
1615
|
+
const { pathParams, queryParams } = params ?? {};
|
|
1616
|
+
const normalizedPathParams = unwrapObject(pathParams);
|
|
1617
|
+
const normalizedQueryParams = unwrapObject(queryParams) ?? {};
|
|
1618
|
+
const filteredQueryParams = Object.fromEntries(Object.entries(normalizedQueryParams).filter(([_, value]) => value !== void 0));
|
|
1619
|
+
const searchParams = new URLSearchParams(filteredQueryParams);
|
|
1620
|
+
return `${baseUrl}${mountRoute}${buildPath(path, normalizedPathParams ?? {})}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* This method returns an array, which can be passed directly to nanostores.
|
|
1624
|
+
*
|
|
1625
|
+
* The returned array is always: path, pathParams (In order they appear in the path), queryParams (In alphabetical order)
|
|
1626
|
+
* Missing pathParams are replaced with "<missing>".
|
|
1627
|
+
* Atoms with undefined values are wrapped in computed atoms that map undefined to "" to avoid nanoquery treating the key as incomplete.
|
|
1628
|
+
* @param path
|
|
1629
|
+
* @param params
|
|
1630
|
+
* @returns
|
|
1631
|
+
* @internal
|
|
1632
|
+
*/
|
|
1633
|
+
function getCacheKey(method, path, params) {
|
|
1634
|
+
if (!params) return [method, path];
|
|
1635
|
+
const { pathParams, queryParams } = params;
|
|
1636
|
+
const pathParamValues = extractPathParams(path).map((name) => pathParams?.[name] ?? "<missing>");
|
|
1637
|
+
const queryParamValues = queryParams ? Object.keys(queryParams).sort().map((key) => {
|
|
1638
|
+
const value = queryParams[key];
|
|
1639
|
+
if (value && typeof value === "object" && "get" in value) return /* @__PURE__ */ computed(value, (v) => v ?? "");
|
|
1640
|
+
return value ?? "";
|
|
1641
|
+
}) : [];
|
|
1642
|
+
return [
|
|
1643
|
+
method,
|
|
1644
|
+
path,
|
|
1645
|
+
...pathParamValues,
|
|
1646
|
+
...queryParamValues
|
|
1647
|
+
];
|
|
1648
|
+
}
|
|
1649
|
+
function isStreamingResponse(response) {
|
|
1650
|
+
const contentType = parseContentType(response.headers.get("content-type"));
|
|
1651
|
+
if (!contentType) return false;
|
|
1652
|
+
if (!(response.headers.get("transfer-encoding") === "chunked")) return false;
|
|
1653
|
+
if (contentType.subtype === "octet-stream") return "octet-stream";
|
|
1654
|
+
if (contentType.subtype === "x-ndjson") return "ndjson";
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* @internal
|
|
1659
|
+
*/
|
|
1660
|
+
function isGetHook(hook) {
|
|
1661
|
+
return typeof hook === "object" && hook !== null && GET_HOOK_SYMBOL in hook && hook[GET_HOOK_SYMBOL] === true;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* @internal
|
|
1665
|
+
*/
|
|
1666
|
+
function isMutatorHook(hook) {
|
|
1667
|
+
return typeof hook === "object" && hook !== null && MUTATOR_HOOK_SYMBOL in hook && hook[MUTATOR_HOOK_SYMBOL] === true;
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* @internal
|
|
1671
|
+
*/
|
|
1672
|
+
function isStore(obj) {
|
|
1673
|
+
return typeof obj === "object" && obj !== null && STORE_SYMBOL in obj && obj[STORE_SYMBOL] === true;
|
|
1674
|
+
}
|
|
1675
|
+
var ClientBuilder = class {
|
|
1676
|
+
#publicConfig;
|
|
1677
|
+
#fragmentConfig;
|
|
1678
|
+
#fetcherConfig;
|
|
1679
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1680
|
+
#createFetcherStore;
|
|
1681
|
+
#createMutatorStore;
|
|
1682
|
+
#invalidateKeys;
|
|
1683
|
+
constructor(publicConfig, fragmentConfig) {
|
|
1684
|
+
this.#publicConfig = publicConfig;
|
|
1685
|
+
this.#fragmentConfig = fragmentConfig;
|
|
1686
|
+
this.#fetcherConfig = publicConfig.fetcherConfig;
|
|
1687
|
+
const [createFetcherStore, createMutatorStore, { invalidateKeys }] = nanoquery({
|
|
1688
|
+
cache: this.#cache,
|
|
1689
|
+
cacheLifetime: Infinity
|
|
1690
|
+
});
|
|
1691
|
+
this.#createFetcherStore = createFetcherStore;
|
|
1692
|
+
this.#createMutatorStore = createMutatorStore;
|
|
1693
|
+
this.#invalidateKeys = invalidateKeys;
|
|
1694
|
+
}
|
|
1695
|
+
get cacheEntries() {
|
|
1696
|
+
return Object.fromEntries(this.#cache.entries());
|
|
1697
|
+
}
|
|
1698
|
+
createStore(input) {
|
|
1699
|
+
if (typeof input === "function") return {
|
|
1700
|
+
factory: input,
|
|
1701
|
+
[STORE_SYMBOL]: true
|
|
1702
|
+
};
|
|
1703
|
+
return {
|
|
1704
|
+
obj: input,
|
|
1705
|
+
[STORE_SYMBOL]: true
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Build a URL for a custom backend call using the configured baseUrl and mountRoute.
|
|
1710
|
+
* Useful for fragment authors who need to make custom fetch calls.
|
|
1711
|
+
*/
|
|
1712
|
+
buildUrl(path, params) {
|
|
1713
|
+
return buildUrl({
|
|
1714
|
+
baseUrl: this.#publicConfig.baseUrl ?? "",
|
|
1715
|
+
mountRoute: getMountRoute({
|
|
1716
|
+
name: this.#fragmentConfig.name,
|
|
1717
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1718
|
+
}),
|
|
1719
|
+
path
|
|
1720
|
+
}, {
|
|
1721
|
+
pathParams: params?.path,
|
|
1722
|
+
queryParams: params?.query
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Get the configured fetcher function for custom backend calls.
|
|
1727
|
+
* Returns fetch with merged options applied.
|
|
1728
|
+
*/
|
|
1729
|
+
getFetcher() {
|
|
1730
|
+
return {
|
|
1731
|
+
fetcher: this.#getFetcher(),
|
|
1732
|
+
defaultOptions: this.#getFetcherOptions()
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
#getFetcher() {
|
|
1736
|
+
if (this.#fetcherConfig?.type === "function") return this.#fetcherConfig.fetcher;
|
|
1737
|
+
return globalThis.fetch.bind(globalThis);
|
|
1738
|
+
}
|
|
1739
|
+
#getFetcherOptions() {
|
|
1740
|
+
if (this.#fetcherConfig?.type === "options") return this.#fetcherConfig.options;
|
|
1741
|
+
}
|
|
1742
|
+
createHook(path, options) {
|
|
1743
|
+
const route = this.#fragmentConfig.routes.find((r) => r.path === path && r.method === "GET" && r.outputSchema !== void 0);
|
|
1744
|
+
if (!route) throw new Error(`Route '${path}' not found or is not a GET route with an output schema.`);
|
|
1745
|
+
return this.#createRouteQueryHook(route, options);
|
|
1746
|
+
}
|
|
1747
|
+
createMutator(method, path, onInvalidate) {
|
|
1748
|
+
const route = this.#fragmentConfig.routes.find((r) => r.method !== "GET" && r.path === path && r.method === method);
|
|
1749
|
+
if (!route) throw new Error(`Route '${path}' not found or is a GET route with an input and output schema.`);
|
|
1750
|
+
return this.#createRouteQueryMutator(route, onInvalidate);
|
|
1751
|
+
}
|
|
1752
|
+
#createRouteQueryHook(route, options = {}) {
|
|
1753
|
+
if (route.method !== "GET") throw new Error(`Only GET routes are supported for hooks. Route '${route.path}' is a ${String(route.method)} route.`);
|
|
1754
|
+
if (!route.outputSchema) throw new Error(`Output schema is required for GET routes. Route '${route.path}' has no output schema.`);
|
|
1755
|
+
const baseUrl = this.#publicConfig.baseUrl ?? "";
|
|
1756
|
+
const mountRoute = getMountRoute({
|
|
1757
|
+
name: this.#fragmentConfig.name,
|
|
1758
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1759
|
+
});
|
|
1760
|
+
const fetcher = this.#getFetcher();
|
|
1761
|
+
const fetcherOptions = this.#getFetcherOptions();
|
|
1762
|
+
const useFetcherOnServer = this.#fetcherConfig?.type === "function" && this.#fetcherConfig.useOnServer;
|
|
1763
|
+
async function callServerSideHandler(params) {
|
|
1764
|
+
const { pathParams, queryParams } = params ?? {};
|
|
1765
|
+
const normalizedPathParams = unwrapObject(pathParams);
|
|
1766
|
+
const normalizedQueryParams = unwrapObject(queryParams) ?? {};
|
|
1767
|
+
const filteredQueryParams = Object.fromEntries(Object.entries(normalizedQueryParams).filter(([_, value]) => value !== void 0));
|
|
1768
|
+
const searchParams = new URLSearchParams(filteredQueryParams);
|
|
1769
|
+
return await route.handler(RequestInputContext.fromSSRContext({
|
|
1770
|
+
method: route.method,
|
|
1771
|
+
path: route.path,
|
|
1772
|
+
pathParams: normalizedPathParams,
|
|
1773
|
+
searchParams
|
|
1774
|
+
}), new RequestOutputContext(route.outputSchema));
|
|
1775
|
+
}
|
|
1776
|
+
async function executeQuery(params) {
|
|
1777
|
+
const { pathParams, queryParams } = params ?? {};
|
|
1778
|
+
if (typeof window === "undefined" && !useFetcherOnServer) return task(async () => callServerSideHandler({
|
|
1779
|
+
pathParams,
|
|
1780
|
+
queryParams
|
|
1781
|
+
}));
|
|
1782
|
+
const url = buildUrl({
|
|
1783
|
+
baseUrl,
|
|
1784
|
+
mountRoute,
|
|
1785
|
+
path: route.path
|
|
1786
|
+
}, {
|
|
1787
|
+
pathParams,
|
|
1788
|
+
queryParams
|
|
1789
|
+
});
|
|
1790
|
+
let response;
|
|
1791
|
+
try {
|
|
1792
|
+
response = fetcherOptions ? await fetcher(url, fetcherOptions) : await fetcher(url);
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
throw FragnoClientFetchError.fromUnknownFetchError(error);
|
|
1795
|
+
}
|
|
1796
|
+
if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
|
|
1797
|
+
return response;
|
|
1798
|
+
}
|
|
1799
|
+
return {
|
|
1800
|
+
route,
|
|
1801
|
+
store: (args) => {
|
|
1802
|
+
const { path, query } = args ?? {};
|
|
1803
|
+
const key = getCacheKey(route.method, route.path, {
|
|
1804
|
+
pathParams: path,
|
|
1805
|
+
queryParams: query
|
|
1806
|
+
});
|
|
1807
|
+
const store = this.#createFetcherStore(key, {
|
|
1808
|
+
fetcher: async () => {
|
|
1809
|
+
const response = await executeQuery({
|
|
1810
|
+
pathParams: path,
|
|
1811
|
+
queryParams: query
|
|
1812
|
+
});
|
|
1813
|
+
const isStreaming = isStreamingResponse(response);
|
|
1814
|
+
if (!isStreaming) return response.json();
|
|
1815
|
+
if (typeof window === "undefined" && !useFetcherOnServer) {
|
|
1816
|
+
if (response.body) await response.body.getReader().cancel();
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
if (isStreaming === "ndjson") {
|
|
1820
|
+
const { items } = await handleNdjsonStreamingFirstItem(response, {
|
|
1821
|
+
setData: (value) => {
|
|
1822
|
+
store.mutate(value);
|
|
1823
|
+
},
|
|
1824
|
+
setError: (value) => {
|
|
1825
|
+
store.set({
|
|
1826
|
+
...store.get(),
|
|
1827
|
+
error: value
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
return items;
|
|
1832
|
+
}
|
|
1833
|
+
if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
|
|
1834
|
+
throw new Error("Unreachable");
|
|
1835
|
+
},
|
|
1836
|
+
onErrorRetry: options?.onErrorRetry,
|
|
1837
|
+
dedupeTime: Infinity
|
|
1838
|
+
});
|
|
1839
|
+
if (typeof window === "undefined") addStore(store);
|
|
1840
|
+
return store;
|
|
1841
|
+
},
|
|
1842
|
+
query: async (args) => {
|
|
1843
|
+
const { path, query } = args ?? {};
|
|
1844
|
+
const response = await executeQuery({
|
|
1845
|
+
pathParams: path,
|
|
1846
|
+
queryParams: query
|
|
1847
|
+
});
|
|
1848
|
+
const isStreaming = isStreamingResponse(response);
|
|
1849
|
+
if (!isStreaming) return await response.json();
|
|
1850
|
+
if (isStreaming === "ndjson") {
|
|
1851
|
+
const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
|
|
1852
|
+
return await streamingPromise;
|
|
1853
|
+
}
|
|
1854
|
+
if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
|
|
1855
|
+
throw new Error("Unreachable");
|
|
1856
|
+
},
|
|
1857
|
+
[GET_HOOK_SYMBOL]: true
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
#createRouteQueryMutator(route, onInvalidate = (invalidate, params) => {
|
|
1861
|
+
invalidate("GET", route.path, params);
|
|
1862
|
+
}) {
|
|
1863
|
+
const method = route.method;
|
|
1864
|
+
const baseUrl = this.#publicConfig.baseUrl ?? "";
|
|
1865
|
+
const mountRoute = getMountRoute({
|
|
1866
|
+
name: this.#fragmentConfig.name,
|
|
1867
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1868
|
+
});
|
|
1869
|
+
const fetcher = this.#getFetcher();
|
|
1870
|
+
const fetcherOptions = this.#getFetcherOptions();
|
|
1871
|
+
const useFetcherOnServer = this.#fetcherConfig?.type === "function" && this.#fetcherConfig.useOnServer;
|
|
1872
|
+
async function executeMutateQuery({ body, path, query }) {
|
|
1873
|
+
if (typeof window === "undefined" && !useFetcherOnServer) return task(async () => route.handler(RequestInputContext.fromSSRContext({
|
|
1874
|
+
inputSchema: route.inputSchema,
|
|
1875
|
+
method,
|
|
1876
|
+
path: route.path,
|
|
1877
|
+
pathParams: path ?? {},
|
|
1878
|
+
searchParams: new URLSearchParams(query),
|
|
1879
|
+
body
|
|
1880
|
+
}), new RequestOutputContext(route.outputSchema)));
|
|
1881
|
+
const url = buildUrl({
|
|
1882
|
+
baseUrl,
|
|
1883
|
+
mountRoute,
|
|
1884
|
+
path: route.path
|
|
1885
|
+
}, {
|
|
1886
|
+
pathParams: path,
|
|
1887
|
+
queryParams: query
|
|
1888
|
+
});
|
|
1889
|
+
let response;
|
|
1890
|
+
try {
|
|
1891
|
+
const preparedBody = prepareClientRequestBody(body, route.contentType);
|
|
1892
|
+
const bodyHeaders = preparedBody.contentType === void 0 || preparedBody.contentType === null ? void 0 : { "Content-Type": preparedBody.contentType };
|
|
1893
|
+
const headers = mergeRequestHeaders(fetcherOptions?.headers, bodyHeaders);
|
|
1894
|
+
const requestOptions = createRequestInitWithBody(method, headers ?? {}, preparedBody.body, fetcherOptions);
|
|
1895
|
+
response = await fetcher(url, requestOptions);
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
throw FragnoClientFetchError.fromUnknownFetchError(error);
|
|
1898
|
+
}
|
|
1899
|
+
if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
|
|
1900
|
+
return response;
|
|
1901
|
+
}
|
|
1902
|
+
const mutatorStore = this.#createMutatorStore(async ({ data }) => {
|
|
1903
|
+
if (typeof window === "undefined") {}
|
|
1904
|
+
const { body, path, query } = data;
|
|
1905
|
+
await assertBodyProvided(body, route.inputSchema, "Body is required.");
|
|
1906
|
+
const response = await executeMutateQuery({
|
|
1907
|
+
body,
|
|
1908
|
+
path,
|
|
1909
|
+
query
|
|
1910
|
+
});
|
|
1911
|
+
onInvalidate(this.#invalidate.bind(this), {
|
|
1912
|
+
pathParams: path ?? {},
|
|
1913
|
+
queryParams: query
|
|
1914
|
+
});
|
|
1915
|
+
if (response.status === 201 || response.status === 204) return;
|
|
1916
|
+
const isStreaming = isStreamingResponse(response);
|
|
1917
|
+
if (!isStreaming) return await response.json();
|
|
1918
|
+
if (typeof window === "undefined") return [];
|
|
1919
|
+
if (isStreaming === "ndjson") {
|
|
1920
|
+
const { firstItem } = await handleNdjsonStreamingFirstItem(response, {
|
|
1921
|
+
setData: (value) => {
|
|
1922
|
+
mutatorStore.set({
|
|
1923
|
+
...mutatorStore.get(),
|
|
1924
|
+
loading: !(Array.isArray(value) && value.length > 0),
|
|
1925
|
+
data: value
|
|
1926
|
+
});
|
|
1927
|
+
},
|
|
1928
|
+
setError: (value) => {
|
|
1929
|
+
mutatorStore.set({
|
|
1930
|
+
...mutatorStore.get(),
|
|
1931
|
+
error: value
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
return [firstItem];
|
|
1936
|
+
}
|
|
1937
|
+
if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
|
|
1938
|
+
throw new Error("Unreachable");
|
|
1939
|
+
}, { onError: (error) => {
|
|
1940
|
+
console.error("Error in mutatorStore", error);
|
|
1941
|
+
} });
|
|
1942
|
+
const mutateQuery = async (data) => {
|
|
1943
|
+
const { body, path, query } = data;
|
|
1944
|
+
await assertBodyProvided(body, route.inputSchema, "Body is required for mutateQuery");
|
|
1945
|
+
const response = await executeMutateQuery({
|
|
1946
|
+
body,
|
|
1947
|
+
path,
|
|
1948
|
+
query
|
|
1949
|
+
});
|
|
1950
|
+
if (response.status === 201 || response.status === 204) return;
|
|
1951
|
+
const isStreaming = isStreamingResponse(response);
|
|
1952
|
+
if (!isStreaming) return await response.json();
|
|
1953
|
+
if (isStreaming === "ndjson") {
|
|
1954
|
+
const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
|
|
1955
|
+
return await streamingPromise;
|
|
1956
|
+
}
|
|
1957
|
+
if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported for mutations");
|
|
1958
|
+
throw new Error("Unreachable");
|
|
1959
|
+
};
|
|
1960
|
+
return {
|
|
1961
|
+
route,
|
|
1962
|
+
mutateQuery,
|
|
1963
|
+
mutatorStore,
|
|
1964
|
+
[MUTATOR_HOOK_SYMBOL]: true
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
#invalidate(method, path, params) {
|
|
1968
|
+
const prefix = getCacheKey(method, path, {
|
|
1969
|
+
pathParams: params?.pathParams,
|
|
1970
|
+
queryParams: params?.queryParams
|
|
1971
|
+
}).map((k) => typeof k === "string" ? k : k.get()).join("");
|
|
1972
|
+
this.#invalidateKeys((key) => key.startsWith(prefix));
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
/**
|
|
1976
|
+
* Create a client builder for fragments using the new fragment definition API.
|
|
1977
|
+
* This is the same as createClientBuilder but works with FragmentDefinition.
|
|
1978
|
+
*/
|
|
1979
|
+
function createClientBuilder(definition, publicConfig, routesOrFactories, authorFetcherConfig) {
|
|
1980
|
+
const routes = resolveRouteFactories({
|
|
1981
|
+
config: {},
|
|
1982
|
+
deps: {},
|
|
1983
|
+
services: {},
|
|
1984
|
+
serviceDeps: {}
|
|
1985
|
+
}, routesOrFactories);
|
|
1986
|
+
const fragmentConfig = {
|
|
1987
|
+
name: definition.name,
|
|
1988
|
+
routes
|
|
1989
|
+
};
|
|
1990
|
+
const mountRoute = getMountRoute({
|
|
1991
|
+
name: definition.name,
|
|
1992
|
+
mountRoute: publicConfig.mountRoute
|
|
1993
|
+
});
|
|
1994
|
+
const mergedFetcherConfig = mergeFetcherConfigs(authorFetcherConfig, publicConfig.fetcherConfig);
|
|
1995
|
+
return new ClientBuilder({
|
|
1996
|
+
...publicConfig,
|
|
1997
|
+
mountRoute,
|
|
1998
|
+
fetcherConfig: mergedFetcherConfig
|
|
1999
|
+
}, fragmentConfig);
|
|
2000
|
+
}
|
|
2001
|
+
//#endregion
|
|
2002
|
+
//#region src/schema.ts
|
|
2003
|
+
function jsonColumn() {
|
|
2004
|
+
return column("json");
|
|
2005
|
+
}
|
|
2006
|
+
schema("api-fragment", (s) => {
|
|
2007
|
+
return s.addTable("api_connection", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string").nullable()).addColumn("baseUrl", column("string")).addColumn("authMode", column("string")).addColumn("status", column("string")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now()))).addTable("secret", (t) => t.addColumn("id", idColumn()).addColumn("connectionId", referenceColumn({ table: "api_connection" })).addColumn("kind", column("string")).addColumn("payload", column("text")).addColumn("expiresAt", column("timestamp").nullable()).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_secret_connection_kind", ["connectionId", "kind"], { unique: true })).addTable("oauthState", (t) => t.addColumn("id", idColumn()).addColumn("connectionId", referenceColumn({ table: "api_connection" })).addColumn("codeVerifier", column("text")).addColumn("redirectUri", column("string")).addColumn("scope", column("string").nullable()).addColumn("expiresAt", column("timestamp")).addColumn("consumedAt", column("timestamp").nullable()).createIndex("idx_oauth_state_connection", ["connectionId"])).addTable("webhookEndpoint", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string")).addColumn("status", column("string")).addColumn("authConfig", jsonColumn()).addColumn("verification", jsonColumn()).addColumn("deliveryIdentity", jsonColumn()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now()))).addTable("webhookSecret", (t) => t.addColumn("id", idColumn()).addColumn("endpointId", referenceColumn({ table: "webhookEndpoint" })).addColumn("ref", column("string")).addColumn("payload", column("text")).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_webhook_secret_endpoint_ref", ["endpointId", "ref"], { unique: true })).alterTable("oauthState", (t) => t.addColumn("authorizationUrl", column("text").nullable())).alterTable("oauthState", (t) => t.createIndex("idx_oauth_state_pending", [
|
|
2008
|
+
"connectionId",
|
|
2009
|
+
"consumedAt",
|
|
2010
|
+
"expiresAt"
|
|
2011
|
+
])).alterTable("webhookEndpoint", (t) => t.addColumn("lastAcceptedAt", column("timestamp").nullable()).addColumn("lastRejectedAt", column("timestamp").nullable()).addColumn("lastRejectionCode", column("string").nullable()));
|
|
2012
|
+
});
|
|
2013
|
+
//#endregion
|
|
2014
|
+
//#region src/definition.ts
|
|
2015
|
+
/** Expiry changes readiness without a write, so a write that starts the countdown schedules it. */
|
|
2016
|
+
const apiFragmentDefinition = defineFragment("api-fragment").extend((x) => x).providesBaseService(() => {}).build();
|
|
2017
|
+
new TextEncoder();
|
|
2018
|
+
new TextDecoder();
|
|
2019
|
+
//#endregion
|
|
2020
|
+
//#region src/webhooks/auth.ts
|
|
2021
|
+
const secretRefSchema = z.string().trim().min(1);
|
|
2022
|
+
const requestValueNameSchema = z.string().trim().min(1);
|
|
2023
|
+
const webhookAuthConfigSchema = z.discriminatedUnion("type", [
|
|
2024
|
+
z.object({ type: z.literal("none") }),
|
|
2025
|
+
z.object({
|
|
2026
|
+
type: z.literal("bearer"),
|
|
2027
|
+
tokenRef: secretRefSchema
|
|
2028
|
+
}),
|
|
2029
|
+
z.object({
|
|
2030
|
+
type: z.literal("apiKey"),
|
|
2031
|
+
location: z.enum(["header", "query"]),
|
|
2032
|
+
name: requestValueNameSchema,
|
|
2033
|
+
secretRef: secretRefSchema
|
|
2034
|
+
}),
|
|
2035
|
+
z.object({
|
|
2036
|
+
type: z.literal("basic"),
|
|
2037
|
+
usernameRef: secretRefSchema,
|
|
2038
|
+
passwordRef: secretRefSchema
|
|
2039
|
+
}),
|
|
2040
|
+
z.object({
|
|
2041
|
+
type: z.literal("hmac"),
|
|
2042
|
+
secretRef: secretRefSchema,
|
|
2043
|
+
algorithm: z.enum([
|
|
2044
|
+
"sha1",
|
|
2045
|
+
"sha256",
|
|
2046
|
+
"sha512"
|
|
2047
|
+
]),
|
|
2048
|
+
signature: z.object({
|
|
2049
|
+
location: z.enum(["header", "query"]),
|
|
2050
|
+
name: requestValueNameSchema,
|
|
2051
|
+
encoding: z.enum([
|
|
2052
|
+
"hex",
|
|
2053
|
+
"base64",
|
|
2054
|
+
"base64url"
|
|
2055
|
+
]),
|
|
2056
|
+
prefix: z.string().optional()
|
|
2057
|
+
}),
|
|
2058
|
+
signedPayload: z.discriminatedUnion("type", [z.object({ type: z.literal("rawBody") }), z.object({
|
|
2059
|
+
type: z.literal("timestampedBody"),
|
|
2060
|
+
prefix: z.string(),
|
|
2061
|
+
timestampHeader: requestValueNameSchema,
|
|
2062
|
+
delimiter: z.string(),
|
|
2063
|
+
toleranceSeconds: z.number().int().positive()
|
|
2064
|
+
})])
|
|
2065
|
+
})
|
|
2066
|
+
]);
|
|
2067
|
+
//#endregion
|
|
2068
|
+
//#region src/webhooks/verification.ts
|
|
2069
|
+
const webhookRequestValueNameSchema$1 = z.string().trim().min(1);
|
|
2070
|
+
const webhookRequestValueSourceSchema = z.discriminatedUnion("type", [
|
|
2071
|
+
z.object({
|
|
2072
|
+
type: z.literal("header"),
|
|
2073
|
+
name: webhookRequestValueNameSchema$1
|
|
2074
|
+
}),
|
|
2075
|
+
z.object({
|
|
2076
|
+
type: z.literal("query"),
|
|
2077
|
+
name: webhookRequestValueNameSchema$1
|
|
2078
|
+
}),
|
|
2079
|
+
z.object({
|
|
2080
|
+
type: z.literal("jsonBodyPath"),
|
|
2081
|
+
path: z.array(z.string().trim().min(1)).min(1)
|
|
2082
|
+
})
|
|
2083
|
+
]);
|
|
2084
|
+
const webhookVerificationPredicateSchema = z.discriminatedUnion("type", [z.object({
|
|
2085
|
+
type: z.literal("present"),
|
|
2086
|
+
source: webhookRequestValueSourceSchema
|
|
2087
|
+
}), z.object({
|
|
2088
|
+
type: z.literal("equals"),
|
|
2089
|
+
source: webhookRequestValueSourceSchema,
|
|
2090
|
+
value: z.string().min(1)
|
|
2091
|
+
})]);
|
|
2092
|
+
const webhookVerificationConfigSchema = z.discriminatedUnion("type", [z.object({ type: z.literal("none") }), z.object({
|
|
2093
|
+
type: z.literal("challenge"),
|
|
2094
|
+
method: z.enum(["GET", "POST"]),
|
|
2095
|
+
when: webhookVerificationPredicateSchema,
|
|
2096
|
+
response: z.object({
|
|
2097
|
+
type: z.literal("echoText"),
|
|
2098
|
+
source: webhookRequestValueSourceSchema
|
|
2099
|
+
})
|
|
2100
|
+
})]);
|
|
2101
|
+
//#endregion
|
|
2102
|
+
//#region src/api-types.ts
|
|
2103
|
+
const tokenEndpointAuthMethodSchema = z.enum([
|
|
2104
|
+
"client_secret_basic",
|
|
2105
|
+
"client_secret_post",
|
|
2106
|
+
"none"
|
|
2107
|
+
]);
|
|
2108
|
+
const authConfigSchema = z.discriminatedUnion("type", [
|
|
2109
|
+
z.object({ type: z.literal("none") }),
|
|
2110
|
+
z.object({
|
|
2111
|
+
type: z.literal("bearer"),
|
|
2112
|
+
token: z.string().min(1)
|
|
2113
|
+
}),
|
|
2114
|
+
z.object({
|
|
2115
|
+
type: z.literal("basic"),
|
|
2116
|
+
username: z.string().min(1),
|
|
2117
|
+
password: z.string().min(1)
|
|
2118
|
+
}),
|
|
2119
|
+
z.object({
|
|
2120
|
+
type: z.literal("client_credentials"),
|
|
2121
|
+
tokenEndpoint: z.url(),
|
|
2122
|
+
clientId: z.string().min(1),
|
|
2123
|
+
clientSecret: z.string().min(1),
|
|
2124
|
+
scopes: z.array(z.string()).optional(),
|
|
2125
|
+
audience: z.string().min(1).optional(),
|
|
2126
|
+
tokenEndpointAuthMethod: z.enum(["client_secret_basic", "client_secret_post"]).default("client_secret_basic")
|
|
2127
|
+
}),
|
|
2128
|
+
z.object({
|
|
2129
|
+
type: z.literal("oauth"),
|
|
2130
|
+
authorizationEndpoint: z.url(),
|
|
2131
|
+
tokenEndpoint: z.url(),
|
|
2132
|
+
clientId: z.string().min(1),
|
|
2133
|
+
clientSecret: z.string().min(1).optional(),
|
|
2134
|
+
scopes: z.array(z.string()).optional(),
|
|
2135
|
+
tokenEndpointAuthMethod: tokenEndpointAuthMethodSchema.default("client_secret_basic"),
|
|
2136
|
+
extraAuthorizationParams: z.record(z.string(), z.string()).optional(),
|
|
2137
|
+
extraTokenParams: z.record(z.string(), z.string()).optional()
|
|
2138
|
+
})
|
|
2139
|
+
]);
|
|
2140
|
+
z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "API connection slugs start with a letter or digit and contain only letters, digits, '.', '_', or '-'.");
|
|
2141
|
+
const createApiConnectionInputSchema = z.object({
|
|
2142
|
+
name: z.string().min(1).optional(),
|
|
2143
|
+
baseUrl: z.url(),
|
|
2144
|
+
auth: authConfigSchema.default({ type: "none" })
|
|
2145
|
+
});
|
|
2146
|
+
const apiConnectionOutputSchema = z.object({
|
|
2147
|
+
slug: z.string(),
|
|
2148
|
+
name: z.string().nullable().optional(),
|
|
2149
|
+
baseUrl: z.string(),
|
|
2150
|
+
authMode: z.string(),
|
|
2151
|
+
status: z.string(),
|
|
2152
|
+
createdAt: z.union([z.string(), z.date()]).optional(),
|
|
2153
|
+
updatedAt: z.union([z.string(), z.date()]).optional()
|
|
2154
|
+
});
|
|
2155
|
+
const apiConnectionsPageSchema = z.object({
|
|
2156
|
+
connections: z.array(apiConnectionOutputSchema),
|
|
2157
|
+
cursor: z.string().nullable().describe("Null means every connection has been listed.")
|
|
2158
|
+
});
|
|
2159
|
+
const apiCredentialsPresenceSchema = z.enum(["present", "missing"]).describe("Whether credentials are stored, not whether the provider accepts them.");
|
|
2160
|
+
/** Sanitized auth state derived from stored configuration; it never proves live provider access. */
|
|
2161
|
+
const apiAuthStatusSchema = z.discriminatedUnion("mode", [
|
|
2162
|
+
z.object({ mode: z.literal("none") }),
|
|
2163
|
+
z.object({
|
|
2164
|
+
mode: z.literal("bearer"),
|
|
2165
|
+
credentials: apiCredentialsPresenceSchema
|
|
2166
|
+
}),
|
|
2167
|
+
z.object({
|
|
2168
|
+
mode: z.literal("basic"),
|
|
2169
|
+
credentials: apiCredentialsPresenceSchema
|
|
2170
|
+
}),
|
|
2171
|
+
z.object({
|
|
2172
|
+
mode: z.literal("client_credentials"),
|
|
2173
|
+
credentials: apiCredentialsPresenceSchema
|
|
2174
|
+
}),
|
|
2175
|
+
z.object({
|
|
2176
|
+
mode: z.literal("oauth"),
|
|
2177
|
+
state: z.enum([
|
|
2178
|
+
"client-missing",
|
|
2179
|
+
"consent-required",
|
|
2180
|
+
"consent-pending",
|
|
2181
|
+
"authorized",
|
|
2182
|
+
"expired"
|
|
2183
|
+
]).describe("Expired means the access token expired and no refresh token is stored; pending means an unexpired authorization link exists.")
|
|
2184
|
+
})
|
|
2185
|
+
]);
|
|
2186
|
+
/** The pending link embeds the callback state, so reading it requires connection-create authority. */
|
|
2187
|
+
const apiOAuthPendingSchema = z.object({ pending: z.object({
|
|
2188
|
+
authorizationUrl: z.url(),
|
|
2189
|
+
expiresAt: z.union([z.string(), z.date()])
|
|
2190
|
+
}).nullable().describe("The newest unexpired, unconsumed authorization link; any pending link completes it.") });
|
|
2191
|
+
const tokenAuthInputSchema = z.object({ token: z.string().min(1) });
|
|
2192
|
+
/** Query parameter carrying the OAuth callback URI for an OAuth start request. */
|
|
2193
|
+
const API_OAUTH_REDIRECT_URI_QUERY_PARAMETER = "redirectUri";
|
|
2194
|
+
z.url().refine((value) => {
|
|
2195
|
+
const protocol = new URL(value).protocol;
|
|
2196
|
+
return protocol === "https:" || protocol === "http:";
|
|
2197
|
+
}, "OAuth redirect URI must use HTTP or HTTPS");
|
|
2198
|
+
const oauthStartInputSchema = z.object({
|
|
2199
|
+
scopes: z.array(z.string()).optional(),
|
|
2200
|
+
extraAuthorizationParams: z.record(z.string(), z.string()).optional(),
|
|
2201
|
+
discardTokens: z.boolean().default(false).describe("Drop stored tokens so status reports pending consent until this new consent completes.")
|
|
2202
|
+
});
|
|
2203
|
+
const apiRequestBodySchema = z.discriminatedUnion("type", [
|
|
2204
|
+
z.object({ type: z.literal("empty") }),
|
|
2205
|
+
z.object({
|
|
2206
|
+
type: z.literal("json"),
|
|
2207
|
+
value: z.unknown()
|
|
2208
|
+
}),
|
|
2209
|
+
z.object({
|
|
2210
|
+
type: z.literal("text"),
|
|
2211
|
+
value: z.string()
|
|
2212
|
+
})
|
|
2213
|
+
]);
|
|
2214
|
+
const apiRequestInputSchema = z.object({
|
|
2215
|
+
method: z.enum([
|
|
2216
|
+
"GET",
|
|
2217
|
+
"POST",
|
|
2218
|
+
"PUT",
|
|
2219
|
+
"PATCH",
|
|
2220
|
+
"DELETE"
|
|
2221
|
+
]),
|
|
2222
|
+
path: z.string().min(1),
|
|
2223
|
+
query: z.record(z.string(), z.string()).optional(),
|
|
2224
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
2225
|
+
body: apiRequestBodySchema,
|
|
2226
|
+
timeoutMs: z.number().int().positive().max(12e4).optional()
|
|
2227
|
+
});
|
|
2228
|
+
const apiResponseBodySchema = z.discriminatedUnion("type", [
|
|
2229
|
+
z.object({
|
|
2230
|
+
type: z.literal("json"),
|
|
2231
|
+
value: z.unknown()
|
|
2232
|
+
}),
|
|
2233
|
+
z.object({
|
|
2234
|
+
type: z.literal("text"),
|
|
2235
|
+
value: z.string()
|
|
2236
|
+
}),
|
|
2237
|
+
z.object({
|
|
2238
|
+
type: z.literal("empty"),
|
|
2239
|
+
value: z.null()
|
|
2240
|
+
})
|
|
2241
|
+
]);
|
|
2242
|
+
const apiHttpResponseSchema = z.object({
|
|
2243
|
+
status: z.number().int(),
|
|
2244
|
+
statusText: z.string(),
|
|
2245
|
+
headers: z.record(z.string(), z.string()),
|
|
2246
|
+
body: apiResponseBodySchema
|
|
2247
|
+
});
|
|
2248
|
+
const apiRequestOutputSchema = z.discriminatedUnion("ok", [z.object({
|
|
2249
|
+
ok: z.literal(true),
|
|
2250
|
+
response: apiHttpResponseSchema,
|
|
2251
|
+
error: z.null()
|
|
2252
|
+
}), z.object({
|
|
2253
|
+
ok: z.literal(false),
|
|
2254
|
+
response: apiHttpResponseSchema.nullable(),
|
|
2255
|
+
error: z.object({
|
|
2256
|
+
code: z.enum([
|
|
2257
|
+
"HTTP_ERROR",
|
|
2258
|
+
"REQUEST_ERROR",
|
|
2259
|
+
"RESPONSE_DECODING_ERROR",
|
|
2260
|
+
"CONNECTION_NOT_FOUND",
|
|
2261
|
+
"CONNECTION_DISABLED"
|
|
2262
|
+
]),
|
|
2263
|
+
message: z.string()
|
|
2264
|
+
})
|
|
2265
|
+
})]);
|
|
2266
|
+
const webhookSecretRefSchema = z.string().trim().min(1);
|
|
2267
|
+
const webhookRequestValueNameSchema = z.string().trim().min(1);
|
|
2268
|
+
const webhookDeliveryIdentitySchema = z.discriminatedUnion("type", [
|
|
2269
|
+
z.object({
|
|
2270
|
+
type: z.literal("header"),
|
|
2271
|
+
name: webhookRequestValueNameSchema
|
|
2272
|
+
}),
|
|
2273
|
+
z.object({
|
|
2274
|
+
type: z.literal("query"),
|
|
2275
|
+
name: webhookRequestValueNameSchema
|
|
2276
|
+
}),
|
|
2277
|
+
z.object({
|
|
2278
|
+
type: z.literal("jsonBodyPath"),
|
|
2279
|
+
path: z.array(z.string().trim().min(1)).min(1)
|
|
2280
|
+
})
|
|
2281
|
+
]);
|
|
2282
|
+
const webhookEndpointAuthInputSchema = z.discriminatedUnion("type", [
|
|
2283
|
+
z.object({ type: z.literal("none") }),
|
|
2284
|
+
z.object({
|
|
2285
|
+
type: z.literal("bearer"),
|
|
2286
|
+
token: webhookSecretRefSchema
|
|
2287
|
+
}),
|
|
2288
|
+
z.object({
|
|
2289
|
+
type: z.literal("apiKey"),
|
|
2290
|
+
location: z.enum(["header", "query"]),
|
|
2291
|
+
name: webhookRequestValueNameSchema,
|
|
2292
|
+
secret: webhookSecretRefSchema
|
|
2293
|
+
}),
|
|
2294
|
+
z.object({
|
|
2295
|
+
type: z.literal("basic"),
|
|
2296
|
+
username: webhookSecretRefSchema,
|
|
2297
|
+
password: webhookSecretRefSchema
|
|
2298
|
+
}),
|
|
2299
|
+
z.object({
|
|
2300
|
+
type: z.literal("hmac"),
|
|
2301
|
+
secret: webhookSecretRefSchema,
|
|
2302
|
+
algorithm: z.enum([
|
|
2303
|
+
"sha1",
|
|
2304
|
+
"sha256",
|
|
2305
|
+
"sha512"
|
|
2306
|
+
]),
|
|
2307
|
+
signature: z.object({
|
|
2308
|
+
location: z.enum(["header", "query"]),
|
|
2309
|
+
name: webhookRequestValueNameSchema,
|
|
2310
|
+
encoding: z.enum([
|
|
2311
|
+
"hex",
|
|
2312
|
+
"base64",
|
|
2313
|
+
"base64url"
|
|
2314
|
+
]),
|
|
2315
|
+
prefix: z.string().optional()
|
|
2316
|
+
}),
|
|
2317
|
+
signedPayload: z.discriminatedUnion("type", [z.object({ type: z.literal("rawBody") }), z.object({
|
|
2318
|
+
type: z.literal("timestampedBody"),
|
|
2319
|
+
prefix: z.string(),
|
|
2320
|
+
timestampHeader: webhookRequestValueNameSchema,
|
|
2321
|
+
delimiter: z.string(),
|
|
2322
|
+
toleranceSeconds: z.number().int().positive()
|
|
2323
|
+
})])
|
|
2324
|
+
})
|
|
2325
|
+
]);
|
|
2326
|
+
const webhookEndpointStatusSchema = z.enum([
|
|
2327
|
+
"draft",
|
|
2328
|
+
"active",
|
|
2329
|
+
"disabled"
|
|
2330
|
+
]);
|
|
2331
|
+
/** Why the receive routes refused a request for an existing endpoint. */
|
|
2332
|
+
const webhookRejectionCodeSchema = z.enum([
|
|
2333
|
+
"WEBHOOK_ENDPOINT_DISABLED",
|
|
2334
|
+
"WEBHOOK_ENDPOINT_DRAFT",
|
|
2335
|
+
"WEBHOOK_AUTH_FAILED",
|
|
2336
|
+
"WEBHOOK_VERIFICATION_NOT_MATCHED",
|
|
2337
|
+
"WEBHOOK_VERIFICATION_RESPONSE_INVALID",
|
|
2338
|
+
"WEBHOOK_BODY_INVALID",
|
|
2339
|
+
"WEBHOOK_DELIVERY_ID_MISSING",
|
|
2340
|
+
"WEBHOOK_DELIVERY_ID_INVALID"
|
|
2341
|
+
]);
|
|
2342
|
+
const webhookDeliveryEvidenceSchema = z.object({
|
|
2343
|
+
lastAcceptedAt: z.string().nullable().describe("Last accepted delivery or answered verification challenge."),
|
|
2344
|
+
lastRejection: z.object({
|
|
2345
|
+
receivedAt: z.string(),
|
|
2346
|
+
code: webhookRejectionCodeSchema
|
|
2347
|
+
}).nullable().describe("Last refused request. Anyone with the URL can cause one, so it is diagnostic, not proof of a provider problem.")
|
|
2348
|
+
}).describe("Requests since the endpoint's configuration last changed.");
|
|
2349
|
+
const webhookEndpointOutputSchema = z.object({
|
|
2350
|
+
id: z.string(),
|
|
2351
|
+
name: z.string(),
|
|
2352
|
+
status: webhookEndpointStatusSchema,
|
|
2353
|
+
authConfig: webhookAuthConfigSchema,
|
|
2354
|
+
verification: webhookVerificationConfigSchema,
|
|
2355
|
+
deliveryIdentity: webhookDeliveryIdentitySchema,
|
|
2356
|
+
secretRefs: z.array(z.string()),
|
|
2357
|
+
deliveries: webhookDeliveryEvidenceSchema,
|
|
2358
|
+
createdAt: z.union([z.string(), z.date()]).optional(),
|
|
2359
|
+
updatedAt: z.union([z.string(), z.date()]).optional()
|
|
2360
|
+
});
|
|
2361
|
+
const createWebhookEndpointInputSchema = z.object({
|
|
2362
|
+
name: z.string().min(1),
|
|
2363
|
+
status: webhookEndpointStatusSchema.default("active"),
|
|
2364
|
+
verification: webhookVerificationConfigSchema,
|
|
2365
|
+
deliveryIdentity: webhookDeliveryIdentitySchema,
|
|
2366
|
+
auth: webhookEndpointAuthInputSchema
|
|
2367
|
+
});
|
|
2368
|
+
const updateWebhookEndpointInputSchema = z.object({
|
|
2369
|
+
name: z.string().min(1).optional(),
|
|
2370
|
+
status: webhookEndpointStatusSchema.optional(),
|
|
2371
|
+
verification: webhookVerificationConfigSchema.optional(),
|
|
2372
|
+
deliveryIdentity: webhookDeliveryIdentitySchema.optional(),
|
|
2373
|
+
auth: webhookEndpointAuthInputSchema.optional()
|
|
2374
|
+
});
|
|
2375
|
+
//#endregion
|
|
2376
|
+
//#region src/routes.ts
|
|
2377
|
+
const webhookEndpointsOutputSchema = z.object({ endpoints: z.array(webhookEndpointOutputSchema) });
|
|
2378
|
+
const oauthStartOutputSchema = z.object({
|
|
2379
|
+
authorizationUrl: z.string(),
|
|
2380
|
+
state: z.string()
|
|
2381
|
+
});
|
|
2382
|
+
const oauthCallbackOutputSchema = z.object({
|
|
2383
|
+
authenticated: z.boolean(),
|
|
2384
|
+
mode: z.string()
|
|
2385
|
+
});
|
|
2386
|
+
//#endregion
|
|
2387
|
+
//#region src/client/client.ts
|
|
2388
|
+
const apiRoutes = [defineRoutes(apiFragmentDefinition).create(({ services, defineRoute, config }) => [
|
|
2389
|
+
defineRoute({
|
|
2390
|
+
method: "PUT",
|
|
2391
|
+
path: "/webhooks/endpoints/:endpointId",
|
|
2392
|
+
inputSchema: createWebhookEndpointInputSchema,
|
|
2393
|
+
outputSchema: webhookEndpointOutputSchema,
|
|
2394
|
+
handler: () => {}
|
|
2395
|
+
}),
|
|
2396
|
+
defineRoute({
|
|
2397
|
+
method: "GET",
|
|
2398
|
+
path: "/webhooks/endpoints",
|
|
2399
|
+
outputSchema: webhookEndpointsOutputSchema,
|
|
2400
|
+
handler: () => {}
|
|
2401
|
+
}),
|
|
2402
|
+
defineRoute({
|
|
2403
|
+
method: "GET",
|
|
2404
|
+
path: "/webhooks/endpoints/:endpointId",
|
|
2405
|
+
outputSchema: webhookEndpointOutputSchema,
|
|
2406
|
+
errorCodes: ["WEBHOOK_ENDPOINT_NOT_FOUND"],
|
|
2407
|
+
handler: () => {}
|
|
2408
|
+
}),
|
|
2409
|
+
defineRoute({
|
|
2410
|
+
method: "GET",
|
|
2411
|
+
path: "/webhooks/endpoints/:endpointId/events",
|
|
2412
|
+
errorCodes: [
|
|
2413
|
+
"WEBHOOK_ENDPOINT_NOT_FOUND",
|
|
2414
|
+
"WEBHOOK_ENDPOINT_DISABLED",
|
|
2415
|
+
"WEBHOOK_ENDPOINT_DRAFT",
|
|
2416
|
+
"WEBHOOK_AUTH_FAILED",
|
|
2417
|
+
"WEBHOOK_VERIFICATION_NOT_MATCHED",
|
|
2418
|
+
"WEBHOOK_VERIFICATION_RESPONSE_INVALID"
|
|
2419
|
+
],
|
|
2420
|
+
handler: () => {}
|
|
2421
|
+
}),
|
|
2422
|
+
defineRoute({
|
|
2423
|
+
method: "POST",
|
|
2424
|
+
path: "/webhooks/endpoints/:endpointId/events",
|
|
2425
|
+
errorCodes: [
|
|
2426
|
+
"WEBHOOK_ENDPOINT_NOT_FOUND",
|
|
2427
|
+
"WEBHOOK_ENDPOINT_DISABLED",
|
|
2428
|
+
"WEBHOOK_ENDPOINT_DRAFT",
|
|
2429
|
+
"WEBHOOK_AUTH_FAILED",
|
|
2430
|
+
"WEBHOOK_VERIFICATION_RESPONSE_INVALID",
|
|
2431
|
+
"WEBHOOK_BODY_INVALID",
|
|
2432
|
+
"WEBHOOK_DELIVERY_ID_MISSING",
|
|
2433
|
+
"WEBHOOK_DELIVERY_ID_INVALID"
|
|
2434
|
+
],
|
|
2435
|
+
handler: () => {}
|
|
2436
|
+
}),
|
|
2437
|
+
defineRoute({
|
|
2438
|
+
method: "PATCH",
|
|
2439
|
+
path: "/webhooks/endpoints/:endpointId",
|
|
2440
|
+
inputSchema: updateWebhookEndpointInputSchema,
|
|
2441
|
+
outputSchema: webhookEndpointOutputSchema,
|
|
2442
|
+
errorCodes: ["WEBHOOK_ENDPOINT_NOT_FOUND"],
|
|
2443
|
+
handler: () => {}
|
|
2444
|
+
}),
|
|
2445
|
+
defineRoute({
|
|
2446
|
+
method: "DELETE",
|
|
2447
|
+
path: "/webhooks/endpoints/:endpointId",
|
|
2448
|
+
handler: () => {}
|
|
2449
|
+
}),
|
|
2450
|
+
defineRoute({
|
|
2451
|
+
method: "PUT",
|
|
2452
|
+
path: "/connections/:slug",
|
|
2453
|
+
inputSchema: createApiConnectionInputSchema,
|
|
2454
|
+
outputSchema: apiConnectionOutputSchema,
|
|
2455
|
+
errorCodes: [
|
|
2456
|
+
"CONNECTION_EXISTS",
|
|
2457
|
+
"BASE_URL_NOT_ALLOWED",
|
|
2458
|
+
"INVALID_SLUG"
|
|
2459
|
+
],
|
|
2460
|
+
handler: () => {}
|
|
2461
|
+
}),
|
|
2462
|
+
defineRoute({
|
|
2463
|
+
method: "GET",
|
|
2464
|
+
path: "/connections",
|
|
2465
|
+
queryParameters: ["cursor"],
|
|
2466
|
+
outputSchema: apiConnectionsPageSchema,
|
|
2467
|
+
errorCodes: ["INVALID_CURSOR"],
|
|
2468
|
+
handler: () => {}
|
|
2469
|
+
}),
|
|
2470
|
+
defineRoute({
|
|
2471
|
+
method: "GET",
|
|
2472
|
+
path: "/connections/:slug",
|
|
2473
|
+
outputSchema: apiConnectionOutputSchema,
|
|
2474
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2475
|
+
handler: () => {}
|
|
2476
|
+
}),
|
|
2477
|
+
defineRoute({
|
|
2478
|
+
method: "PUT",
|
|
2479
|
+
path: "/connections/:slug/configuration",
|
|
2480
|
+
inputSchema: createApiConnectionInputSchema,
|
|
2481
|
+
outputSchema: apiConnectionOutputSchema,
|
|
2482
|
+
errorCodes: ["CONNECTION_NOT_FOUND", "BASE_URL_NOT_ALLOWED"],
|
|
2483
|
+
handler: () => {}
|
|
2484
|
+
}),
|
|
2485
|
+
defineRoute({
|
|
2486
|
+
method: "DELETE",
|
|
2487
|
+
path: "/connections/:slug",
|
|
2488
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2489
|
+
handler: () => {}
|
|
2490
|
+
}),
|
|
2491
|
+
defineRoute({
|
|
2492
|
+
method: "GET",
|
|
2493
|
+
path: "/connections/:slug/auth/status",
|
|
2494
|
+
outputSchema: apiAuthStatusSchema,
|
|
2495
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2496
|
+
handler: () => {}
|
|
2497
|
+
}),
|
|
2498
|
+
defineRoute({
|
|
2499
|
+
method: "GET",
|
|
2500
|
+
path: "/connections/:slug/auth/oauth/pending",
|
|
2501
|
+
outputSchema: apiOAuthPendingSchema,
|
|
2502
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2503
|
+
handler: () => {}
|
|
2504
|
+
}),
|
|
2505
|
+
defineRoute({
|
|
2506
|
+
method: "POST",
|
|
2507
|
+
path: "/connections/:slug/auth/token",
|
|
2508
|
+
inputSchema: tokenAuthInputSchema,
|
|
2509
|
+
outputSchema: apiAuthStatusSchema,
|
|
2510
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2511
|
+
handler: () => {}
|
|
2512
|
+
}),
|
|
2513
|
+
defineRoute({
|
|
2514
|
+
method: "POST",
|
|
2515
|
+
path: "/connections/:slug/auth/oauth/start",
|
|
2516
|
+
inputSchema: oauthStartInputSchema,
|
|
2517
|
+
outputSchema: oauthStartOutputSchema,
|
|
2518
|
+
queryParameters: [API_OAUTH_REDIRECT_URI_QUERY_PARAMETER],
|
|
2519
|
+
errorCodes: [
|
|
2520
|
+
"INVALID_OAUTH_REDIRECT_URI",
|
|
2521
|
+
"OAUTH_REDIRECT_URI_NOT_ALLOWED",
|
|
2522
|
+
"CONNECTION_NOT_FOUND",
|
|
2523
|
+
"AUTH_NOT_CONFIGURED",
|
|
2524
|
+
"AUTH_NOT_OAUTH",
|
|
2525
|
+
"OAUTH_ERROR"
|
|
2526
|
+
],
|
|
2527
|
+
handler: () => {}
|
|
2528
|
+
}),
|
|
2529
|
+
defineRoute({
|
|
2530
|
+
method: "GET",
|
|
2531
|
+
path: "/oauth/callback",
|
|
2532
|
+
outputSchema: oauthCallbackOutputSchema,
|
|
2533
|
+
errorCodes: [
|
|
2534
|
+
"CONNECTION_NOT_FOUND",
|
|
2535
|
+
"AUTH_NOT_CONFIGURED",
|
|
2536
|
+
"AUTH_NOT_OAUTH",
|
|
2537
|
+
"OAUTH_ERROR",
|
|
2538
|
+
"INVALID_OAUTH_STATE"
|
|
2539
|
+
],
|
|
2540
|
+
handler: () => {}
|
|
2541
|
+
}),
|
|
2542
|
+
defineRoute({
|
|
2543
|
+
method: "DELETE",
|
|
2544
|
+
path: "/connections/:slug/auth",
|
|
2545
|
+
outputSchema: apiAuthStatusSchema,
|
|
2546
|
+
errorCodes: ["CONNECTION_NOT_FOUND"],
|
|
2547
|
+
handler: () => {}
|
|
2548
|
+
}),
|
|
2549
|
+
defineRoute({
|
|
2550
|
+
method: "POST",
|
|
2551
|
+
path: "/connections/:slug/request",
|
|
2552
|
+
inputSchema: apiRequestInputSchema,
|
|
2553
|
+
outputSchema: apiRequestOutputSchema,
|
|
2554
|
+
handler: () => {}
|
|
2555
|
+
})
|
|
2556
|
+
])];
|
|
2557
|
+
function createApiFragmentClients(fragnoConfig = {}) {
|
|
2558
|
+
const builder = createClientBuilder(apiFragmentDefinition, fragnoConfig, apiRoutes);
|
|
2559
|
+
return {
|
|
2560
|
+
useConnections: builder.createHook("/connections"),
|
|
2561
|
+
useConnection: builder.createHook("/connections/:slug"),
|
|
2562
|
+
useAuthStatus: builder.createHook("/connections/:slug/auth/status"),
|
|
2563
|
+
usePendingOAuth: builder.createHook("/connections/:slug/auth/oauth/pending"),
|
|
2564
|
+
createConnection: builder.createMutator("PUT", "/connections/:slug"),
|
|
2565
|
+
deleteConnection: builder.createMutator("DELETE", "/connections/:slug"),
|
|
2566
|
+
setBearerToken: builder.createMutator("POST", "/connections/:slug/auth/token"),
|
|
2567
|
+
startOAuth: builder.createMutator("POST", "/connections/:slug/auth/oauth/start"),
|
|
2568
|
+
deleteAuth: builder.createMutator("DELETE", "/connections/:slug/auth"),
|
|
2569
|
+
request: builder.createMutator("POST", "/connections/:slug/request"),
|
|
2570
|
+
useWebhookEndpoints: builder.createHook("/webhooks/endpoints"),
|
|
2571
|
+
useWebhookEndpoint: builder.createHook("/webhooks/endpoints/:endpointId"),
|
|
2572
|
+
createWebhookEndpoint: builder.createMutator("PUT", "/webhooks/endpoints/:endpointId"),
|
|
2573
|
+
updateWebhookEndpoint: builder.createMutator("PATCH", "/webhooks/endpoints/:endpointId"),
|
|
2574
|
+
deleteWebhookEndpoint: builder.createMutator("DELETE", "/webhooks/endpoints/:endpointId")
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
//#endregion
|
|
2578
|
+
export { atom as a, isStore as i, isGetHook as n, isReadableAtom as o, isMutatorHook as r, createApiFragmentClients as t };
|
|
2579
|
+
|
|
2580
|
+
//# sourceMappingURL=client-3ZV9sw3i.js.map
|