@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.
Files changed (33) hide show
  1. package/README.md +159 -25
  2. package/dist/browser/client/react.d.ts +143 -94
  3. package/dist/browser/client/react.d.ts.map +1 -1
  4. package/dist/browser/client/react.js +105 -13
  5. package/dist/browser/client/react.js.map +1 -1
  6. package/dist/browser/client/solid.d.ts +143 -94
  7. package/dist/browser/client/solid.d.ts.map +1 -1
  8. package/dist/browser/client/solid.js +25 -11
  9. package/dist/browser/client/solid.js.map +1 -1
  10. package/dist/browser/client/svelte.d.ts +143 -94
  11. package/dist/browser/client/svelte.d.ts.map +1 -1
  12. package/dist/browser/client/svelte.js +11 -3
  13. package/dist/browser/client/svelte.js.map +1 -1
  14. package/dist/browser/client/vanilla.d.ts +142 -93
  15. package/dist/browser/client/vanilla.d.ts.map +1 -1
  16. package/dist/browser/client/vanilla.js +21 -1
  17. package/dist/browser/client/vanilla.js.map +1 -1
  18. package/dist/browser/client/vue.d.ts +73 -24
  19. package/dist/browser/client/vue.d.ts.map +1 -1
  20. package/dist/browser/client/vue.js +23 -1
  21. package/dist/browser/client/vue.js.map +1 -1
  22. package/dist/browser/index.d.ts +700 -301
  23. package/dist/browser/index.d.ts.map +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/browser/{src-DbMX_NY6.js → src-BNcd9ogF.js} +556 -348
  26. package/dist/browser/src-BNcd9ogF.js.map +1 -0
  27. package/dist/node/index.d.ts +546 -147
  28. package/dist/node/index.d.ts.map +1 -1
  29. package/dist/node/index.js +133 -103
  30. package/dist/node/index.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -0
  32. package/package.json +31 -52
  33. 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/client/internal/ndjson-streaming.js
569
+ //#region ../fragno/dist/util/nanostores.js
593
570
  /**
594
- * Creates a promise that rejects when the abort signal is triggered
571
+ * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
595
572
  */
596
- function createAbortPromise(abortSignal) {
597
- return new Promise((_, reject) => {
598
- const abortHandler = () => {
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
- * Handles NDJSON streaming responses by returning the first item from the fetcher
607
- * and then continuing to stream updates via the store's mutate method.
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
- async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
619
- if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
620
- const { abortSignal } = options;
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
- * Continues streaming the remaining items in the background
675
- */
676
- async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
677
- let buffer = initialBuffer;
678
- try {
679
- while (true) {
680
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
681
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
682
- if (done) {
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.1.0/node_modules/nanostores/task/index.js
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.1.0/node_modules/nanostores/clean-stores/index.js
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.1.0/node_modules/nanostores/atom/index.js
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.1.0/node_modules/nanostores/lifecycle/index.js
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.1.0/node_modules/nanostores/computed/index.js
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) value.then((asyncValue) => {
919
- if (previousArgs === args) $computed.set(asyncValue);
920
- });
921
- else {
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.1.0/node_modules/nanostores/map/index.js
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/util/nanostores.js
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
- * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
912
+ * Error thrown when a request is aborted (e.g., user cancels request, timeout).
991
913
  */
992
- function unwrapAtom(value) {
993
- if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
994
- return value;
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
- * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
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
- function unwrapObject(params) {
1001
- if (!params) return;
1002
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
1003
- }
1004
- function isReadableAtom(value) {
1005
- if (!value) return false;
1006
- if (typeof value !== "object" || value === null) return false;
1007
- if (!("get" in value) || typeof value.get !== "function") return false;
1008
- if (!("lc" in value) || typeof value.lc !== "number") return false;
1009
- if (!("notify" in value) || typeof value.notify !== "function") return false;
1010
- if (!("off" in value) || typeof value.off !== "function") return false;
1011
- if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
1012
- if (!("value" in value)) return false;
1013
- return true;
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.1.0/node_modules/@nanostores/query/dist/nanoquery.js
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(obj) {
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(this.#fragmentConfig),
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(this.#fragmentConfig);
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(this.#fragmentConfig);
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
- response = await fetcher(url, {
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: body !== void 0 ? JSON.stringify(body) : void 0
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
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required.");
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
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required for mutateQuery");
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 = publicConfig.mountRoute ?? `/${definition.name}`;
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).withDependencies(() => {}).providesBaseService(() => {}).build();
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 ResponseSchema = z.object({
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 NewResponseSchema = ResponseSchema.omit({
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: NewResponseSchema,
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: ["NOT_FOUND", "STATIC_FORM_READ_ONLY"],
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(ResponseSchema),
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: ResponseSchema,
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-DbMX_NY6.js.map
2157
+ //# sourceMappingURL=src-BNcd9ogF.js.map