@fragno-dev/forms 0.1.0 → 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.
@@ -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) {
@@ -444,170 +518,6 @@ var RequestOutputContext = class extends OutputContext {
444
518
  }
445
519
  };
446
520
 
447
- //#endregion
448
- //#region ../fragno/dist/api/internal/path.js
449
- /**
450
- * Extract parameter names from a path pattern at runtime.
451
- * Examples:
452
- * - "/users/:id" => ["id"]
453
- * - "/files/**" => ["**"]
454
- * - "/files/**:rest" => ["rest"]
455
- */
456
- function extractPathParams(pathPattern) {
457
- const segments = pathPattern.split("/").filter((s) => s.length > 0);
458
- const names = [];
459
- for (const segment of segments) {
460
- if (segment.startsWith(":")) {
461
- names.push(segment.slice(1));
462
- continue;
463
- }
464
- if (segment === "**") {
465
- names.push("**");
466
- continue;
467
- }
468
- if (segment.startsWith("**:")) {
469
- names.push(segment.slice(3));
470
- continue;
471
- }
472
- }
473
- return names;
474
- }
475
- /**
476
- * Build a concrete path by replacing placeholders in a path pattern with values.
477
- *
478
- * Supports the same placeholder syntax as the matcher:
479
- * - Named parameter ":name" is URL-encoded as a single segment
480
- * - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
481
- * - Named wildcard "**:name" inserts the remainder from the named key
482
- *
483
- * Examples:
484
- * - buildPath("/users/:id", { id: "123" }) => "/users/123"
485
- * - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
486
- * - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
487
- */
488
- function buildPath(pathPattern, params) {
489
- const patternSegments = pathPattern.split("/");
490
- const builtSegments = [];
491
- for (const segment of patternSegments) {
492
- if (segment.length === 0) {
493
- builtSegments.push("");
494
- continue;
495
- }
496
- if (segment.startsWith(":")) {
497
- const name = segment.slice(1);
498
- const value = params[name];
499
- if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
500
- builtSegments.push(encodeURIComponent(value));
501
- continue;
502
- }
503
- if (segment === "**") {
504
- const value = params["**"];
505
- if (value === void 0) throw new Error("Missing value for path wildcard **");
506
- builtSegments.push(value);
507
- continue;
508
- }
509
- if (segment.startsWith("**:")) {
510
- const name = segment.slice(3);
511
- const value = params[name];
512
- if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
513
- builtSegments.push(value);
514
- continue;
515
- }
516
- builtSegments.push(segment);
517
- }
518
- return builtSegments.join("/");
519
- }
520
-
521
- //#endregion
522
- //#region ../fragno/dist/client/client-error.js
523
- /**
524
- * Base error class for all Fragno client errors.
525
- */
526
- var FragnoClientError = class extends Error {
527
- #code;
528
- constructor(message, code, options = {}) {
529
- super(message, { cause: options.cause });
530
- this.name = "FragnoClientError";
531
- this.#code = code;
532
- }
533
- get code() {
534
- return this.#code;
535
- }
536
- };
537
- var FragnoClientFetchError = class extends FragnoClientError {
538
- constructor(message, code, options = {}) {
539
- super(message, code, options);
540
- this.name = "FragnoClientFetchError";
541
- }
542
- static fromUnknownFetchError(error) {
543
- if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
544
- if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
545
- return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
546
- }
547
- };
548
- /**
549
- * Error thrown when a network request fails (e.g., no internet connection, DNS failure).
550
- */
551
- var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
552
- constructor(message = "Network request failed", options = {}) {
553
- super(message, "NETWORK_ERROR", options);
554
- this.name = "FragnoClientFetchNetworkError";
555
- }
556
- };
557
- /**
558
- * Error thrown when a request is aborted (e.g., user cancels request, timeout).
559
- */
560
- var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
561
- constructor(message = "Request was aborted", options = {}) {
562
- super(message, "ABORT_ERROR", options);
563
- this.name = "FragnoClientFetchAbortError";
564
- }
565
- };
566
- /**
567
- * Error thrown when the API result is unexpected, e.g. no json is returned.
568
- */
569
- var FragnoClientUnknownApiError = class extends FragnoClientError {
570
- #status;
571
- constructor(message = "Unknown API error", status, options = {}) {
572
- super(message, "UNKNOWN_API_ERROR", options);
573
- this.name = "FragnoClientUnknownApiError";
574
- this.#status = status;
575
- }
576
- get status() {
577
- return this.#status;
578
- }
579
- };
580
- var FragnoClientApiError = class FragnoClientApiError$1 extends FragnoClientError {
581
- #status;
582
- constructor({ message, code }, status, options = {}) {
583
- super(message, code, options);
584
- this.name = "FragnoClientApiError";
585
- this.#status = status;
586
- }
587
- get status() {
588
- return this.#status;
589
- }
590
- /**
591
- * The error code returned by the API.
592
- *
593
- * The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
594
- * for forward compatibility with future error codes.
595
- */
596
- get code() {
597
- return super.code;
598
- }
599
- static async fromResponse(response) {
600
- const unknown = await response.json();
601
- const status = response.status;
602
- if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
603
- if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
604
- return new FragnoClientApiError$1({
605
- message: unknown.message,
606
- code: unknown.code
607
- }, status);
608
- }
609
- };
610
-
611
521
  //#endregion
