@fragno-dev/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.turbo/turbo-build.log +61 -0
  2. package/.turbo/turbo-types$colon$check.log +2 -0
  3. package/dist/api/api.d.ts +2 -0
  4. package/dist/api/api.js +3 -0
  5. package/dist/api-CBDGZiLC.d.ts +278 -0
  6. package/dist/api-CBDGZiLC.d.ts.map +1 -0
  7. package/dist/api-DgHfYjq2.js +54 -0
  8. package/dist/api-DgHfYjq2.js.map +1 -0
  9. package/dist/client/client.d.ts +3 -0
  10. package/dist/client/client.js +6 -0
  11. package/dist/client/client.svelte.d.ts +33 -0
  12. package/dist/client/client.svelte.d.ts.map +1 -0
  13. package/dist/client/client.svelte.js +123 -0
  14. package/dist/client/client.svelte.js.map +1 -0
  15. package/dist/client/react.d.ts +58 -0
  16. package/dist/client/react.d.ts.map +1 -0
  17. package/dist/client/react.js +80 -0
  18. package/dist/client/react.js.map +1 -0
  19. package/dist/client/vanilla.d.ts +61 -0
  20. package/dist/client/vanilla.d.ts.map +1 -0
  21. package/dist/client/vanilla.js +136 -0
  22. package/dist/client/vanilla.js.map +1 -0
  23. package/dist/client/vue.d.ts +39 -0
  24. package/dist/client/vue.d.ts.map +1 -0
  25. package/dist/client/vue.js +108 -0
  26. package/dist/client/vue.js.map +1 -0
  27. package/dist/client-DWjxKDnE.js +703 -0
  28. package/dist/client-DWjxKDnE.js.map +1 -0
  29. package/dist/client-XFdAy-IQ.d.ts +287 -0
  30. package/dist/client-XFdAy-IQ.d.ts.map +1 -0
  31. package/dist/integrations/astro.d.ts +18 -0
  32. package/dist/integrations/astro.d.ts.map +1 -0
  33. package/dist/integrations/astro.js +16 -0
  34. package/dist/integrations/astro.js.map +1 -0
  35. package/dist/integrations/next-js.d.ts +15 -0
  36. package/dist/integrations/next-js.d.ts.map +1 -0
  37. package/dist/integrations/next-js.js +17 -0
  38. package/dist/integrations/next-js.js.map +1 -0
  39. package/dist/integrations/react-ssr.d.ts +19 -0
  40. package/dist/integrations/react-ssr.d.ts.map +1 -0
  41. package/dist/integrations/react-ssr.js +38 -0
  42. package/dist/integrations/react-ssr.js.map +1 -0
  43. package/dist/integrations/svelte-kit.d.ts +21 -0
  44. package/dist/integrations/svelte-kit.d.ts.map +1 -0
  45. package/dist/integrations/svelte-kit.js +18 -0
  46. package/dist/integrations/svelte-kit.js.map +1 -0
  47. package/dist/mod.d.ts +3 -0
  48. package/dist/mod.js +177 -0
  49. package/dist/mod.js.map +1 -0
  50. package/dist/route-Bp6eByhz.js +331 -0
  51. package/dist/route-Bp6eByhz.js.map +1 -0
  52. package/dist/ssr-tJHqcNSw.js +48 -0
  53. package/dist/ssr-tJHqcNSw.js.map +1 -0
  54. package/package.json +127 -0
  55. package/src/api/api.test.ts +140 -0
  56. package/src/api/api.ts +106 -0
  57. package/src/api/error.ts +47 -0
  58. package/src/api/fragment.test.ts +509 -0
  59. package/src/api/fragment.ts +277 -0
  60. package/src/api/internal/path-runtime.test.ts +121 -0
  61. package/src/api/internal/path-type.test.ts +602 -0
  62. package/src/api/internal/path.ts +322 -0
  63. package/src/api/internal/response-stream.ts +118 -0
  64. package/src/api/internal/route.test.ts +56 -0
  65. package/src/api/internal/route.ts +9 -0
  66. package/src/api/request-input-context.test.ts +437 -0
  67. package/src/api/request-input-context.ts +201 -0
  68. package/src/api/request-middleware.test.ts +544 -0
  69. package/src/api/request-middleware.ts +126 -0
  70. package/src/api/request-output-context.test.ts +626 -0
  71. package/src/api/request-output-context.ts +175 -0
  72. package/src/api/route.test.ts +176 -0
  73. package/src/api/route.ts +152 -0
  74. package/src/client/client-builder.test.ts +264 -0
  75. package/src/client/client-error.test.ts +15 -0
  76. package/src/client/client-error.ts +141 -0
  77. package/src/client/client-types.test.ts +493 -0
  78. package/src/client/client.ssr.test.ts +173 -0
  79. package/src/client/client.svelte.test.ts +837 -0
  80. package/src/client/client.svelte.ts +278 -0
  81. package/src/client/client.test.ts +1690 -0
  82. package/src/client/client.ts +1035 -0
  83. package/src/client/component.test.svelte +21 -0
  84. package/src/client/internal/ndjson-streaming.test.ts +457 -0
  85. package/src/client/internal/ndjson-streaming.ts +248 -0
  86. package/src/client/react.test.ts +947 -0
  87. package/src/client/react.ts +241 -0
  88. package/src/client/vanilla.test.ts +867 -0
  89. package/src/client/vanilla.ts +265 -0
  90. package/src/client/vue.test.ts +754 -0
  91. package/src/client/vue.ts +242 -0
  92. package/src/http/http-status.ts +60 -0
  93. package/src/integrations/astro.ts +17 -0
  94. package/src/integrations/next-js.ts +31 -0
  95. package/src/integrations/react-ssr.ts +40 -0
  96. package/src/integrations/svelte-kit.ts +41 -0
  97. package/src/mod.ts +20 -0
  98. package/src/util/async.test.ts +85 -0
  99. package/src/util/async.ts +96 -0
  100. package/src/util/content-type.test.ts +136 -0
  101. package/src/util/content-type.ts +84 -0
  102. package/src/util/nanostores.test.ts +28 -0
  103. package/src/util/nanostores.ts +65 -0
  104. package/src/util/ssr.ts +75 -0
  105. package/src/util/types-util.ts +16 -0
  106. package/tsconfig.json +10 -0
  107. package/tsdown.config.ts +21 -0
  108. package/vitest.config.ts +10 -0
