@fragno-dev/cloudflare-fragment 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +92 -0
  3. package/dist/browser/client/react.d.ts +290 -0
  4. package/dist/browser/client/react.d.ts.map +1 -0
  5. package/dist/browser/client/react.js +166 -0
  6. package/dist/browser/client/react.js.map +1 -0
  7. package/dist/browser/client/solid.d.ts +292 -0
  8. package/dist/browser/client/solid.d.ts.map +1 -0
  9. package/dist/browser/client/solid.js +136 -0
  10. package/dist/browser/client/solid.js.map +1 -0
  11. package/dist/browser/client/svelte.d.ts +287 -0
  12. package/dist/browser/client/svelte.d.ts.map +1 -0
  13. package/dist/browser/client/svelte.js +134 -0
  14. package/dist/browser/client/svelte.js.map +1 -0
  15. package/dist/browser/client/vanilla.d.ts +316 -0
  16. package/dist/browser/client/vanilla.d.ts.map +1 -0
  17. package/dist/browser/client/vanilla.js +160 -0
  18. package/dist/browser/client/vanilla.js.map +1 -0
  19. package/dist/browser/client/vue.d.ts +290 -0
  20. package/dist/browser/client/vue.d.ts.map +1 -0
  21. package/dist/browser/client/vue.js +133 -0
  22. package/dist/browser/client/vue.js.map +1 -0
  23. package/dist/browser/client-Bk-J98pf.d.ts +679 -0
  24. package/dist/browser/client-Bk-J98pf.d.ts.map +1 -0
  25. package/dist/browser/index.d.ts +2027 -0
  26. package/dist/browser/index.d.ts.map +1 -0
  27. package/dist/browser/index.js +27 -0
  28. package/dist/browser/index.js.map +1 -0
  29. package/dist/browser/schema-Bt-h9kGf.js +2348 -0
  30. package/dist/browser/schema-Bt-h9kGf.js.map +1 -0
  31. package/dist/node/cloudflare-api.d.ts +106 -0
  32. package/dist/node/cloudflare-api.d.ts.map +1 -0
  33. package/dist/node/cloudflare-api.js +146 -0
  34. package/dist/node/cloudflare-api.js.map +1 -0
  35. package/dist/node/contracts.d.ts +288 -0
  36. package/dist/node/contracts.d.ts.map +1 -0
  37. package/dist/node/contracts.js +66 -0
  38. package/dist/node/contracts.js.map +1 -0
  39. package/dist/node/definition.d.ts +339 -0
  40. package/dist/node/definition.d.ts.map +1 -0
  41. package/dist/node/definition.js +417 -0
  42. package/dist/node/definition.js.map +1 -0
  43. package/dist/node/deployment-tag.d.ts +13 -0
  44. package/dist/node/deployment-tag.d.ts.map +1 -0
  45. package/dist/node/deployment-tag.js +73 -0
  46. package/dist/node/deployment-tag.js.map +1 -0
  47. package/dist/node/index.d.ts +786 -0
  48. package/dist/node/index.d.ts.map +1 -0
  49. package/dist/node/index.js +35 -0
  50. package/dist/node/index.js.map +1 -0
  51. package/dist/node/routes.d.ts +520 -0
  52. package/dist/node/routes.d.ts.map +1 -0
  53. package/dist/node/routes.js +100 -0
  54. package/dist/node/routes.js.map +1 -0
  55. package/dist/node/schema.d.ts +11 -0
  56. package/dist/node/schema.d.ts.map +1 -0
  57. package/dist/node/schema.js +24 -0
  58. package/dist/node/schema.js.map +1 -0
  59. package/dist/node/script-name.d.ts +13 -0
  60. package/dist/node/script-name.d.ts.map +1 -0
  61. package/dist/node/script-name.js +35 -0
  62. package/dist/node/script-name.js.map +1 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -0
  64. package/package.json +98 -0
