@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.
- package/.turbo/turbo-build.log +61 -0
- package/.turbo/turbo-types$colon$check.log +2 -0
- package/dist/api/api.d.ts +2 -0
- package/dist/api/api.js +3 -0
- package/dist/api-CBDGZiLC.d.ts +278 -0
- package/dist/api-CBDGZiLC.d.ts.map +1 -0
- package/dist/api-DgHfYjq2.js +54 -0
- package/dist/api-DgHfYjq2.js.map +1 -0
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +6 -0
- package/dist/client/client.svelte.d.ts +33 -0
- package/dist/client/client.svelte.d.ts.map +1 -0
- package/dist/client/client.svelte.js +123 -0
- package/dist/client/client.svelte.js.map +1 -0
- package/dist/client/react.d.ts +58 -0
- package/dist/client/react.d.ts.map +1 -0
- package/dist/client/react.js +80 -0
- package/dist/client/react.js.map +1 -0
- package/dist/client/vanilla.d.ts +61 -0
- package/dist/client/vanilla.d.ts.map +1 -0
- package/dist/client/vanilla.js +136 -0
- package/dist/client/vanilla.js.map +1 -0
- package/dist/client/vue.d.ts +39 -0
- package/dist/client/vue.d.ts.map +1 -0
- package/dist/client/vue.js +108 -0
- package/dist/client/vue.js.map +1 -0
- package/dist/client-DWjxKDnE.js +703 -0
- package/dist/client-DWjxKDnE.js.map +1 -0
- package/dist/client-XFdAy-IQ.d.ts +287 -0
- package/dist/client-XFdAy-IQ.d.ts.map +1 -0
- package/dist/integrations/astro.d.ts +18 -0
- package/dist/integrations/astro.d.ts.map +1 -0
- package/dist/integrations/astro.js +16 -0
- package/dist/integrations/astro.js.map +1 -0
- package/dist/integrations/next-js.d.ts +15 -0
- package/dist/integrations/next-js.d.ts.map +1 -0
- package/dist/integrations/next-js.js +17 -0
- package/dist/integrations/next-js.js.map +1 -0
- package/dist/integrations/react-ssr.d.ts +19 -0
- package/dist/integrations/react-ssr.d.ts.map +1 -0
- package/dist/integrations/react-ssr.js +38 -0
- package/dist/integrations/react-ssr.js.map +1 -0
- package/dist/integrations/svelte-kit.d.ts +21 -0
- package/dist/integrations/svelte-kit.d.ts.map +1 -0
- package/dist/integrations/svelte-kit.js +18 -0
- package/dist/integrations/svelte-kit.js.map +1 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.js +177 -0
- package/dist/mod.js.map +1 -0
- package/dist/route-Bp6eByhz.js +331 -0
- package/dist/route-Bp6eByhz.js.map +1 -0
- package/dist/ssr-tJHqcNSw.js +48 -0
- package/dist/ssr-tJHqcNSw.js.map +1 -0
- package/package.json +127 -0
- package/src/api/api.test.ts +140 -0
- package/src/api/api.ts +106 -0
- package/src/api/error.ts +47 -0
- package/src/api/fragment.test.ts +509 -0
- package/src/api/fragment.ts +277 -0
- package/src/api/internal/path-runtime.test.ts +121 -0
- package/src/api/internal/path-type.test.ts +602 -0
- package/src/api/internal/path.ts +322 -0
- package/src/api/internal/response-stream.ts +118 -0
- package/src/api/internal/route.test.ts +56 -0
- package/src/api/internal/route.ts +9 -0
- package/src/api/request-input-context.test.ts +437 -0
- package/src/api/request-input-context.ts +201 -0
- package/src/api/request-middleware.test.ts +544 -0
- package/src/api/request-middleware.ts +126 -0
- package/src/api/request-output-context.test.ts +626 -0
- package/src/api/request-output-context.ts +175 -0
- package/src/api/route.test.ts +176 -0
- package/src/api/route.ts +152 -0
- package/src/client/client-builder.test.ts +264 -0
- package/src/client/client-error.test.ts +15 -0
- package/src/client/client-error.ts +141 -0
- package/src/client/client-types.test.ts +493 -0
- package/src/client/client.ssr.test.ts +173 -0
- package/src/client/client.svelte.test.ts +837 -0
- package/src/client/client.svelte.ts +278 -0
- package/src/client/client.test.ts +1690 -0
- package/src/client/client.ts +1035 -0
- package/src/client/component.test.svelte +21 -0
- package/src/client/internal/ndjson-streaming.test.ts +457 -0
- package/src/client/internal/ndjson-streaming.ts +248 -0
- package/src/client/react.test.ts +947 -0
- package/src/client/react.ts +241 -0
- package/src/client/vanilla.test.ts +867 -0
- package/src/client/vanilla.ts +265 -0
- package/src/client/vue.test.ts +754 -0
- package/src/client/vue.ts +242 -0
- package/src/http/http-status.ts +60 -0
- package/src/integrations/astro.ts +17 -0
- package/src/integrations/next-js.ts +31 -0
- package/src/integrations/react-ssr.ts +40 -0
- package/src/integrations/svelte-kit.ts +41 -0
- package/src/mod.ts +20 -0
- package/src/util/async.test.ts +85 -0
- package/src/util/async.ts +96 -0
- package/src/util/content-type.test.ts +136 -0
- package/src/util/content-type.ts +84 -0
- package/src/util/nanostores.test.ts +28 -0
- package/src/util/nanostores.ts +65 -0
- package/src/util/ssr.ts +75 -0
- package/src/util/types-util.ts +16 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +21 -0
- 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
|
+
}
|