@@ -0,0 +1,248 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import {
3
+ FragnoClientError,
4
+ FragnoClientFetchError,
5
+ FragnoClientFetchAbortError,
6
+ FragnoClientUnknownApiError,
7
+ } from "../client-error";
8
+
9
+ /**
10
+ * Creates a promise that rejects when the abort signal is triggered
11
+ */
12
+ function createAbortPromise(abortSignal: AbortSignal): Promise<never> {
13
+ return new Promise<never>((_, reject) => {
14
+ const abortHandler = () => {
15
+ reject(new FragnoClientFetchAbortError("Operation was aborted"));
16
+ };
17
+
18
+ if (abortSignal.aborted) {
19
+ abortHandler();
20
+ } else {
21
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
22
+ }
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Result of NDJSON streaming that includes the first item and a promise for the streaming continuation
28
+ */
29
+ export interface NdjsonStreamingResult<T> {
30
+ firstItem: T;
31
+ streamingPromise: Promise<T[]>;
32
+ }
33
+
34
+ export interface NdjsonStreamingStore<
35
+ TOutputSchema extends StandardSchemaV1,
36
+ TErrorCode extends string,
37
+ > {
38
+ setData(value: StandardSchemaV1.InferOutput<TOutputSchema>): void;
39
+ setError(value: FragnoClientError<TErrorCode>): void;
40
+ }
41
+
42
+ /**
43
+ * Handles NDJSON streaming responses by returning the first item from the fetcher
44
+ * and then continuing to stream updates via the store's mutate method.
45
+ *
46
+ * This makes it so that we can wait until the first chunk before updating the store, if we did
47
+ * not do this, `loading` would briefly be false before the first item would be populated in the
48
+ * result.
49
+ *
50
+ * @param response - The fetch Response object containing the NDJSON stream
51
+ * @param store - The fetcher store to update with streaming data
52
+ * @param abortSignal - Optional AbortSignal to cancel the streaming operation
53
+ * @returns A promise that resolves to an object containing the first item and a streaming promise
54
+ */
55
+ export async function handleNdjsonStreamingFirstItem<
56
+ TOutputSchema extends StandardSchemaV1,
57
+ TErrorCode extends string,
58
+ >(
59
+ response: Response,
60
+ store?: NdjsonStreamingStore<TOutputSchema, TErrorCode>,
61
+ options: { abortSignal?: AbortSignal } = {},
62
+ ): Promise<NdjsonStreamingResult<StandardSchemaV1.InferOutput<TOutputSchema>>> {
63
+ if (!response.body) {
64
+ throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
65
+ }
66
+
67
+ const { abortSignal } = options;
68
+
69
+ if (abortSignal?.aborted) {
70
+ throw new FragnoClientFetchAbortError("Operation was aborted");
71
+ }
72
+
73
+ const decoder = new TextDecoder();
74
+ const reader = response.body.getReader();
75
+ let buffer = "";
76
+ let firstItem: StandardSchemaV1.InferOutput<TOutputSchema> | null = null;
77
+ const items: StandardSchemaV1.InferOutput<TOutputSchema>[] = [];
78
+
79
+ try {
80
+ // Read until we get the first item
81
+ while (firstItem === null) {
82
+ // Check for abort signal before each read
83
+ if (abortSignal?.aborted) {
84
+ reader.releaseLock();
85
+ throw new FragnoClientFetchAbortError("Operation was aborted");
86
+ }
87
+
88
+ const { done, value } = await (abortSignal
89
+ ? Promise.race([reader.read(), createAbortPromise(abortSignal)])
90
+ : reader.read());
91
+
92
+ if (done) {
93
+ break;
94
+ }
95
+
96
+ // Decode the chunk and add to buffer
97
+ buffer += decoder.decode(value, { stream: true });
98
+
99
+ // Process complete lines
100
+ const lines = buffer.split("\n");
101
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
102
+
103
+ for (const line of lines) {
104
+ if (!line.trim()) {
105
+ continue;
106
+ }
107
+
108
+ try {
109
+ const jsonObject = JSON.parse(line) as StandardSchemaV1.InferOutput<TOutputSchema>;
110
+ items.push(jsonObject);
111
+
112
+ if (firstItem === null) {
113
+ firstItem = jsonObject;
114
+ // We don't call store.setKey here for the first item
115
+ // The caller will handle it via the return value
116
+
117
+ // Start background streaming for remaining items and return the promise
118
+ const streamingPromise = continueStreaming(
119
+ reader,
120
+ decoder,
121
+ buffer,
122
+ items,
123
+ store,
124
+ abortSignal,
125
+ );
126
+ return {
127
+ firstItem,
128
+ streamingPromise,
129
+ };
130
+ }
131
+ } catch (parseError) {
132
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, {
133
+ cause: parseError,
134
+ });
135
+ }
136
+ }
137
+ }
138
+
139
+ // If we get here and haven't returned a first item, the stream was empty
140
+ if (firstItem === null) {
141
+ reader.releaseLock();
142
+ throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
143
+ }
144
+
145
+ // This should never be reached, but TypeScript needs it
146
+ reader.releaseLock();
147
+ throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
148
+ } catch (error) {
149
+ // Handle errors during streaming
150
+ if (error instanceof FragnoClientError) {
151
+ store?.setError(error);
152
+ throw error;
153
+ } else {
154
+ // TODO: Not sure about the typing here
155
+ const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, {
156
+ cause: error,
157
+ }) as unknown as FragnoClientError<TErrorCode>;
158
+ store?.setError(clientError);
159
+ throw clientError;
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Continues streaming the remaining items in the background
166
+ */
167
+ // FIXME: Shitty code
168
+ async function continueStreaming<TOutputSchema extends StandardSchemaV1, TErrorCode extends string>(
169
+ reader: ReadableStreamDefaultReader<Uint8Array>,
170
+ decoder: TextDecoder,
171
+ initialBuffer: string,
172
+ items: StandardSchemaV1.InferOutput<TOutputSchema>[],
173
+ store?: NdjsonStreamingStore<TOutputSchema, TErrorCode>,
174
+ abortSignal?: AbortSignal,
175
+ ): Promise<StandardSchemaV1.InferOutput<TOutputSchema>[]> {
176
+ let buffer = initialBuffer;
177
+
178
+ try {
179
+ while (true) {
180
+ // Check for abort signal before each read
181
+ if (abortSignal?.aborted) {
182
+ throw new FragnoClientFetchAbortError("Operation was aborted");
183
+ }
184
+
185
+ const { done, value } = await (abortSignal
186
+ ? Promise.race([reader.read(), createAbortPromise(abortSignal)])
187
+ : reader.read());
188
+
189
+ if (done) {
190
+ // Process any remaining buffer content
191
+ if (buffer.trim()) {
192
+ const lines = buffer.split("\n");
193
+ for (const line of lines) {
194
+ if (!line.trim()) continue;
195
+
196
+ try {
197
+ const jsonObject = JSON.parse(line) as StandardSchemaV1.InferOutput<TOutputSchema>;
198
+ items.push(jsonObject);
199
+ store?.setData([...items]);
200
+ } catch (parseError) {
201
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, {
202
+ cause: parseError,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ break;
208
+ }
209
+
210
+ // Decode the chunk and add to buffer
211
+ buffer += decoder.decode(value, { stream: true });
212
+
213
+ // Process complete lines
214
+ const lines = buffer.split("\n");
215
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
216
+
217
+ for (const line of lines) {
218
+ if (!line.trim()) continue;
219
+
220
+ try {
221
+ const jsonObject = JSON.parse(line) as StandardSchemaV1.InferOutput<TOutputSchema>;
222
+ items.push(jsonObject);
223
+ store?.setData([...items]);
224
+ } catch (parseError) {
225
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, {
226
+ cause: parseError,
227
+ });
228
+ }
229
+ }
230
+ }
231
+ } catch (error) {
232
+ if (error instanceof FragnoClientError) {
233
+ store?.setError(error);
234
+ } else {
235
+ const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, {
236
+ cause: error,
237
+ }) as unknown as FragnoClientError<TErrorCode>;
238
+ store?.setError(clientError);
239
+ throw clientError;
240
+ }
241
+
242
+ throw error;
243
+ } finally {
244
+ reader.releaseLock();
245
+ }
246
+
247
+ return items;
248
+ }