612
522
  //#region ../fragno/dist/util/content-type.js
613
523
  /**
@@ -656,142 +566,36 @@ function parseContentType(contentType) {
656
566
  }
657
567
 
658
568
  //#endregion
659
- //#region ../fragno/dist/client/internal/ndjson-streaming.js
569
+ //#region ../fragno/dist/util/nanostores.js
660
570
  /**
661
- * 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.
662
572
  */
663
- function createAbortPromise(abortSignal) {
664
- return new Promise((_, reject) => {
665
- const abortHandler = () => {
666
- reject(new FragnoClientFetchAbortError("Operation was aborted"));
667
- };
668
- if (abortSignal.aborted) abortHandler();
669
- else abortSignal.addEventListener("abort", abortHandler, { once: true });
670
- });
573
+ function unwrapAtom(value) {
574
+ if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
575
+ return value;
671
576
  }
672
577
  /**
673
- * Handles NDJSON streaming responses by returning the first item from the fetcher
674
- * and then continuing to stream updates via the store's mutate method.
675
- *
676
- * This makes it so that we can wait until the first chunk before updating the store, if we did
677
- * not do this, `loading` would briefly be false before the first item would be populated in the
678
- * result.
679
- *
680
- * @param response - The fetch Response object containing the NDJSON stream
681
- * @param store - The fetcher store to update with streaming data
682
- * @param abortSignal - Optional AbortSignal to cancel the streaming operation
683
- * @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.
684
580
  */
685
- async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
686
- if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
687
- const { abortSignal } = options;
688
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
689
- const decoder = new TextDecoder();
690
- const reader = response.body.getReader();
691
- let buffer = "";
692
- let firstItem = null;
693
- const items = [];
694
- try {
695
- while (firstItem === null) {
696
- if (abortSignal?.aborted) {
697
- reader.releaseLock();
698
- throw new FragnoClientFetchAbortError("Operation was aborted");
699
- }
700
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
701
- if (done) break;
702
- buffer += decoder.decode(value, { stream: true });
703
- const lines = buffer.split("\n");
704
- buffer = lines.pop() || "";
705
- for (const line of lines) {
706
- if (!line.trim()) continue;
707
- try {
708
- const jsonObject = JSON.parse(line);
709
- items.push(jsonObject);
710
- if (firstItem === null) {
711
- firstItem = jsonObject;
712
- const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
713
- return {
714
- firstItem,
715
- streamingPromise
716
- };
717
- }
718
- } catch (parseError) {
719
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, { cause: parseError });
720
- }
721
- }
722
- }
723
- if (firstItem === null) {
724
- reader.releaseLock();
725
- throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
726
- }
727
- reader.releaseLock();
728
- throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
729
- } catch (error) {
730
- if (error instanceof FragnoClientError) {
731
- store?.setError(error);
732
- throw error;
733
- } else {
734
- const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
735
- store?.setError(clientError);
736
- throw clientError;
737
- }
738
- }
581
+ function unwrapObject(params) {
582
+ if (!params) return;
583
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
739
584
  }
