@fragno-dev/forms 0.0.3 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -25
- package/dist/browser/client/react.d.ts +143 -94
- package/dist/browser/client/react.d.ts.map +1 -1
- package/dist/browser/client/react.js +105 -13
- package/dist/browser/client/react.js.map +1 -1
- package/dist/browser/client/solid.d.ts +143 -94
- package/dist/browser/client/solid.d.ts.map +1 -1
- package/dist/browser/client/solid.js +25 -11
- package/dist/browser/client/solid.js.map +1 -1
- package/dist/browser/client/svelte.d.ts +143 -94
- package/dist/browser/client/svelte.d.ts.map +1 -1
- package/dist/browser/client/svelte.js +11 -3
- package/dist/browser/client/svelte.js.map +1 -1
- package/dist/browser/client/vanilla.d.ts +142 -93
- package/dist/browser/client/vanilla.d.ts.map +1 -1
- package/dist/browser/client/vanilla.js +21 -1
- package/dist/browser/client/vanilla.js.map +1 -1
- package/dist/browser/client/vue.d.ts +73 -24
- package/dist/browser/client/vue.d.ts.map +1 -1
- package/dist/browser/client/vue.js +23 -1
- package/dist/browser/client/vue.js.map +1 -1
- package/dist/browser/index.d.ts +700 -301
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/{src-DbMX_NY6.js → src-BNcd9ogF.js} +556 -348
- package/dist/browser/src-BNcd9ogF.js.map +1 -0
- package/dist/node/index.d.ts +546 -147
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +133 -103
- package/dist/node/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +31 -52
- package/dist/browser/src-DbMX_NY6.js.map +0 -1
|
@@ -15,6 +15,80 @@ function resolveRouteFactories(context, routesOrFactories) {
|
|
|
15
15
|
return routes$1;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region ../fragno/dist/api/internal/path.js
|
|
20
|
+
/**
|
|
21
|
+
* Extract parameter names from a path pattern at runtime.
|
|
22
|
+
* Examples:
|
|
23
|
+
* - "/users/:id" => ["id"]
|
|
24
|
+
* - "/files/**" => ["**"]
|
|
25
|
+
* - "/files/**:rest" => ["rest"]
|
|
26
|
+
*/
|
|
27
|
+
function extractPathParams(pathPattern) {
|
|
28
|
+
const segments = pathPattern.split("/").filter((s) => s.length > 0);
|
|
29
|
+
const names = [];
|
|
30
|
+
for (const segment of segments) {
|
|
31
|
+
if (segment.startsWith(":")) {
|
|
32
|
+
names.push(segment.slice(1));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (segment === "**") {
|
|
36
|
+
names.push("**");
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (segment.startsWith("**:")) {
|
|
40
|
+
names.push(segment.slice(3));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build a concrete path by replacing placeholders in a path pattern with values.
|
|
48
|
+
*
|
|
49
|
+
* Supports the same placeholder syntax as the matcher:
|
|
50
|
+
* - Named parameter ":name" is URL-encoded as a single segment
|
|
51
|
+
* - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
|
|
52
|
+
* - Named wildcard "**:name" inserts the remainder from the named key
|
|
53
|
+
*
|
|
54
|
+
* Examples:
|
|
55
|
+
* - buildPath("/users/:id", { id: "123" }) => "/users/123"
|
|
56
|
+
* - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
|
|
57
|
+
* - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
|
|
58
|
+
*/
|
|
59
|
+
function buildPath(pathPattern, params) {
|
|
60
|
+
const patternSegments = pathPattern.split("/");
|
|
61
|
+
const builtSegments = [];
|
|
62
|
+
for (const segment of patternSegments) {
|
|
63
|
+
if (segment.length === 0) {
|
|
64
|
+
builtSegments.push("");
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (segment.startsWith(":")) {
|
|
68
|
+
const name = segment.slice(1);
|
|
69
|
+
const value = params[name];
|
|
70
|
+
if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
|
|
71
|
+
builtSegments.push(encodeURIComponent(value));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (segment === "**") {
|
|
75
|
+
const value = params["**"];
|
|
76
|
+
if (value === void 0) throw new Error("Missing value for path wildcard **");
|
|
77
|
+
builtSegments.push(value);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (segment.startsWith("**:")) {
|
|
81
|
+
const name = segment.slice(3);
|
|
82
|
+
const value = params[name];
|
|
83
|
+
if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
|
|
84
|
+
builtSegments.push(value);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
builtSegments.push(segment);
|
|
88
|
+
}
|
|
89
|
+
return builtSegments.join("/");
|
|
90
|
+
}
|
|
91
|
+
|
|
18
92
|
//#endregion
|
|
19
93
|
//#region ../fragno/dist/api/internal/route.js
|
|
20
94
|
function getMountRoute(opts) {
|
|
@@ -161,6 +235,72 @@ var RequestInputContext = class RequestInputContext$1 {
|
|
|
161
235
|
return this.#body;
|
|
162
236
|
}
|
|
163
237
|
/**
|
|
238
|
+
* Access the request body as FormData.
|
|
239
|
+
*
|
|
240
|
+
* Use this method when handling file uploads or multipart form submissions.
|
|
241
|
+
* The request must have been sent with Content-Type: multipart/form-data.
|
|
242
|
+
*
|
|
243
|
+
* @throws Error if the request body is not FormData
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* defineRoute({
|
|
248
|
+
* method: "POST",
|
|
249
|
+
* path: "/upload",
|
|
250
|
+
* async handler(ctx, res) {
|
|
251
|
+
* const formData = ctx.formData();
|
|
252
|
+
* const file = formData.get("file") as File;
|
|
253
|
+
* const description = formData.get("description") as string;
|
|
254
|
+
* // ... process file
|
|
255
|
+
* }
|
|
256
|
+
* });
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
formData() {
|
|
260
|
+
if (!(this.#parsedBody instanceof FormData)) throw new Error("Request body is not FormData. Ensure the request was sent with Content-Type: multipart/form-data.");
|
|
261
|
+
return this.#parsedBody;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Check if the request body is FormData.
|
|
265
|
+
*
|
|
266
|
+
* Useful for routes that accept both JSON and FormData payloads.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* defineRoute({
|
|
271
|
+
* method: "POST",
|
|
272
|
+
* path: "/upload",
|
|
273
|
+
* async handler(ctx, res) {
|
|
274
|
+
* if (ctx.isFormData()) {
|
|
275
|
+
* const formData = ctx.formData();
|
|
276
|
+
* // handle file upload
|
|
277
|
+
* } else {
|
|
278
|
+
* const json = await ctx.input.valid();
|
|
279
|
+
* // handle JSON payload
|
|
280
|
+
* }
|
|
281
|
+
* }
|
|
282
|
+
* });
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
isFormData() {
|
|
286
|
+
return this.#parsedBody instanceof FormData;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Access the request body as a ReadableStream (application/octet-stream).
|
|
290
|
+
*
|
|
291
|
+
* @throws Error if the request body is not a ReadableStream
|
|
292
|
+
*/
|
|
293
|
+
bodyStream() {
|
|
294
|
+
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.");
|
|
295
|
+
return this.#parsedBody;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Check if the request body is a ReadableStream.
|
|
299
|
+
*/
|
|
300
|
+
isBodyStream() {
|
|
301
|
+
return this.#parsedBody instanceof ReadableStream;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
164
304
|
* Input validation context (only if inputSchema is defined)
|
|
165
305
|
* @remarks `InputContext`
|
|
166
306
|
*/
|
|
@@ -177,6 +317,7 @@ var RequestInputContext = class RequestInputContext$1 {
|
|
|
177
317
|
async #validateInput() {
|
|
178
318
|
if (!this.#inputSchema) throw new Error("No input schema defined for this route");
|
|
179
319
|
if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
|
|
320
|
+
if (this.#parsedBody instanceof ReadableStream) throw new Error("Schema validation is only supported for JSON data, not FormData, Blob, or ReadableStream");
|
|
180
321
|
const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
|
|
181
322
|
if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
|
|
182
323
|
return result.value;
|
|
@@ -377,170 +518,6 @@ var RequestOutputContext = class extends OutputContext {
|
|
|
377
518
|
}
|
|
378
519
|
};
|
|
379
520
|
|
|
380
|
-
//#endregion
|
|
381
|
-
//#region ../fragno/dist/api/internal/path.js
|
|
382
|
-
/**
|
|
383
|
-
* Extract parameter names from a path pattern at runtime.
|
|
384
|
-
* Examples:
|
|
385
|
-
* - "/users/:id" => ["id"]
|
|
386
|
-
* - "/files/**" => ["**"]
|
|
387
|
-
* - "/files/**:rest" => ["rest"]
|
|
388
|
-
*/
|
|
389
|
-
function extractPathParams(pathPattern) {
|
|
390
|
-
const segments = pathPattern.split("/").filter((s) => s.length > 0);
|
|
391
|
-
const names = [];
|
|
392
|
-
for (const segment of segments) {
|
|
393
|
-
if (segment.startsWith(":")) {
|
|
394
|
-
names.push(segment.slice(1));
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
if (segment === "**") {
|
|
398
|
-
names.push("**");
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (segment.startsWith("**:")) {
|
|
402
|
-
names.push(segment.slice(3));
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return names;
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Build a concrete path by replacing placeholders in a path pattern with values.
|
|
410
|
-
*
|
|
411
|
-
* Supports the same placeholder syntax as the matcher:
|
|
412
|
-
* - Named parameter ":name" is URL-encoded as a single segment
|
|
413
|
-
* - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
|
|
414
|
-
* - Named wildcard "**:name" inserts the remainder from the named key
|
|
415
|
-
*
|
|
416
|
-
* Examples:
|
|
417
|
-
* - buildPath("/users/:id", { id: "123" }) => "/users/123"
|
|
418
|
-
* - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
|
|
419
|
-
* - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
|
|
420
|
-
*/
|
|
421
|
-
function buildPath(pathPattern, params) {
|
|
422
|
-
const patternSegments = pathPattern.split("/");
|
|
423
|
-
const builtSegments = [];
|
|
424
|
-
for (const segment of patternSegments) {
|
|
425
|
-
if (segment.length === 0) {
|
|
426
|
-
builtSegments.push("");
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
if (segment.startsWith(":")) {
|
|
430
|
-
const name = segment.slice(1);
|
|
431
|
-
const value = params[name];
|
|
432
|
-
if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
|
|
433
|
-
builtSegments.push(encodeURIComponent(value));
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
if (segment === "**") {
|
|
437
|
-
const value = params["**"];
|
|
438
|
-
if (value === void 0) throw new Error("Missing value for path wildcard **");
|
|
439
|
-
builtSegments.push(value);
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
if (segment.startsWith("**:")) {
|
|
443
|
-
const name = segment.slice(3);
|
|
444
|
-
const value = params[name];
|
|
445
|
-
if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
|
|
446
|
-
builtSegments.push(value);
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
builtSegments.push(segment);
|
|
450
|
-
}
|
|
451
|
-
return builtSegments.join("/");
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
//#endregion
|
|
455
|
-
//#region ../fragno/dist/client/client-error.js
|
|
456
|
-
/**
|
|
457
|
-
* Base error class for all Fragno client errors.
|
|
458
|
-
*/
|
|
459
|
-
var FragnoClientError = class extends Error {
|
|
460
|
-
#code;
|
|
461
|
-
constructor(message, code, options = {}) {
|
|
462
|
-
super(message, { cause: options.cause });
|
|
463
|
-
this.name = "FragnoClientError";
|
|
464
|
-
this.#code = code;
|
|
465
|
-
}
|
|
466
|
-
get code() {
|
|
467
|
-
return this.#code;
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
var FragnoClientFetchError = class extends FragnoClientError {
|
|
471
|
-
constructor(message, code, options = {}) {
|
|
472
|
-
super(message, code, options);
|
|
473
|
-
this.name = "FragnoClientFetchError";
|
|
474
|
-
}
|
|
475
|
-
static fromUnknownFetchError(error) {
|
|
476
|
-
if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
477
|
-
if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
|
|
478
|
-
return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
/**
|
|
482
|
-
* Error thrown when a network request fails (e.g., no internet connection, DNS failure).
|
|
483
|
-
*/
|
|
484
|
-
var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
|
|
485
|
-
constructor(message = "Network request failed", options = {}) {
|
|
486
|
-
super(message, "NETWORK_ERROR", options);
|
|
487
|
-
this.name = "FragnoClientFetchNetworkError";
|
|
488
|
-
}
|
|
489
|
-
};
|
|
490
|
-
/**
|
|
491
|
-
* Error thrown when a request is aborted (e.g., user cancels request, timeout).
|
|
492
|
-
*/
|
|
493
|
-
var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
|
|
494
|
-
constructor(message = "Request was aborted", options = {}) {
|
|
495
|
-
super(message, "ABORT_ERROR", options);
|
|
496
|
-
this.name = "FragnoClientFetchAbortError";
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
/**
|
|
500
|
-
* Error thrown when the API result is unexpected, e.g. no json is returned.
|
|
501
|
-
*/
|
|
502
|
-
var FragnoClientUnknownApiError = class extends FragnoClientError {
|
|
503
|
-
#status;
|
|
504
|
-
constructor(message = "Unknown API error", status, options = {}) {
|
|
505
|
-
super(message, "UNKNOWN_API_ERROR", options);
|
|
506
|
-
this.name = "FragnoClientUnknownApiError";
|
|
507
|
-
this.#status = status;
|
|
508
|
-
}
|
|
509
|
-
get status() {
|
|
510
|
-
return this.#status;
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
var FragnoClientApiError = class FragnoClientApiError$1 extends FragnoClientError {
|
|
514
|
-
#status;
|
|
515
|
-
constructor({ message, code }, status, options = {}) {
|
|
516
|
-
super(message, code, options);
|
|
517
|
-
this.name = "FragnoClientApiError";
|
|
518
|
-
this.#status = status;
|
|
519
|
-
}
|
|
520
|
-
get status() {
|
|
521
|
-
return this.#status;
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* The error code returned by the API.
|
|
525
|
-
*
|
|
526
|
-
* The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
|
|
527
|
-
* for forward compatibility with future error codes.
|
|
528
|
-
*/
|
|
529
|
-
get code() {
|
|
530
|
-
return super.code;
|
|
531
|
-
}
|
|
532
|
-
static async fromResponse(response) {
|
|
533
|
-
const unknown = await response.json();
|
|
534
|
-
const status = response.status;
|
|
535
|
-
if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
536
|
-
if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
537
|
-
return new FragnoClientApiError$1({
|
|
538
|
-
message: unknown.message,
|
|
539
|
-
code: unknown.code
|
|
540
|
-
}, status);
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
|
|
544
521
|
//#endregion
|
|
545
522
|
//#region ../fragno/dist/util/content-type.js
|
|
546
523
|
/**
|
|
@@ -589,142 +566,36 @@ function parseContentType(contentType) {
|
|
|
589
566
|
}
|
|
590
567
|
|
|
591
568
|
//#endregion
|
|
592
|
-
//#region ../fragno/dist/
|
|
569
|
+
//#region ../fragno/dist/util/nanostores.js
|
|
593
570
|
/**
|
|
594
|
-
*
|
|
571
|
+
* Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
|
|
595
572
|
*/
|
|
596
|
-
function
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
reject(new FragnoClientFetchAbortError("Operation was aborted"));
|
|
600
|
-
};
|
|
601
|
-
if (abortSignal.aborted) abortHandler();
|
|
602
|
-
else abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
603
|
-
});
|
|
573
|
+
function unwrapAtom(value) {
|
|
574
|
+
if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
|
|
575
|
+
return value;
|
|
604
576
|
}
|
|
605
577
|
/**
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* This makes it so that we can wait until the first chunk before updating the store, if we did
|
|
610
|
-
* not do this, `loading` would briefly be false before the first item would be populated in the
|
|
611
|
-
* result.
|
|
612
|
-
*
|
|
613
|
-
* @param response - The fetch Response object containing the NDJSON stream
|
|
614
|
-
* @param store - The fetcher store to update with streaming data
|
|
615
|
-
* @param abortSignal - Optional AbortSignal to cancel the streaming operation
|
|
616
|
-
* @returns A promise that resolves to an object containing the first item and a streaming promise
|
|
578
|
+
* Normalizes an object where values can be plain values, Atoms, or Vue Refs.
|
|
579
|
+
* Returns a new object with all values normalized to plain values.
|
|
617
580
|
*/
|
|
618
|
-
|
|
619
|
-
if (!
|
|
620
|
-
|
|
621
|
-
if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
622
|
-
const decoder = new TextDecoder();
|
|
623
|
-
const reader = response.body.getReader();
|
|
624
|
-
let buffer = "";
|
|
625
|
-
let firstItem = null;
|
|
626
|
-
const items = [];
|
|
627
|
-
try {
|
|
628
|
-
while (firstItem === null) {
|
|
629
|
-
if (abortSignal?.aborted) {
|
|
630
|
-
reader.releaseLock();
|
|
631
|
-
throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
632
|
-
}
|
|
633
|
-
const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
|
|
634
|
-
if (done) break;
|
|
635
|
-
buffer += decoder.decode(value, { stream: true });
|
|
636
|
-
const lines = buffer.split("\n");
|
|
637
|
-
buffer = lines.pop() || "";
|
|
638
|
-
for (const line of lines) {
|
|
639
|
-
if (!line.trim()) continue;
|
|
640
|
-
try {
|
|
641
|
-
const jsonObject = JSON.parse(line);
|
|
642
|
-
items.push(jsonObject);
|
|
643
|
-
if (firstItem === null) {
|
|
644
|
-
firstItem = jsonObject;
|
|
645
|
-
const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
|
|
646
|
-
return {
|
|
647
|
-
firstItem,
|
|
648
|
-
streamingPromise
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
} catch (parseError) {
|
|
652
|
-
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, { cause: parseError });
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
if (firstItem === null) {
|
|
657
|
-
reader.releaseLock();
|
|
658
|
-
throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
|
|
659
|
-
}
|
|
660
|
-
reader.releaseLock();
|
|
661
|
-
throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
|
|
662
|
-
} catch (error) {
|
|
663
|
-
if (error instanceof FragnoClientError) {
|
|
664
|
-
store?.setError(error);
|
|
665
|
-
throw error;
|
|
666
|
-
} else {
|
|
667
|
-
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
|
|
668
|
-
store?.setError(clientError);
|
|
669
|
-
throw clientError;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
581
|
+
function unwrapObject(params) {
|
|
582
|
+
if (!params) return;
|
|
583
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
|
|
672
584
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (buffer.trim()) {
|
|
684
|
-
const lines$1 = buffer.split("\n");
|
|
685
|
-
for (const line of lines$1) {
|
|
686
|
-
if (!line.trim()) continue;
|
|
687
|
-
try {
|
|
688
|
-
const jsonObject = JSON.parse(line);
|
|
689
|
-
items.push(jsonObject);
|
|
690
|
-
store?.setData([...items]);
|
|
691
|
-
} catch (parseError) {
|
|
692
|
-
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
698
|
-
buffer += decoder.decode(value, { stream: true });
|
|
699
|
-
const lines = buffer.split("\n");
|
|
700
|
-
buffer = lines.pop() || "";
|
|
701
|
-
for (const line of lines) {
|
|
702
|
-
if (!line.trim()) continue;
|
|
703
|
-
try {
|
|
704
|
-
const jsonObject = JSON.parse(line);
|
|
705
|
-
items.push(jsonObject);
|
|
706
|
-
store?.setData([...items]);
|
|
707
|
-
} catch (parseError) {
|
|
708
|
-
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
} catch (error) {
|
|
713
|
-
if (error instanceof FragnoClientError) store?.setError(error);
|
|
714
|
-
else {
|
|
715
|
-
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
|
|
716
|
-
store?.setError(clientError);
|
|
717
|
-
throw clientError;
|
|
718
|
-
}
|
|
719
|
-
throw error;
|
|
720
|
-
} finally {
|
|
721
|
-
reader.releaseLock();
|
|
722
|
-
}
|
|
723
|
-
return items;
|
|
585
|
+
function isReadableAtom(value) {
|
|
586
|
+
if (!value) return false;
|
|
587
|
+
if (typeof value !== "object" || value === null) return false;
|
|
588
|
+
if (!("get" in value) || typeof value.get !== "function") return false;
|
|
589
|
+
if (!("lc" in value) || typeof value.lc !== "number") return false;
|
|
590
|
+
if (!("notify" in value) || typeof value.notify !== "function") return false;
|
|
591
|
+
if (!("off" in value) || typeof value.off !== "function") return false;
|
|
592
|
+
if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
|
|
593
|
+
if (!("value" in value)) return false;
|
|
594
|
+
return true;
|
|
724
595
|
}
|
|
725
596
|
|
|
726
597
|
//#endregion
|
|
727
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
598
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/task/index.js
|
|
728
599
|
let tasks = 0;
|
|
729
600
|
let resolves = [];
|
|
730
601
|
function startTask() {
|
|
@@ -746,11 +617,11 @@ function task(cb) {
|
|
|
746
617
|
}
|
|
747
618
|
|
|
748
619
|
//#endregion
|
|
749
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
620
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/clean-stores/index.js
|
|
750
621
|
let clean = Symbol("clean");
|
|
751
622
|
|
|
752
623
|
//#endregion
|
|
753
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
624
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/atom/index.js
|
|
754
625
|
let listenerQueue = [];
|
|
755
626
|
let lqIndex = 0;
|
|
756
627
|
const QUEUE_ITEMS_PER_LISTENER = 4;
|
|
@@ -762,6 +633,7 @@ const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
|
|
|
762
633
|
if (!$atom.lc) $atom.listen(() => {})();
|
|
763
634
|
return $atom.value;
|
|
764
635
|
},
|
|
636
|
+
init: initialValue,
|
|
765
637
|
lc: 0,
|
|
766
638
|
listen(listener) {
|
|
767
639
|
$atom.lc = listeners.push(listener);
|
|
@@ -808,7 +680,7 @@ const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
|
|
|
808
680
|
};
|
|
809
681
|
|
|
810
682
|
//#endregion
|
|
811
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
683
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/lifecycle/index.js
|
|
812
684
|
const START = 0;
|
|
813
685
|
const STOP = 1;
|
|
814
686
|
const MOUNT = 5;
|
|
@@ -903,7 +775,21 @@ let onMount = ($store, initialize) => {
|
|
|
903
775
|
};
|
|
904
776
|
|
|
905
777
|
//#endregion
|
|
906
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
778
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/warn/index.js
|
|
779
|
+
let warned = {};
|
|
780
|
+
function warn(text) {
|
|
781
|
+
if (!warned[text]) {
|
|
782
|
+
warned[text] = true;
|
|
783
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
784
|
+
console.groupCollapsed("Nano Stores: " + text);
|
|
785
|
+
console.trace("Source of deprecated call");
|
|
786
|
+
console.groupEnd();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/computed/index.js
|
|
907
793
|
let computedStore = (stores$1, cb, batched$1) => {
|
|
908
794
|
if (!Array.isArray(stores$1)) stores$1 = [stores$1];
|
|
909
795
|
let previousArgs;
|
|
@@ -915,10 +801,12 @@ let computedStore = (stores$1, cb, batched$1) => {
|
|
|
915
801
|
if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) {
|
|
916
802
|
previousArgs = args;
|
|
917
803
|
let value = cb(...args);
|
|
918
|
-
if (value && value.then && value.t)
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
804
|
+
if (value && value.then && value.t) {
|
|
805
|
+
warn("Use @nanostores/async for async computed. We will remove Promise support in computed() in Nano Stores 2.0");
|
|
806
|
+
value.then((asyncValue) => {
|
|
807
|
+
if (previousArgs === args) $computed.set(asyncValue);
|
|
808
|
+
});
|
|
809
|
+
} else {
|
|
922
810
|
$computed.set(value);
|
|
923
811
|
currentEpoch = epoch;
|
|
924
812
|
}
|
|
@@ -948,7 +836,7 @@ const computed = /* @__NO_SIDE_EFFECTS__ */ (stores$1, fn) => computedStore(stor
|
|
|
948
836
|
const batched = /* @__NO_SIDE_EFFECTS__ */ (stores$1, fn) => computedStore(stores$1, fn, true);
|
|
949
837
|
|
|
950
838
|
//#endregion
|
|
951
|
-
//#region ../../node_modules/.pnpm/nanostores@1.
|
|
839
|
+
//#region ../../node_modules/.pnpm/nanostores@1.2.0/node_modules/nanostores/map/index.js
|
|
952
840
|
const map = /* @__NO_SIDE_EFFECTS__ */ (initial = {}) => {
|
|
953
841
|
let $map = atom(initial);
|
|
954
842
|
$map.setKey = function(key, value) {
|
|
@@ -985,33 +873,94 @@ function getInitialData(key) {
|
|
|
985
873
|
}
|
|
986
874
|
|
|
987
875
|
//#endregion
|
|
988
|
-
//#region ../fragno/dist/
|
|
876
|
+
//#region ../fragno/dist/client/client-error.js
|
|
877
|
+
/**
|
|
878
|
+
* Base error class for all Fragno client errors.
|
|
879
|
+
*/
|
|
880
|
+
var FragnoClientError = class extends Error {
|
|
881
|
+
#code;
|
|
882
|
+
constructor(message, code, options = {}) {
|
|
883
|
+
super(message, { cause: options.cause });
|
|
884
|
+
this.name = "FragnoClientError";
|
|
885
|
+
this.#code = code;
|
|
886
|
+
}
|
|
887
|
+
get code() {
|
|
888
|
+
return this.#code;
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
var FragnoClientFetchError = class extends FragnoClientError {
|
|
892
|
+
constructor(message, code, options = {}) {
|
|
893
|
+
super(message, code, options);
|
|
894
|
+
this.name = "FragnoClientFetchError";
|
|
895
|
+
}
|
|
896
|
+
static fromUnknownFetchError(error) {
|
|
897
|
+
if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
898
|
+
if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
|
|
899
|
+
return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* Error thrown when a network request fails (e.g., no internet connection, DNS failure).
|
|
904
|
+
*/
|
|
905
|
+
var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
|
|
906
|
+
constructor(message = "Network request failed", options = {}) {
|
|
907
|
+
super(message, "NETWORK_ERROR", options);
|
|
908
|
+
this.name = "FragnoClientFetchNetworkError";
|
|
909
|
+
}
|
|
910
|
+
};
|
|
989
911
|
/**
|
|
990
|
-
*
|
|
912
|
+
* Error thrown when a request is aborted (e.g., user cancels request, timeout).
|
|
991
913
|
*/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
914
|
+
var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
|
|
915
|
+
constructor(message = "Request was aborted", options = {}) {
|
|
916
|
+
super(message, "ABORT_ERROR", options);
|
|
917
|
+
this.name = "FragnoClientFetchAbortError";
|
|
918
|
+
}
|
|
919
|
+
};
|
|
996
920
|
/**
|
|
997
|
-
*
|
|
998
|
-
* Returns a new object with all values normalized to plain values.
|
|
921
|
+
* Error thrown when the API result is unexpected, e.g. no json is returned.
|
|
999
922
|
*/
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
923
|
+
var FragnoClientUnknownApiError = class extends FragnoClientError {
|
|
924
|
+
#status;
|
|
925
|
+
constructor(message = "Unknown API error", status, options = {}) {
|
|
926
|
+
super(message, "UNKNOWN_API_ERROR", options);
|
|
927
|
+
this.name = "FragnoClientUnknownApiError";
|
|
928
|
+
this.#status = status;
|
|
929
|
+
}
|
|
930
|
+
get status() {
|
|
931
|
+
return this.#status;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
var FragnoClientApiError = class FragnoClientApiError$1 extends FragnoClientError {
|
|
935
|
+
#status;
|
|
936
|
+
constructor({ message, code }, status, options = {}) {
|
|
937
|
+
super(message, code, options);
|
|
938
|
+
this.name = "FragnoClientApiError";
|
|
939
|
+
this.#status = status;
|
|
940
|
+
}
|
|
941
|
+
get status() {
|
|
942
|
+
return this.#status;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* The error code returned by the API.
|
|
946
|
+
*
|
|
947
|
+
* The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
|
|
948
|
+
* for forward compatibility with future error codes.
|
|
949
|
+
*/
|
|
950
|
+
get code() {
|
|
951
|
+
return super.code;
|
|
952
|
+
}
|
|
953
|
+
static async fromResponse(response) {
|
|
954
|
+
const unknown = await response.json();
|
|
955
|
+
const status = response.status;
|
|
956
|
+
if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
957
|
+
if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
|
|
958
|
+
return new FragnoClientApiError$1({
|
|
959
|
+
message: unknown.message,
|
|
960
|
+
code: unknown.code
|
|
961
|
+
}, status);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
1015
964
|
|
|
1016
965
|
//#endregion
|
|
1017
966
|
//#region ../fragno/dist/client/internal/fetcher-merge.js
|
|
@@ -1047,6 +996,141 @@ function mergeHeaders(author, user) {
|
|
|
1047
996
|
return merged;
|
|
1048
997
|
}
|
|
1049
998
|
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region ../fragno/dist/client/internal/ndjson-streaming.js
|
|
1001
|
+
/**
|
|
1002
|
+
* Creates a promise that rejects when the abort signal is triggered
|
|
1003
|
+
*/
|
|
1004
|
+
function createAbortPromise(abortSignal) {
|
|
1005
|
+
return new Promise((_, reject) => {
|
|
1006
|
+
const abortHandler = () => {
|
|
1007
|
+
reject(new FragnoClientFetchAbortError("Operation was aborted"));
|
|
1008
|
+
};
|
|
1009
|
+
if (abortSignal.aborted) abortHandler();
|
|
1010
|
+
else abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Handles NDJSON streaming responses by returning the first item from the fetcher
|
|
1015
|
+
* and then continuing to stream updates via the store's mutate method.
|
|
1016
|
+
*
|
|
1017
|
+
* This makes it so that we can wait until the first chunk before updating the store, if we did
|
|
1018
|
+
* not do this, `loading` would briefly be false before the first item would be populated in the
|
|
1019
|
+
* result.
|
|
1020
|
+
*
|
|
1021
|
+
* @param response - The fetch Response object containing the NDJSON stream
|
|
1022
|
+
* @param store - The fetcher store to update with streaming data
|
|
1023
|
+
* @param abortSignal - Optional AbortSignal to cancel the streaming operation
|
|
1024
|
+
* @returns A promise that resolves to an object containing the first item and a streaming promise
|
|
1025
|
+
*/
|
|
1026
|
+
async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
|
|
1027
|
+
if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
|
|
1028
|
+
const { abortSignal } = options;
|
|
1029
|
+
if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1030
|
+
const decoder = new TextDecoder();
|
|
1031
|
+
const reader = response.body.getReader();
|
|
1032
|
+
let buffer = "";
|
|
1033
|
+
let firstItem = null;
|
|
1034
|
+
const items = [];
|
|
1035
|
+
try {
|
|
1036
|
+
while (firstItem === null) {
|
|
1037
|
+
if (abortSignal?.aborted) {
|
|
1038
|
+
reader.releaseLock();
|
|
1039
|
+
throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1040
|
+
}
|
|
1041
|
+
const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
|
|
1042
|
+
if (done) break;
|
|
1043
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1044
|
+
const lines = buffer.split("\n");
|
|
1045
|
+
buffer = lines.pop() || "";
|
|
1046
|
+
for (const line of lines) {
|
|
1047
|
+
if (!line.trim()) continue;
|
|
1048
|
+
try {
|
|
1049
|
+
const jsonObject = JSON.parse(line);
|
|
1050
|
+
items.push(jsonObject);
|
|
1051
|
+
if (firstItem === null) {
|
|
1052
|
+
firstItem = jsonObject;
|
|
1053
|
+
const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
|
|
1054
|
+
return {
|
|
1055
|
+
firstItem,
|
|
1056
|
+
streamingPromise
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
} catch (parseError) {
|
|
1060
|
+
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, { cause: parseError });
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (firstItem === null) {
|
|
1065
|
+
reader.releaseLock();
|
|
1066
|
+
throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
|
|
1067
|
+
}
|
|
1068
|
+
reader.releaseLock();
|
|
1069
|
+
throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
if (error instanceof FragnoClientError) {
|
|
1072
|
+
store?.setError(error);
|
|
1073
|
+
throw error;
|
|
1074
|
+
} else {
|
|
1075
|
+
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
|
|
1076
|
+
store?.setError(clientError);
|
|
1077
|
+
throw clientError;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Continues streaming the remaining items in the background
|
|
1083
|
+
*/
|
|
1084
|
+
async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
|
|
1085
|
+
let buffer = initialBuffer;
|
|
1086
|
+
try {
|
|
1087
|
+
while (true) {
|
|
1088
|
+
if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
|
|
1089
|
+
const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
|
|
1090
|
+
if (done) {
|
|
1091
|
+
if (buffer.trim()) {
|
|
1092
|
+
const lines$1 = buffer.split("\n");
|
|
1093
|
+
for (const line of lines$1) {
|
|
1094
|
+
if (!line.trim()) continue;
|
|
1095
|
+
try {
|
|
1096
|
+
const jsonObject = JSON.parse(line);
|
|
1097
|
+
items.push(jsonObject);
|
|
1098
|
+
store?.setData([...items]);
|
|
1099
|
+
} catch (parseError) {
|
|
1100
|
+
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1107
|
+
const lines = buffer.split("\n");
|
|
1108
|
+
buffer = lines.pop() || "";
|
|
1109
|
+
for (const line of lines) {
|
|
1110
|
+
if (!line.trim()) continue;
|
|
1111
|
+
try {
|
|
1112
|
+
const jsonObject = JSON.parse(line);
|
|
1113
|
+
items.push(jsonObject);
|
|
1114
|
+
store?.setData([...items]);
|
|
1115
|
+
} catch (parseError) {
|
|
1116
|
+
throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
if (error instanceof FragnoClientError) store?.setError(error);
|
|
1122
|
+
else {
|
|
1123
|
+
const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
|
|
1124
|
+
store?.setError(clientError);
|
|
1125
|
+
throw clientError;
|
|
1126
|
+
}
|
|
1127
|
+
throw error;
|
|
1128
|
+
} finally {
|
|
1129
|
+
reader.releaseLock();
|
|
1130
|
+
}
|
|
1131
|
+
return items;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1050
1134
|
//#endregion
|
|
1051
1135
|
//#region ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
|
|
1052
1136
|
let createNanoEvents = () => ({
|
|
@@ -1063,7 +1147,7 @@ let createNanoEvents = () => ({
|
|
|
1063
1147
|
});
|
|
1064
1148
|
|
|
1065
1149
|
//#endregion
|
|
1066
|
-
//#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.
|
|
1150
|
+
//#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.2.0/node_modules/@nanostores/query/dist/nanoquery.js
|
|
1067
1151
|
function defaultOnErrorRetry({ retryCount }) {
|
|
1068
1152
|
return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
|
|
1069
1153
|
}
|
|
@@ -1420,6 +1504,97 @@ const GET_HOOK_SYMBOL = Symbol("fragno-get-hook");
|
|
|
1420
1504
|
const MUTATOR_HOOK_SYMBOL = Symbol("fragno-mutator-hook");
|
|
1421
1505
|
const STORE_SYMBOL = Symbol("fragno-store");
|
|
1422
1506
|
/**
|
|
1507
|
+
* Check if a value contains files that should be sent as FormData.
|
|
1508
|
+
* @internal
|
|
1509
|
+
*/
|
|
1510
|
+
function containsFiles(value) {
|
|
1511
|
+
if (value instanceof File || value instanceof Blob) return true;
|
|
1512
|
+
if (value instanceof FormData) return true;
|
|
1513
|
+
if (typeof value === "object" && value !== null) return Object.values(value).some((v) => v instanceof File || v instanceof Blob || v instanceof FormData);
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Convert an object containing files to FormData.
|
|
1518
|
+
* Handles nested File/Blob values by appending them directly.
|
|
1519
|
+
* Other values are JSON-stringified.
|
|
1520
|
+
* @internal
|
|
1521
|
+
*/
|
|
1522
|
+
function toFormData(value) {
|
|
1523
|
+
const formData = new FormData();
|
|
1524
|
+
for (const [key, val] of Object.entries(value)) if (val instanceof File) formData.append(key, val, val.name);
|
|
1525
|
+
else if (val instanceof Blob) formData.append(key, val);
|
|
1526
|
+
else if (val !== void 0 && val !== null) formData.append(key, typeof val === "string" ? val : JSON.stringify(val));
|
|
1527
|
+
return formData;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Prepare request body and headers for sending.
|
|
1531
|
+
* Handles FormData (file uploads) vs JSON data.
|
|
1532
|
+
* @internal
|
|
1533
|
+
*/
|
|
1534
|
+
function prepareRequestBody(body, contentType) {
|
|
1535
|
+
if (body === void 0) return { body: void 0 };
|
|
1536
|
+
if (contentType === "application/octet-stream") {
|
|
1537
|
+
if (body instanceof ReadableStream || body instanceof Blob || body instanceof File || body instanceof ArrayBuffer || body instanceof Uint8Array) return {
|
|
1538
|
+
body,
|
|
1539
|
+
headers: { "Content-Type": "application/octet-stream" }
|
|
1540
|
+
};
|
|
1541
|
+
throw new Error("Octet-stream routes only accept Blob, File, ArrayBuffer, Uint8Array, or ReadableStream bodies.");
|
|
1542
|
+
}
|
|
1543
|
+
if (body instanceof FormData) return { body };
|
|
1544
|
+
if (body instanceof File) {
|
|
1545
|
+
const formData = new FormData();
|
|
1546
|
+
formData.append("file", body, body.name);
|
|
1547
|
+
return { body: formData };
|
|
1548
|
+
}
|
|
1549
|
+
if (body instanceof Blob) {
|
|
1550
|
+
const formData = new FormData();
|
|
1551
|
+
formData.append("file", body);
|
|
1552
|
+
return { body: formData };
|
|
1553
|
+
}
|
|
1554
|
+
if (typeof body === "object" && body !== null && containsFiles(body)) return { body: toFormData(body) };
|
|
1555
|
+
return {
|
|
1556
|
+
body: JSON.stringify(body),
|
|
1557
|
+
headers: { "Content-Type": "application/json" }
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
async function schemaAllowsUndefined(schema) {
|
|
1561
|
+
try {
|
|
1562
|
+
return !(await schema["~standard"].validate(void 0)).issues;
|
|
1563
|
+
} catch {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
async function assertBodyProvided(body, inputSchema, errorMessage) {
|
|
1568
|
+
if (typeof body !== "undefined" || inputSchema === void 0) return;
|
|
1569
|
+
if (await schemaAllowsUndefined(inputSchema)) return;
|
|
1570
|
+
throw new Error(errorMessage);
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Merge request headers from multiple sources.
|
|
1574
|
+
* Returns undefined if there are no headers to merge.
|
|
1575
|
+
* @internal
|
|
1576
|
+
*/
|
|
1577
|
+
function mergeRequestHeaders(...headerSources) {
|
|
1578
|
+
const result = {};
|
|
1579
|
+
let hasHeaders = false;
|
|
1580
|
+
for (const source of headerSources) {
|
|
1581
|
+
if (!source) continue;
|
|
1582
|
+
if (source instanceof Headers) for (const [key, value] of source.entries()) {
|
|
1583
|
+
result[key] = value;
|
|
1584
|
+
hasHeaders = true;
|
|
1585
|
+
}
|
|
1586
|
+
else if (Array.isArray(source)) for (const [key, value] of source) {
|
|
1587
|
+
result[key] = value;
|
|
1588
|
+
hasHeaders = true;
|
|
1589
|
+
}
|
|
1590
|
+
else for (const [key, value] of Object.entries(source)) {
|
|
1591
|
+
result[key] = value;
|
|
1592
|
+
hasHeaders = true;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return hasHeaders ? result : void 0;
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1423
1598
|
* @internal
|
|
1424
1599
|
*/
|
|
1425
1600
|
function buildUrl(config, params) {
|
|
@@ -1504,9 +1679,13 @@ var ClientBuilder = class {
|
|
|
1504
1679
|
get cacheEntries() {
|
|
1505
1680
|
return Object.fromEntries(this.#cache.entries());
|
|
1506
1681
|
}
|
|
1507
|
-
createStore(
|
|
1682
|
+
createStore(input) {
|
|
1683
|
+
if (typeof input === "function") return {
|
|
1684
|
+
factory: input,
|
|
1685
|
+
[STORE_SYMBOL]: true
|
|
1686
|
+
};
|
|
1508
1687
|
return {
|
|
1509
|
-
obj,
|
|
1688
|
+
obj: input,
|
|
1510
1689
|
[STORE_SYMBOL]: true
|
|
1511
1690
|
};
|
|
1512
1691
|
}
|
|
@@ -1517,7 +1696,10 @@ var ClientBuilder = class {
|
|
|
1517
1696
|
buildUrl(path, params) {
|
|
1518
1697
|
return buildUrl({
|
|
1519
1698
|
baseUrl: this.#publicConfig.baseUrl ?? "",
|
|
1520
|
-
mountRoute: getMountRoute(
|
|
1699
|
+
mountRoute: getMountRoute({
|
|
1700
|
+
name: this.#fragmentConfig.name,
|
|
1701
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1702
|
+
}),
|
|
1521
1703
|
path
|
|
1522
1704
|
}, {
|
|
1523
1705
|
pathParams: params?.path,
|
|
@@ -1536,7 +1718,7 @@ var ClientBuilder = class {
|
|
|
1536
1718
|
}
|
|
1537
1719
|
#getFetcher() {
|
|
1538
1720
|
if (this.#fetcherConfig?.type === "function") return this.#fetcherConfig.fetcher;
|
|
1539
|
-
return fetch;
|
|
1721
|
+
return globalThis.fetch.bind(globalThis);
|
|
1540
1722
|
}
|
|
1541
1723
|
#getFetcherOptions() {
|
|
1542
1724
|
if (this.#fetcherConfig?.type === "options") return this.#fetcherConfig.options;
|
|
@@ -1555,7 +1737,10 @@ var ClientBuilder = class {
|
|
|
1555
1737
|
if (route.method !== "GET") throw new Error(`Only GET routes are supported for hooks. Route '${route.path}' is a ${route.method} route.`);
|
|
1556
1738
|
if (!route.outputSchema) throw new Error(`Output schema is required for GET routes. Route '${route.path}' has no output schema.`);
|
|
1557
1739
|
const baseUrl = this.#publicConfig.baseUrl ?? "";
|
|
1558
|
-
const mountRoute = getMountRoute(
|
|
1740
|
+
const mountRoute = getMountRoute({
|
|
1741
|
+
name: this.#fragmentConfig.name,
|
|
1742
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1743
|
+
});
|
|
1559
1744
|
const fetcher = this.#getFetcher();
|
|
1560
1745
|
const fetcherOptions = this.#getFetcherOptions();
|
|
1561
1746
|
async function callServerSideHandler(params) {
|
|
@@ -1663,7 +1848,10 @@ var ClientBuilder = class {
|
|
|
1663
1848
|
#createRouteQueryMutator(route, onInvalidate = (invalidate, params) => invalidate("GET", route.path, params)) {
|
|
1664
1849
|
const method = route.method;
|
|
1665
1850
|
const baseUrl = this.#publicConfig.baseUrl ?? "";
|
|
1666
|
-
const mountRoute = getMountRoute(
|
|
1851
|
+
const mountRoute = getMountRoute({
|
|
1852
|
+
name: this.#fragmentConfig.name,
|
|
1853
|
+
mountRoute: this.#publicConfig.mountRoute
|
|
1854
|
+
});
|
|
1667
1855
|
const fetcher = this.#getFetcher();
|
|
1668
1856
|
const fetcherOptions = this.#getFetcherOptions();
|
|
1669
1857
|
async function executeMutateQuery({ body, path, query }) {
|
|
@@ -1685,11 +1873,16 @@ var ClientBuilder = class {
|
|
|
1685
1873
|
});
|
|
1686
1874
|
let response;
|
|
1687
1875
|
try {
|
|
1688
|
-
|
|
1876
|
+
const { body: preparedBody, headers: bodyHeaders } = prepareRequestBody(body, route.contentType);
|
|
1877
|
+
const mergedHeaders = mergeRequestHeaders(fetcherOptions?.headers, bodyHeaders);
|
|
1878
|
+
const requestOptions = {
|
|
1689
1879
|
...fetcherOptions,
|
|
1690
1880
|
method,
|
|
1691
|
-
body:
|
|
1692
|
-
|
|
1881
|
+
body: preparedBody,
|
|
1882
|
+
...mergedHeaders ? { headers: mergedHeaders } : {}
|
|
1883
|
+
};
|
|
1884
|
+
if (preparedBody instanceof ReadableStream) requestOptions.duplex = "half";
|
|
1885
|
+
response = await fetcher(url, requestOptions);
|
|
1693
1886
|
} catch (error) {
|
|
1694
1887
|
throw FragnoClientFetchError.fromUnknownFetchError(error);
|
|
1695
1888
|
}
|
|
@@ -1699,7 +1892,7 @@ var ClientBuilder = class {
|
|
|
1699
1892
|
const mutatorStore = this.#createMutatorStore(async ({ data }) => {
|
|
1700
1893
|
if (typeof window === "undefined") {}
|
|
1701
1894
|
const { body, path, query } = data;
|
|
1702
|
-
|
|
1895
|
+
await assertBodyProvided(body, route.inputSchema, "Body is required.");
|
|
1703
1896
|
const response = await executeMutateQuery({
|
|
1704
1897
|
body,
|
|
1705
1898
|
path,
|
|
@@ -1738,7 +1931,7 @@ var ClientBuilder = class {
|
|
|
1738
1931
|
} });
|
|
1739
1932
|
const mutateQuery = async (data) => {
|
|
1740
1933
|
const { body, path, query } = data;
|
|
1741
|
-
|
|
1934
|
+
await assertBodyProvided(body, route.inputSchema, "Body is required for mutateQuery");
|
|
1742
1935
|
const response = await executeMutateQuery({
|
|
1743
1936
|
body,
|
|
1744
1937
|
path,
|
|
@@ -1784,7 +1977,10 @@ function createClientBuilder(definition, publicConfig, routesOrFactories, author
|
|
|
1784
1977
|
name: definition.name,
|
|
1785
1978
|
routes: routes$1
|
|
1786
1979
|
};
|
|
1787
|
-
const mountRoute =
|
|
1980
|
+
const mountRoute = getMountRoute({
|
|
1981
|
+
name: definition.name,
|
|
1982
|
+
mountRoute: publicConfig.mountRoute
|
|
1983
|
+
});
|
|
1788
1984
|
const mergedFetcherConfig = mergeFetcherConfigs(authorFetcherConfig, publicConfig.fetcherConfig);
|
|
1789
1985
|
return new ClientBuilder({
|
|
1790
1986
|
...publicConfig,
|
|
@@ -1795,7 +1991,7 @@ function createClientBuilder(definition, publicConfig, routesOrFactories, author
|
|
|
1795
1991
|
|
|
1796
1992
|
//#endregion
|
|
1797
1993
|
//#region src/definition.ts
|
|
1798
|
-
const formsFragmentDef = defineFragment("forms").extend((x) => x).
|
|
1994
|
+
const formsFragmentDef = defineFragment("forms").extend((x) => x).providesBaseService(() => {}).build();
|
|
1799
1995
|
|
|
1800
1996
|
//#endregion
|
|
1801
1997
|
//#region src/models.ts
|
|
@@ -1810,11 +2006,11 @@ const FormStatusSchema = z.enum([
|
|
|
1810
2006
|
const FormSchema = z.object({
|
|
1811
2007
|
id: z.string(),
|
|
1812
2008
|
title: z.string(),
|
|
1813
|
-
description: z.string().nullable(),
|
|
1814
|
-
slug: z.string(),
|
|
2009
|
+
description: z.string().nullable().optional(),
|
|
2010
|
+
slug: z.string().min(1).max(255).toLowerCase().trim(),
|
|
1815
2011
|
status: FormStatusSchema,
|
|
1816
2012
|
dataSchema: JSONSchemaSchema,
|
|
1817
|
-
uiSchema: UISchemaElementSchema,
|
|
2013
|
+
uiSchema: UISchemaElementSchema.optional(),
|
|
1818
2014
|
version: z.number(),
|
|
1819
2015
|
createdAt: z.date(),
|
|
1820
2016
|
updatedAt: z.date()
|
|
@@ -1830,7 +2026,7 @@ const ResponseMetadataSchema = z.object({
|
|
|
1830
2026
|
ip: z.union([z.ipv4(), z.ipv6()]).nullable(),
|
|
1831
2027
|
userAgent: z.string().max(4096).nullable()
|
|
1832
2028
|
});
|
|
1833
|
-
const
|
|
2029
|
+
const FormResponseSchema = z.object({
|
|
1834
2030
|
id: z.string(),
|
|
1835
2031
|
formId: z.string().nullable().describe("Form ID (static form ID or database form external ID)"),
|
|
1836
2032
|
formVersion: z.number(),
|
|
@@ -1839,7 +2035,7 @@ const ResponseSchema = z.object({
|
|
|
1839
2035
|
ip: z.string().max(45).nullable(),
|
|
1840
2036
|
userAgent: z.string().max(512).nullable()
|
|
1841
2037
|
});
|
|
1842
|
-
const
|
|
2038
|
+
const NewFormResponseSchema = FormResponseSchema.omit({
|
|
1843
2039
|
id: true,
|
|
1844
2040
|
submittedAt: true,
|
|
1845
2041
|
formId: true,
|
|
@@ -1861,7 +2057,7 @@ const publicRoutes = defineRoutes(formsFragmentDef).create(({ services, defineRo
|
|
|
1861
2057
|
}), defineRoute({
|
|
1862
2058
|
method: "POST",
|
|
1863
2059
|
path: "/:slug/submit",
|
|
1864
|
-
inputSchema:
|
|
2060
|
+
inputSchema: NewFormResponseSchema,
|
|
1865
2061
|
outputSchema: z.string(),
|
|
1866
2062
|
errorCodes: [
|
|
1867
2063
|
"NOT_FOUND",
|
|
@@ -1879,19 +2075,30 @@ const adminRoutes = defineRoutes(formsFragmentDef).create(({ services, defineRou
|
|
|
1879
2075
|
outputSchema: z.array(FormSchema),
|
|
1880
2076
|
handler: () => {}
|
|
1881
2077
|
}),
|
|
2078
|
+
defineRoute({
|
|
2079
|
+
method: "GET",
|
|
2080
|
+
path: "/admin/forms/:id",
|
|
2081
|
+
outputSchema: FormSchema,
|
|
2082
|
+
errorCodes: ["NOT_FOUND"],
|
|
2083
|
+
handler: () => {}
|
|
2084
|
+
}),
|
|
1882
2085
|
defineRoute({
|
|
1883
2086
|
method: "POST",
|
|
1884
2087
|
path: "/admin/forms",
|
|
1885
2088
|
inputSchema: NewFormSchema,
|
|
1886
2089
|
outputSchema: z.string(),
|
|
1887
|
-
errorCodes: ["CREATE_FAILED"],
|
|
2090
|
+
errorCodes: ["CREATE_FAILED", "INVALID_JSON_SCHEMA"],
|
|
1888
2091
|
handler: () => {}
|
|
1889
2092
|
}),
|
|
1890
2093
|
defineRoute({
|
|
1891
2094
|
method: "PUT",
|
|
1892
2095
|
path: "/admin/forms/:id",
|
|
1893
2096
|
inputSchema: UpdateFormSchema,
|
|
1894
|
-
errorCodes: [
|
|
2097
|
+
errorCodes: [
|
|
2098
|
+
"NOT_FOUND",
|
|
2099
|
+
"STATIC_FORM_READ_ONLY",
|
|
2100
|
+
"INVALID_JSON_SCHEMA"
|
|
2101
|
+
],
|
|
1895
2102
|
handler: () => {}
|
|
1896
2103
|
}),
|
|
1897
2104
|
defineRoute({
|
|
@@ -1904,13 +2111,13 @@ const adminRoutes = defineRoutes(formsFragmentDef).create(({ services, defineRou
|
|
|
1904
2111
|
method: "GET",
|
|
1905
2112
|
path: "/admin/forms/:id/submissions",
|
|
1906
2113
|
queryParameters: ["sortOrder"],
|
|
1907
|
-
outputSchema: z.array(
|
|
2114
|
+
outputSchema: z.array(FormResponseSchema),
|
|
1908
2115
|
handler: () => {}
|
|
1909
2116
|
}),
|
|
1910
2117
|
defineRoute({
|
|
1911
2118
|
method: "GET",
|
|
1912
2119
|
path: "/admin/submissions/:id",
|
|
1913
|
-
outputSchema:
|
|
2120
|
+
outputSchema: FormResponseSchema,
|
|
1914
2121
|
errorCodes: ["NOT_FOUND"],
|
|
1915
2122
|
handler: () => {}
|
|
1916
2123
|
}),
|
|
@@ -1935,6 +2142,7 @@ function createFormsClients(fragnoConfig) {
|
|
|
1935
2142
|
useForm: b.createHook("/:slug"),
|
|
1936
2143
|
useSubmitForm: b.createMutator("POST", "/:slug/submit"),
|
|
1937
2144
|
useForms: b.createHook("/admin/forms"),
|
|
2145
|
+
useFormById: b.createHook("/admin/forms/:id"),
|
|
1938
2146
|
useCreateForm: b.createMutator("POST", "/admin/forms"),
|
|
1939
2147
|
useUpdateForm: b.createMutator("PUT", "/admin/forms/:id"),
|
|
1940
2148
|
useDeleteForm: b.createMutator("DELETE", "/admin/forms/:id"),
|
|
@@ -1946,4 +2154,4 @@ function createFormsClients(fragnoConfig) {
|
|
|
1946
2154
|
|
|
1947
2155
|
//#endregion
|
|
1948
2156
|
export { atom, createFormsClients, createFormsFragment, formsFragmentDef, isGetHook, isMutatorHook, isReadableAtom, isStore, routes };
|
|
1949
|
-
//# sourceMappingURL=src-
|
|
2157
|
+
//# sourceMappingURL=src-BNcd9ogF.js.map
|