@fragno-dev/chatno 0.0.9 → 0.0.11

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 (40) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE.md +16 -0
  3. package/package.json +12 -11
  4. package/src/index.test.ts +66 -0
  5. package/src/index.ts +36 -20
  6. package/src/vitest.config.ts +3 -0
  7. package/.turbo/turbo-build.log +0 -44
  8. package/.turbo/turbo-types$colon$check.log +0 -1
  9. package/dist/browser/client/react.d.ts +0 -15
  10. package/dist/browser/client/react.d.ts.map +0 -1
  11. package/dist/browser/client/react.js +0 -75
  12. package/dist/browser/client/react.js.map +0 -1
  13. package/dist/browser/client/solid.d.ts +0 -14
  14. package/dist/browser/client/solid.d.ts.map +0 -1
  15. package/dist/browser/client/solid.js +0 -122
  16. package/dist/browser/client/solid.js.map +0 -1
  17. package/dist/browser/client/svelte.d.ts +0 -14
  18. package/dist/browser/client/svelte.d.ts.map +0 -1
  19. package/dist/browser/client/svelte.js +0 -126
  20. package/dist/browser/client/svelte.js.map +0 -1
  21. package/dist/browser/client/vanilla.d.ts +0 -15
  22. package/dist/browser/client/vanilla.d.ts.map +0 -1
  23. package/dist/browser/client/vanilla.js +0 -136
  24. package/dist/browser/client/vanilla.js.map +0 -1
  25. package/dist/browser/client/vue.d.ts +0 -15
  26. package/dist/browser/client/vue.d.ts.map +0 -1
  27. package/dist/browser/client/vue.js +0 -111
  28. package/dist/browser/client/vue.js.map +0 -1
  29. package/dist/browser/index.d.ts +0 -73
  30. package/dist/browser/index.d.ts.map +0 -1
  31. package/dist/browser/index.js +0 -3
  32. package/dist/browser/src-DdjU_SuS.js +0 -1525
  33. package/dist/browser/src-DdjU_SuS.js.map +0 -1
  34. package/dist/node/index.d.ts +0 -73
  35. package/dist/node/index.d.ts.map +0 -1
  36. package/dist/node/index.js +0 -74
  37. package/dist/node/index.js.map +0 -1
  38. package/dist/node/server/chatno-api.js +0 -108
  39. package/dist/node/server/chatno-api.js.map +0 -1
  40. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,1525 +0,0 @@
