@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,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Readable } from "svelte/store";
|
|
3
|
+
import { useFragno } from "./client.svelte";
|
|
4
|
+
|
|
5
|
+
export let clientObj: Record<string, unknown>;
|
|
6
|
+
export let hookName: string;
|
|
7
|
+
export let args: Record<string, unknown> = {};
|
|
8
|
+
|
|
9
|
+
const fragnoHooks = useFragno(clientObj);
|
|
10
|
+
const hookResult = fragnoHooks[hookName](args);
|
|
11
|
+
|
|
12
|
+
// Export hook results so they can be accessed from tests
|
|
13
|
+
export const loading = hookResult.loading as Readable<boolean>;
|
|
14
|
+
export const data = hookResult.data as Readable<unknown>;
|
|
15
|
+
export const error = hookResult.error as Readable<Error | undefined>;
|
|
16
|
+
export const mutate = hookResult.mutate as CallableFunction;
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<div data-testid="test-component">
|
|
20
|
+
Test Component - Hook: {hookName}
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { handleNdjsonStreamingFirstItem, type NdjsonStreamingStore } from "./ndjson-streaming";
|
|
3
|
+
import { FragnoClientError, FragnoClientFetchAbortError } from "../client-error";
|
|
4
|
+
import { nanoquery } from "@nanostores/query";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
7
|
+
import { createAsyncIteratorFromCallback } from "../../util/async";
|
|
8
|
+
|
|
9
|
+
describe("handleNdjsonStreaming", () => {
|
|
10
|
+
test("should return first item and continue streaming updates", async () => {
|
|
11
|
+
// Create a mock response with streaming body
|
|
12
|
+
const mockReader = {
|
|
13
|
+
read: vi
|
|
14
|
+
.fn()
|
|
15
|
+
.mockResolvedValueOnce({
|
|
16
|
+
done: false,
|
|
17
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
18
|
+
})
|
|
19
|
+
.mockResolvedValueOnce({
|
|
20
|
+
done: false,
|
|
21
|
+
value: new TextEncoder().encode('{"id": 2, "name": "Item 2"}\n'),
|
|
22
|
+
})
|
|
23
|
+
.mockResolvedValueOnce({
|
|
24
|
+
done: false,
|
|
25
|
+
value: new TextEncoder().encode('{"id": 3, "name": "Item 3"}\n'),
|
|
26
|
+
})
|
|
27
|
+
.mockResolvedValueOnce({ done: true, value: undefined }),
|
|
28
|
+
releaseLock: vi.fn(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const mockResponse = {
|
|
32
|
+
body: {
|
|
33
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
34
|
+
},
|
|
35
|
+
} as unknown as Response;
|
|
36
|
+
|
|
37
|
+
const mockStore = {
|
|
38
|
+
setData: vi.fn(),
|
|
39
|
+
setError: vi.fn(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const { firstItem, streamingPromise } = await handleNdjsonStreamingFirstItem(
|
|
43
|
+
mockResponse,
|
|
44
|
+
mockStore,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Verify the result structure
|
|
48
|
+
expect(firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
49
|
+
expect(streamingPromise).toBeInstanceOf(Promise);
|
|
50
|
+
|
|
51
|
+
// Wait for the streaming to complete
|
|
52
|
+
const streamingResult = await streamingPromise;
|
|
53
|
+
expect(streamingResult).toEqual([
|
|
54
|
+
{ id: 1, name: "Item 1" },
|
|
55
|
+
{ id: 2, name: "Item 2" },
|
|
56
|
+
{ id: 3, name: "Item 3" },
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// Verify the store was updated with all items
|
|
60
|
+
expect(mockStore.setData).toHaveBeenCalledWith([
|
|
61
|
+
{ id: 1, name: "Item 1" },
|
|
62
|
+
{ id: 2, name: "Item 2" },
|
|
63
|
+
{ id: 3, name: "Item 3" },
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// Verify the reader was properly released
|
|
67
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should handle empty stream", async () => {
|
|
71
|
+
const mockReader = {
|
|
72
|
+
read: vi.fn().mockResolvedValue({ done: true, value: undefined }),
|
|
73
|
+
releaseLock: vi.fn(),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mockResponse = {
|
|
77
|
+
body: {
|
|
78
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
79
|
+
},
|
|
80
|
+
} as unknown as Response;
|
|
81
|
+
|
|
82
|
+
const mockStore = {
|
|
83
|
+
setData: vi.fn(),
|
|
84
|
+
setError: vi.fn(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Should throw an error for empty stream
|
|
88
|
+
await expect(handleNdjsonStreamingFirstItem(mockResponse, mockStore)).rejects.toThrow(
|
|
89
|
+
"NDJSON stream contained no valid items",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("should handle response without body", async () => {
|
|
94
|
+
const mockResponse = {
|
|
95
|
+
body: null,
|
|
96
|
+
} as unknown as Response;
|
|
97
|
+
|
|
98
|
+
const mockStore = {
|
|
99
|
+
setData: vi.fn(),
|
|
100
|
+
setError: vi.fn(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Should throw an error for response without body
|
|
104
|
+
await expect(handleNdjsonStreamingFirstItem(mockResponse, mockStore)).rejects.toThrow(
|
|
105
|
+
"Streaming response has no body",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("should handle abort signal that is already aborted", async () => {
|
|
110
|
+
const mockResponse = {
|
|
111
|
+
body: {
|
|
112
|
+
getReader: vi.fn(),
|
|
113
|
+
},
|
|
114
|
+
} as unknown as Response;
|
|
115
|
+
|
|
116
|
+
const mockStore = {
|
|
117
|
+
setData: vi.fn(),
|
|
118
|
+
setError: vi.fn(),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const abortController = new AbortController();
|
|
122
|
+
abortController.abort();
|
|
123
|
+
|
|
124
|
+
// Should throw an abort error immediately
|
|
125
|
+
await expect(
|
|
126
|
+
handleNdjsonStreamingFirstItem(mockResponse, mockStore, {
|
|
127
|
+
abortSignal: abortController.signal,
|
|
128
|
+
}),
|
|
129
|
+
).rejects.toThrow(FragnoClientFetchAbortError);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("should provide streaming promise that can be awaited", async () => {
|
|
133
|
+
const abortController = new AbortController();
|
|
134
|
+
|
|
135
|
+
const mockReader = {
|
|
136
|
+
read: vi
|
|
137
|
+
.fn()
|
|
138
|
+
.mockResolvedValueOnce({
|
|
139
|
+
done: false,
|
|
140
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
141
|
+
})
|
|
142
|
+
.mockResolvedValueOnce({
|
|
143
|
+
done: false,
|
|
144
|
+
value: new TextEncoder().encode('{"id": 2, "name": "Item 2"}\n'),
|
|
145
|
+
})
|
|
146
|
+
.mockResolvedValue({ done: true, value: undefined }),
|
|
147
|
+
releaseLock: vi.fn(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockResponse = {
|
|
151
|
+
body: {
|
|
152
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
153
|
+
},
|
|
154
|
+
} as unknown as Response;
|
|
155
|
+
|
|
156
|
+
const mockStore = {
|
|
157
|
+
setData: vi.fn(),
|
|
158
|
+
setError: vi.fn(),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const result = await handleNdjsonStreamingFirstItem(mockResponse, mockStore, {
|
|
162
|
+
abortSignal: abortController.signal,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// The function should succeed in getting the first item
|
|
166
|
+
expect(result.firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
167
|
+
|
|
168
|
+
// The streaming promise should be available for awaiting
|
|
169
|
+
expect(result.streamingPromise).toBeInstanceOf(Promise);
|
|
170
|
+
|
|
171
|
+
// The streaming promise should resolve when all data is processed
|
|
172
|
+
await expect(result.streamingPromise).resolves.toEqual([
|
|
173
|
+
{ id: 1, name: "Item 1" },
|
|
174
|
+
{ id: 2, name: "Item 2" },
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
// Verify all items were processed
|
|
178
|
+
expect(mockStore.setData).toHaveBeenCalledWith([
|
|
179
|
+
{ id: 1, name: "Item 1" },
|
|
180
|
+
{ id: 2, name: "Item 2" },
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// Verify the reader was properly released
|
|
184
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("should handle abort signal with no additional streaming data", async () => {
|
|
188
|
+
const abortController = new AbortController();
|
|
189
|
+
|
|
190
|
+
const mockReader = {
|
|
191
|
+
read: vi
|
|
192
|
+
.fn()
|
|
193
|
+
.mockResolvedValueOnce({
|
|
194
|
+
done: false,
|
|
195
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
196
|
+
})
|
|
197
|
+
.mockResolvedValue({ done: true, value: undefined }),
|
|
198
|
+
releaseLock: vi.fn(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const mockResponse = {
|
|
202
|
+
body: {
|
|
203
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
204
|
+
},
|
|
205
|
+
} as unknown as Response;
|
|
206
|
+
|
|
207
|
+
const mockStore = {
|
|
208
|
+
setData: vi.fn(),
|
|
209
|
+
setError: vi.fn(),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const result = await handleNdjsonStreamingFirstItem(mockResponse, mockStore, {
|
|
213
|
+
abortSignal: abortController.signal,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Verify the first item is returned
|
|
217
|
+
expect(result.firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
218
|
+
|
|
219
|
+
// The streaming promise should resolve successfully since there's no more data
|
|
220
|
+
await expect(result.streamingPromise).resolves.toEqual([{ id: 1, name: "Item 1" }]);
|
|
221
|
+
|
|
222
|
+
// Verify the reader was properly released
|
|
223
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("should complete streaming promise when all data is processed", async () => {
|
|
227
|
+
const mockReader = {
|
|
228
|
+
read: vi
|
|
229
|
+
.fn()
|
|
230
|
+
.mockResolvedValueOnce({
|
|
231
|
+
done: false,
|
|
232
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
233
|
+
})
|
|
234
|
+
.mockResolvedValueOnce({
|
|
235
|
+
done: false,
|
|
236
|
+
value: new TextEncoder().encode('{"id": 2, "name": "Item 2"}\n'),
|
|
237
|
+
})
|
|
238
|
+
.mockResolvedValueOnce({ done: true, value: undefined }),
|
|
239
|
+
releaseLock: vi.fn(),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const mockResponse = {
|
|
243
|
+
body: {
|
|
244
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
245
|
+
},
|
|
246
|
+
} as unknown as Response;
|
|
247
|
+
|
|
248
|
+
const mockStore = {
|
|
249
|
+
setData: vi.fn(),
|
|
250
|
+
setError: vi.fn(),
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const result = await handleNdjsonStreamingFirstItem(mockResponse, mockStore);
|
|
254
|
+
|
|
255
|
+
// Verify the first item is returned
|
|
256
|
+
expect(result.firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
257
|
+
|
|
258
|
+
// The streaming promise should resolve successfully
|
|
259
|
+
await expect(result.streamingPromise).resolves.toEqual([
|
|
260
|
+
{ id: 1, name: "Item 1" },
|
|
261
|
+
{ id: 2, name: "Item 2" },
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
// Verify all items were processed
|
|
265
|
+
expect(mockStore.setData).toHaveBeenCalledWith([
|
|
266
|
+
{ id: 1, name: "Item 1" },
|
|
267
|
+
{ id: 2, name: "Item 2" },
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
// Verify the reader was properly released
|
|
271
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("should handle streaming errors and set them in the store", async () => {
|
|
275
|
+
const mockReader = {
|
|
276
|
+
read: vi
|
|
277
|
+
.fn()
|
|
278
|
+
.mockResolvedValueOnce({
|
|
279
|
+
done: false,
|
|
280
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
281
|
+
})
|
|
282
|
+
.mockRejectedValueOnce(new Error("Network error")),
|
|
283
|
+
releaseLock: vi.fn(),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const mockResponse = {
|
|
287
|
+
body: {
|
|
288
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
289
|
+
},
|
|
290
|
+
} as unknown as Response;
|
|
291
|
+
|
|
292
|
+
const mockStore = {
|
|
293
|
+
setData: vi.fn(),
|
|
294
|
+
setError: vi.fn(),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const result = await handleNdjsonStreamingFirstItem(mockResponse, mockStore);
|
|
298
|
+
|
|
299
|
+
// Verify the first item is returned
|
|
300
|
+
expect(result.firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
301
|
+
|
|
302
|
+
// The streaming promise should reject with the error
|
|
303
|
+
await expect(result.streamingPromise).rejects.toThrow("Unknown streaming error");
|
|
304
|
+
|
|
305
|
+
// Verify an error was set in the store
|
|
306
|
+
expect(mockStore.setError).toHaveBeenCalledWith(expect.any(Object));
|
|
307
|
+
|
|
308
|
+
// Verify the reader was properly released
|
|
309
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("should support abort signal for interrupting hanging operations", async () => {
|
|
313
|
+
// This test validates that the abort signal pattern is correctly implemented
|
|
314
|
+
// The actual interruption behavior depends on the browser's ReadableStream implementation
|
|
315
|
+
const abortController = new AbortController();
|
|
316
|
+
|
|
317
|
+
const mockReader = {
|
|
318
|
+
read: vi
|
|
319
|
+
.fn()
|
|
320
|
+
.mockResolvedValueOnce({
|
|
321
|
+
done: false,
|
|
322
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
323
|
+
})
|
|
324
|
+
.mockResolvedValue({ done: true, value: undefined }),
|
|
325
|
+
releaseLock: vi.fn(),
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const mockResponse = {
|
|
329
|
+
body: {
|
|
330
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
331
|
+
},
|
|
332
|
+
} as unknown as Response;
|
|
333
|
+
|
|
334
|
+
const mockStore = {
|
|
335
|
+
setData: vi.fn(),
|
|
336
|
+
setError: vi.fn(),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const result = await handleNdjsonStreamingFirstItem(mockResponse, mockStore, {
|
|
340
|
+
abortSignal: abortController.signal,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Verify the first item is returned
|
|
344
|
+
expect(result.firstItem).toEqual({ id: 1, name: "Item 1" });
|
|
345
|
+
|
|
346
|
+
// The streaming promise should complete successfully since there's no more data
|
|
347
|
+
await expect(result.streamingPromise).resolves.toEqual([{ id: 1, name: "Item 1" }]);
|
|
348
|
+
|
|
349
|
+
expect(mockReader.releaseLock).toHaveBeenCalled();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("should update store as stream progresses", async () => {
|
|
353
|
+
const mockReader = {
|
|
354
|
+
read: vi
|
|
355
|
+
.fn()
|
|
356
|
+
.mockResolvedValueOnce({
|
|
357
|
+
done: false,
|
|
358
|
+
value: new TextEncoder().encode('{"id": 1, "name": "Item 1"}\n'),
|
|
359
|
+
})
|
|
360
|
+
.mockImplementationOnce(async () => {
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
362
|
+
return {
|
|
363
|
+
done: false,
|
|
364
|
+
value: new TextEncoder().encode('{"id": 2, "name": "Item 2"}\n'),
|
|
365
|
+
};
|
|
366
|
+
})
|
|
367
|
+
.mockImplementationOnce(async () => {
|
|
368
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
369
|
+
return {
|
|
370
|
+
done: false,
|
|
371
|
+
value: new TextEncoder().encode('{"id": 3, "name": "Item 3"}\n'),
|
|
372
|
+
};
|
|
373
|
+
})
|
|
374
|
+
.mockResolvedValueOnce({ done: true, value: undefined }),
|
|
375
|
+
releaseLock: vi.fn(),
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const mockResponse = {
|
|
379
|
+
body: {
|
|
380
|
+
getReader: vi.fn().mockReturnValue(mockReader),
|
|
381
|
+
},
|
|
382
|
+
} as unknown as Response;
|
|
383
|
+
|
|
384
|
+
const [, createMutatorStore] = nanoquery();
|
|
385
|
+
|
|
386
|
+
const _schema = z.array(
|
|
387
|
+
z.object({
|
|
388
|
+
id: z.number(),
|
|
389
|
+
name: z.string(),
|
|
390
|
+
}),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const mutatorStore = createMutatorStore<
|
|
394
|
+
undefined,
|
|
395
|
+
StandardSchemaV1.InferOutput<typeof _schema>,
|
|
396
|
+
FragnoClientError<string>
|
|
397
|
+
>(() => {
|
|
398
|
+
return Promise.resolve([]);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const storeAdapter: NdjsonStreamingStore<typeof _schema, string> = {
|
|
402
|
+
setData: (value) => {
|
|
403
|
+
mutatorStore.set({
|
|
404
|
+
...mutatorStore.get(),
|
|
405
|
+
data: value,
|
|
406
|
+
});
|
|
407
|
+
},
|
|
408
|
+
setError: (value) => {
|
|
409
|
+
mutatorStore.set({
|
|
410
|
+
...mutatorStore.get(),
|
|
411
|
+
error: value,
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const itt = createAsyncIteratorFromCallback(mutatorStore.listen);
|
|
417
|
+
const promise = handleNdjsonStreamingFirstItem(mockResponse, storeAdapter);
|
|
418
|
+
|
|
419
|
+
// We immediately skip to 2 items being available, because normally the fetcher method would
|
|
420
|
+
// return the first item (the `Promise.resolve` above).
|
|
421
|
+
{
|
|
422
|
+
const { value } = await itt.next();
|
|
423
|
+
expect(value).toEqual({
|
|
424
|
+
data: [
|
|
425
|
+
{ id: 1, name: "Item 1" },
|
|
426
|
+
{ id: 2, name: "Item 2" },
|
|
427
|
+
],
|
|
428
|
+
loading: false,
|
|
429
|
+
error: undefined,
|
|
430
|
+
mutate: expect.any(Function),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
{
|
|
435
|
+
const { value } = await itt.next();
|
|
436
|
+
expect(value).toEqual({
|
|
437
|
+
data: [
|
|
438
|
+
{ id: 1, name: "Item 1" },
|
|
439
|
+
{ id: 2, name: "Item 2" },
|
|
440
|
+
{ id: 3, name: "Item 3" },
|
|
441
|
+
],
|
|
442
|
+
loading: false,
|
|
443
|
+
error: undefined,
|
|
444
|
+
mutate: expect.any(Function),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const { streamingPromise } = await promise;
|
|
449
|
+
|
|
450
|
+
const result = await streamingPromise;
|
|
451
|
+
expect(result).toEqual([
|
|
452
|
+
{ id: 1, name: "Item 1" },
|
|
453
|
+
{ id: 2, name: "Item 2" },
|
|
454
|
+
{ id: 3, name: "Item 3" },
|
|
455
|
+
]);
|
|
456
|
+
});
|
|
457
|
+
});
|