@@ -0,0 +1,2348 @@
1
+ import { defineFragment, defineRoutes } from "@fragno-dev/core";
2
+ import Cloudflare, { NotFoundError, toFile } from "cloudflare";
3
+ import { z } from "zod";
4
+ import { column, idColumn, referenceColumn, schema } from "@fragno-dev/db/schema";
5
+
6
+ //#region ../fragno/dist/api/route.js
7
+ /**
8
+ * Helper to resolve route factories into routes
9
+ * @internal
10
+ */
11
+ function resolveRouteFactories(context, routesOrFactories) {
12
+ const routes = [];
13
+ for (const item of routesOrFactories) if (typeof item === "function") {
14
+ const factoryRoutes = item(context);
15
+ routes.push(...factoryRoutes);
16
+ } else routes.push(item);
17
+ return routes;
18
+ }
19
+
20
+ //#endregion
21
+ //#region ../fragno/dist/api/internal/path.js
22
+ /**
23
+ * Extract parameter names from a path pattern at runtime.
24
+ * Examples:
25
+ * - "/users/:id" => ["id"]
26
+ * - "/files/**" => ["**"]
27
+ * - "/files/**:rest" => ["rest"]
28
+ */
29
+ function extractPathParams(pathPattern) {
30
+ const segments = pathPattern.split("/").filter((s) => s.length > 0);
31
+ const names = [];
32
+ for (const segment of segments) {
33
+ if (segment.startsWith(":")) {
34
+ names.push(segment.slice(1));
35
+ continue;
36
+ }
37
+ if (segment === "**") {
38
+ names.push("**");
39
+ continue;
40
+ }
41
+ if (segment.startsWith("**:")) {
42
+ names.push(segment.slice(3));
43
+ continue;
44
+ }
45
+ }
46
+ return names;
47
+ }
48
+ /**
49
+ * Build a concrete path by replacing placeholders in a path pattern with values.
50
+ *
51
+ * Supports the same placeholder syntax as the matcher:
52
+ * - Named parameter ":name" is URL-encoded as a single segment
53
+ * - Anonymous wildcard "**" inserts the remainder as-is (slashes preserved)
54
+ * - Named wildcard "**:name" inserts the remainder from the named key
55
+ *
56
+ * Examples:
57
+ * - buildPath("/users/:id", { id: "123" }) => "/users/123"
58
+ * - buildPath("/files/**", { "**": "a/b" }) => "/files/a/b"
59
+ * - buildPath("/files/**:rest", { rest: "a/b" }) => "/files/a/b"
60
+ */
61
+ function buildPath(pathPattern, params) {
62
+ const patternSegments = pathPattern.split("/");
63
+ const builtSegments = [];
64
+ for (const segment of patternSegments) {
65
+ if (segment.length === 0) {
66
+ builtSegments.push("");
67
+ continue;
68
+ }
69
+ if (segment.startsWith(":")) {
70
+ const name = segment.slice(1);
71
+ const value = params[name];
72
+ if (value === void 0) throw new Error(`Missing value for path parameter :${name}`);
73
+ builtSegments.push(encodeURIComponent(value));
74
+ continue;
75
+ }
76
+ if (segment === "**") {
77
+ const value = params["**"];
78
+ if (value === void 0) throw new Error("Missing value for path wildcard **");
79
+ builtSegments.push(value);
80
+ continue;
81
+ }
82
+ if (segment.startsWith("**:")) {
83
+ const name = segment.slice(3);
84
+ const value = params[name];
85
+ if (value === void 0) throw new Error(`Missing value for path wildcard **:${name}`);
86
+ builtSegments.push(value);
87
+ continue;
88
+ }
89
+ builtSegments.push(segment);
90
+ }
91
+ return builtSegments.join("/");
92
+ }
93
+
94
+ //#endregion
95
+ //#region ../fragno/dist/api/internal/route.js
96
+ function getMountRoute(opts) {
97
+ const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
98
+ if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
99
+ return mountRoute;
100
+ }
101
+
102
+ //#endregion
103
+ //#region ../fragno/dist/api/error.js
104
+ var FragnoApiError = class extends Error {
105
+ #status;
106
+ #code;
107
+ constructor({ message, code }, status) {
108
+ super(message);
109
+ this.name = "FragnoApiError";
110
+ this.#status = status;
111
+ this.#code = code;
112
+ }
113
+ get status() {
114
+ return this.#status;
115
+ }
116
+ get code() {
117
+ return this.#code;
118
+ }
119
+ toResponse() {
120
+ return Response.json({
121
+ message: this.message,
122
+ code: this.code
123
+ }, { status: this.status });
124
+ }
125
+ };
126
+ var FragnoApiValidationError = class extends FragnoApiError {
127
+ #issues;
128
+ constructor(message, issues) {
129
+ super({
130
+ message,
131
+ code: "FRAGNO_VALIDATION_ERROR"
132
+ }, 400);
133
+ this.name = "FragnoApiValidationError";
134
+ this.#issues = issues;
135
+ }
136
+ get issues() {
137
+ return this.#issues;
138
+ }
139
+ toResponse() {
140
+ return Response.json({
141
+ message: this.message,
142
+ issues: this.#issues,
143
+ code: this.code
144
+ }, { status: this.status });
145
+ }
146
+ };
147
+
148
+ //#endregion
149
+ //#region ../fragno/dist/api/request-input-context.js
150
+ var RequestInputContext = class RequestInputContext {
151
+ #path;
152
+ #method;
153
+ #pathParams;
154
+ #searchParams;
155
+ #headers;
156
+ #body;
157
+ #parsedBody;
158
+ #inputSchema;
159
+ #shouldValidateInput;
160
+ constructor(config) {
161
+ this.#path = config.path;
162
+ this.#method = config.method;
163
+ this.#pathParams = config.pathParams;
164
+ this.#searchParams = config.searchParams;
165
+ this.#headers = config.headers;
166
+ this.#body = config.rawBody;
167
+ this.#parsedBody = config.parsedBody;
168
+ this.#inputSchema = config.inputSchema;
169
+ this.#shouldValidateInput = config.shouldValidateInput ?? true;
170
+ }
171
+ /**
172
+ * Create a RequestContext from a Request object for server-side handling
173
+ */
174
+ static async fromRequest(config) {
175
+ return new RequestInputContext({
176
+ method: config.method,
177
+ path: config.path,
178
+ pathParams: config.state.pathParams,
179
+ searchParams: config.state.searchParams,
180
+ headers: config.state.headers,
181
+ parsedBody: config.state.body,
182
+ rawBody: config.rawBody,
183
+ inputSchema: config.inputSchema,
184
+ shouldValidateInput: config.shouldValidateInput
185
+ });
186
+ }
187
+ /**
188
+ * Create a RequestContext for server-side rendering contexts (no Request object)
189
+ */
190
+ static fromSSRContext(config) {
191
+ return new RequestInputContext({
192
+ method: config.method,
193
+ path: config.path,
194
+ pathParams: config.pathParams,
195
+ searchParams: config.searchParams ?? new URLSearchParams(),
196
+ headers: config.headers ?? new Headers(),
197
+ parsedBody: "body" in config ? config.body : void 0,
198
+ inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
199
+ shouldValidateInput: false
200
+ });
201
+ }
202
+ /**
203
+ * The HTTP method as string (e.g., `GET`, `POST`)
204
+ */
205
+ get method() {
206
+ return this.#method;
207
+ }
208
+ /**
209
+ * The matched route path (e.g., `/users/:id`)
210
+ * @remarks `string`
211
+ */
212
+ get path() {
213
+ return this.#path;
214
+ }
215
+ /**
216
+ * Extracted path parameters as object (e.g., `{ id: '123' }`)
217
+ * @remarks `Record<string, string>`
218
+ */
219
+ get pathParams() {
220
+ return this.#pathParams;
221
+ }
222
+ /**
223
+ * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
224
+ * @remarks `URLSearchParams`
225
+ */
226
+ get query() {
227
+ return this.#searchParams;
228
+ }
229
+ /**
230
+ * [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers
231
+ * @remarks `Headers`
232
+ */
233
+ get headers() {
234
+ return this.#headers;
235
+ }
236
+ get rawBody() {
237
+ return this.#body;
238
+ }
239
+ /**
240
+ * Access the request body as FormData.
241
+ *
242
+ * Use this method when handling file uploads or multipart form submissions.
243
+ * The request must have been sent with Content-Type: multipart/form-data.
244
+ *
245
+ * @throws Error if the request body is not FormData
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * defineRoute({
250
+ * method: "POST",
251
+ * path: "/upload",
252
+ * async handler(ctx, res) {
253
+ * const formData = ctx.formData();
254
+ * const file = formData.get("file") as File;
255
+ * const description = formData.get("description") as string;
256
+ * // ... process file
257
+ * }
258
+ * });
259
+ * ```
260
+ */
261
+ formData() {
262
+ if (!(this.#parsedBody instanceof FormData)) throw new Error("Request body is not FormData. Ensure the request was sent with Content-Type: multipart/form-data.");
263
+ return this.#parsedBody;
264
+ }
265
+ /**
266
+ * Check if the request body is FormData.
267
+ *
268
+ * Useful for routes that accept both JSON and FormData payloads.
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * defineRoute({
273
+ * method: "POST",
274
+ * path: "/upload",
275
+ * async handler(ctx, res) {
276
+ * if (ctx.isFormData()) {
277
+ * const formData = ctx.formData();
278
+ * // handle file upload
279
+ * } else {
280
+ * const json = await ctx.input.valid();
281
+ * // handle JSON payload
282
+ * }
283
+ * }
284
+ * });
285
+ * ```
286
+ */
287
+ isFormData() {
288
+ return this.#parsedBody instanceof FormData;
289
+ }
290
+ /**
291
+ * Access the request body as a ReadableStream (application/octet-stream).
292
+ *
293
+ * @throws Error if the request body is not a ReadableStream
294
+ */
295
+ bodyStream() {
296
+ if (!(this.#parsedBody instanceof ReadableStream)) throw new Error("Request body is not a ReadableStream. Ensure the request was sent with Content-Type: application/octet-stream.");
297
+ return this.#parsedBody;
298
+ }
299
+ /**
300
+ * Check if the request body is a ReadableStream.
301
+ */
302
+ isBodyStream() {
303
+ return this.#parsedBody instanceof ReadableStream;
304
+ }
305
+ /**
306
+ * Input validation context (only if inputSchema is defined)
307
+ * @remarks `InputContext`
308
+ */
309
+ get input() {
310
+ if (!this.#inputSchema) return;
311
+ return {
312
+ schema: this.#inputSchema,
313
+ valid: async () => {
314
+ if (!this.#shouldValidateInput) return this.#parsedBody;
315
+ return this.#validateInput();
316
+ }
317
+ };
318
+ }
319
+ async #validateInput() {
320
+ if (!this.#inputSchema) throw new Error("No input schema defined for this route");
321
+ if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
322
+ if (this.#parsedBody instanceof ReadableStream) throw new Error("Schema validation is only supported for JSON data, not FormData, Blob, or ReadableStream");
323
+ const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
324
+ if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
325
+ return result.value;
326
+ }
327
+ };
328
+
329
+ //#endregion
330
+ //#region ../fragno/dist/api/internal/response-stream.js
331
+ var ResponseStream = class {
332
+ #writer;
333
+ #encoder;
334
+ #abortSubscribers = [];
335
+ #responseReadable;
336
+ #aborted = false;
337
+ #closed = false;
338
+ /**
339
+ * Whether the stream has been aborted.
340
+ */
341
+ get aborted() {
342
+ return this.#aborted;
343
+ }
344
+ /**
345
+ * Whether the stream has been closed normally.
346
+ */
347
+ get closed() {
348
+ return this.#closed;
349
+ }
350
+ /**
351
+ * The readable stream that the response is piped to.
352
+ */
353
+ get responseReadable() {
354
+ return this.#responseReadable;
355
+ }
356
+ constructor(writable, readable) {
357
+ this.#writer = writable.getWriter();
358
+ this.#encoder = new TextEncoder();
359
+ const reader = readable.getReader();
360
+ this.#abortSubscribers.push(async () => {
361
+ await reader.cancel();
362
+ });
363
+ this.#responseReadable = new ReadableStream({
364
+ async pull(controller) {
365
+ const { done, value } = await reader.read();
366
+ if (done) controller.close();
367
+ else controller.enqueue(value);
368
+ },
369
+ cancel: () => {
370
+ this.abort();
371
+ }
372
+ });
373
+ }
374
+ async writeRaw(input) {
375
+ try {
376
+ if (typeof input === "string") input = this.#encoder.encode(input);
377
+ await this.#writer.write(input);
378
+ } catch {}
379
+ }
380
+ write(input) {
381
+ return this.writeRaw(JSON.stringify(input) + "\n");
382
+ }
383
+ sleep(ms) {
384
+ return new Promise((res) => setTimeout(res, ms));
385
+ }
386
+ async close() {
387
+ try {
388
+ await this.#writer.close();
389
+ } catch {} finally {
390
+ this.#closed = true;
391
+ }
392
+ }
393
+ onAbort(listener) {
394
+ this.#abortSubscribers.push(listener);
395
+ }
396
+ /**
397
+ * Abort the stream.
398
+ * You can call this method when stream is aborted by external event.
399
+ */
400
+ abort() {
401
+ if (!this.aborted) {
402
+ this.#aborted = true;
403
+ this.#abortSubscribers.forEach((subscriber) => subscriber());
404
+ }
405
+ }
406
+ };
407
+
408
+ //#endregion
409
+ //#region ../fragno/dist/api/request-output-context.js
410
+ /**
411
+ * Utility function to merge headers from multiple sources.
412
+ * Later headers override earlier ones.
413
+ */
414
+ function mergeHeaders$1(...headerSources) {
415
+ const mergedHeaders = new Headers();
416
+ for (const headerSource of headerSources) {
417
+ if (!headerSource) continue;
418
+ if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
419
+ else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
420
+ else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
421
+ }
422
+ return mergedHeaders;
423
+ }
424
+ var OutputContext = class {
425
+ /**
426
+ * Creates an error response.
427
+ *
428
+ * Shortcut for `throw new FragnoApiError(...)`
429
+ */
430
+ error = ({ message, code }, initOrStatus, headers) => {
431
+ if (typeof initOrStatus === "undefined") return Response.json({
432
+ message,
433
+ code
434
+ }, {
435
+ status: 500,
436
+ headers
437
+ });
438
+ if (typeof initOrStatus === "number") return Response.json({
439
+ message,
440
+ code
441
+ }, {
442
+ status: initOrStatus,
443
+ headers
444
+ });
445
+ const mergedHeaders = mergeHeaders$1(initOrStatus.headers, headers);
446
+ return Response.json({
447
+ message,
448
+ code
449
+ }, {
450
+ status: initOrStatus.status,
451
+ headers: mergedHeaders
452
+ });
453
+ };
454
+ empty = (initOrStatus, headers) => {
455
+ const defaultHeaders = {};
456
+ if (typeof initOrStatus === "undefined") {
457
+ const mergedHeaders$1 = mergeHeaders$1(defaultHeaders, headers);
458
+ return new Response(null, {
459
+ status: 201,
460
+ headers: mergedHeaders$1
461
+ });
462
+ }
463
+ if (typeof initOrStatus === "number") {
464
+ const mergedHeaders$1 = mergeHeaders$1(defaultHeaders, headers);
465
+ return new Response(null, {
466
+ status: initOrStatus,
467
+ headers: mergedHeaders$1
468
+ });
469
+ }
470
+ const mergedHeaders = mergeHeaders$1(defaultHeaders, initOrStatus.headers, headers);
471
+ return new Response(null, {
472
+ status: initOrStatus.status,
473
+ headers: mergedHeaders
474
+ });
475
+ };
476
+ json = (object, initOrStatus, headers) => {
477
+ if (typeof initOrStatus === "undefined") return Response.json(object, {
478
+ status: 200,
479
+ headers
480
+ });
481
+ if (typeof initOrStatus === "number") return Response.json(object, {
482
+ status: initOrStatus,
483
+ headers
484
+ });
485
+ const mergedHeaders = mergeHeaders$1(initOrStatus.headers, headers);
486
+ return Response.json(object, {
487
+ status: initOrStatus.status,
488
+ headers: mergedHeaders
489
+ });
490
+ };
491
+ jsonStream = (cb, { onError, headers } = {}) => {
492
+ const defaultHeaders = {
493
+ "content-type": "application/x-ndjson; charset=utf-8",
494
+ "transfer-encoding": "chunked",
495
+ "cache-control": "no-cache"
496
+ };
497
+ const { readable, writable } = new TransformStream();
498
+ const stream = new ResponseStream(writable, readable);
499
+ (async () => {
500
+ try {
501
+ await cb(stream);
502
+ } catch (e) {
503
+ if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
504
+ else console.error(e);
505
+ } finally {
506
+ stream.close();
507
+ }
508
+ })();
509
+ return new Response(stream.responseReadable, {
510
+ status: 200,
511
+ headers: mergeHeaders$1(defaultHeaders, headers)
512
+ });
513
+ };
514
+ };
515
+ var RequestOutputContext = class extends OutputContext {
516
+ #outputSchema;
517
+ constructor(outputSchema) {
518
+ super();
519
+ this.#outputSchema = outputSchema;
520
+ }
521
+ };
522
+
523
+ //#endregion
524
+ //#region ../fragno/dist/util/content-type.js
525
+ /**
526
+ * Parses a content-type header string into its components
527
+ *
528
+ * @param contentType - The content-type header value to parse
529
+ * @returns A ParsedContentType object or null if the input is invalid
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * const { type, subtype, mediaType, parameters }
534
+ * = parseContentType("application/json; charset=utf-8");
535
+ * console.assert(type === "application");
536
+ * console.assert(subtype === "json");
537
+ * console.assert(mediaType === "application/json");
538
+ * console.assert(parameters["charset"] === "utf-8");
539
+ */
540
+ function parseContentType(contentType) {
541
+ if (!contentType || typeof contentType !== "string") return null;
542
+ const trimmed = contentType.trim();
543
+ if (!trimmed) return null;
544
+ const parts = trimmed.split(";").map((part) => part.trim());
545
+ const mediaType = parts[0];
546
+ if (!mediaType) return null;
547
+ const typeParts = mediaType.split("/");
548
+ if (typeParts.length !== 2) return null;
549
+ const [type, subtype] = typeParts.map((part) => part.trim().toLowerCase());
550
+ if (!type || !subtype) return null;
551
+ const parameters = {};
552
+ for (let i = 1; i < parts.length; i++) {
553
+ const param = parts[i];
554
+ const equalIndex = param.indexOf("=");
555
+ if (equalIndex > 0) {
556
+ const key = param.slice(0, equalIndex).trim().toLowerCase();
557
+ let value = param.slice(equalIndex + 1).trim();
558
+ if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
559
+ if (key) parameters[key] = value;
560
+ }
561
+ }
562
+ return {
563
+ type,
564
+ subtype,
565
+ mediaType: `${type}/${subtype}`,
566
+ parameters
567
+ };
568
+ }
569
+
570
+ //#endregion
571
+ //#region ../fragno/dist/util/nanostores.js
572
+ /**
573
+ * Normalizes a value that could be a plain value, an Atom, or a Vue Ref to a plain value.
574
+ */
575
+ function unwrapAtom(value) {
576
+ if (value && typeof value === "object" && "get" in value && typeof value.get === "function") return value.get();
577
+ return value;
578
+ }
579
+ /**
580
+ * Normalizes an object where values can be plain values, Atoms, or Vue Refs.
581
+ * Returns a new object with all values normalized to plain values.
582
+ */
583
+ function unwrapObject(params) {
584
+ if (!params) return;
585
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, unwrapAtom(value)]));
586
+ }
587
+ function isReadableAtom(value) {
588
+ if (!value) return false;
589
+ if (typeof value !== "object" || value === null) return false;
590
+ if (!("get" in value) || typeof value.get !== "function") return false;
591
+ if (!("lc" in value) || typeof value.lc !== "number") return false;
592
+ if (!("notify" in value) || typeof value.notify !== "function") return false;
593
+ if (!("off" in value) || typeof value.off !== "function") return false;
594
+ if (!("subscribe" in value) || typeof value.subscribe !== "function") return false;
595
+ if (!("value" in value)) return false;
596
+ return true;
597
+ }
598
+
599
+ //#endregion
600
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/task/index.js
601
+ let tasks = 0;
602
+ let resolves = [];
603
+ function startTask() {
604
+ tasks += 1;
605
+ return () => {
606
+ tasks -= 1;
607
+ if (tasks === 0) {
608
+ let prevResolves = resolves;
609
+ resolves = [];
610
+ for (let i of prevResolves) i();
611
+ }
612
+ };
613
+ }
614
+ function task(cb) {
615
+ let endTask = startTask();
616
+ let promise = cb().finally(endTask);
617
+ promise.t = true;
618
+ return promise;
619
+ }
620
+
621
+ //#endregion
622
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.js
623
+ let clean = Symbol("clean");
624
+
625
+ //#endregion
626
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/atom/index.js
627
+ let listenerQueue = [];
628
+ let lqIndex = 0;
629
+ const QUEUE_ITEMS_PER_LISTENER = 4;
630
+ let epoch = 0;
631
+ const atom = /* @__NO_SIDE_EFFECTS__ */ (initialValue) => {
632
+ let listeners = [];
633
+ let $atom = {
634
+ get() {
635
+ if (!$atom.lc) $atom.listen(() => {})();
636
+ return $atom.value;
637
+ },
638
+ lc: 0,
639
+ listen(listener) {
640
+ $atom.lc = listeners.push(listener);
641
+ return () => {
642
+ for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length;) if (listenerQueue[i] === listener) listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER);
643
+ else i += QUEUE_ITEMS_PER_LISTENER;
644
+ let index = listeners.indexOf(listener);
645
+ if (~index) {
646
+ listeners.splice(index, 1);
647
+ if (!--$atom.lc) $atom.off();
648
+ }
649
+ };
650
+ },
651
+ notify(oldValue, changedKey) {
652
+ epoch++;
653
+ let runListenerQueue = !listenerQueue.length;
654
+ for (let listener of listeners) listenerQueue.push(listener, $atom.value, oldValue, changedKey);
655
+ if (runListenerQueue) {
656
+ for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) listenerQueue[lqIndex](listenerQueue[lqIndex + 1], listenerQueue[lqIndex + 2], listenerQueue[lqIndex + 3]);
657
+ listenerQueue.length = 0;
658
+ }
659
+ },
660
+ off() {},
661
+ set(newValue) {
662
+ let oldValue = $atom.value;
663
+ if (oldValue !== newValue) {
664
+ $atom.value = newValue;
665
+ $atom.notify(oldValue);
666
+ }
667
+ },
668
+ subscribe(listener) {
669
+ let unbind = $atom.listen(listener);
670
+ listener($atom.value);
671
+ return unbind;
672
+ },
673
+ value: initialValue
674
+ };
675
+ $atom[clean] = () => {
676
+ listeners = [];
677
+ $atom.lc = 0;
678
+ $atom.off();
679
+ };
680
+ return $atom;
681
+ };
682
+
683
+ //#endregion
684
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/lifecycle/index.js
685
+ const START = 0;
686
+ const STOP = 1;
687
+ const MOUNT = 5;
688
+ const UNMOUNT = 6;
689
+ const REVERT_MUTATION = 10;
690
+ let on = (object, listener, eventKey, mutateStore) => {
691
+ object.events = object.events || {};
692
+ if (!object.events[eventKey + REVERT_MUTATION]) object.events[eventKey + REVERT_MUTATION] = mutateStore((eventProps) => {
693
+ object.events[eventKey].reduceRight((event, l) => (l(event), event), {
694
+ shared: {},
695
+ ...eventProps
696
+ });
697
+ });
698
+ object.events[eventKey] = object.events[eventKey] || [];
699
+ object.events[eventKey].push(listener);
700
+ return () => {
701
+ let currentListeners = object.events[eventKey];
702
+ let index = currentListeners.indexOf(listener);
703
+ currentListeners.splice(index, 1);
704
+ if (!currentListeners.length) {
705
+ delete object.events[eventKey];
706
+ object.events[eventKey + REVERT_MUTATION]();
707
+ delete object.events[eventKey + REVERT_MUTATION];
708
+ }
709
+ };
710
+ };
711
+ let onStart = ($store, listener) => on($store, listener, START, (runListeners) => {
712
+ let originListen = $store.listen;
713
+ $store.listen = (arg) => {
714
+ if (!$store.lc && !$store.starting) {
715
+ $store.starting = true;
716
+ runListeners();
717
+ delete $store.starting;
718
+ }
719
+ return originListen(arg);
720
+ };
721
+ return () => {
722
+ $store.listen = originListen;
723
+ };
724
+ });
725
+ let onStop = ($store, listener) => on($store, listener, STOP, (runListeners) => {
726
+ let originOff = $store.off;
727
+ $store.off = () => {
728
+ runListeners();
729
+ originOff();
730
+ };
731
+ return () => {
732
+ $store.off = originOff;
733
+ };
734
+ });
735
+ let STORE_UNMOUNT_DELAY = 1e3;
736
+ let onMount = ($store, initialize) => {
737
+ let listener = (payload) => {
738
+ let destroy = initialize(payload);
739
+ if (destroy) $store.events[UNMOUNT].push(destroy);
740
+ };
741
+ return on($store, listener, MOUNT, (runListeners) => {
742
+ let originListen = $store.listen;
743
+ $store.listen = (...args) => {
744
+ if (!$store.lc && !$store.active) {
745
+ $store.active = true;
746
+ runListeners();
747
+ }
748
+ return originListen(...args);
749
+ };
750
+ let originOff = $store.off;
751
+ $store.events[UNMOUNT] = [];
752
+ $store.off = () => {
753
+ originOff();
754
+ setTimeout(() => {
755
+ if ($store.active && !$store.lc) {
756
+ $store.active = false;
757
+ for (let destroy of $store.events[UNMOUNT]) destroy();
758
+ $store.events[UNMOUNT] = [];
759
+ }
760
+ }, STORE_UNMOUNT_DELAY);
761
+ };
762
+ {
763
+ let originClean = $store[clean];
764
+ $store[clean] = () => {
765
+ for (let destroy of $store.events[UNMOUNT]) destroy();
766
+ $store.events[UNMOUNT] = [];
767
+ $store.active = false;
768
+ originClean();
769
+ };
770
+ }
771
+ return () => {
772
+ $store.listen = originListen;
773
+ $store.off = originOff;
774
+ };
775
+ });
776
+ };
777
+
778
+ //#endregion
779
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/computed/index.js
780
+ let computedStore = (stores, cb, batched) => {
781
+ if (!Array.isArray(stores)) stores = [stores];
782
+ let previousArgs;
783
+ let currentEpoch;
784
+ let set = () => {
785
+ if (currentEpoch === epoch) return;
786
+ currentEpoch = epoch;
787
+ let args = stores.map(($store) => $store.get());
788
+ if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) {
789
+ previousArgs = args;
790
+ let value = cb(...args);
791
+ if (value && value.then && value.t) value.then((asyncValue) => {
792
+ if (previousArgs === args) $computed.set(asyncValue);
793
+ });
794
+ else {
795
+ $computed.set(value);
796
+ currentEpoch = epoch;
797
+ }
798
+ }
799
+ };
800
+ let $computed = /* @__PURE__ */ atom(void 0);
801
+ let get = $computed.get;
802
+ $computed.get = () => {
803
+ set();
804
+ return get();
805
+ };
806
+ let timer;
807
+ let run = batched ? () => {
808
+ clearTimeout(timer);
809
+ timer = setTimeout(set);
810
+ } : set;
811
+ onMount($computed, () => {
812
+ let unbinds = stores.map(($store) => $store.listen(run));
813
+ set();
814
+ return () => {
815
+ for (let unbind of unbinds) unbind();
816
+ };
817
+ });
818
+ return $computed;
819
+ };
820
+ const computed = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn);
821
+ const batched = /* @__NO_SIDE_EFFECTS__ */ (stores, fn) => computedStore(stores, fn, true);
822
+
823
+ //#endregion
824
+ //#region ../../node_modules/.pnpm/nanostores@1.1.0/node_modules/nanostores/map/index.js
825
+ const map = /* @__NO_SIDE_EFFECTS__ */ (initial = {}) => {
826
+ let $map = /* @__PURE__ */ atom(initial);
827
+ $map.setKey = function(key, value) {
828
+ let oldMap = $map.value;
829
+ if (typeof value === "undefined" && key in $map.value) {
830
+ $map.value = { ...$map.value };
831
+ delete $map.value[key];
832
+ $map.notify(oldMap, key);
833
+ } else if ($map.value[key] !== value) {
834
+ $map.value = {
835
+ ...$map.value,
836
+ [key]: value
837
+ };
838
+ $map.notify(oldMap, key);
839
+ }
840
+ };
841
+ return $map;
842
+ };
843
+
844
+ //#endregion
845
+ //#region ../fragno/dist/util/ssr.js
846
+ let stores = [];
847
+ const SSR_ENABLED = false;
848
+ function addStore(store) {
849
+ stores.push(store);
850
+ }
851
+ let clientInitialData;
852
+ function getInitialData(key) {
853
+ if (clientInitialData?.has(key)) {
854
+ const data = clientInitialData.get(key);
855
+ clientInitialData.delete(key);
856
+ return data;
857
+ }
858
+ }
859
+
860
+ //#endregion
861
+ //#region ../fragno/dist/client/client-error.js
862
+ /**
863
+ * Base error class for all Fragno client errors.
864
+ */
865
+ var FragnoClientError = class extends Error {
866
+ #code;
867
+ constructor(message, code, options = {}) {
868
+ super(message, { cause: options.cause });
869
+ this.name = "FragnoClientError";
870
+ this.#code = code;
871
+ }
872
+ get code() {
873
+ return this.#code;
874
+ }
875
+ };
876
+ var FragnoClientFetchError = class extends FragnoClientError {
877
+ constructor(message, code, options = {}) {
878
+ super(message, code, options);
879
+ this.name = "FragnoClientFetchError";
880
+ }
881
+ static fromUnknownFetchError(error) {
882
+ if (!(error instanceof Error)) return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
883
+ if (error.name === "AbortError") return new FragnoClientFetchAbortError("Request was aborted", { cause: error });
884
+ return new FragnoClientFetchNetworkError("Network request failed", { cause: error });
885
+ }
886
+ };
887
+ /**
888
+ * Error thrown when a network request fails (e.g., no internet connection, DNS failure).
889
+ */
890
+ var FragnoClientFetchNetworkError = class extends FragnoClientFetchError {
891
+ constructor(message = "Network request failed", options = {}) {
892
+ super(message, "NETWORK_ERROR", options);
893
+ this.name = "FragnoClientFetchNetworkError";
894
+ }
895
+ };
896
+ /**
897
+ * Error thrown when a request is aborted (e.g., user cancels request, timeout).
898
+ */
899
+ var FragnoClientFetchAbortError = class extends FragnoClientFetchError {
900
+ constructor(message = "Request was aborted", options = {}) {
901
+ super(message, "ABORT_ERROR", options);
902
+ this.name = "FragnoClientFetchAbortError";
903
+ }
904
+ };
905
+ /**
906
+ * Error thrown when the API result is unexpected, e.g. no json is returned.
907
+ */
908
+ var FragnoClientUnknownApiError = class extends FragnoClientError {
909
+ #status;
910
+ constructor(message = "Unknown API error", status, options = {}) {
911
+ super(message, "UNKNOWN_API_ERROR", options);
912
+ this.name = "FragnoClientUnknownApiError";
913
+ this.#status = status;
914
+ }
915
+ get status() {
916
+ return this.#status;
917
+ }
918
+ };
919
+ var FragnoClientApiError = class FragnoClientApiError extends FragnoClientError {
920
+ #status;
921
+ constructor({ message, code }, status, options = {}) {
922
+ super(message, code, options);
923
+ this.name = "FragnoClientApiError";
924
+ this.#status = status;
925
+ }
926
+ get status() {
927
+ return this.#status;
928
+ }
929
+ /**
930
+ * The error code returned by the API.
931
+ *
932
+ * The type is `TErrorCode` (the set of known error codes for this route), but may also be a string
933
+ * for forward compatibility with future error codes.
934
+ */
935
+ get code() {
936
+ return super.code;
937
+ }
938
+ static async fromResponse(response) {
939
+ const unknown = await response.json();
940
+ const status = response.status;
941
+ if (!("message" in unknown || "code" in unknown)) return new FragnoClientUnknownApiError("Unknown API error", status);
942
+ if (!(typeof unknown.message === "string" && typeof unknown.code === "string")) return new FragnoClientUnknownApiError("Unknown API error", status);
943
+ return new FragnoClientApiError({
944
+ message: unknown.message,
945
+ code: unknown.code
946
+ }, status);
947
+ }
948
+ };
949
+
950
+ //#endregion
951
+ //#region ../fragno/dist/client/internal/fetcher-merge.js
952
+ /**
953
+ * Merge two fetcher configurations, with user config taking precedence.
954
+ * If user provides a custom function, it takes full precedence.
955
+ * Otherwise, deep merge RequestInit options.
956
+ */
957
+ function mergeFetcherConfigs(authorConfig, userConfig) {
958
+ if (userConfig?.type === "function") return userConfig;
959
+ if (!userConfig && authorConfig?.type === "function") return authorConfig;
960
+ const authorOpts = authorConfig?.type === "options" ? authorConfig.options : {};
961
+ const userOpts = userConfig?.type === "options" ? userConfig.options : {};
962
+ if (Object.keys(authorOpts).length === 0 && Object.keys(userOpts).length === 0) return;
963
+ return {
964
+ type: "options",
965
+ options: {
966
+ ...authorOpts,
967
+ ...userOpts,
968
+ headers: mergeHeaders(authorOpts.headers, userOpts.headers)
969
+ }
970
+ };
971
+ }
972
+ /**
973
+ * Merge headers from author and user configs.
974
+ * User headers override author headers.
975
+ */
976
+ function mergeHeaders(author, user) {
977
+ if (!author && !user) return;
978
+ const merged = new Headers(author);
979
+ new Headers(user).forEach((value, key) => merged.set(key, value));
980
+ if (merged.keys().next().done) return;
981
+ return merged;
982
+ }
983
+
984
+ //#endregion
985
+ //#region ../fragno/dist/client/internal/ndjson-streaming.js
986
+ /**
987
+ * Creates a promise that rejects when the abort signal is triggered
988
+ */
989
+ function createAbortPromise(abortSignal) {
990
+ return new Promise((_, reject) => {
991
+ const abortHandler = () => {
992
+ reject(new FragnoClientFetchAbortError("Operation was aborted"));
993
+ };
994
+ if (abortSignal.aborted) abortHandler();
995
+ else abortSignal.addEventListener("abort", abortHandler, { once: true });
996
+ });
997
+ }
998
+ /**
999
+ * Handles NDJSON streaming responses by returning the first item from the fetcher
1000
+ * and then continuing to stream updates via the store's mutate method.
1001
+ *
1002
+ * This makes it so that we can wait until the first chunk before updating the store, if we did
1003
+ * not do this, `loading` would briefly be false before the first item would be populated in the
1004
+ * result.
1005
+ *
1006
+ * @param response - The fetch Response object containing the NDJSON stream
1007
+ * @param store - The fetcher store to update with streaming data
1008
+ * @param abortSignal - Optional AbortSignal to cancel the streaming operation
1009
+ * @returns A promise that resolves to an object containing the first item and a streaming promise
1010
+ */
1011
+ async function handleNdjsonStreamingFirstItem(response, store, options = {}) {
1012
+ if (!response.body) throw new FragnoClientFetchError("Streaming response has no body", "NO_BODY");
1013
+ const { abortSignal } = options;
1014
+ if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
1015
+ const decoder = new TextDecoder();
1016
+ const reader = response.body.getReader();
1017
+ let buffer = "";
1018
+ let firstItem = null;
1019
+ const items = [];
1020
+ try {
1021
+ while (firstItem === null) {
1022
+ if (abortSignal?.aborted) {
1023
+ reader.releaseLock();
1024
+ throw new FragnoClientFetchAbortError("Operation was aborted");
1025
+ }
1026
+ const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
1027
+ if (done) break;
1028
+ buffer += decoder.decode(value, { stream: true });
1029
+ const lines = buffer.split("\n");
1030
+ buffer = lines.pop() || "";
1031
+ for (const line of lines) {
1032
+ if (!line.trim()) continue;
1033
+ try {
1034
+ const jsonObject = JSON.parse(line);
1035
+ items.push(jsonObject);
1036
+ if (firstItem === null) {
1037
+ firstItem = jsonObject;
1038
+ const streamingPromise = continueStreaming(reader, decoder, buffer, items, store, abortSignal);
1039
+ return {
1040
+ firstItem,
1041
+ streamingPromise
1042
+ };
1043
+ }
1044
+ } catch (parseError) {
1045
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 500, { cause: parseError });
1046
+ }
1047
+ }
1048
+ }
1049
+ if (firstItem === null) {
1050
+ reader.releaseLock();
1051
+ throw new FragnoClientUnknownApiError("NDJSON stream contained no valid items", 500);
1052
+ }
1053
+ reader.releaseLock();
1054
+ throw new FragnoClientFetchError("Unexpected end of stream processing", "NO_BODY");
1055
+ } catch (error) {
1056
+ if (error instanceof FragnoClientError) {
1057
+ store?.setError(error);
1058
+ throw error;
1059
+ } else {
1060
+ const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 500, { cause: error });
1061
+ store?.setError(clientError);
1062
+ throw clientError;
1063
+ }
1064
+ }
1065
+ }
1066
+ /**
1067
+ * Continues streaming the remaining items in the background
1068
+ */
1069
+ async function continueStreaming(reader, decoder, initialBuffer, items, store, abortSignal) {
1070
+ let buffer = initialBuffer;
1071
+ try {
1072
+ while (true) {
1073
+ if (abortSignal?.aborted) throw new FragnoClientFetchAbortError("Operation was aborted");
1074
+ const { done, value } = await (abortSignal ? Promise.race([reader.read(), createAbortPromise(abortSignal)]) : reader.read());
1075
+ if (done) {
1076
+ if (buffer.trim()) {
1077
+ const lines$1 = buffer.split("\n");
1078
+ for (const line of lines$1) {
1079
+ if (!line.trim()) continue;
1080
+ try {
1081
+ const jsonObject = JSON.parse(line);
1082
+ items.push(jsonObject);
1083
+ store?.setData([...items]);
1084
+ } catch (parseError) {
1085
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
1086
+ }
1087
+ }
1088
+ }
1089
+ break;
1090
+ }
1091
+ buffer += decoder.decode(value, { stream: true });
1092
+ const lines = buffer.split("\n");
1093
+ buffer = lines.pop() || "";
1094
+ for (const line of lines) {
1095
+ if (!line.trim()) continue;
1096
+ try {
1097
+ const jsonObject = JSON.parse(line);
1098
+ items.push(jsonObject);
1099
+ store?.setData([...items]);
1100
+ } catch (parseError) {
1101
+ throw new FragnoClientUnknownApiError("Failed to parse NDJSON line", 400, { cause: parseError });
1102
+ }
1103
+ }
1104
+ }
1105
+ } catch (error) {
1106
+ if (error instanceof FragnoClientError) store?.setError(error);
1107
+ else {
1108
+ const clientError = new FragnoClientUnknownApiError("Unknown streaming error", 400, { cause: error });
1109
+ store?.setError(clientError);
1110
+ throw clientError;
1111
+ }
1112
+ throw error;
1113
+ } finally {
1114
+ reader.releaseLock();
1115
+ }
1116
+ return items;
1117
+ }
1118
+
1119
+ //#endregion
1120
+ //#region ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
1121
+ let createNanoEvents = () => ({
1122
+ emit(event, ...args) {
1123
+ for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) callbacks[i](...args);
1124
+ },
1125
+ events: {},
1126
+ on(event, cb) {
1127
+ (this.events[event] ||= []).push(cb);
1128
+ return () => {
1129
+ this.events[event] = this.events[event]?.filter((i) => cb !== i);
1130
+ };
1131
+ }
1132
+ });
1133
+
1134
+ //#endregion
1135
+ //#region ../../node_modules/.pnpm/@nanostores+query@0.3.4_nanostores@1.1.0/node_modules/@nanostores/query/dist/nanoquery.js
1136
+ function defaultOnErrorRetry({ retryCount }) {
1137
+ return ~~((Math.random() + .5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3;
1138
+ }
1139
+ const nanoqueryFactory = ([isAppVisible, visibilityChangeSubscribe, reconnectChangeSubscribe]) => {
1140
+ const nanoquery = ({ cache = /* @__PURE__ */ new Map(), fetcher: globalFetcher, ...globalSettings } = {}) => {
1141
+ const events = createNanoEvents();
1142
+ let focus = true;
1143
+ visibilityChangeSubscribe(() => {
1144
+ focus = isAppVisible();
1145
+ focus && events.emit(FOCUS);
1146
+ });
1147
+ reconnectChangeSubscribe(() => events.emit(RECONNECT));
1148
+ const _revalidateOnInterval = /* @__PURE__ */ new Map(), _errorInvalidateTimeouts = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Map();
1149
+ let rewrittenSettings = {};
1150
+ const getCachedValueByKey = (key) => {
1151
+ const fromCache = cache.get(key);
1152
+ if (!fromCache) return [];
1153
+ return (fromCache.expires || 0) > getNow() ? [fromCache.data, fromCache.error] : [];
1154
+ };
1155
+ const runFetcher = async ([key, keyParts], store, settings) => {
1156
+ if (!focus) return;
1157
+ const set = (v) => {
1158
+ if (store.key === key) {
1159
+ store.set(v);
1160
+ events.emit(SET_CACHE, key, v, true);
1161
+ }
1162
+ };
1163
+ const setAsLoading = (prev) => {
1164
+ set({
1165
+ ...prev === void 0 ? {} : { data: prev },
1166
+ ...loading,
1167
+ promise: _runningFetches.get(key)
1168
+ });
1169
+ };
1170
+ let { dedupeTime = 4e3, cacheLifetime = Infinity, fetcher, onErrorRetry = defaultOnErrorRetry } = {
1171
+ ...settings,
1172
+ ...rewrittenSettings
1173
+ };
1174
+ if (cacheLifetime < dedupeTime) cacheLifetime = dedupeTime;
1175
+ const now = getNow();
1176
+ if (_runningFetches.has(key)) {
1177
+ if (!store.value.loading) setAsLoading(getCachedValueByKey(key)[0]);
1178
+ return;
1179
+ }
1180
+ let cachedValue, cachedError;
1181
+ const fromCache = cache.get(key);
1182
+ if (fromCache?.data !== void 0 || fromCache?.error) {
1183
+ [cachedValue, cachedError] = getCachedValueByKey(key);
1184
+ if ((fromCache.created || 0) + dedupeTime > now) {
1185
+ if (store.value.data != cachedValue || store.value.error != cachedError) set({
1186
+ ...notLoading,
1187
+ data: cachedValue,
1188
+ error: cachedError
1189
+ });
1190
+ return;
1191
+ }
1192
+ }
1193
+ const finishTask = startTask();
1194
+ try {
1195
+ clearTimeout(_errorInvalidateTimeouts.get(key));
1196
+ const promise = fetcher(...keyParts);
1197
+ _runningFetches.set(key, promise);
1198
+ setAsLoading(cachedValue);
1199
+ const res = await promise;
1200
+ cache.set(key, {
1201
+ data: res,
1202
+ created: getNow(),
1203
+ expires: getNow() + cacheLifetime
1204
+ });
1205
+ set({
1206
+ data: res,
1207
+ ...notLoading
1208
+ });
1209
+ } catch (error) {
1210
+ settings.onError?.(error);
1211
+ const retryCount = (cache.get(key)?.retryCount || 0) + 1;
1212
+ cache.set(key, {
1213
+ error,
1214
+ created: getNow(),
1215
+ expires: getNow() + cacheLifetime,
1216
+ retryCount
1217
+ });
1218
+ if (onErrorRetry) {
1219
+ const timer = onErrorRetry({
1220
+ error,
1221
+ key,
1222
+ retryCount
1223
+ });
1224
+ if (timer) _errorInvalidateTimeouts.set(key, setTimeout(() => {
1225
+ invalidateKeys(key);
1226
+ cache.set(key, { retryCount });
1227
+ }, timer));
1228
+ }
1229
+ set({
1230
+ data: store.value.data,
1231
+ error,
1232
+ ...notLoading
1233
+ });
1234
+ } finally {
1235
+ finishTask();
1236
+ _runningFetches.delete(key);
1237
+ }
1238
+ };
1239
+ const createFetcherStore = (keyInput, { fetcher = globalFetcher, ...fetcherSettings } = {}) => {
1240
+ if (!fetcher) throw new Error("You need to set up either global fetcher of fetcher in createFetcherStore");
1241
+ const fetcherStore = /* @__PURE__ */ map({ ...notLoading }), settings = {
1242
+ ...globalSettings,
1243
+ ...fetcherSettings,
1244
+ fetcher
1245
+ };
1246
+ fetcherStore._ = fetcherSymbol;
1247
+ fetcherStore.invalidate = () => {
1248
+ const { key } = fetcherStore;
1249
+ if (key) invalidateKeys(key);
1250
+ };
1251
+ fetcherStore.revalidate = () => {
1252
+ const { key } = fetcherStore;
1253
+ if (key) revalidateKeys(key);
1254
+ };
1255
+ fetcherStore.mutate = (data) => {
1256
+ const { key } = fetcherStore;
1257
+ if (key) mutateCache(key, data);
1258
+ };
1259
+ fetcherStore.fetch = async () => {
1260
+ let resolve;
1261
+ const promise = new Promise((r) => resolve = r);
1262
+ const unsub = fetcherStore.listen(({ error, data }) => {
1263
+ if (error !== void 0) resolve({ error });
1264
+ if (data !== void 0) resolve({ data });
1265
+ });
1266
+ return promise.finally(unsub);
1267
+ };
1268
+ let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore;
1269
+ let evtUnsubs = [];
1270
+ onStart(fetcherStore, () => {
1271
+ const firstRun = !keysInternalUnsub;
1272
+ [keyStore, keysInternalUnsub] = getKeyStore(keyInput);
1273
+ keyUnsub = keyStore.subscribe((currentKeys) => {
1274
+ if (currentKeys) {
1275
+ const [newKey, keyParts] = currentKeys;
1276
+ fetcherStore.key = newKey;
1277
+ runFetcher([newKey, keyParts], fetcherStore, settings);
1278
+ prevKey = newKey;
1279
+ prevKeyParts = keyParts;
1280
+ } else {
1281
+ fetcherStore.key = prevKey = prevKeyParts = void 0;
1282
+ fetcherStore.set({ ...notLoading });
1283
+ }
1284
+ });
1285
+ const currentKeyValue = keyStore.get();
1286
+ if (currentKeyValue) {
1287
+ [prevKey, prevKeyParts] = currentKeyValue;
1288
+ if (firstRun) handleNewListener();
1289
+ }
1290
+ const { revalidateInterval = 0, revalidateOnFocus, revalidateOnReconnect } = settings;
1291
+ const runRefetcher = () => {
1292
+ if (prevKey) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
1293
+ };
1294
+ if (revalidateInterval > 0) _revalidateOnInterval.set(keyInput, setInterval(runRefetcher, revalidateInterval));
1295
+ if (revalidateOnFocus) evtUnsubs.push(events.on(FOCUS, runRefetcher));
1296
+ if (revalidateOnReconnect) evtUnsubs.push(events.on(RECONNECT, runRefetcher));
1297
+ const cacheKeyChangeHandler = (keySelector) => {
1298
+ if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
1299
+ };
1300
+ evtUnsubs.push(events.on(INVALIDATE_KEYS, cacheKeyChangeHandler), events.on(REVALIDATE_KEYS, cacheKeyChangeHandler), events.on(SET_CACHE, (keySelector, data, full) => {
1301
+ if (prevKey && testKeyAgainstSelector(prevKey, keySelector) && fetcherStore.value !== data && fetcherStore.value.data !== data) fetcherStore.set(full ? data : {
1302
+ data,
1303
+ ...notLoading
1304
+ });
1305
+ }));
1306
+ });
1307
+ const handleNewListener = () => {
1308
+ if (prevKey && prevKeyParts) runFetcher([prevKey, prevKeyParts], fetcherStore, settings);
1309
+ };
1310
+ const originListen = fetcherStore.listen;
1311
+ fetcherStore.listen = (listener) => {
1312
+ const unsub = originListen(listener);
1313
+ listener(fetcherStore.value);
1314
+ handleNewListener();
1315
+ return unsub;
1316
+ };
1317
+ onStop(fetcherStore, () => {
1318
+ fetcherStore.value = { ...notLoading };
1319
+ keysInternalUnsub?.();
1320
+ evtUnsubs.forEach((fn) => fn());
1321
+ evtUnsubs = [];
1322
+ keyUnsub?.();
1323
+ clearInterval(_revalidateOnInterval.get(keyInput));
1324
+ });
1325
+ return fetcherStore;
1326
+ };
1327
+ const iterOverCache = (keySelector, cb) => {
1328
+ for (const key of cache.keys()) if (testKeyAgainstSelector(key, keySelector)) cb(key);
1329
+ };
1330
+ const invalidateKeys = (keySelector) => {
1331
+ iterOverCache(keySelector, (key) => {
1332
+ cache.delete(key);
1333
+ });
1334
+ events.emit(INVALIDATE_KEYS, keySelector);
1335
+ };
1336
+ const revalidateKeys = (keySelector) => {
1337
+ iterOverCache(keySelector, (key) => {
1338
+ const cached = cache.get(key);
1339
+ if (cached) cache.set(key, {
1340
+ ...cached,
1341
+ created: -Infinity
1342
+ });
1343
+ });
1344
+ events.emit(REVALIDATE_KEYS, keySelector);
1345
+ };
1346
+ const mutateCache = (keySelector, data) => {
1347
+ iterOverCache(keySelector, (key) => {
1348
+ if (data === void 0) cache.delete(key);
1349
+ else cache.set(key, {
1350
+ data,
1351
+ created: getNow(),
1352
+ expires: getNow() + (globalSettings.cacheLifetime ?? 8e3)
1353
+ });
1354
+ });
1355
+ events.emit(SET_CACHE, keySelector, data);
1356
+ };
1357
+ function createMutatorStore(mutator, opts) {
1358
+ const { throttleCalls, onError } = opts ?? {
1359
+ throttleCalls: true,
1360
+ onError: globalSettings?.onError
1361
+ };
1362
+ const mutate = async (data) => {
1363
+ if (throttleCalls && store.value?.loading) return;
1364
+ const newMutator = rewrittenSettings.fetcher ?? mutator;
1365
+ const keysToInvalidate = [], keysToRevalidate = [];
1366
+ const safeKeySet = (k, v) => {
1367
+ if (store.lc) store.setKey(k, v);
1368
+ };
1369
+ try {
1370
+ store.set({
1371
+ error: void 0,
1372
+ data: void 0,
1373
+ mutate,
1374
+ ...loading
1375
+ });
1376
+ const result = await newMutator({
1377
+ data,
1378
+ invalidate: (key) => {
1379
+ keysToInvalidate.push(key);
1380
+ },
1381
+ revalidate: (key) => {
1382
+ keysToRevalidate.push(key);
1383
+ },
1384
+ getCacheUpdater: (key, shouldRevalidate = true) => [(newVal) => {
1385
+ mutateCache(key, newVal);
1386
+ if (shouldRevalidate) keysToRevalidate.push(key);
1387
+ }, cache.get(key)?.data]
1388
+ });
1389
+ safeKeySet("data", result);
1390
+ return result;
1391
+ } catch (error) {
1392
+ onError?.(error);
1393
+ safeKeySet("error", error);
1394
+ store.setKey("error", error);
1395
+ } finally {
1396
+ safeKeySet("loading", false);
1397
+ keysToInvalidate.forEach(invalidateKeys);
1398
+ keysToRevalidate.forEach(revalidateKeys);
1399
+ }
1400
+ };
1401
+ const store = /* @__PURE__ */ map({
1402
+ mutate,
1403
+ ...notLoading
1404
+ });
1405
+ onStop(store, () => store.set({
1406
+ mutate,
1407
+ ...notLoading
1408
+ }));
1409
+ store.mutate = mutate;
1410
+ return store;
1411
+ }
1412
+ const __unsafeOverruleSettings = (data) => {
1413
+ console.warn(`You should only use __unsafeOverruleSettings in test environment`);
1414
+ rewrittenSettings = data;
1415
+ };
1416
+ return [
1417
+ createFetcherStore,
1418
+ createMutatorStore,
1419
+ {
1420
+ __unsafeOverruleSettings,
1421
+ invalidateKeys,
1422
+ revalidateKeys,
1423
+ mutateCache
1424
+ }
1425
+ ];
1426
+ };
1427
+ function isSomeKey(key) {
1428
+ return typeof key === "string" || typeof key === "number" || key === true;
1429
+ }
1430
+ const getKeyStore = (keys) => {
1431
+ if (isSomeKey(keys)) return [/* @__PURE__ */ atom(["" + keys, [keys]]), () => {}];
1432
+ const keyParts = [];
1433
+ const $key = /* @__PURE__ */ atom(null);
1434
+ const keysAsStoresToIndexes = /* @__PURE__ */ new Map();
1435
+ const setKeyStoreValue = () => {
1436
+ if (keyParts.some((v) => v === null || v === void 0 || v === false)) $key.set(null);
1437
+ else $key.set([keyParts.join(""), keyParts]);
1438
+ };
1439
+ for (let i = 0; i < keys.length; i++) {
1440
+ const keyOrStore = keys[i];
1441
+ if (isSomeKey(keyOrStore)) keyParts.push(keyOrStore);
1442
+ else {
1443
+ keyParts.push(null);
1444
+ keysAsStoresToIndexes.set(keyOrStore, i);
1445
+ }
1446
+ }
1447
+ const storesAsArray = [...keysAsStoresToIndexes.keys()];
1448
+ const $storeKeys = /* @__PURE__ */ batched(storesAsArray, (...storeValues) => {
1449
+ for (let i = 0; i < storeValues.length; i++) {
1450
+ const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store);
1451
+ keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i];
1452
+ }
1453
+ setKeyStoreValue();
1454
+ });
1455
+ setKeyStoreValue();
1456
+ return [$key, $storeKeys.subscribe(noop)];
1457
+ };
1458
+ function noop() {}
1459
+ const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, REVALIDATE_KEYS = 4, SET_CACHE = 5;
1460
+ const testKeyAgainstSelector = (key, selector) => {
1461
+ if (Array.isArray(selector)) return selector.includes(key);
1462
+ else if (typeof selector === "function") return selector(key);
1463
+ else return key === selector;
1464
+ };
1465
+ const getNow = () => (/* @__PURE__ */ new Date()).getTime();
1466
+ const fetcherSymbol = Symbol();
1467
+ const loading = { loading: true }, notLoading = { loading: false };
1468
+ return nanoquery;
1469
+ };
1470
+ const subscribe = (name, fn) => {
1471
+ if (!(typeof window === "undefined")) addEventListener(name, fn);
1472
+ };
1473
+ const nanoquery = nanoqueryFactory([
1474
+ () => !document.hidden,
1475
+ (cb) => subscribe("visibilitychange", cb),
1476
+ (cb) => subscribe("online", cb)
1477
+ ]);
1478
+
1479
+ //#endregion
1480
+ //#region ../fragno/dist/client/client.js
1481
+ /**
1482
+ * Symbols used to identify hook types
1483
+ */
1484
+ const GET_HOOK_SYMBOL = Symbol("fragno-get-hook");
1485
+ const MUTATOR_HOOK_SYMBOL = Symbol("fragno-mutator-hook");
1486
+ const STORE_SYMBOL = Symbol("fragno-store");
1487
+ /**
1488
+ * Check if a value contains files that should be sent as FormData.
1489
+ * @internal
1490
+ */
1491
+ function containsFiles(value) {
1492
+ if (value instanceof File || value instanceof Blob) return true;
1493
+ if (value instanceof FormData) return true;
1494
+ if (typeof value === "object" && value !== null) return Object.values(value).some((v) => v instanceof File || v instanceof Blob || v instanceof FormData);
1495
+ return false;
1496
+ }
1497
+ /**
1498
+ * Convert an object containing files to FormData.
1499
+ * Handles nested File/Blob values by appending them directly.
1500
+ * Other values are JSON-stringified.
1501
+ * @internal
1502
+ */
1503
+ function toFormData(value) {
1504
+ const formData = new FormData();
1505
+ for (const [key, val] of Object.entries(value)) if (val instanceof File) formData.append(key, val, val.name);
1506
+ else if (val instanceof Blob) formData.append(key, val);
1507
+ else if (val !== void 0 && val !== null) formData.append(key, typeof val === "string" ? val : JSON.stringify(val));
1508
+ return formData;
1509
+ }
1510
+ /**
1511
+ * Prepare request body and headers for sending.
1512
+ * Handles FormData (file uploads) vs JSON data.
1513
+ * @internal
1514
+ */
1515
+ function prepareRequestBody(body, contentType) {
1516
+ if (body === void 0) return { body: void 0 };
1517
+ if (contentType === "application/octet-stream") {
1518
+ if (body instanceof ReadableStream || body instanceof Blob || body instanceof File || body instanceof ArrayBuffer || body instanceof Uint8Array) return {
1519
+ body,
1520
+ headers: { "Content-Type": "application/octet-stream" }
1521
+ };
1522
+ throw new Error("Octet-stream routes only accept Blob, File, ArrayBuffer, Uint8Array, or ReadableStream bodies.");
1523
+ }
1524
+ if (body instanceof FormData) return { body };
1525
+ if (body instanceof File) {
1526
+ const formData = new FormData();
1527
+ formData.append("file", body, body.name);
1528
+ return { body: formData };
1529
+ }
1530
+ if (body instanceof Blob) {
1531
+ const formData = new FormData();
1532
+ formData.append("file", body);
1533
+ return { body: formData };
1534
+ }
1535
+ if (typeof body === "object" && body !== null && containsFiles(body)) return { body: toFormData(body) };
1536
+ return {
1537
+ body: JSON.stringify(body),
1538
+ headers: { "Content-Type": "application/json" }
1539
+ };
1540
+ }
1541
+ async function schemaAllowsUndefined(schema) {
1542
+ try {
1543
+ return !(await schema["~standard"].validate(void 0)).issues;
1544
+ } catch {
1545
+ return false;
1546
+ }
1547
+ }
1548
+ async function assertBodyProvided(body, inputSchema, errorMessage) {
1549
+ if (typeof body !== "undefined" || inputSchema === void 0) return;
1550
+ if (await schemaAllowsUndefined(inputSchema)) return;
1551
+ throw new Error(errorMessage);
1552
+ }
1553
+ /**
1554
+ * Merge request headers from multiple sources.
1555
+ * Returns undefined if there are no headers to merge.
1556
+ * @internal
1557
+ */
1558
+ function mergeRequestHeaders(...headerSources) {
1559
+ const result = {};
1560
+ let hasHeaders = false;
1561
+ for (const source of headerSources) {
1562
+ if (!source) continue;
1563
+ if (source instanceof Headers) for (const [key, value] of source.entries()) {
1564
+ result[key] = value;
1565
+ hasHeaders = true;
1566
+ }
1567
+ else if (Array.isArray(source)) for (const [key, value] of source) {
1568
+ result[key] = value;
1569
+ hasHeaders = true;
1570
+ }
1571
+ else for (const [key, value] of Object.entries(source)) {
1572
+ result[key] = value;
1573
+ hasHeaders = true;
1574
+ }
1575
+ }
1576
+ return hasHeaders ? result : void 0;
1577
+ }
1578
+ /**
1579
+ * @internal
1580
+ */
1581
+ function buildUrl(config, params) {
1582
+ const { baseUrl = "", mountRoute, path } = config;
1583
+ const { pathParams, queryParams } = params ?? {};
1584
+ const normalizedPathParams = unwrapObject(pathParams);
1585
+ const normalizedQueryParams = unwrapObject(queryParams) ?? {};
1586
+ const filteredQueryParams = Object.fromEntries(Object.entries(normalizedQueryParams).filter(([_, value]) => value !== void 0));
1587
+ const searchParams = new URLSearchParams(filteredQueryParams);
1588
+ return `${baseUrl}${mountRoute}${buildPath(path, normalizedPathParams ?? {})}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
1589
+ }
1590
+ /**
1591
+ * This method returns an array, which can be passed directly to nanostores.
1592
+ *
1593
+ * The returned array is always: path, pathParams (In order they appear in the path), queryParams (In alphabetical order)
1594
+ * Missing pathParams are replaced with "<missing>".
1595
+ * Atoms with undefined values are wrapped in computed atoms that map undefined to "" to avoid nanoquery treating the key as incomplete.
1596
+ * @param path
1597
+ * @param params
1598
+ * @returns
1599
+ * @internal
1600
+ */
1601
+ function getCacheKey(method, path, params) {
1602
+ if (!params) return [method, path];
1603
+ const { pathParams, queryParams } = params;
1604
+ const pathParamValues = extractPathParams(path).map((name) => pathParams?.[name] ?? "<missing>");
1605
+ const queryParamValues = queryParams ? Object.keys(queryParams).sort().map((key) => {
1606
+ const value = queryParams[key];
1607
+ if (value && typeof value === "object" && "get" in value) return /* @__PURE__ */ computed(value, (v) => v ?? "");
1608
+ return value ?? "";
1609
+ }) : [];
1610
+ return [
1611
+ method,
1612
+ path,
1613
+ ...pathParamValues,
1614
+ ...queryParamValues
1615
+ ];
1616
+ }
1617
+ function isStreamingResponse(response) {
1618
+ const contentType = parseContentType(response.headers.get("content-type"));
1619
+ if (!contentType) return false;
1620
+ if (!(response.headers.get("transfer-encoding") === "chunked")) return false;
1621
+ if (contentType.subtype === "octet-stream") return "octet-stream";
1622
+ if (contentType.subtype === "x-ndjson") return "ndjson";
1623
+ return false;
1624
+ }
1625
+ /**
1626
+ * @internal
1627
+ */
1628
+ function isGetHook(hook) {
1629
+ return typeof hook === "object" && hook !== null && GET_HOOK_SYMBOL in hook && hook[GET_HOOK_SYMBOL] === true;
1630
+ }
1631
+ /**
1632
+ * @internal
1633
+ */
1634
+ function isMutatorHook(hook) {
1635
+ return typeof hook === "object" && hook !== null && MUTATOR_HOOK_SYMBOL in hook && hook[MUTATOR_HOOK_SYMBOL] === true;
1636
+ }
1637
+ /**
1638
+ * @internal
1639
+ */
1640
+ function isStore(obj) {
1641
+ return typeof obj === "object" && obj !== null && STORE_SYMBOL in obj && obj[STORE_SYMBOL] === true;
1642
+ }
1643
+ var ClientBuilder = class {
1644
+ #publicConfig;
1645
+ #fragmentConfig;
1646
+ #fetcherConfig;
1647
+ #cache = /* @__PURE__ */ new Map();
1648
+ #createFetcherStore;
1649
+ #createMutatorStore;
1650
+ #invalidateKeys;
1651
+ constructor(publicConfig, fragmentConfig) {
1652
+ this.#publicConfig = publicConfig;
1653
+ this.#fragmentConfig = fragmentConfig;
1654
+ this.#fetcherConfig = publicConfig.fetcherConfig;
1655
+ const [createFetcherStore, createMutatorStore, { invalidateKeys }] = nanoquery({ cache: this.#cache });
1656
+ this.#createFetcherStore = createFetcherStore;
1657
+ this.#createMutatorStore = createMutatorStore;
1658
+ this.#invalidateKeys = invalidateKeys;
1659
+ }
1660
+ get cacheEntries() {
1661
+ return Object.fromEntries(this.#cache.entries());
1662
+ }
1663
+ createStore(input) {
1664
+ if (typeof input === "function") return {
1665
+ factory: input,
1666
+ [STORE_SYMBOL]: true
1667
+ };
1668
+ return {
1669
+ obj: input,
1670
+ [STORE_SYMBOL]: true
1671
+ };
1672
+ }
1673
+ /**
1674
+ * Build a URL for a custom backend call using the configured baseUrl and mountRoute.
1675
+ * Useful for fragment authors who need to make custom fetch calls.
1676
+ */
1677
+ buildUrl(path, params) {
1678
+ return buildUrl({
1679
+ baseUrl: this.#publicConfig.baseUrl ?? "",
1680
+ mountRoute: getMountRoute({
1681
+ name: this.#fragmentConfig.name,
1682
+ mountRoute: this.#publicConfig.mountRoute
1683
+ }),
1684
+ path
1685
+ }, {
1686
+ pathParams: params?.path,
1687
+ queryParams: params?.query
1688
+ });
1689
+ }
1690
+ /**
1691
+ * Get the configured fetcher function for custom backend calls.
1692
+ * Returns fetch with merged options applied.
1693
+ */
1694
+ getFetcher() {
1695
+ return {
1696
+ fetcher: this.#getFetcher(),
1697
+ defaultOptions: this.#getFetcherOptions()
1698
+ };
1699
+ }
1700
+ #getFetcher() {
1701
+ if (this.#fetcherConfig?.type === "function") return this.#fetcherConfig.fetcher;
1702
+ return globalThis.fetch.bind(globalThis);
1703
+ }
1704
+ #getFetcherOptions() {
1705
+ if (this.#fetcherConfig?.type === "options") return this.#fetcherConfig.options;
1706
+ }
1707
+ createHook(path, options) {
1708
+ const route = this.#fragmentConfig.routes.find((r) => r.path === path && r.method === "GET" && r.outputSchema !== void 0);
1709
+ if (!route) throw new Error(`Route '${path}' not found or is not a GET route with an output schema.`);
1710
+ return this.#createRouteQueryHook(route, options);
1711
+ }
1712
+ createMutator(method, path, onInvalidate) {
1713
+ const route = this.#fragmentConfig.routes.find((r) => r.method !== "GET" && r.path === path && r.method === method);
1714
+ if (!route) throw new Error(`Route '${path}' not found or is a GET route with an input and output schema.`);
1715
+ return this.#createRouteQueryMutator(route, onInvalidate);
1716
+ }
1717
+ #createRouteQueryHook(route, options = {}) {
1718
+ if (route.method !== "GET") throw new Error(`Only GET routes are supported for hooks. Route '${route.path}' is a ${route.method} route.`);
1719
+ if (!route.outputSchema) throw new Error(`Output schema is required for GET routes. Route '${route.path}' has no output schema.`);
1720
+ const baseUrl = this.#publicConfig.baseUrl ?? "";
1721
+ const mountRoute = getMountRoute({
1722
+ name: this.#fragmentConfig.name,
1723
+ mountRoute: this.#publicConfig.mountRoute
1724
+ });
1725
+ const fetcher = this.#getFetcher();
1726
+ const fetcherOptions = this.#getFetcherOptions();
1727
+ async function callServerSideHandler(params) {
1728
+ const { pathParams, queryParams } = params ?? {};
1729
+ const normalizedPathParams = unwrapObject(pathParams);
1730
+ const normalizedQueryParams = unwrapObject(queryParams) ?? {};
1731
+ const filteredQueryParams = Object.fromEntries(Object.entries(normalizedQueryParams).filter(([_, value]) => value !== void 0));
1732
+ const searchParams = new URLSearchParams(filteredQueryParams);
1733
+ return await route.handler(RequestInputContext.fromSSRContext({
1734
+ method: route.method,
1735
+ path: route.path,
1736
+ pathParams: normalizedPathParams,
1737
+ searchParams
1738
+ }), new RequestOutputContext(route.outputSchema));
1739
+ }
1740
+ async function executeQuery(params) {
1741
+ const { pathParams, queryParams } = params ?? {};
1742
+ if (typeof window === "undefined") return task(async () => callServerSideHandler({
1743
+ pathParams,
1744
+ queryParams
1745
+ }));
1746
+ const url = buildUrl({
1747
+ baseUrl,
1748
+ mountRoute,
1749
+ path: route.path
1750
+ }, {
1751
+ pathParams,
1752
+ queryParams
1753
+ });
1754
+ let response;
1755
+ try {
1756
+ response = fetcherOptions ? await fetcher(url, fetcherOptions) : await fetcher(url);
1757
+ } catch (error) {
1758
+ throw FragnoClientFetchError.fromUnknownFetchError(error);
1759
+ }
1760
+ if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
1761
+ return response;
1762
+ }
1763
+ return {
1764
+ route,
1765
+ store: (args) => {
1766
+ const { path, query } = args ?? {};
1767
+ const key = getCacheKey(route.method, route.path, {
1768
+ pathParams: path,
1769
+ queryParams: query
1770
+ });
1771
+ const store = this.#createFetcherStore(key, {
1772
+ fetcher: async () => {
1773
+ if (SSR_ENABLED) {
1774
+ const initialData = getInitialData(key.map((d) => typeof d === "string" ? d : d.get()).join(""));
1775
+ if (initialData) return initialData;
1776
+ }
1777
+ const response = await executeQuery({
1778
+ pathParams: path,
1779
+ queryParams: query
1780
+ });
1781
+ const isStreaming = isStreamingResponse(response);
1782
+ if (!isStreaming) return response.json();
1783
+ if (typeof window === "undefined") return [];
1784
+ if (isStreaming === "ndjson") {
1785
+ const { firstItem } = await handleNdjsonStreamingFirstItem(response, {
1786
+ setData: (value) => {
1787
+ store.set({
1788
+ ...store.get(),
1789
+ loading: !(Array.isArray(value) && value.length > 0),
1790
+ data: value
1791
+ });
1792
+ },
1793
+ setError: (value) => {
1794
+ store.set({
1795
+ ...store.get(),
1796
+ error: value
1797
+ });
1798
+ }
1799
+ });
1800
+ return [firstItem];
1801
+ }
1802
+ if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1803
+ throw new Error("Unreachable");
1804
+ },
1805
+ onErrorRetry: options?.onErrorRetry,
1806
+ dedupeTime: Infinity
1807
+ });
1808
+ if (typeof window === "undefined") addStore(store);
1809
+ return store;
1810
+ },
1811
+ query: async (args) => {
1812
+ const { path, query } = args ?? {};
1813
+ const response = await executeQuery({
1814
+ pathParams: path,
1815
+ queryParams: query
1816
+ });
1817
+ const isStreaming = isStreamingResponse(response);
1818
+ if (!isStreaming) return await response.json();
1819
+ if (isStreaming === "ndjson") {
1820
+ const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
1821
+ return await streamingPromise;
1822
+ }
1823
+ if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1824
+ throw new Error("Unreachable");
1825
+ },
1826
+ [GET_HOOK_SYMBOL]: true
1827
+ };
1828
+ }
1829
+ #createRouteQueryMutator(route, onInvalidate = (invalidate, params) => invalidate("GET", route.path, params)) {
1830
+ const method = route.method;
1831
+ const baseUrl = this.#publicConfig.baseUrl ?? "";
1832
+ const mountRoute = getMountRoute({
1833
+ name: this.#fragmentConfig.name,
1834
+ mountRoute: this.#publicConfig.mountRoute
1835
+ });
1836
+ const fetcher = this.#getFetcher();
1837
+ const fetcherOptions = this.#getFetcherOptions();
1838
+ async function executeMutateQuery({ body, path, query }) {
1839
+ if (typeof window === "undefined") return task(async () => route.handler(RequestInputContext.fromSSRContext({
1840
+ inputSchema: route.inputSchema,
1841
+ method,
1842
+ path: route.path,
1843
+ pathParams: path ?? {},
1844
+ searchParams: new URLSearchParams(query),
1845
+ body
1846
+ }), new RequestOutputContext(route.outputSchema)));
1847
+ const url = buildUrl({
1848
+ baseUrl,
1849
+ mountRoute,
1850
+ path: route.path
1851
+ }, {
1852
+ pathParams: path,
1853
+ queryParams: query
1854
+ });
1855
+ let response;
1856
+ try {
1857
+ const { body: preparedBody, headers: bodyHeaders } = prepareRequestBody(body, route.contentType);
1858
+ const mergedHeaders = mergeRequestHeaders(fetcherOptions?.headers, bodyHeaders);
1859
+ const requestOptions = {
1860
+ ...fetcherOptions,
1861
+ method,
1862
+ body: preparedBody,
1863
+ ...mergedHeaders ? { headers: mergedHeaders } : {}
1864
+ };
1865
+ if (preparedBody instanceof ReadableStream) requestOptions.duplex = "half";
1866
+ response = await fetcher(url, requestOptions);
1867
+ } catch (error) {
1868
+ throw FragnoClientFetchError.fromUnknownFetchError(error);
1869
+ }
1870
+ if (!response.ok) throw await FragnoClientApiError.fromResponse(response);
1871
+ return response;
1872
+ }
1873
+ const mutatorStore = this.#createMutatorStore(async ({ data }) => {
1874
+ if (typeof window === "undefined") {}
1875
+ const { body, path, query } = data;
1876
+ await assertBodyProvided(body, route.inputSchema, "Body is required.");
1877
+ const response = await executeMutateQuery({
1878
+ body,
1879
+ path,
1880
+ query
1881
+ });
1882
+ onInvalidate(this.#invalidate.bind(this), {
1883
+ pathParams: path ?? {},
1884
+ queryParams: query
1885
+ });
1886
+ if (response.status === 201 || response.status === 204) return;
1887
+ const isStreaming = isStreamingResponse(response);
1888
+ if (!isStreaming) return response.json();
1889
+ if (typeof window === "undefined") return [];
1890
+ if (isStreaming === "ndjson") {
1891
+ const { firstItem } = await handleNdjsonStreamingFirstItem(response, {
1892
+ setData: (value) => {
1893
+ mutatorStore.set({
1894
+ ...mutatorStore.get(),
1895
+ loading: !(Array.isArray(value) && value.length > 0),
1896
+ data: value
1897
+ });
1898
+ },
1899
+ setError: (value) => {
1900
+ mutatorStore.set({
1901
+ ...mutatorStore.get(),
1902
+ error: value
1903
+ });
1904
+ }
1905
+ });
1906
+ return [firstItem];
1907
+ }
1908
+ if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported.");
1909
+ throw new Error("Unreachable");
1910
+ }, { onError: (error) => {
1911
+ console.error("Error in mutatorStore", error);
1912
+ } });
1913
+ const mutateQuery = async (data) => {
1914
+ const { body, path, query } = data;
1915
+ await assertBodyProvided(body, route.inputSchema, "Body is required for mutateQuery");
1916
+ const response = await executeMutateQuery({
1917
+ body,
1918
+ path,
1919
+ query
1920
+ });
1921
+ if (response.status === 201 || response.status === 204) return;
1922
+ const isStreaming = isStreamingResponse(response);
1923
+ if (!isStreaming) return response.json();
1924
+ if (isStreaming === "ndjson") {
1925
+ const { streamingPromise } = await handleNdjsonStreamingFirstItem(response);
1926
+ return await streamingPromise;
1927
+ }
1928
+ if (isStreaming === "octet-stream") throw new Error("Octet-stream streaming is not supported for mutations");
1929
+ throw new Error("Unreachable");
1930
+ };
1931
+ return {
1932
+ route,
1933
+ mutateQuery,
1934
+ mutatorStore,
1935
+ [MUTATOR_HOOK_SYMBOL]: true
1936
+ };
1937
+ }
1938
+ #invalidate(method, path, params) {
1939
+ const prefix = getCacheKey(method, path, {
1940
+ pathParams: params?.pathParams,
1941
+ queryParams: params?.queryParams
1942
+ }).map((k) => typeof k === "string" ? k : k.get()).join("");
1943
+ this.#invalidateKeys((key) => key.startsWith(prefix));
1944
+ }
1945
+ };
1946
+ /**
1947
+ * Create a client builder for fragments using the new fragment definition API.
1948
+ * This is the same as createClientBuilder but works with FragmentDefinition.
1949
+ */
1950
+ function createClientBuilder(definition, publicConfig, routesOrFactories, authorFetcherConfig) {
1951
+ const routes = resolveRouteFactories({
1952
+ config: {},
1953
+ deps: {},
1954
+ services: {},
1955
+ serviceDeps: {}
1956
+ }, routesOrFactories);
1957
+ const fragmentConfig = {
1958
+ name: definition.name,
1959
+ routes
1960
+ };
1961
+ const mountRoute = getMountRoute({
1962
+ name: definition.name,
1963
+ mountRoute: publicConfig.mountRoute
1964
+ });
1965
+ const mergedFetcherConfig = mergeFetcherConfigs(authorFetcherConfig, publicConfig.fetcherConfig);
1966
+ return new ClientBuilder({
1967
+ ...publicConfig,
1968
+ mountRoute,
1969
+ fetcherConfig: mergedFetcherConfig
1970
+ }, fragmentConfig);
1971
+ }
1972
+
1973
+ //#endregion
1974
+ //#region src/deployment-tag.ts
1975
+ const DEFAULT_DEPLOYMENT_TAG_PREFIX = "fragno";
1976
+ const MAX_TAG_LENGTH = 63;
1977
+ const APP_TAG_SEGMENT = "app";
1978
+ const DEPLOYMENT_TAG_SEGMENT = "dep";
1979
+ const sanitizeCloudflareTag = (value) => {
1980
+ return value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+/, "").replace(/-+$/, "").replace(/--+/g, "-");
1981
+ };
1982
+ const normalizeCloudflareDeploymentTagPrefix = (prefix) => {
1983
+ const normalized = sanitizeCloudflareTag(prefix ?? DEFAULT_DEPLOYMENT_TAG_PREFIX);
1984
+ if (!normalized) throw new Error("Cloudflare deployment tag prefix must contain at least one alphanumeric character.");
1985
+ return normalized;
1986
+ };
1987
+ const trimTrailingHyphens = (value) => value.replace(/-+$/, "");
1988
+ const capCloudflareTagPrefix = (prefix, kind, normalizedId, label) => {
1989
+ const maxPrefixLength = MAX_TAG_LENGTH - `-${kind}-`.length - normalizedId.length;
1990
+ if (maxPrefixLength < 1) throw new Error(`Cloudflare ${label} tag '${normalizedId}' exceeds the ${MAX_TAG_LENGTH} character limit even with the shortest possible prefix.`);
1991
+ const cappedPrefix = trimTrailingHyphens(prefix.slice(0, maxPrefixLength));
1992
+ if (!cappedPrefix) throw new Error(`Cloudflare ${label} tag prefix must retain at least one alphanumeric character after capping.`);
1993
+ return cappedPrefix;
1994
+ };
1995
+ const buildCloudflareScopedTag = (id, prefix, kind, label) => {
1996
+ const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);
1997
+ const normalizedId = sanitizeCloudflareTag(id);
1998
+ if (!normalizedId) throw new Error(`Cloudflare ${label} id must contain at least one alphanumeric character.`);
1999
+ return `${capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label)}-${kind}-${normalizedId}`;
2000
+ };
2001
+ const readCloudflareScopedTagId = (tag, prefix, kind, label) => {
2002
+ const normalizedTag = sanitizeCloudflareTag(tag);
2003
+ const marker = `-${kind}-`;
2004
+ const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);
2005
+ let markerIndex = normalizedTag.indexOf(marker);
2006
+ while (markerIndex >= 1) {
2007
+ const normalizedId = normalizedTag.slice(markerIndex + marker.length);
2008
+ if (normalizedId) try {
2009
+ const expectedPrefix = capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label);
2010
+ if (normalizedTag.slice(0, markerIndex) === expectedPrefix) return normalizedId;
2011
+ } catch {}
2012
+ markerIndex = normalizedTag.indexOf(marker, markerIndex + 1);
2013
+ }
2014
+ return null;
2015
+ };
2016
+ const findCloudflareScopedTag = (tags, prefix, kind, label) => {
2017
+ const marker = `-${kind}-`;
2018
+ for (const tag of tags) {
2019
+ const normalizedTag = sanitizeCloudflareTag(tag);
2020
+ if (!normalizedTag.includes(marker)) continue;
2021
+ if (readCloudflareScopedTagId(normalizedTag, prefix, kind, label) !== null) return normalizedTag;
2022
+ }
2023
+ return null;
2024
+ };
2025
+ const buildCloudflareAppTag = (appId, prefix) => {
2026
+ return buildCloudflareScopedTag(appId, prefix, APP_TAG_SEGMENT, "app");
2027
+ };
2028
+ const buildCloudflareDeploymentTag = (deploymentId, prefix) => {
2029
+ return buildCloudflareScopedTag(deploymentId, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
2030
+ };
2031
+ const getCloudflareAppIdFromTag = (tag, prefix) => {
2032
+ return readCloudflareScopedTagId(tag, prefix, APP_TAG_SEGMENT, "app");
2033
+ };
2034
+ const getCloudflareDeploymentIdFromTag = (tag, prefix) => {
2035
+ return readCloudflareScopedTagId(tag, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
2036
+ };
2037
+ const findCloudflareAppTag = (tags, prefix) => {
2038
+ return findCloudflareScopedTag(tags, prefix, APP_TAG_SEGMENT, "app");
2039
+ };
2040
+ const findCloudflareDeploymentTag = (tags, prefix) => {
2041
+ return findCloudflareScopedTag(tags, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
2042
+ };
2043
+
2044
+ //#endregion
2045
+ //#region src/cloudflare-api.ts
2046
+ const isRecord = (value) => typeof value === "object" && value !== null;
2047
+ const readNamespaceCandidate = (value) => {
2048
+ if (typeof value !== "string") return null;
2049
+ const trimmed = value.trim();
2050
+ return trimmed.length > 0 ? trimmed : null;
2051
+ };
2052
+ const unique = (items) => {
2053
+ return Array.from(new Set(items));
2054
+ };
2055
+ const hasBindingDescriptor = (value) => {
2056
+ return typeof value === "object" && value !== null && "binding" in value;
2057
+ };
2058
+ const resolveCloudflareDispatchNamespaceName = (dispatcher) => {
2059
+ if (typeof dispatcher === "string") {
2060
+ const namespace = readNamespaceCandidate(dispatcher);
2061
+ if (namespace) return namespace;
2062
+ } else if (hasBindingDescriptor(dispatcher)) {
2063
+ const namespace = readNamespaceCandidate(dispatcher.namespace);
2064
+ if (namespace) return namespace;
2065
+ }
2066
+ throw new Error("Cloudflare fragment requires a dispatch namespace name. Pass `dispatcher: \"my-namespace\"` or `dispatcher: { binding: env.DISPATCHER, namespace: \"my-namespace\" }`.");
2067
+ };
2068
+ const buildCloudflareScriptTags = (dynamicTags, staticTags = []) => {
2069
+ const normalizedDynamicTags = Array.isArray(dynamicTags) ? dynamicTags : [dynamicTags];
2070
+ return unique([...staticTags, ...normalizedDynamicTags].map(sanitizeCloudflareTag).filter((tag) => tag.length > 0));
2071
+ };
2072
+ const createCloudflareApiClient = ({ apiToken, fetchImplementation, maxRetries = 0 }) => {
2073
+ return new Cloudflare({
2074
+ apiToken,
2075
+ fetch: fetchImplementation,
2076
+ maxRetries
2077
+ });
2078
+ };
2079
+ const listCloudflareScriptTags = async (client, input) => {
2080
+ try {
2081
+ const tags = await client.workersForPlatforms.dispatch.namespaces.scripts.tags.list(input.dispatchNamespace, input.scriptName, { account_id: input.accountId });
2082
+ const results = [];
2083
+ for await (const tag of tags) results.push(tag);
2084
+ return results;
2085
+ } catch (error) {
2086
+ if (error instanceof NotFoundError) return [];
2087
+ throw error;
2088
+ }
2089
+ };
2090
+ const getCloudflareCurrentDeployment = async (client, input) => {
2091
+ const deploymentTag = findCloudflareDeploymentTag(await listCloudflareScriptTags(client, input), input.deploymentTagPrefix);
2092
+ if (deploymentTag === null) return null;
2093
+ return {
2094
+ deploymentTag,
2095
+ deploymentId: getCloudflareDeploymentIdFromTag(deploymentTag, input.deploymentTagPrefix)
2096
+ };
2097
+ };
2098
+ const getCloudflareScriptState = async (client, input) => {
2099
+ try {
2100
+ const script = await client.workersForPlatforms.dispatch.namespaces.scripts.get(input.dispatchNamespace, input.scriptName, { account_id: input.accountId });
2101
+ return {
2102
+ etag: script.script?.etag ?? null,
2103
+ modifiedOn: script.modified_on ?? null
2104
+ };
2105
+ } catch (error) {
2106
+ if (error instanceof NotFoundError) return null;
2107
+ throw error;
2108
+ }
2109
+ };
2110
+ const deployCloudflareWorker = async (client, input) => {
2111
+ const moduleFile = await toFile(new TextEncoder().encode(input.moduleContent), input.entrypoint, { type: "application/javascript+module" });
2112
+ const params = {
2113
+ account_id: input.accountId,
2114
+ metadata: {
2115
+ main_module: input.entrypoint,
2116
+ compatibility_date: input.compatibilityDate,
2117
+ compatibility_flags: input.compatibilityFlags.length > 0 ? input.compatibilityFlags : void 0,
2118
+ tags: input.tags && input.tags.length > 0 ? input.tags : void 0
2119
+ },
2120
+ files: [moduleFile]
2121
+ };
2122
+ return await client.workersForPlatforms.dispatch.namespaces.scripts.update(input.dispatchNamespace, input.scriptName, params, input.ifMatch ? { headers: { "If-Match": input.ifMatch } } : void 0);
2123
+ };
2124
+ const reconcileCloudflareWorkerDeployment = async (client, input) => {
2125
+ const appTag = buildCloudflareAppTag(input.appId, input.deploymentTagPrefix);
2126
+ const deploymentTag = buildCloudflareDeploymentTag(input.deploymentId, input.deploymentTagPrefix);
2127
+ try {
2128
+ return {
2129
+ action: "uploaded",
2130
+ deploymentTag,
2131
+ response: await deployCloudflareWorker(client, {
2132
+ accountId: input.accountId,
2133
+ dispatchNamespace: input.dispatchNamespace,
2134
+ scriptName: input.scriptName,
2135
+ entrypoint: input.entrypoint,
2136
+ moduleContent: input.moduleContent,
2137
+ compatibilityDate: input.compatibilityDate,
2138
+ compatibilityFlags: input.compatibilityFlags,
2139
+ tags: buildCloudflareScriptTags([appTag, deploymentTag], input.scriptTags),
2140
+ ifMatch: input.expectedLiveEtag
2141
+ })
2142
+ };
2143
+ } catch (error) {
2144
+ if (getCloudflareApiError(error)?.status !== 412) throw error;
2145
+ const [currentDeployment, scriptState] = await Promise.all([getCloudflareCurrentDeployment(client, {
2146
+ accountId: input.accountId,
2147
+ dispatchNamespace: input.dispatchNamespace,
2148
+ scriptName: input.scriptName,
2149
+ deploymentTagPrefix: input.deploymentTagPrefix
2150
+ }), getCloudflareScriptState(client, {
2151
+ accountId: input.accountId,
2152
+ dispatchNamespace: input.dispatchNamespace,
2153
+ scriptName: input.scriptName
2154
+ })]);
2155
+ if (currentDeployment?.deploymentTag === deploymentTag) return {
2156
+ action: "already-deployed",
2157
+ deploymentTag,
2158
+ currentDeploymentTag: currentDeployment.deploymentTag,
2159
+ currentDeploymentId: currentDeployment.deploymentId,
2160
+ currentEtag: scriptState?.etag ?? null,
2161
+ currentModifiedOn: scriptState?.modifiedOn ?? null
2162
+ };
2163
+ if (currentDeployment !== null) return {
2164
+ action: "superseded",
2165
+ deploymentTag,
2166
+ currentDeploymentTag: currentDeployment.deploymentTag,
2167
+ currentDeploymentId: currentDeployment.deploymentId,
2168
+ currentEtag: scriptState?.etag ?? null,
2169
+ currentModifiedOn: scriptState?.modifiedOn ?? null
2170
+ };
2171
+ throw error;
2172
+ }
2173
+ };
2174
+ const getCloudflareApiError = (error) => {
2175
+ if (!(error instanceof Cloudflare.APIError)) return null;
2176
+ const [firstError] = error.errors;
2177
+ return {
2178
+ status: error.status,
2179
+ code: firstError?.code === void 0 || firstError?.code === null ? error.name : String(firstError.code),
2180
+ message: firstError?.message ?? error.message,
2181
+ details: isRecord(error.error) ? error.error : void 0
2182
+ };
2183
+ };
2184
+
2185
+ //#endregion
2186
+ //#region src/contracts.ts
2187
+ const SUPPORTED_DEPLOYMENT_FORMAT = "esmodule";
2188
+ const DEFAULT_DEPLOYMENT_ENTRYPOINT = "index.mjs";
2189
+ const cloudflareCompatibilityDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "compatibilityDate must use YYYY-MM-DD.");
2190
+ const cloudflareDeploymentStatusSchema = z.enum([
2191
+ "queued",
2192
+ "deploying",
2193
+ "succeeded",
2194
+ "failed"
2195
+ ]);
2196
+ const cloudflareDeployScriptSchema = z.object({
2197
+ type: z.enum([SUPPORTED_DEPLOYMENT_FORMAT]).default(SUPPORTED_DEPLOYMENT_FORMAT),
2198
+ entrypoint: z.string().trim().min(1).default(DEFAULT_DEPLOYMENT_ENTRYPOINT),
2199
+ content: z.string()
2200
+ });
2201
+ const cloudflareDeployRequestSchema = z.object({
2202
+ script: cloudflareDeployScriptSchema,
2203
+ compatibilityDate: cloudflareCompatibilityDateSchema.optional(),
2204
+ compatibilityFlags: z.array(z.string().min(1)).optional()
2205
+ });
2206
+ const cloudflareTimestampSchema = z.string().datetime({ offset: true });
2207
+ const cloudflareProviderSummarySchema = z.object({
2208
+ etag: z.string().nullable(),
2209
+ modifiedOn: cloudflareTimestampSchema.nullable()
2210
+ });
2211
+ const cloudflareDeploymentSummarySchema = z.object({
2212
+ id: z.string(),
2213
+ appId: z.string(),
2214
+ scriptName: z.string(),
2215
+ status: cloudflareDeploymentStatusSchema,
2216
+ format: z.literal(SUPPORTED_DEPLOYMENT_FORMAT),
2217
+ entrypoint: z.string(),
2218
+ sourceByteLength: z.number().int().nonnegative(),
2219
+ compatibilityDate: cloudflareCompatibilityDateSchema,
2220
+ compatibilityFlags: z.array(z.string()),
2221
+ attemptCount: z.number().int().nonnegative(),
2222
+ queuedAt: cloudflareTimestampSchema,
2223
+ startedAt: cloudflareTimestampSchema.nullable(),
2224
+ completedAt: cloudflareTimestampSchema.nullable(),
2225
+ errorCode: z.string().nullable(),
2226
+ errorMessage: z.string().nullable(),
2227
+ cloudflare: cloudflareProviderSummarySchema.nullable(),
2228
+ createdAt: cloudflareTimestampSchema,
2229
+ updatedAt: cloudflareTimestampSchema
2230
+ });
2231
+ const cloudflareDeploymentDetailSchema = cloudflareDeploymentSummarySchema.extend({ sourceCode: z.string() });
2232
+ const cloudflareAppSummarySchema = z.object({
2233
+ id: z.string(),
2234
+ scriptName: z.string(),
2235
+ latestDeployment: cloudflareDeploymentSummarySchema.nullable(),
2236
+ createdAt: cloudflareTimestampSchema,
2237
+ updatedAt: cloudflareTimestampSchema
2238
+ });
2239
+ const cloudflareAppStateSchema = cloudflareAppSummarySchema.extend({
2240
+ liveDeployment: cloudflareDeploymentSummarySchema.nullable(),
2241
+ liveDeploymentError: z.string().nullable(),
2242
+ deployments: z.array(cloudflareDeploymentSummarySchema)
2243
+ });
2244
+ const cloudflareAppListSchema = z.object({ apps: z.array(cloudflareAppSummarySchema) });
2245
+ const cloudflareDeploymentListSchema = z.object({ deployments: z.array(cloudflareDeploymentSummarySchema) });
2246
+
2247
+ //#endregion
2248
+ //#region src/script-name.ts
2249
+ const DEFAULT_SCRIPT_NAME_SEPARATOR = "-";
2250
+ const DEFAULT_MAX_SCRIPT_NAME_LENGTH = 63;
2251
+ const normalizeSeparator = (value) => {
2252
+ const cleaned = (value ?? DEFAULT_SCRIPT_NAME_SEPARATOR).replace(/[^a-z0-9-]+/gi, "-");
2253
+ return cleaned.length > 0 ? cleaned : DEFAULT_SCRIPT_NAME_SEPARATOR;
2254
+ };
2255
+ const sanitizeSegment = (value) => {
2256
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").replace(/-+$/, "").replace(/-{2,}/g, "-");
2257
+ };
2258
+ const hashString = (value) => {
2259
+ let hash = 2166136261;
2260
+ for (const char of value) {
2261
+ hash ^= char.charCodeAt(0);
2262
+ hash = Math.imul(hash, 16777619);
2263
+ }
2264
+ return (hash >>> 0).toString(16).padStart(8, "0");
2265
+ };
2266
+ const resolveCloudflareScriptName = (appId, config = {}) => {
2267
+ const separator = normalizeSeparator(config.scriptNameSeparator);
2268
+ const maxLength = Math.max(16, config.maxScriptNameLength ?? DEFAULT_MAX_SCRIPT_NAME_LENGTH);
2269
+ const base = [
2270
+ config.scriptNamePrefix,
2271
+ appId,
2272
+ config.scriptNameSuffix
2273
+ ].map((segment) => sanitizeSegment(segment ?? "")).filter(Boolean).join(separator) || "app";
2274
+ if (base.length <= maxLength) return base;
2275
+ const hash = hashString(base);
2276
+ const budget = Math.max(1, maxLength - hash.length - separator.length);
2277
+ return `${base.slice(0, budget).replace(/-+$/, "") || "app"}${separator}${hash}`.slice(0, maxLength).replace(/-+$/, "");
2278
+ };
2279
+
2280
+ //#endregion
2281
+ //#region src/definition.ts
2282
+ const cloudflareFragmentDefinition = defineFragment("cloudflare-fragment").extend((x) => x).withDependencies(() => {}).providesService("cloudflare", ({ deps }) => ({ getClient: () => deps.cloudflare })).providesBaseService(() => {}).build();
2283
+
2284
+ //#endregion
2285
+ //#region src/routes.ts
2286
+ const cloudflareRoutesFactory = defineRoutes(cloudflareFragmentDefinition).create(({ services, defineRoute }) => {
2287
+ return [
2288
+ defineRoute({
2289
+ method: "GET",
2290
+ path: "/apps",
2291
+ outputSchema: cloudflareAppListSchema,
2292
+ handler: () => {}
2293
+ }),
2294
+ defineRoute({
2295
+ method: "POST",
2296
+ path: "/apps/:appId/deployments",
2297
+ inputSchema: cloudflareDeployRequestSchema,
2298
+ outputSchema: cloudflareDeploymentSummarySchema,
2299
+ errorCodes: ["INVALID_DEPLOY_INPUT", "UNSUPPORTED_DEPLOY_REQUEST"],
2300
+ handler: () => {}
2301
+ }),
2302
+ defineRoute({
2303
+ method: "GET",
2304
+ path: "/apps/:appId/deployments",
2305
+ outputSchema: cloudflareDeploymentListSchema,
2306
+ errorCodes: ["APP_NOT_FOUND"],
2307
+ handler: () => {}
2308
+ }),
2309
+ defineRoute({
2310
+ method: "GET",
2311
+ path: "/deployments/:deploymentId",
2312
+ outputSchema: cloudflareDeploymentDetailSchema,
2313
+ errorCodes: ["DEPLOYMENT_NOT_FOUND"],
2314
+ handler: () => {}
2315
+ }),
2316
+ defineRoute({
2317
+ method: "GET",
2318
+ path: "/apps/:appId",
2319
+ outputSchema: cloudflareAppStateSchema,
2320
+ errorCodes: ["APP_NOT_FOUND"],
2321
+ handler: () => {}
2322
+ })
2323
+ ];
2324
+ });
2325
+
2326
+ //#endregion
2327
+ //#region src/schema.ts
2328
+ const cloudflareSchema = schema("cloudflare-fragment", (s) => {
2329
+ return s.addTable("app", (t) => {
2330
+ return t.addColumn("id", idColumn()).addColumn("scriptName", column("string")).addColumn("liveDeploymentId", column("string").nullable()).addColumn("liveCloudflareEtag", column("string").nullable()).addColumn("firstDeploymentLeaseId", column("string").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_app_scriptName", ["scriptName"], { unique: true });
2331
+ }).addTable("deployment", (t) => {
2332
+ return t.addColumn("id", idColumn()).addColumn("appId", referenceColumn()).addColumn("status", column("string")).addColumn("format", column("string")).addColumn("entrypoint", column("string")).addColumn("scriptName", column("string")).addColumn("sourceCode", column("string")).addColumn("sourceByteLength", column("integer")).addColumn("compatibilityDate", column("string")).addColumn("compatibilityFlags", column("json")).addColumn("attemptCount", column("integer").defaultTo(0)).addColumn("startedAt", column("timestamp").nullable()).addColumn("completedAt", column("timestamp").nullable()).addColumn("errorCode", column("string").nullable()).addColumn("errorMessage", column("string").nullable()).addColumn("cloudflareEtag", column("string").nullable()).addColumn("cloudflareModifiedOn", column("string").nullable()).addColumn("cloudflareResponse", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_deployment_app_createdAt", ["appId", "createdAt"]).createIndex("idx_deployment_status_createdAt", ["status", "createdAt"]);
2333
+ }).addReference("app", {
2334
+ type: "one",
2335
+ from: {
2336
+ table: "deployment",
2337
+ column: "appId"
2338
+ },
2339
+ to: {
2340
+ table: "app",
2341
+ column: "id"
2342
+ }
2343
+ });
2344
+ });
2345
+
2346
+ //#endregion
2347
+ export { findCloudflareAppTag as A, atom as B, getCloudflareScriptState as C, DEFAULT_DEPLOYMENT_TAG_PREFIX as D, resolveCloudflareDispatchNamespaceName as E, sanitizeCloudflareTag as F, createClientBuilder as I, isGetHook as L, getCloudflareAppIdFromTag as M, getCloudflareDeploymentIdFromTag as N, buildCloudflareAppTag as O, normalizeCloudflareDeploymentTagPrefix as P, isMutatorHook as R, getCloudflareCurrentDeployment as S, reconcileCloudflareWorkerDeployment as T, isReadableAtom as V, cloudflareDeploymentSummarySchema as _, DEFAULT_SCRIPT_NAME_SEPARATOR as a, deployCloudflareWorker as b, SUPPORTED_DEPLOYMENT_FORMAT as c, cloudflareAppSummarySchema as d, cloudflareDeployRequestSchema as f, cloudflareDeploymentStatusSchema as g, cloudflareDeploymentListSchema as h, DEFAULT_MAX_SCRIPT_NAME_LENGTH as i, findCloudflareDeploymentTag as j, buildCloudflareDeploymentTag as k, cloudflareAppListSchema as l, cloudflareDeploymentDetailSchema as m, cloudflareRoutesFactory as n, resolveCloudflareScriptName as o, cloudflareDeployScriptSchema as p, cloudflareFragmentDefinition as r, DEFAULT_DEPLOYMENT_ENTRYPOINT as s, cloudflareSchema as t, cloudflareAppStateSchema as u, buildCloudflareScriptTags as v, listCloudflareScriptTags as w, getCloudflareApiError as x, createCloudflareApiClient as y, isStore as z };
2348
+ //# sourceMappingURL=schema-Bt-h9kGf.js.map