1
- import { createFragment, defineFragment, defineRoute, defineRoutes } from "@fragno-dev/core";
2
- import { z } from "zod";
3
- import { atom, batched, computed, map, onStart, onStop, startTask, task } from "nanostores";
4
- import OpenAI from "openai";
5
-
6
- //#region ../../packages/fragno/dist/api-DngJDcmO.js
7
- var FragnoApiError = class extends Error {
8
- #status;
9
- #code;
10
- constructor({ message, code }, status) {
11
- super(message);
12
- this.name = "FragnoApiError";
13
- this.#status = status;
14
- this.#code = code;
15
- }
16
- get status() {
17
- return this.#status;
18
- }
19
- get code() {
20
- return this.#code;
21
- }
22
- toResponse() {
23
- return Response.json({
24
- message: this.message,
25
- code: this.code
26
- }, { status: this.status });
27
- }
28
- };
29
- var FragnoApiValidationError = class extends FragnoApiError {
30
- #issues;
31
- constructor(message, issues) {
32
- super({
33
- message,
34
- code: "FRAGNO_VALIDATION_ERROR"
35
- }, 400);
36
- this.name = "FragnoApiValidationError";
37
- this.#issues = issues;
38
- }
39
- get issues() {
40
- return this.#issues;
41
- }
42
- toResponse() {
43
- return Response.json({
44
- message: this.message,
45
- issues: this.#issues,
46
- code: this.code
47
- }, { status: this.status });
48
- }
49
- };
50
-
51
- //#endregion
52
- //#region ../../packages/fragno/dist/route-B4RbOWjd.js
53
- function getMountRoute(opts) {
54
- const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
55
- if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
56
- return mountRoute;
57
- }
58
- var RequestInputContext = class RequestInputContext$1 {
59
- #path;
60
- #method;
61
- #pathParams;
62
- #searchParams;
63
- #body;
64
- #inputSchema;
65
- #shouldValidateInput;
66
- constructor(config) {
67
- this.#path = config.path;
68
- this.#method = config.method;
69
- this.#pathParams = config.pathParams;
70
- this.#searchParams = config.searchParams;
71
- this.#body = config.body;
72
- this.#inputSchema = config.inputSchema;
73
- this.#shouldValidateInput = config.shouldValidateInput ?? true;
74
- }
75
- /**
76
- * Create a RequestContext from a Request object for server-side handling
77
- */
78
- static async fromRequest(config) {
79
- const url = new URL(config.request.url);
80
- const request = config.request.clone();
81
- const json = request.body instanceof ReadableStream ? await request.json() : void 0;
82
- return new RequestInputContext$1({
83
- method: config.method,
84
- path: config.path,
85
- pathParams: config.pathParams,
86
- searchParams: url.searchParams,
87
- body: json,
88
- inputSchema: config.inputSchema,
89
- shouldValidateInput: config.shouldValidateInput
90
- });
91
- }
92
- /**
93
- * Create a RequestContext for server-side rendering contexts (no Request object)
94
- */
95
- static fromSSRContext(config) {
96
- return new RequestInputContext$1({
97
- method: config.method,
98
- path: config.path,
99
- pathParams: config.pathParams,
100
- searchParams: config.searchParams ?? new URLSearchParams(),
101
- body: "body" in config ? config.body : void 0,
102
- inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
103
- shouldValidateInput: false
104
- });
105
- }
106
- /**
107
- * The HTTP method as string (e.g., `GET`, `POST`)
108
- */
109
- get method() {
110
- return this.#method;
111
- }
112
- /**
113
- * The matched route path (e.g., `/users/:id`)
114
- * @remarks `string`
115
- */
116
- get path() {
117
- return this.#path;
118
- }
119
- /**
120
- * Extracted path parameters as object (e.g., `{ id: '123' }`)
121
- * @remarks `Record<string, string>`
122
- */
123
- get pathParams() {
124
- return this.#pathParams;
125
- }
126
- /**
127
- * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
128
- * @remarks `URLSearchParams`
129
- */
130
- get query() {
131
- return this.#searchParams;
132
- }
133
- /**
134
- * @internal
135
- */
136
- get rawBody() {
137
- return this.#body;
138
- }
139
- /**
140
- * Input validation context (only if inputSchema is defined)
141
- * @remarks `InputContext`
142
- */
143
- get input() {
144
- if (!this.#inputSchema) return;
145
- return {
146
- schema: this.#inputSchema,
147
- valid: async () => {
148
- if (!this.#shouldValidateInput) return this.#body;
149
- return this.#validateInput();
150
- }
151
- };
152
- }
153
- async #validateInput() {
154
- if (!this.#inputSchema) throw new Error("No input schema defined for this route");
155
- if (this.#body instanceof FormData || this.#body instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
156
- const result = await this.#inputSchema["~standard"].validate(this.#body);
157
- if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
158
- return result.value;
159
- }
160
- };
161
- var ResponseStream = class {
162
- #writer;
163
- #encoder;
164
- #abortSubscribers = [];
165
- #responseReadable;
166
- #aborted = false;
167
- #closed = false;
168
- /**
169
- * Whether the stream has been aborted.
170
- */
171
- get aborted() {
172
- return this.#aborted;
173
- }
174
- /**
175
- * Whether the stream has been closed normally.
176
- */
177
- get closed() {
178
- return this.#closed;
179
- }
180
- /**
181
- * The readable stream that the response is piped to.
182
- */
183
- get responseReadable() {
184
- return this.#responseReadable;
185
- }
186
- constructor(writable, readable) {
187
- this.#writer = writable.getWriter();
188
- this.#encoder = new TextEncoder();
189
- const reader = readable.getReader();
190
- this.#abortSubscribers.push(async () => {
191
- await reader.cancel();
192
- });
193
- this.#responseReadable = new ReadableStream({
194
- async pull(controller) {
195
- const { done, value } = await reader.read();
196
- if (done) controller.close();
197
- else controller.enqueue(value);
198
- },
199
- cancel: () => {
200
- this.abort();
201
- }
202
- });
203
- }
204
- async writeRaw(input) {
205
- try {
206
- if (typeof input === "string") input = this.#encoder.encode(input);
207
- await this.#writer.write(input);
208
- } catch {}
209
- }
210
- write(input) {
211
- return this.writeRaw(JSON.stringify(input) + "\n");
212
- }
213
- sleep(ms) {
214
- return new Promise((res) => setTimeout(res, ms));
215
- }
216
- async close() {
217
- try {
218
- await this.#writer.close();
219
- } catch {} finally {
220
- this.#closed = true;
221
- }
222
- }
223
- onAbort(listener) {
224
- this.#abortSubscribers.push(listener);
225
- }
226
- /**
227
- * Abort the stream.
228
- * You can call this method when stream is aborted by external event.
229
- */
230
- abort() {
231
- if (!this.aborted) {
232
- this.#aborted = true;
233
- this.#abortSubscribers.forEach((subscriber) => subscriber());
234
- }
235
- }
236
- };
237
- /**
238
- * Utility function to merge headers from multiple sources.
239
- * Later headers override earlier ones.
240
- */
241
- function mergeHeaders(...headerSources) {
242
- const mergedHeaders = new Headers();
243
- for (const headerSource of headerSources) {
244
- if (!headerSource) continue;
245
- if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
246
- else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
247
- else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
248
- }
249
- return mergedHeaders;
250
- }
251
- var OutputContext = class {
252
- /**
253
- * Creates an error response.
254
- *
255
- * Shortcut for `throw new FragnoApiError(...)`
256
- */
257
- error({ message, code }, initOrStatus, headers) {
258
- if (typeof initOrStatus === "undefined") return Response.json({
259
- message,
260
- code
261
- }, {
262
- status: 500,
263
- headers
264
- });
265
- if (typeof initOrStatus === "number") return Response.json({
266
- message,
267
- code
268
- }, {
269
- status: initOrStatus,
270
- headers
271
- });
272
- const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
273
- return Response.json({
274
- message,
275
- code
276
- }, {
277
- status: initOrStatus.status,
278
- headers: mergedHeaders
279
- });
280
- }
281
- empty(initOrStatus, headers) {
282
- const defaultHeaders = {};
283
- if (typeof initOrStatus === "undefined") {
284
- const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
285
- return Response.json(null, {
286
- status: 201,
287
- headers: mergedHeaders$1
288
- });
289
- }
290
- if (typeof initOrStatus === "number") {
291
- const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
292
- return Response.json(null, {
293
- status: initOrStatus,
294
- headers: mergedHeaders$1
295
- });
296
- }
297
- const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
298
- return Response.json(null, {
299
- status: initOrStatus.status,
300
- headers: mergedHeaders
301
- });
302
- }
303
- json(object, initOrStatus, headers) {
304
- if (typeof initOrStatus === "undefined") return Response.json(object, {
305
- status: 200,
306
- headers
307
- });
308
- if (typeof initOrStatus === "number") return Response.json(object, {
309
- status: initOrStatus,
310
- headers
311
- });
312
- const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
313
- return Response.json(object, {
314
- status: initOrStatus.status,
315
- headers: mergedHeaders
316
- });
317
- }
318
- jsonStream = (cb, { onError, headers } = {}) => {
319
- const defaultHeaders = {
320
- "content-type": "application/x-ndjson; charset=utf-8",
321
- "transfer-encoding": "chunked",
322
- "cache-control": "no-cache"
323
- };
324
- const { readable, writable } = new TransformStream();
325
- const stream = new ResponseStream(writable, readable);
326
- (async () => {
327
- try {
328
- await cb(stream);
329
- } catch (e) {
330
- if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
331
- else console.error(e);
332
- } finally {
333
- stream.close();
334
- }
335
- })();
336
- return new Response(stream.responseReadable, {
337
- status: 200,
338
- headers: mergeHeaders(defaultHeaders, headers)
339
- });
340
- };
341
- };
342
- var RequestOutputContext = class extends OutputContext {
343
- #outputSchema;
344
- constructor(outputSchema) {
345
- super();
346
- this.#outputSchema = outputSchema;
347
- }
348
- };
349
- function resolveRouteFactories(context, routesOrFactories) {
350
- const routes$1 = [];
351
- for (const item of routesOrFactories) if (typeof item === "function") {
352
- const factoryRoutes = item(context);
353
- routes$1.push(...factoryRoutes);
354
- } else routes$1.push(item);
355
- return routes$1;
356
- }
357
-
358
- //#endregion
359
- //#region ../../packages/fragno/dist/ssr-CamRrMc0.js
360
- let stores = [];
361
- const SSR_ENABLED = false;
362
- function addStore(store) {
363
- stores.push(store);
364
- }
365
- let clientInitialData;
366
- function getInitialData(key) {
367
- if (clientInitialData?.has(key)) {
368
- const data = clientInitialData.get(key);
369
- clientInitialData.delete(key);
370
- return data;
371
- }
372
- }
373
-
374
- //#endregion
375
- //#region ../../node_modules/.bun/nanoevents@9.1.0/node_modules/nanoevents/index.js
376
- let createNanoEvents = () => ({
377
- emit(event, ...args) {
378
- for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) callbacks[i](...args);
379
- },
380
- events: {},
381
- on(event, cb) {
382
- (this.events[event] ||= []).push(cb);
383
- return () => {
384
- this.events[event] = this.events[event]?.filter((i) => cb !== i);
385
- };
386
- }
387
- });
388
-
389
- //#endregion
390
- //#region ../../node_modules/.bun/@nanostores+query@0.3.4+e0f9796391ddf78a/node_modules/@nanostores/query/dist/nanoquery.js
391
- function defaultOnErrorRetry({ retryCount }) {
392
- return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
393
- }
394
- const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectChangeSubscribe]) => {
395
- const nanoquery$1 = ({ cache = /* @__PURE__ */ new Map(), fetcher: globalFetcher,...globalSettings } = {}) => {
396
- const events = createNanoEvents();
397
- let focus = true;
398
- visibilityChangeSubscribe(() => {
399
- focus = isAppVisible();
400
- focus && events.emit(FOCUS);
401
- });
402
- reconnectChangeSubscribe(() => events.emit(RECONNECT));
403
- const _revalidateOnInterval = /* @__PURE__ */ new Map(), _errorInvalidateTimeouts = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Map();
404
- let rewrittenSettings = {};
405
- const getCachedValueByKey = (key) => {
406
- const fromCache = cache.get(key);
407
- if (!fromCache) return [];
408
- return (fromCache.expires || 0) > getNow() ? [fromCache.data, fromCache.error] : [];
409
- };
410
- const runFetcher = async ([key, keyParts], store, settings) => {
411
- if (!focus) return;
412
- const set = (v) => {
413
- if (store.key === key) {
414
- store.set(v);
415
- events.emit(SET_CACHE, key, v, true);
416
- }
417
- };
418
- const setAsLoading = (prev) => {
419
- set({
420
- ...prev === void 0 ? {} : { data: prev },
421
- ...loading,
422
- promise: _runningFetches.get(key)
423
- });
424
- };
425
- let { dedupeTime = 4e3, cacheLifetime = Infinity, fetcher, onErrorRetry = defaultOnErrorRetry } = {
426
- ...settings,
427
- ...rewrittenSettings
428
- };
429
- if (cacheLifetime < dedupeTime) cacheLifetime = dedupeTime;
430
- const now = getNow();
431
- if (_runningFetches.has(key)) {
432
- if (!store.value.loading) setAsLoading(getCachedValueByKey(key)[0]);
433
- return;
434
- }
435
- let cachedValue, cachedError;
436
- const fromCache = cache.get(key);
437
- if (fromCache?.data !== void 0 || fromCache?.error) {
438
- [cachedValue, cachedError] = getCachedValueByKey(key);
439
- if ((fromCache.created || 0) + dedupeTime > now) {
440
- if (store.value.data != cachedValue || store.value.error != cachedError) set({
441
- ...notLoading,
442
- data: cachedValue,
443
- error: cachedError
444
- });
445
- return;
446
- }
447
- }
448
- const finishTask = startTask();
449
- try {
450
- clearTimeout(_errorInvalidateTimeouts.get(key));
451
- const promise = fetcher(...keyParts);
452
- _runningFetches.set(key, promise);
453
- setAsLoading(cachedValue);
454
- const res = await promise;
455
- cache.set(key, {
456
- data: res,
457
- created: getNow(),
458
- expires: getNow() + cacheLifetime
459
- });
460
- set({
461
- data: res,
462
- ...notLoading
463
- });
464
- } catch (error) {
465
- settings.onError?.(error);
466
- const retryCount = (cache.get(key)?.retryCount || 0) + 1;
467
- cache.set(key, {
468
- error,
469
- created: getNow(),
470
- expires: getNow() + cacheLifetime,
471
- retryCount
472
- });
473
- if (onErrorRetry) {
474
- const timer = onErrorRetry({
475
- error,
476
- key,
477
- retryCount
478
- });
479
- if (timer) _errorInvalidateTimeouts.set(key, setTimeout(() => {
480
- invalidateKeys(key);
481
- cache.set(key, { retryCount });
482
- }, timer));
483
- }
484
- set({
485
- data: store.value.data,
486
- error,
487
- ...notLoading
488
- });
489
- } finally {
490
- finishTask();
491
- _runningFetches.delete(key);
492
- }
493
- };
494
- const createFetcherStore = (keyInput, { fetcher = globalFetcher,...fetcherSettings } = {}) => {
495
- if (!fetcher) throw new Error("You need to set up either global fetcher of fetcher in createFetcherStore");
496
- const fetcherStore = map({ ...notLoading }), settings = {
497
- ...globalSettings,
498
- ...fetcherSettings,
499
- fetcher
500
- };
501
- fetcherStore._ = fetcherSymbol;
502
- fetcherStore.invalidate = () => {
503
- const { key } = fetcherStore;
504
- if (key) invalidateKeys(key);
505
- };
506
- fetcherStore.revalidate = () => {
507
- const { key } = fetcherStore;
508
- if (key) revalidateKeys(key);
509
- };
510
- fetcherStore.mutate = (data) => {
511
- const { key } = fetcherStore;
512
- if (key) mutateCache(key, data);
513
- };
514
- fetcherStore.fetch = async () => {
515
- let resolve;
516
- const promise = new Promise((r) => resolve = r);
517
- const unsub = fetcherStore.listen(({ error, data }) => {
518
- if (error !== void 0) resolve({ error });
519
- if (data !== void 0) resolve({ data });
520
- });
521
- return promise.finally(unsub);
522
- };
523
- let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore;
524
- let evtUnsubs = [];
525
- onStart(fetcherStore, () => {
526
- const firstRun = !keysInternalUnsub;
527
- [keyStore, keysInternalUnsub] = getKeyStore(keyInput);
528
- keyUnsub = keyStore.subscribe((currentKeys) => {
529
- if (currentKeys) {
530
- const [newKey, keyParts] = currentKeys;
531
- fetcherStore.key = newKey;
532
- runFetcher([newKey, keyParts], fetcherStore, settings);
533
- prevKey = newKey;
534
- prevKeyParts = keyParts;
535
- } else {
536
- fetcherStore.key = prevKey = prevKeyParts = void 0;
537
- fetcherStore.set({ ...notLoading });
538
- }
539
- });
540
- const currentKeyValue = keyStore.get();
541
- if (currentKeyValue) {
542
- [prevKey, prevKeyParts] = currentKeyValue;
543
- if (firstRun) handleNewListener();
544
- }
545
- const { revalidateInterval = 0, revalidateOnFocus, revalidateOnReconnect } = settings;
546
- const runRefetcher = () => {
547
- if (prevKey) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
548
- };
549
- if (revalidateInterval > 0) _revalidateOnInterval.set(keyInput, setInterval(runRefetcher, revalidateInterval));
550
- if (revalidateOnFocus) evtUnsubs.push(events.on(FOCUS, runRefetcher));
551
- if (revalidateOnReconnect) evtUnsubs.push(events.on(RECONNECT, runRefetcher));
552
- const cacheKeyChangeHandler = (keySelector) => {
553
- if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
554
- };
555
- evtUnsubs.push(events.on(INVALIDATE_KEYS, cacheKeyChangeHandler), events.on(REVALIDATE_KEYS, cacheKeyChangeHandler), events.on(SET_CACHE, (keySelector, data, full) => {
556
- if (prevKey && testKeyAgainstSelector(prevKey, keySelector) && fetcherStore.value !== data && fetcherStore.value.data !== data) fetcherStore.set(full ? data : {
557
- data,
558
- ...notLoading
559
- });
560
- }));
561
- });
562
- const handleNewListener = () => {
563
- if (prevKey && prevKeyParts) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
564
- };
565
- const originListen = fetcherStore.listen;
566
- fetcherStore.listen = (listener) => {
567
- const unsub = originListen(listener);
568
- listener(fetcherStore.value);
569
- handleNewListener();
570
- return unsub;
571
- };
572
- onStop(fetcherStore, () => {
573
- fetcherStore.value = { ...notLoading };
574
- keysInternalUnsub?.();
575
- evtUnsubs.forEach((fn) => fn());
576
- evtUnsubs = [];
577
- keyUnsub?.();
578
- clearInterval(_revalidateOnInterval.get(keyInput));
579
- });
580
- return fetcherStore;
581
- };
582
- const iterOverCache = (keySelector, cb) => {
583
- for (const key of cache.keys()) if (testKeyAgainstSelector(key, keySelector)) cb(key);
584
- };
585
- const invalidateKeys = (keySelector) => {
586
- iterOverCache(keySelector, (key) => {
587
- cache.delete(key);
588
- });
589
- events.emit(INVALIDATE_KEYS, keySelector);
590
- };
591
- const revalidateKeys = (keySelector) => {
592
- iterOverCache(keySelector, (key) => {
593
- const cached = cache.get(key);
594
- if (cached) cache.set(key, {
595
- ...cached,
596
- created: -Infinity
597
- });
598
- });
599
- events.emit(REVALIDATE_KEYS, keySelector);
600
- };
601
- const mutateCache = (keySelector, data) => {
602
- iterOverCache(keySelector, (key) => {
603
- if (data === void 0) cache.delete(key);
604
- else cache.set(key, {
605
- data,
606
- created: getNow(),
607
- expires: getNow() + (globalSettings.cacheLifetime ?? 8e3)
608
- });
609
- });
610
- events.emit(SET_CACHE, keySelector, data);
611
- };
612
- function createMutatorStore(mutator, opts) {
613
- const { throttleCalls, onError } = opts ?? {
614
- throttleCalls: true,
615
- onError: globalSettings?.onError
616
- };
617
- const mutate = async (data) => {
618
- if (throttleCalls && store.value?.loading) return;
619
- const newMutator = rewrittenSettings.fetcher ?? mutator;
620
- const keysToInvalidate = [], keysToRevalidate = [];
621
- const safeKeySet = (k, v) => {
622
- if (store.lc) store.setKey(k, v);
623
- };
624
- try {
625
- store.set({
626
- error: void 0,
627
- data: void 0,
628
- mutate,
629
- ...loading
630
- });
631
- const result = await newMutator({
632
- data,
633
- invalidate: (key) => {
634
- keysToInvalidate.push(key);
635
- },
636
- revalidate: (key) => {
637
- keysToRevalidate.push(key);
638
- },
639
- getCacheUpdater: (key, shouldRevalidate = true) => [(newVal) => {
640
- mutateCache(key, newVal);
641
- if (shouldRevalidate) keysToRevalidate.push(key);
642
- }, cache.get(key)?.data]
643
- });
644
- safeKeySet("data", result);
645
- return result;
646
- } catch (error) {
647
- onError?.(error);
648
- safeKeySet("error", error);
649
- store.setKey("error", error);
650
- } finally {
651
- safeKeySet("loading", false);
652
- keysToInvalidate.forEach(invalidateKeys);
653
- keysToRevalidate.forEach(revalidateKeys);
654
- }
655
- };
656
- const store = map({
657
- mutate,
658
- ...notLoading
659
- });
660
- onStop(store, () => store.set({
661
- mutate,
662
- ...notLoading
663
- }));
664
- store.mutate = mutate;
665
- return store;
666
- }
667
- const __unsafeOverruleSettings = (data) => {
668
- console.warn(`You should only use __unsafeOverruleSettings in test environment`);
669
- rewrittenSettings = data;
670
- };
671
- return [
672
- createFetcherStore,
673
- createMutatorStore,
674
- {
675
- __unsafeOverruleSettings,
676
- invalidateKeys,
677
- revalidateKeys,
678
- mutateCache
679
- }
680
- ];
681
- };
682
- function isSomeKey(key) {
683
- return typeof key === "string" || typeof key === "number" || key === true;
684
- }
685
- const getKeyStore = (keys) => {
686
- if (isSomeKey(keys)) return [atom(["" + keys, [keys]]), () => {}];
687
- const keyParts = [];
688
- const $key = atom(null);
689
- const keysAsStoresToIndexes = /* @__PURE__ */ new Map();
690
- const setKeyStoreValue = () => {
691
- if (keyParts.some((v) => v === null || v === void 0 || v === false)) $key.set(null);
692
- else $key.set([keyParts.join(""), keyParts]);
693
- };
694
- for (let i = 0; i < keys.length; i++) {
695
- const keyOrStore = keys[i];
696
- if (isSomeKey(keyOrStore)) keyParts.push(keyOrStore);
697
- else {
698
- keyParts.push(null);
699
- keysAsStoresToIndexes.set(keyOrStore, i);
700
- }
701
- }
702
- const storesAsArray = [...keysAsStoresToIndexes.keys()];
703
- const $storeKeys = batched(storesAsArray, (...storeValues) => {
704
- for (let i = 0; i < storeValues.length; i++) {
705
- const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store);
706
- keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i];
707
- }
708
- setKeyStoreValue();
709
- });
710
- setKeyStoreValue();
711
- return [$key, $storeKeys.subscribe(noop)];
712
- };
713
- function noop() {}
714
- const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, REVALIDATE_KEYS = 4, SET_CACHE = 5;
715
- const testKeyAgainstSelector = (key, selector) => {
716
- if (Array.isArray(selector)) return selector.includes(key);
717
- else if (typeof selector === "function") return selector(key);
718
- else return key === selector;
719
- };
720
- const getNow = () => (/* @__PURE__ */ new Date()).getTime();
721
- const fetcherSymbol = Symbol();
722
- const loading = { loading: true }, notLoading = { loading: false };
723
- return nanoquery$1;
724
- };
725
- const subscribe = (name, fn) => {
726
- if (!(typeof window === "undefined")) addEventListener(name, fn);
727
- };
728
- const nanoquery = nanoqueryFactory([
729
- () => !document.hidden,
730
- (cb) => subscribe("visibilitychange", cb),
731
- (cb) => subscribe("online", cb)
732
- ]);
733
-
734
- //#endregion
735
- //#region ../../packages/fragno/dist/client-D5ORmjBP.js
736
- /**
737
- * Extract parameter names from a path pattern at runtime.
738
- * Examples:
739
- * - "/users/:id" => ["id"]
740
- * - "/files/**" => ["**"]
741
- * - "/files/**:rest" => ["rest"]
742
- */
743
- function extractPathParams(pathPattern) {
744
- const segments = pathPattern.split("/").filter((s) => s.length > 0);
745
- const names = [];
746
- for (const segment of segments) {
747
- if (segment.startsWith(":")) {
748
- names.push(segment.slice(1));
749
- continue;
750
- }
751
- if (segment === "**") {
752
- names.push("**");
753
- continue;
754
- }
755
- if (segment.startsWith("**:")) {
756
- names.push(segment.slice(3));
757
- continue;
758
- }
759
- }
760
- return names;
761
- }
762
- /**
763
- * Build a concrete path by replacing placeholders in a path pattern with values.
764
- *
765
- * Supports the same placeholder syntax as the matcher:
766
- * - Named parameter ":name" is URL-encoded as a single segment
767
- * - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
768
- * - Named wildcard "**:name" inserts the remainder from the named key
769
- *
770
- * Examples:
771
- * - buildPath("/users/:id", { id: "123" }) => "/users/123"
772
- * - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
773
- * - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
774
- */
775
- function buildPath(pathPattern, params) {
776
- const patternSegments = pathPattern.split("/");
777
- const builtSegments = [];
778
- for (const segment of patternSegments) {
779
- if (segment.length === 0) {
780
- builtSegments.push("");
781
- continue;
782
- }
783
- if (segment.startsWith(":")) {
784
- const name = segment.slice(1);
785
- const value = params[name];
786
- if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
787
- builtSegments.push(encodeURIComponent(value));
788
- continue;
789
- }
790
- if (segment === "**") {
791
- const value = params["**"];
792
- if (value === void 0) throw new Error("Missing value for path wildcard **");
793
- builtSegments.push(value);
794
- continue;
795
- }
796
- if (segment.startsWith("**:")) {
797
- const name = segment.slice(3);
798
- const value = params[name];
799
- if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
800
- builtSegments.push(value);
801
- continue;
802
- }
803
- builtSegments.push(segment);
804
- }
805
- return builtSegments.join("/");
806
- }
807
- /**
808
- * Base error class for all Fragno client errors.
809
- */
810
- var FragnoClientError = class extends Error {
811
- #code;
812
- constructor(message, code, options = {}) {
813
- super(message, { cause: options.cause });
814
- this.name = "FragnoClientError";
815
- this.#code = code;
816
- }
817
- get code() {
818
- return this.#code;
819
- }
820
- };
821
- var FragnoClientFetchError = class extends FragnoClientError {
822
- constructor(message, code, options = {}) {
823
- super(message, code, options);
824
- this.name = "FragnoClientFetchError";
825
- }
826
- static fromUnknownFetchError(error) {
827
- if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
828
- if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
829
- return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
830
- }
831
- };
832
- /**
833
- * Error thrown when a network request fails (e.g., no internet connection, DNS failure).
834
- */
835
- var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
836
- constructor(message = "Network request failed", options = {}) {
837
- super(message, "NETWORK_ERROR", options);
838
- this.name = "FragnoClientFetchNetworkError";
839
- }
840
- };
841
- /**
842
- * Error thrown when a request is aborted (e.g., user cancels request, timeout).
843
- */
844
- var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
845
- constructor(message = "Request was aborted", options = {}) {
846
- super(message, "ABORT_ERROR", options);
847
- this.name = "FragnoClientFetchAbortError";
848
- }
849
- };
850
- /**
851
- * Error thrown when the API result is unexpected, e.g. no json is returned.
852
- */
853
- var FragnoClientUnknownApiError = class extends FragnoClientError {
854
- #status;
855
- constructor(message = "Unknown API error", status, options = {}) {
856
- super(message, "UNKNOWN_API_ERROR", options);
857
- this.name = "FragnoClientUnknownApiError";
858
- this.#status = status;
859
- }
860
- get status() {
861
- return this.#status;
862
- }
863
- };
864
- var FragnoClientApiError = class FragnoClientApiError$1 extends FragnoClientError {
865
- #status;
866
- constructor({ message, code }, status, options = {}) {
867
- super(message, code, options);
868
- this.name = "FragnoClientApiError";
869
- this.#status = status;
870
- }
871
- get status() {
872
- return this.#status;
873
- }
874
- /**
875
- * The error code returned by the API.
876
- *
877
- * The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
878
- * for forward compatibility with future error codes.
879
- */
880
- get code() {
881
- return super.code;
882
- }
883
- static async fromResponse(response) {
884
- const unknown = await response.json();
885
- const status = response.status;
886
- if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
887
- if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
888
- return new FragnoClientApiError$1({
889
- message: unknown.message,
890
- code: unknown.code
891
- }, status);
892
- }
893
- };
894
- /**
895
- * Parses a content-type header string into its components
896
- *
897
- * @param contentType - The content-type header value to parse
898
- * @returns A ParsedContentType object or null if the input is invalid
899
- *
900
- * @example
901
- * ```ts
902
- * const { type, subtype, mediaType, parameters }
903
- * = parseContentType("application/json; charset=utf-8");
904
- * console.assert(type === "application");
905
- * console.assert(subtype === "json");
906
- * console.assert(mediaType === "application/json");
907
- * console.assert(parameters["charset"] === "utf-8");
908
- */
909
- function parseContentType(contentType) {
910
- if (!contentType || typeof contentType !== "string") return null;
911
- const trimmed = contentType.trim();
912
- if (!trimmed) return null;
913
- const parts = trimmed.split(";").map((part) => part.trim());
914
- const mediaType = parts[0];
915
- if (!mediaType) return null;
916
- const typeParts = mediaType.split("/");
917
- if (typeParts.length !== 2) return null;
918
- const [type, subtype] = typeParts.map((part) => part.trim().toLowerCase());
919
- if (!type || !subtype) return null;
920
- const parameters = {};
921
- for (let i = 1; i < parts.length; i++) {
922
- const param = parts[i];
923
- const equalIndex = param.indexOf("=");
924
- if (equalIndex > 0) {
925
- const key = param.slice(0, equalIndex).trim().toLowerCase();
926
- let value = param.slice(equalIndex + 1).trim();
927
- if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
928
- if (key) parameters[key] = value;
929
- }
930
- }
931
- return {
932
- type,
933
- subtype,
934
- mediaType: `${type}/${subtype}`,
935
- parameters
936
- };
937
- }
938
- /**
939
- * Creates a promise that rejects when the abort signal is triggered
940
- */
941
- function createAbortPromise(abortSignal) {
942
- return new Promise((_, reject) => {
943
- const abortHandler = () => {
944
- reject(new FragnoClientFetchAbortError("Operation was aborted"));
945
- };
946
- if (abortSignal.aborted) abortHandler();
947
- else abortSignal.addEventListener("abort", abortHandler, { once: true });
948
- });
949
- }
950
- /**
951
- * Handles NDJSON streaming responses by returning the first item from the fetcher
952
- * and then continuing to stream updates via the store's mutate method.
953
- *
954
- * This makes it so that we can wait until the first chunk before updating the store, if we did
955
- * not do this, `loading` would briefly be false before the first item would be populated in the
956
- * result.
957
- *
958
- * @param response - The fetch Response object containing the NDJSON stream
959
- * @param store - The fetcher store to update with streaming data
960
- * @param abortSignal - Optional AbortSignal to cancel the streaming operation
961
- * @returns A promise that resolves to an object containing the first item and a streaming promise
962
- */
963
- async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
964
- if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
965
- const { abortSignal } = options;
966
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
967
- const decoder = new TextDecoder();
968
- const reader = response.body.getReader();
969
- let buffer = "";
970
- let firstItem = null;
971
- const items = [];
972
- try {
973
- while (firstItem === null) {
974
- if (abortSignal?.aborted) {
975
- reader.releaseLock();
976
- throw new FragnoClientFetchAbortError("Operation was aborted");
977
- }
978
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
979
- if (done) break;
980
- buffer += decoder.decode(value, { stream: true });
981
- const lines = buffer.split("\n");
982
- buffer = lines.pop() || "";
983
- for (const line of lines) {
984
- if (!line.trim()) continue;
985
- try {
986
- const jsonObject = JSON.parse(line);
987
- items.push(jsonObject);
988
- if (firstItem === null) {
989
- firstItem = jsonObject;
990
- const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
991
- return {
992
- firstItem,
993
- streamingPromise
994
- };
995
- }
996
- } catch (parseError) {
997
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, { cause: parseError });
998
- }
999
- }
1000
- }
1001
- if (firstItem === null) {
1002
- reader.releaseLock();
1003
- throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
1004
- }
1005
- reader.releaseLock();
1006
- throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
1007
- } catch (error) {
1008
- if (error instanceof FragnoClientError) {
1009
- store?.setError(error);
1010
- throw error;
1011
- } else {
1012
- const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
1013
- store?.setError(clientError);
1014
- throw clientError;
1015
- }
1016
- }
1017
- }
1018
- /**
1019
- * Continues streaming the remaining items in the background
1020
- */
1021
- async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
1022
- let buffer = initialBuffer;
1023
- try {
1024
- while (true) {
1025
- if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
1026
- const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
1027
- if (done) {
1028
- if (buffer.trim()) {
1029
- const lines$1 = buffer.split("\n");
1030
- for (const line of lines$1) {
1031
- if (!line.trim()) continue;
1032
- try {
1033
- const jsonObject = JSON.parse(line);
1034
- items.push(jsonObject);
1035
- store?.setData([...items]);
1036
- } catch (parseError) {
1037
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
1038
- }
1039
- }
1040
- }
1041
- break;
1042
- }
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
- store?.setData([...items]);
1052
- } catch (parseError) {
1053
- throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
1054
- }
1055
- }
1056
- }
1057
- } catch (error) {
1058
- if (error instanceof FragnoClientError) store?.setError(error);
1059
- else {
1060
- const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
1061
- store?.setError(clientError);
1062
- throw clientError;
1063
- }
1064
- throw error;
1065
- } finally {
1066
- reader.releaseLock();
1067
- }
1068
- return items;
1069
- }
1070
- /**
1071
- * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
1072
- */
1073
- function unwrapAtom(value) {
1074
- if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
1075
- return value;
1076
- }
1077
- /**
1078
- * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
1079
- * Returns a new object with all values normalized to plain values.
1080
- */
1081
- function unwrapObject(params) {
1082
- if (!params) return;
1083
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
1084
- }
1085
- function isReadableAtom(value) {
1086
- if (!value) return false;
1087
- if (typeof value !== "object" || value === null) return false;
1088
- if (!("get" in value) || typeof value.get !== "function") return false;
1089
- if (!("lc" in value) || typeof value.lc !== "number") return false;
1090
- if (!("notify" in value) || typeof value.notify !== "function") return false;
1091
- if (!("off" in value) || typeof value.off !== "function") return false;
1092
- if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
1093
- if (!("value" in value)) return false;
1094
- return true;
1095
- }
1096
- /**
1097
- * Symbols used to identify hook types
1098
- */
1099
- const GET_HOOK_SYMBOL = Symbol("fragno-get-hook");
1100
- const MUTATOR_HOOK_SYMBOL = Symbol("fragno-mutator-hook");
1101
- const STORE_SYMBOL = Symbol("fragno-store");
1102
- function buildUrl(config, params) {
1103
- const { baseUrl = "", mountRoute, path } = config;
1104
- const { pathParams, queryParams } = params ?? {};
1105
- const normalizedPathParams = unwrapObject(pathParams);
1106
- const normalizedQueryParams = unwrapObject(queryParams) ?? {};
1107
- const searchParams = new URLSearchParams(normalizedQueryParams);
1108
- return `${baseUrl}${mountRoute}${buildPath(path, normalizedPathParams ?? {})}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
1109
- }
1110
- /**
1111
- * This method returns an array, which can be passed directly to nanostores.
1112
- *
1113
- * The returned array is always: path, pathParams (In order they appear in the path), queryParams (In alphabetical order)
1114
- * Missing pathParams are replaced with "<missing>".
1115
- * @param path
1116
- * @param params
1117
- * @returns
1118
- */
1119
- function getCacheKey(method, path, params) {
1120
- if (!params) return [method, path];
1121
- const { pathParams, queryParams } = params;
1122
- const pathParamValues = extractPathParams(path).map((name) => pathParams?.[name] ?? "<missing>");
1123
- const queryParamValues = queryParams ? Object.keys(queryParams).sort().map((key) => queryParams[key]) : [];
1124
- return [
1125
- method,
1126
- path,
1127
- ...pathParamValues,
1128
- ...queryParamValues
1129
- ];
1130
- }
1131
- function isStreamingResponse(response) {
1132
- const contentType = parseContentType(response.headers.get("content-type"));
1133
- if (!contentType) return false;
1134
- if (!(response.headers.get("transfer-encoding") === "chunked")) return false;
1135
- if (contentType.subtype === "octet-stream") return "octet-stream";
1136
- if (contentType.subtype === "x-ndjson") return "ndjson";
1137
- return false;
1138
- }
1139
- function isGetHook(hook) {
1140
- return typeof hook === "object" && hook !== null && GET_HOOK_SYMBOL in hook && hook[GET_HOOK_SYMBOL] === true;
1141
- }
1142
- function isMutatorHook(hook) {
1143
- return typeof hook === "object" && hook !== null && MUTATOR_HOOK_SYMBOL in hook && hook[MUTATOR_HOOK_SYMBOL] === true;
1144
- }
1145
- function isStore(obj) {
1146
- return typeof obj === "object" && obj !== null && STORE_SYMBOL in obj && obj[STORE_SYMBOL] === true;
1147
- }
1148
- var ClientBuilder = class {
1149
- #publicConfig;
1150
- #fragmentConfig;
1151
- #cache = /* @__PURE__ */ new Map();
1152
- #createFetcherStore;
1153
- #createMutatorStore;
1154
- #invalidateKeys;
1155
- constructor(publicConfig, fragmentConfig) {
1156
- this.#publicConfig = publicConfig;
1157
- this.#fragmentConfig = fragmentConfig;
1158
- const [createFetcherStore, createMutatorStore, { invalidateKeys }] = nanoquery({ cache: this.#cache });
1159
- this.#createFetcherStore = createFetcherStore;
1160
- this.#createMutatorStore = createMutatorStore;
1161
- this.#invalidateKeys = invalidateKeys;
1162
- }
1163
- get cacheEntries() {
1164
- return Object.fromEntries(this.#cache.entries());
1165
- }
1166
- createStore(obj) {
1167
- return {
1168
- obj,
1169
- [STORE_SYMBOL]: true
1170
- };
1171
- }
1172
- createHook(path, options) {
1173
- const route = this.#fragmentConfig.routes.find((r) => r.path === path && r.method === "GET" && r.outputSchema !== void 0);
1174
- if (!route) throw new Error(`Route '${path}' not found or is not a GET route with an output schema.`);
1175
- return this.#createRouteQueryHook(route, options);
1176
- }
1177
- createMutator(method, path, onInvalidate) {
1178
- const route = this.#fragmentConfig.routes.find((r) => r.method !== "GET" && r.path === path && r.method === method);
1179
- if (!route) throw new Error(`Route '${path}' not found or is a GET route with an input and output schema.`);
1180
- return this.#createRouteQueryMutator(route, onInvalidate);
1181
- }
1182
- #createRouteQueryHook(route, options = {}) {
1183
- if (route.method !== "GET") throw new Error(`Only GET routes are supported for hooks. Route '${route.path}' is a ${route.method} route.`);
1184
- if (!route.outputSchema) throw new Error(`Output schema is required for GET routes. Route '${route.path}' has no output schema.`);
1185
- const baseUrl = this.#publicConfig.baseUrl ?? "";
1186
- const mountRoute = getMountRoute(this.#fragmentConfig);
1187
- async function callServerSideHandler(params) {
1188
- const { pathParams, queryParams } = params ?? {};
1189
- const normalizedPathParams = unwrapObject(pathParams);
1190
- const normalizedQueryParams = unwrapObject(queryParams) ?? {};
1191
- const searchParams = new URLSearchParams(normalizedQueryParams);
1192
- return await route.handler(RequestInputContext.fromSSRContext({
1193
- method: route.method,
1194
- path: route.path,
1195
- pathParams: normalizedPathParams,
1196
- searchParams
1197
- }), new RequestOutputContext(route.outputSchema));
1198
- }
1199
- async function executeQuery(params) {
1200
- const { pathParams, queryParams } = params ?? {};
1201
- if (typeof window === "undefined") return task(async () => callServerSideHandler({
1202
- pathParams,
1203
- queryParams
1204
- }));
1205
- const url = buildUrl({
1206
- baseUrl,
1207
- mountRoute,
1208
- path: route.path
1209
- }, {
1210
- pathParams,
1211
- queryParams
1212
- });
1213
- let response;
1214
- try {
1215
- response = await fetch(url);
1216
- } catch (error) {
1217
- throw FragnoClientFetchError.fromUnknownFetchError(error);
1218
- }
1219
- if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
1220
- return response;
1221
- }
1222
- return {
1223
- route,
1224
- store: (args) => {
1225
- const { path, query } = args ?? {};
1226
- const key = getCacheKey(route.method, route.path, {
1227
- pathParams: path,
1228
- queryParams: query
1229
- });
1230
- const store = this.#createFetcherStore(key, {
1231
- fetcher: async () => {
1232
- if (SSR_ENABLED) {
1233
- const initialData = getInitialData(key.map((d) => typeof d === "string" ? d : d.get()).join(""));
1234
- if (initialData) return initialData;
1235
- }
1236
- const response = await executeQuery({
1237
- pathParams: path,
1238
- queryParams: query
1239
- });
1240
- const isStreaming = isStreamingResponse(response);
1241
- if (!isStreaming) return response.json();
1242
- if (typeof window === "undefined") return [];
1243
- if (isStreaming === "ndjson") {
1244
- const { firstItem } = await handleNdjsonStreamingFirstItem(response, {
1245
- setData: (value) => {
1246
- store.set({
1247
- ...store.get(),
1248
- loading: !(Array.isArray(value) && value.length > 0),
1249
- data: value
1250
- });
1251
- },
1252
- setError: (value) => {
1253
- store.set({
1254
- ...store.get(),
1255
- error: value
1256
- });
1257
- }
1258
- });
1259
- return [firstItem];
1260
- }
1261
- if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1262
- throw new Error("Unreachable");
1263
- },
1264
- onErrorRetry: options?.onErrorRetry,
1265
- dedupeTime: Infinity
1266
- });
1267
- if (typeof window === "undefined") addStore(store);
1268
- return store;
1269
- },
1270
- query: async (args) => {
1271
- const { path, query } = args ?? {};
1272
- const response = await executeQuery({
1273
- pathParams: path,
1274
- queryParams: query
1275
- });
1276
- const isStreaming = isStreamingResponse(response);
1277
- if (!isStreaming) return await response.json();
1278
- if (isStreaming === "ndjson") {
1279
- const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
1280
- return await streamingPromise;
1281
- }
1282
- if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1283
- throw new Error("Unreachable");
1284
- },
1285
- [GET_HOOK_SYMBOL]: true
1286
- };
1287
- }
1288
- #createRouteQueryMutator(route, onInvalidate = (invalidate, params) => invalidate("GET", route.path, params)) {
1289
- const method = route.method;
1290
- const baseUrl = this.#publicConfig.baseUrl ?? "";
1291
- const mountRoute = getMountRoute(this.#fragmentConfig);
1292
- async function executeMutateQuery({ body, path, query }) {
1293
- if (typeof window === "undefined") return task(async () => route.handler(RequestInputContext.fromSSRContext({
1294
- inputSchema: route.inputSchema,
1295
- method,
1296
- path: route.path,
1297
- pathParams: path ?? {},
1298
- searchParams: new URLSearchParams(query),
1299
- body
1300
- }), new RequestOutputContext(route.outputSchema)));
1301
- const url = buildUrl({
1302
- baseUrl,
1303
- mountRoute,
1304
- path: route.path
1305
- }, {
1306
- pathParams: path,
1307
- queryParams: query
1308
- });
1309
- let response;
1310
- try {
1311
- response = await fetch(url, {
1312
- method,
1313
- body: body !== void 0 ? JSON.stringify(body) : void 0
1314
- });
1315
- } catch (error) {
1316
- throw FragnoClientFetchError.fromUnknownFetchError(error);
1317
- }
1318
- if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
1319
- return response;
1320
- }
1321
- const mutatorStore = this.#createMutatorStore(async ({ data }) => {
1322
- if (typeof window === "undefined") {}
1323
- const { body, path, query } = data;
1324
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required.");
1325
- const response = await executeMutateQuery({
1326
- body,
1327
- path,
1328
- query
1329
- });
1330
- onInvalidate(this.#invalidate.bind(this), {
1331
- pathParams: path ?? {},
1332
- queryParams: query
1333
- });
1334
- if (response.status === 201 || response.status === 204) return;
1335
- const isStreaming = isStreamingResponse(response);
1336
- if (!isStreaming) return response.json();
1337
- if (typeof window === "undefined") return [];
1338
- if (isStreaming === "ndjson") {
1339
- const { firstItem } = await handleNdjsonStreamingFirstItem(response, {
1340
- setData: (value) => {
1341
- mutatorStore.set({
1342
- ...mutatorStore.get(),
1343
- loading: !(Array.isArray(value) && value.length > 0),
1344
- data: value
1345
- });
1346
- },
1347
- setError: (value) => {
1348
- mutatorStore.set({
1349
- ...mutatorStore.get(),
1350
- error: value
1351
- });
1352
- }
1353
- });
1354
- return [firstItem];
1355
- }
1356
- if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1357
- throw new Error("Unreachable");
1358
- }, { onError: (error) => {
1359
- console.error("Error in mutatorStore", error);
1360
- } });
1361
- const mutateQuery = async (data) => {
1362
- const { body, path, query } = data;
1363
- if (typeof body === "undefined" && route.inputSchema !== void 0) throw new Error("Body is required for mutateQuery");
1364
- const response = await executeMutateQuery({
1365
- body,
1366
- path,
1367
- query
1368
- });
1369
- if (response.status === 201 || response.status === 204) return;
1370
- const isStreaming = isStreamingResponse(response);
1371
- if (!isStreaming) return response.json();
1372
- if (isStreaming === "ndjson") {
1373
- const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
1374
- return await streamingPromise;
1375
- }
1376
- if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported for mutations");
1377
- throw new Error("Unreachable");
1378
- };
1379
- return {
1380
- route,
1381
- mutateQuery,
1382
- mutatorStore,
1383
- [MUTATOR_HOOK_SYMBOL]: true
1384
- };
1385
- }
1386
- #invalidate(method, path, params) {
1387
- const prefix = getCacheKey(method, path, {
1388
- pathParams: params?.pathParams,
1389
- queryParams: params?.queryParams
1390
- }).map((k) => typeof k === "string" ? k : k.get()).join("");
1391
- this.#invalidateKeys((key) => key.startsWith(prefix));
1392
- }
1393
- };
1394
- function createClientBuilder(fragmentBuilder, publicConfig, routesOrFactories) {
1395
- const definition = fragmentBuilder.definition;
1396
- const routes$1 = resolveRouteFactories({
1397
- config: {},
1398
- deps: {},
1399
- services: {}
1400
- }, routesOrFactories);
1401
- const fragmentConfig = {
1402
- name: definition.name,
1403
- routes: routes$1
1404
- };
1405
- const mountRoute = publicConfig.mountRoute ?? `/${definition.name}`;
1406
- return new ClientBuilder({
1407
- ...publicConfig,
1408
- mountRoute
1409
- }, fragmentConfig);
1410
- }
1411
-
1412
- //#endregion
1413
- //#region src/server/chatno-api.ts
1414
- const ChatMessageSchema = z.object({
1415
- type: z.literal("chat"),
1416
- id: z.string(),
1417
- role: z.enum(["user", "assistant"]),
1418
- content: z.string()
1419
- });
1420
- const FunctionCallMessageSchema = z.object({
1421
- type: z.literal("functionCall"),
1422
- id: z.string(),
1423
- functionCallId: z.string(),
1424
- name: z.string(),
1425
- arguments: z.string()
1426
- });
1427
- const FunctionCallOutputMessageSchema = z.object({
1428
- type: z.literal("functionCallOutput"),
1429
- id: z.string(),
1430
- functionCallId: z.string(),
1431
- output: z.string(),
1432
- status: z.enum([
1433
- "inProgress",
1434
- "completed",
1435
- "incomplete"
1436
- ])
1437
- });
1438
- const InputMessageSchema = z.discriminatedUnion("type", [
1439
- ChatMessageSchema,
1440
- FunctionCallMessageSchema,
1441
- FunctionCallOutputMessageSchema
1442
- ]);
1443
- const ChatStreamRequestSchema = z.object({ messages: z.array(InputMessageSchema) });
1444
- const ResponseTextDeltaEventSchema = z.object({
1445
- type: z.literal("response.output_text.delta"),
1446
- content_index: z.number(),
1447
- delta: z.string(),
1448
- item_id: z.string(),
1449
- output_index: z.number(),
1450
- sequence_number: z.number()
1451
- });
1452
- const ResponseTextDoneEventSchema = z.object({
1453
- type: z.literal("response.output_text.done"),
1454
- content_index: z.number(),
1455
- item_id: z.string(),
1456
- output_index: z.number(),
1457
- sequence_number: z.number(),
1458
- text: z.string()
1459
- });
1460
- const ResponseEventSchema = z.discriminatedUnion("type", [ResponseTextDeltaEventSchema, ResponseTextDoneEventSchema]);
1461
- const chatRouteFactory = defineRoutes().create(({ config, deps }) => {
1462
- return [defineRoute({
1463
- method: "POST",
1464
- path: "/chat/stream",
1465
- inputSchema: ChatStreamRequestSchema,
1466
- outputSchema: z.array(ResponseEventSchema),
1467
- handler: () => {}
1468
- })];
1469
- });
1470
-
1471
- //#endregion
1472
- //#region src/index.ts
1473
- const healthRoute = defineRoute({
1474
- method: "GET",
1475
- path: "/health",
1476
- outputSchema: z.object({ status: z.literal("ok") }),
1477
- handler: () => {}
1478
- });
1479
- const simpleStreamRoute = defineRoute({
1480
- method: "GET",
1481
- path: "/simple-stream",
1482
- outputSchema: z.array(z.object({ message: z.string() })),
1483
- handler: () => {}
1484
- });
1485
- const DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;
1486
- const chatnoDefinition = defineFragment("chatno").withDependencies(() => {}).withServices(() => {});
1487
- const routes = [
1488
- chatRouteFactory,
1489
- healthRoute,
1490
- simpleStreamRoute
1491
- ];
1492
- function createChatno(chatnoConfig, fragnoConfig = {}) {
1493
- const config = {
1494
- model: chatnoConfig.model ?? "gpt-5-nano",
1495
- systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
1496
- };
1497
- return createFragment(chatnoDefinition, {
1498
- ...chatnoConfig,
1499
- ...config
1500
- }, routes, fragnoConfig);
1501
- }
1502
- function createChatnoClients(fragnoConfig) {
1503
- const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);
1504
- const chatStream = cb.createMutator("POST", "/chat/stream");
1505
- const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {
1506
- return (data ?? []).filter((item) => item.type === "response.output_text.delta").map((item) => item.delta).join("");
1507
- });
1508
- function sendMessage(message) {
1509
- chatStream.mutatorStore.mutate({ body: { messages: [{
1510
- type: "chat",
1511
- id: crypto.randomUUID(),
1512
- role: "user",
1513
- content: message
1514
- }] } });
1515
- }
1516
- return { useSendMessage: cb.createStore({
1517
- response: aggregatedMessage,
1518
- responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),
1519
- sendMessage
1520
- }) };
1521
- }
1522
-
1523
- //#endregion
1524
- export { isReadableAtom as a, isMutatorHook as i, createChatnoClients as n, isStore as o, isGetHook as r, createChatno as t };
1525
- //# sourceMappingURL=src-DdjU_SuS.js.map