740
- /**
741
- * Continues streaming the remaining items in the background
742
- */
743
- async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
744
- let buffer = initialBuffer;
745
- try {
746
- while (true) {
747
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
748
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
749
- if (done) {
750
- if (buffer.trim()) {
751
- const lines$1 = buffer.split("\n");
752
- for (const line of lines$1) {
753
- if (!line.trim()) continue;
754
- try {
755
- const jsonObject = JSON.parse(line);
756
- items.push(jsonObject);
757
- store?.setData([...items]);
758
- } catch (parseError) {
759
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
760
- }
761
- }
762
- }
763
- break;
764
- }
765
- buffer += decoder.decode(value, { stream: true });
766
- const lines = buffer.split("\n");
767
- buffer = lines.pop() || "";
768
- for (const line of lines) {
769
- if (!line.trim()) continue;
770
- try {
771
- const jsonObject = JSON.parse(line);
772
- items.push(jsonObject);
773
- store?.setData([...items]);
774
- } catch (parseError) {
775
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
776
- }
777
- }
778
- }
779
- } catch (error) {
780
- if (error instanceof FragnoClientError) store?.setError(error);
781
- else {
782
- const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
783
- store?.setError(clientError);
784
- throw clientError;
785
- }
786
- throw error;
787
- } finally {
788
- reader.releaseLock();
789
- }
790
- 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;
791
595
  }
792
596
 
793
597
  //#endregion
794
- //#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
795
599
  let tasks = 0;
796
600
  let resolves = [];
797
601
  function startTask() {
@@ -813,11 +617,11 @@ function task(cb) {
813
617
  }
814
618
 
815
619
  //#endregion
816
- //#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
817
621
  let clean = Symbol("clean");
818
622
 
819
623
  //#endregion
820
- //#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
821
625
  let listenerQueue = [];
822
626
  let lqIndex = 0;
823
627
  const QUEUE_ITEMS_PER_LISTENER = 4;
@@ -829,6 +633,7 @@ const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
829
633
  if (!$atom.lc) $atom.listen(() => {})();
830
634
  return $atom.value;
831
635
  },
636
+ init: initialValue,
832
637
  lc: 0,
833
638
  listen(listener) {
834
639
  $atom.lc = listeners.push(listener);
@@ -875,7 +680,7 @@ const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
875
680
  };
876
681
 
877
682
  //#endregion
878
- //#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
879
684
  const START = 0;
880
685
  const STOP = 1;
881
686
  const MOUNT = 5;
@@ -970,7 +775,21 @@ let onMount = ($store, initialize) => {
970
775
  };
971
776
 
972
777
  //#endregion
973
- //#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
974
793
  let computedStore = (stores$1, cb, batched$1) => {
975
794
  if (!Array.isArray(stores$1)) stores$1 = [stores$1];
976
795
  let previousArgs;
@@ -982,10 +801,12 @@ let computedStore = (stores$1, cb, batched$1) => {
982
801
  if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) {
983
802
  previousArgs = args;
984
803
  let value = cb(...args);
985
- if (value && value.then && value.t) value.then((asyncValue) => {
986
- if (previousArgs === args) $computed.set(asyncValue);
987
- });
988
- 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 {
989
810
  $computed.set(value);
990
811
  currentEpoch = epoch;
991
812
  }
@@ -1015,7 +836,7 @@ const computed = /* @__NO_SIDE_EFFECTS__ */ (stores$1, fn) => computedStore(stor
1015
836
  const batched = /* @__NO_SIDE_EFFECTS__ */ (stores$1, fn) => computedStore(stores$1, fn, true);
1016
837
 
1017
838
  //#endregion
1018
- //#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
1019
840
  const map = /* @__NO_SIDE_EFFECTS__ */ (initial = {}) => {
1020
841
  let $map = atom(initial);
1021
842
  $map.setKey = function(key, value) {
@@ -1052,33 +873,94 @@ function getInitialData(key) {
1052
873
  }
1053
874
 
1054
875
  //#endregion
1055
- //#region ../fragno/dist/util/nanostores.js
876
+ //#region ../fragno/dist/client/client-error.js
1056
877
  /**
1057
- * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
878
+ * Base error class for all Fragno client errors.
1058
879
  */
1059
- function unwrapAtom(value) {
1060
- if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
1061
- return value;
1062
- }
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
+ };
1063
902
  /**
1064
- * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
1065
- * Returns a new object with all values normalized to plain values.
903
+ * Error thrown when a network request fails (e.g., no internet connection, DNS failure).
1066
904
  */
1067
- function unwrapObject(params) {
1068
- if (!params) return;
1069
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
1070
- }
1071
- function isReadableAtom(value) {
1072
- if (!value) return false;
1073
- if (typeof value !== "object" || value === null) return false;
1074
- if (!("get" in value) || typeof value.get !== "function") return false;
1075
- if (!("lc" in value) || typeof value.lc !== "number") return false;
1076
- if (!("notify" in value) || typeof value.notify !== "function") return false;
1077
- if (!("off" in value) || typeof value.off !== "function") return false;
1078
- if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
1079
- if (!("value" in value)) return false;
1080
- return true;
1081
- }
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
+ };
911
+ /**
912
+ * Error thrown when a request is aborted (e.g., user cancels request, timeout).
913
+ */
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
+ };
920
+ /**
921
+ * Error thrown when the API result is unexpected, e.g. no json is returned.
922
+ */
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
+ };
1082
964
 
1083
965
  //#endregion
1084
966
  //#region ../fragno/dist/client/internal/fetcher-merge.js
@@ -1114,6 +996,141 @@ function mergeHeaders(author, user) {
1114
996
  return merged;
1115
997
  }
1116
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
+
1117
1134
  //#endregion
1118
1135
  //#region ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
1119
1136
  let createNanoEvents = () => ({
@@ -1130,7 +1147,7 @@ let createNanoEvents = () => ({
1130
1147
  });
1131
1148
 
1132
1149
  //#endregion
1133
- //#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
1134
1151
  function defaultOnErrorRetry({ retryCount }) {
1135
1152
  return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
1136
1153
  }
@@ -1540,6 +1557,18 @@ function prepareRequestBody(body, contentType) {
1540
1557
  headers: { "Content-Type": "application/json" }
1541
1558
  };
1542
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
+ }
1543
1572
  /**
1544
1573
  * Merge request headers from multiple sources.
1545
1574
  * Returns undefined if there are no headers to merge.
@@ -1650,9 +1679,13 @@ var ClientBuilder = class {
1650
1679
  get cacheEntries() {
1651
1680
  return Object.fromEntries(this.#cache.entries());
1652
1681
  }
1653
- createStore(obj) {
1682
+ createStore(input) {
1683
+ if (typeof input === "function") return {
1684
+ factory: input,
1685
+ [STORE_SYMBOL]: true
1686
+ };
1654
1687
  return {
1655
- obj,
1688
+ obj: input,
1656
1689
  [STORE_SYMBOL]: true
1657
1690
  };
1658
1691
  }
@@ -1685,7 +1718,7 @@ var ClientBuilder = class {
1685
1718
  }
1686
1719
  #getFetcher() {
1687
1720
  if (this.#fetcherConfig?.type === "function") return this.#fetcherConfig.fetcher;
1688
- return fetch;
1721
+ return globalThis.fetch.bind(globalThis);
1689
1722
  }
1690
1723
  #getFetcherOptions() {
1691
1724
  if (this.#fetcherConfig?.type === "options") return this.#fetcherConfig.options;
@@ -1859,7 +1892,7 @@ var ClientBuilder = class {
1859
1892
  const mutatorStore = this.#createMutatorStore(async ({ data }) => {
1860
1893
  if (typeof window === "undefined") {}
1861
1894
  const { body, path, query } = data;
1862
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required.");
1895
+ await assertBodyProvided(body, route.inputSchema, "Body is required.");
1863
1896
  const response = await executeMutateQuery({
1864
1897
  body,
1865
1898
  path,
@@ -1898,7 +1931,7 @@ var ClientBuilder = class {
1898
1931
  } });
1899
1932
  const mutateQuery = async (data) => {
1900
1933
  const { body, path, query } = data;
1901
- 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");
1902
1935
  const response = await executeMutateQuery({
1903
1936
  body,
1904
1937
  path,
@@ -1958,7 +1991,7 @@ function createClientBuilder(definition, publicConfig, routesOrFactories, author
1958
1991
 
1959
1992
  //#endregion
1960
1993
  //#region src/definition.ts
1961
- const formsFragmentDef = defineFragment("forms").extend((x) => x).withDependencies(() => {}).providesBaseService(() => {}).build();
1994
+ const formsFragmentDef = defineFragment("forms").extend((x) => x).providesBaseService(() => {}).build();
1962
1995
 
1963
1996
  //#endregion
1964
1997
  //#region src/models.ts
@@ -2121,4 +2154,4 @@ function createFormsClients(fragnoConfig) {
2121
2154
 
2122
2155
  //#endregion
2123
2156
  export { atom, createFormsClients, createFormsFragment, formsFragmentDef, isGetHook, isMutatorHook, isReadableAtom, isStore, routes };
2124
- //# sourceMappingURL=src-BBaSaf29.js.map
2157
+ //# sourceMappingURL=src-BNcd9ogF.js.map