@fragno-dev/core 0.1.7 → 0.1.8

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 (79) hide show
  1. package/.turbo/turbo-build.log +45 -53
  2. package/CHANGELOG.md +6 -0
  3. package/dist/api/api.d.ts +2 -2
  4. package/dist/api/api.js +3 -2
  5. package/dist/api/fragment-builder.d.ts +2 -4
  6. package/dist/api/fragment-builder.js +1 -1
  7. package/dist/api/fragment-instantiation.d.ts +2 -4
  8. package/dist/api/fragment-instantiation.js +3 -5
  9. package/dist/api/route.d.ts +2 -3
  10. package/dist/api/route.js +1 -1
  11. package/dist/api-BFrUCIsF.d.ts +963 -0
  12. package/dist/api-BFrUCIsF.d.ts.map +1 -0
  13. package/dist/client/client.d.ts +1 -3
  14. package/dist/client/client.js +4 -5
  15. package/dist/client/client.svelte.d.ts +2 -3
  16. package/dist/client/client.svelte.d.ts.map +1 -1
  17. package/dist/client/client.svelte.js +4 -5
  18. package/dist/client/client.svelte.js.map +1 -1
  19. package/dist/client/react.d.ts +2 -3
  20. package/dist/client/react.d.ts.map +1 -1
  21. package/dist/client/react.js +4 -5
  22. package/dist/client/react.js.map +1 -1
  23. package/dist/client/solid.d.ts +2 -3
  24. package/dist/client/solid.d.ts.map +1 -1
  25. package/dist/client/solid.js +4 -5
  26. package/dist/client/solid.js.map +1 -1
  27. package/dist/client/vanilla.d.ts +2 -3
  28. package/dist/client/vanilla.d.ts.map +1 -1
  29. package/dist/client/vanilla.js +4 -5
  30. package/dist/client/vanilla.js.map +1 -1
  31. package/dist/client/vue.d.ts +2 -3
  32. package/dist/client/vue.d.ts.map +1 -1
  33. package/dist/client/vue.js +4 -5
  34. package/dist/client/vue.js.map +1 -1
  35. package/dist/{client-C5LsYHEI.js → client-DAFHcKqA.js} +4 -4
  36. package/dist/{client-C5LsYHEI.js.map → client-DAFHcKqA.js.map} +1 -1
  37. package/dist/fragment-builder-Boh2vNHq.js +108 -0
  38. package/dist/fragment-builder-Boh2vNHq.js.map +1 -0
  39. package/dist/fragment-instantiation-DUT-HLl1.js +898 -0
  40. package/dist/fragment-instantiation-DUT-HLl1.js.map +1 -0
  41. package/dist/integrations/react-ssr.js +1 -1
  42. package/dist/mod.d.ts +2 -4
  43. package/dist/mod.js +4 -6
  44. package/dist/{route-C5Uryylh.js → route-C4CyNHkC.js} +8 -3
  45. package/dist/route-C4CyNHkC.js.map +1 -0
  46. package/dist/{ssr-BByDVfFD.js → ssr-kyKI7pqH.js} +1 -1
  47. package/dist/{ssr-BByDVfFD.js.map → ssr-kyKI7pqH.js.map} +1 -1
  48. package/dist/test/test.d.ts +6 -7
  49. package/dist/test/test.d.ts.map +1 -1
  50. package/dist/test/test.js +9 -7
  51. package/dist/test/test.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/api/api.ts +45 -6
  54. package/src/api/fragment-builder.ts +463 -25
  55. package/src/api/fragment-instantiation.test.ts +249 -7
  56. package/src/api/fragment-instantiation.ts +283 -16
  57. package/src/api/fragment-services.test.ts +462 -0
  58. package/src/api/fragment.test.ts +65 -17
  59. package/src/api/request-middleware.test.ts +6 -3
  60. package/src/api/route.test.ts +111 -1
  61. package/src/api/route.ts +323 -14
  62. package/src/mod.ts +11 -1
  63. package/src/test/test.test.ts +20 -15
  64. package/src/test/test.ts +48 -9
  65. package/dist/api-BWN97TOr.d.ts +0 -377
  66. package/dist/api-BWN97TOr.d.ts.map +0 -1
  67. package/dist/api-DngJDcmO.js +0 -54
  68. package/dist/api-DngJDcmO.js.map +0 -1
  69. package/dist/fragment-builder-DOnCVBqc.js +0 -47
  70. package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
  71. package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
  72. package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
  73. package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
  74. package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
  75. package/dist/request-output-context-CdIjwmEN.js +0 -320
  76. package/dist/request-output-context-CdIjwmEN.js.map +0 -1
  77. package/dist/route-Bl9Zr1Yv.d.ts +0 -26
  78. package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
  79. package/dist/route-C5Uryylh.js.map +0 -1
@@ -0,0 +1,898 @@
1
+ import { r as resolveRouteFactories } from "./route-C4CyNHkC.js";
2
+ import { addRoute, createRouter, findRoute } from "rou3";
3
+
4
+ //#region src/api/error.ts
5
+ var FragnoApiError = class extends Error {
6
+ #status;
7
+ #code;
8
+ constructor({ message, code }, status) {
9
+ super(message);
10
+ this.name = "FragnoApiError";
11
+ this.#status = status;
12
+ this.#code = code;
13
+ }
14
+ get status() {
15
+ return this.#status;
16
+ }
17
+ get code() {
18
+ return this.#code;
19
+ }
20
+ toResponse() {
21
+ return Response.json({
22
+ message: this.message,
23
+ code: this.code
24
+ }, { status: this.status });
25
+ }
26
+ };
27
+ var FragnoApiValidationError = class extends FragnoApiError {
28
+ #issues;
29
+ constructor(message, issues) {
30
+ super({
31
+ message,
32
+ code: "FRAGNO_VALIDATION_ERROR"
33
+ }, 400);
34
+ this.name = "FragnoApiValidationError";
35
+ this.#issues = issues;
36
+ }
37
+ get issues() {
38
+ return this.#issues;
39
+ }
40
+ toResponse() {
41
+ return Response.json({
42
+ message: this.message,
43
+ issues: this.#issues,
44
+ code: this.code
45
+ }, { status: this.status });
46
+ }
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/api/api.ts
51
+ function addRoute$1(route) {
52
+ return route;
53
+ }
54
+
55
+ //#endregion
56
+ //#region src/api/internal/route.ts
57
+ function getMountRoute(opts) {
58
+ const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
59
+ if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
60
+ return mountRoute;
61
+ }
62
+
63
+ //#endregion
64
+ //#region src/api/request-input-context.ts
65
+ var RequestInputContext = class RequestInputContext {
66
+ #path;
67
+ #method;
68
+ #pathParams;
69
+ #searchParams;
70
+ #headers;
71
+ #body;
72
+ #parsedBody;
73
+ #inputSchema;
74
+ #shouldValidateInput;
75
+ constructor(config) {
76
+ this.#path = config.path;
77
+ this.#method = config.method;
78
+ this.#pathParams = config.pathParams;
79
+ this.#searchParams = config.searchParams;
80
+ this.#headers = config.headers;
81
+ this.#body = config.rawBody;
82
+ this.#parsedBody = config.parsedBody;
83
+ this.#inputSchema = config.inputSchema;
84
+ this.#shouldValidateInput = config.shouldValidateInput ?? true;
85
+ }
86
+ /**
87
+ * Create a RequestContext from a Request object for server-side handling
88
+ */
89
+ static async fromRequest(config) {
90
+ return new RequestInputContext({
91
+ method: config.method,
92
+ path: config.path,
93
+ pathParams: config.state.pathParams,
94
+ searchParams: config.state.searchParams,
95
+ headers: config.state.headers,
96
+ parsedBody: config.state.body,
97
+ rawBody: config.rawBody,
98
+ inputSchema: config.inputSchema,
99
+ shouldValidateInput: config.shouldValidateInput
100
+ });
101
+ }
102
+ /**
103
+ * Create a RequestContext for server-side rendering contexts (no Request object)
104
+ */
105
+ static fromSSRContext(config) {
106
+ return new RequestInputContext({
107
+ method: config.method,
108
+ path: config.path,
109
+ pathParams: config.pathParams,
110
+ searchParams: config.searchParams ?? new URLSearchParams(),
111
+ headers: config.headers ?? new Headers(),
112
+ parsedBody: "body" in config ? config.body : void 0,
113
+ inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
114
+ shouldValidateInput: false
115
+ });
116
+ }
117
+ /**
118
+ * The HTTP method as string (e.g., `GET`, `POST`)
119
+ */
120
+ get method() {
121
+ return this.#method;
122
+ }
123
+ /**
124
+ * The matched route path (e.g., `/users/:id`)
125
+ * @remarks `string`
126
+ */
127
+ get path() {
128
+ return this.#path;
129
+ }
130
+ /**
131
+ * Extracted path parameters as object (e.g., `{ id: '123' }`)
132
+ * @remarks `Record<string, string>`
133
+ */
134
+ get pathParams() {
135
+ return this.#pathParams;
136
+ }
137
+ /**
138
+ * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
139
+ * @remarks `URLSearchParams`
140
+ */
141
+ get query() {
142
+ return this.#searchParams;
143
+ }
144
+ /**
145
+ * [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers
146
+ * @remarks `Headers`
147
+ */
148
+ get headers() {
149
+ return this.#headers;
150
+ }
151
+ get rawBody() {
152
+ return this.#body;
153
+ }
154
+ /**
155
+ * Input validation context (only if inputSchema is defined)
156
+ * @remarks `InputContext`
157
+ */
158
+ get input() {
159
+ if (!this.#inputSchema) return;
160
+ return {
161
+ schema: this.#inputSchema,
162
+ valid: async () => {
163
+ if (!this.#shouldValidateInput) return this.#parsedBody;
164
+ return this.#validateInput();
165
+ }
166
+ };
167
+ }
168
+ async #validateInput() {
169
+ if (!this.#inputSchema) throw new Error("No input schema defined for this route");
170
+ if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
171
+ const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
172
+ if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
173
+ return result.value;
174
+ }
175
+ };
176
+
177
+ //#endregion
178
+ //#region src/api/internal/response-stream.ts
179
+ var ResponseStream = class {
180
+ #writer;
181
+ #encoder;
182
+ #abortSubscribers = [];
183
+ #responseReadable;
184
+ #aborted = false;
185
+ #closed = false;
186
+ /**
187
+ * Whether the stream has been aborted.
188
+ */
189
+ get aborted() {
190
+ return this.#aborted;
191
+ }
192
+ /**
193
+ * Whether the stream has been closed normally.
194
+ */
195
+ get closed() {
196
+ return this.#closed;
197
+ }
198
+ /**
199
+ * The readable stream that the response is piped to.
200
+ */
201
+ get responseReadable() {
202
+ return this.#responseReadable;
203
+ }
204
+ constructor(writable, readable) {
205
+ this.#writer = writable.getWriter();
206
+ this.#encoder = new TextEncoder();
207
+ const reader = readable.getReader();
208
+ this.#abortSubscribers.push(async () => {
209
+ await reader.cancel();
210
+ });
211
+ this.#responseReadable = new ReadableStream({
212
+ async pull(controller) {
213
+ const { done, value } = await reader.read();
214
+ if (done) controller.close();
215
+ else controller.enqueue(value);
216
+ },
217
+ cancel: () => {
218
+ this.abort();
219
+ }
220
+ });
221
+ }
222
+ async writeRaw(input) {
223
+ try {
224
+ if (typeof input === "string") input = this.#encoder.encode(input);
225
+ await this.#writer.write(input);
226
+ } catch {}
227
+ }
228
+ write(input) {
229
+ return this.writeRaw(JSON.stringify(input) + "\n");
230
+ }
231
+ sleep(ms) {
232
+ return new Promise((res) => setTimeout(res, ms));
233
+ }
234
+ async close() {
235
+ try {
236
+ await this.#writer.close();
237
+ } catch {} finally {
238
+ this.#closed = true;
239
+ }
240
+ }
241
+ onAbort(listener) {
242
+ this.#abortSubscribers.push(listener);
243
+ }
244
+ /**
245
+ * Abort the stream.
246
+ * You can call this method when stream is aborted by external event.
247
+ */
248
+ abort() {
249
+ if (!this.aborted) {
250
+ this.#aborted = true;
251
+ this.#abortSubscribers.forEach((subscriber) => subscriber());
252
+ }
253
+ }
254
+ };
255
+
256
+ //#endregion
257
+ //#region src/api/request-output-context.ts
258
+ /**
259
+ * Utility function to merge headers from multiple sources.
260
+ * Later headers override earlier ones.
261
+ */
262
+ function mergeHeaders(...headerSources) {
263
+ const mergedHeaders = new Headers();
264
+ for (const headerSource of headerSources) {
265
+ if (!headerSource) continue;
266
+ if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
267
+ else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
268
+ else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
269
+ }
270
+ return mergedHeaders;
271
+ }
272
+ var OutputContext = class {
273
+ /**
274
+ * Creates an error response.
275
+ *
276
+ * Shortcut for `throw new FragnoApiError(...)`
277
+ */
278
+ error = ({ message, code }, initOrStatus, headers) => {
279
+ if (typeof initOrStatus === "undefined") return Response.json({
280
+ message,
281
+ code
282
+ }, {
283
+ status: 500,
284
+ headers
285
+ });
286
+ if (typeof initOrStatus === "number") return Response.json({
287
+ message,
288
+ code
289
+ }, {
290
+ status: initOrStatus,
291
+ headers
292
+ });
293
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
294
+ return Response.json({
295
+ message,
296
+ code
297
+ }, {
298
+ status: initOrStatus.status,
299
+ headers: mergedHeaders
300
+ });
301
+ };
302
+ empty = (initOrStatus, headers) => {
303
+ const defaultHeaders = {};
304
+ if (typeof initOrStatus === "undefined") {
305
+ const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
306
+ return new Response(null, {
307
+ status: 201,
308
+ headers: mergedHeaders$1
309
+ });
310
+ }
311
+ if (typeof initOrStatus === "number") {
312
+ const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
313
+ return new Response(null, {
314
+ status: initOrStatus,
315
+ headers: mergedHeaders$1
316
+ });
317
+ }
318
+ const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
319
+ return new Response(null, {
320
+ status: initOrStatus.status,
321
+ headers: mergedHeaders
322
+ });
323
+ };
324
+ json = (object, initOrStatus, headers) => {
325
+ if (typeof initOrStatus === "undefined") return Response.json(object, {
326
+ status: 200,
327
+ headers
328
+ });
329
+ if (typeof initOrStatus === "number") return Response.json(object, {
330
+ status: initOrStatus,
331
+ headers
332
+ });
333
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
334
+ return Response.json(object, {
335
+ status: initOrStatus.status,
336
+ headers: mergedHeaders
337
+ });
338
+ };
339
+ jsonStream = (cb, { onError, headers } = {}) => {
340
+ const defaultHeaders = {
341
+ "content-type": "application/x-ndjson; charset=utf-8",
342
+ "transfer-encoding": "chunked",
343
+ "cache-control": "no-cache"
344
+ };
345
+ const { readable, writable } = new TransformStream();
346
+ const stream = new ResponseStream(writable, readable);
347
+ (async () => {
348
+ try {
349
+ await cb(stream);
350
+ } catch (e) {
351
+ if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
352
+ else console.error(e);
353
+ } finally {
354
+ stream.close();
355
+ }
356
+ })();
357
+ return new Response(stream.responseReadable, {
358
+ status: 200,
359
+ headers: mergeHeaders(defaultHeaders, headers)
360
+ });
361
+ };
362
+ };
363
+ var RequestOutputContext = class extends OutputContext {
364
+ #outputSchema;
365
+ constructor(outputSchema) {
366
+ super();
367
+ this.#outputSchema = outputSchema;
368
+ }
369
+ };
370
+
371
+ //#endregion
372
+ //#region src/api/mutable-request-state.ts
373
+ /**
374
+ * Holds mutable request state that can be modified by middleware and consumed by handlers.
375
+ *
376
+ * This class provides a structural way for middleware to modify request data:
377
+ * - Path parameters can be modified
378
+ * - Query/search parameters can be modified
379
+ * - Request body can be overridden
380
+ * - Request headers can be modified
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * // In middleware
385
+ * const state = new MutableRequestState({
386
+ * pathParams: { id: "123" },
387
+ * searchParams: new URLSearchParams("?role=user"),
388
+ * body: { name: "John" },
389
+ * headers: new Headers()
390
+ * });
391
+ *
392
+ * // Modify query parameters
393
+ * state.searchParams.set("role", "admin");
394
+ *
395
+ * // Override body
396
+ * state.setBody({ name: "Jane" });
397
+ *
398
+ * // Modify headers
399
+ * state.headers.set("X-Custom", "value");
400
+ * ```
401
+ */
402
+ var MutableRequestState = class {
403
+ #pathParams;
404
+ #searchParams;
405
+ #headers;
406
+ #initialBody;
407
+ #bodyOverride;
408
+ constructor(config) {
409
+ this.#pathParams = config.pathParams;
410
+ this.#searchParams = config.searchParams;
411
+ this.#headers = config.headers;
412
+ this.#initialBody = config.body;
413
+ this.#bodyOverride = void 0;
414
+ }
415
+ /**
416
+ * Path parameters extracted from the route.
417
+ * Can be modified directly (e.g., `state.pathParams.id = "456"`).
418
+ */
419
+ get pathParams() {
420
+ return this.#pathParams;
421
+ }
422
+ /**
423
+ * URLSearchParams for query parameters.
424
+ * Can be modified using URLSearchParams API (e.g., `state.searchParams.set("key", "value")`).
425
+ */
426
+ get searchParams() {
427
+ return this.#searchParams;
428
+ }
429
+ /**
430
+ * Request headers.
431
+ * Can be modified using Headers API (e.g., `state.headers.set("X-Custom", "value")`).
432
+ */
433
+ get headers() {
434
+ return this.#headers;
435
+ }
436
+ /**
437
+ * Get the current body value.
438
+ * Returns the override if set, otherwise the initial body.
439
+ */
440
+ get body() {
441
+ return this.#bodyOverride !== void 0 ? this.#bodyOverride : this.#initialBody;
442
+ }
443
+ /**
444
+ * Override the request body.
445
+ * This allows middleware to replace the body that will be seen by the handler.
446
+ *
447
+ * @param body - The new body value
448
+ *
449
+ * @example
450
+ * ```typescript
451
+ * // In middleware
452
+ * state.setBody({ modifiedField: "new value" });
453
+ * ```
454
+ */
455
+ setBody(body) {
456
+ this.#bodyOverride = body;
457
+ }
458
+ /**
459
+ * Check if the body has been overridden by middleware.
460
+ */
461
+ get hasBodyOverride() {
462
+ return this.#bodyOverride !== void 0;
463
+ }
464
+ };
465
+
466
+ //#endregion
467
+ //#region src/api/request-middleware.ts
468
+ var RequestMiddlewareOutputContext = class extends OutputContext {
469
+ #deps;
470
+ #services;
471
+ constructor(deps, services) {
472
+ super();
473
+ this.#deps = deps;
474
+ this.#services = services;
475
+ }
476
+ get deps() {
477
+ return this.#deps;
478
+ }
479
+ get services() {
480
+ return this.#services;
481
+ }
482
+ };
483
+ var RequestMiddlewareInputContext = class {
484
+ #options;
485
+ #route;
486
+ #state;
487
+ constructor(routes, options) {
488
+ this.#options = options;
489
+ this.#state = options.state;
490
+ const route = routes.find((route$1) => route$1.path === options.path && route$1.method === options.method);
491
+ if (!route) throw new Error(`Route not found: ${options.path} ${options.method}`);
492
+ this.#route = route;
493
+ }
494
+ get path() {
495
+ return this.#options.path;
496
+ }
497
+ get method() {
498
+ return this.#options.method;
499
+ }
500
+ get pathParams() {
501
+ return this.#state.pathParams;
502
+ }
503
+ get queryParams() {
504
+ return this.#state.searchParams;
505
+ }
506
+ get headers() {
507
+ return this.#state.headers;
508
+ }
509
+ get inputSchema() {
510
+ return this.#route.inputSchema;
511
+ }
512
+ get outputSchema() {
513
+ return this.#route.outputSchema;
514
+ }
515
+ /**
516
+ * Access to the mutable request state.
517
+ * Use this to modify query parameters, path parameters, or request body.
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * // Modify body
522
+ * requestState.setBody({ modified: true });
523
+ *
524
+ * // Query params are already accessible via queryParams getter
525
+ * // Path params are already accessible via pathParams getter
526
+ * ```
527
+ */
528
+ get requestState() {
529
+ return this.#state;
530
+ }
531
+ ifMatchesRoute = async (method, path, handler) => {
532
+ if (this.path !== path || this.method !== method) return;
533
+ return await handler(await RequestInputContext.fromRequest({
534
+ request: this.#options.request,
535
+ method: this.#options.method,
536
+ path,
537
+ pathParams: this.pathParams,
538
+ inputSchema: this.#route.inputSchema,
539
+ state: this.#state
540
+ }), new RequestOutputContext(this.#route.outputSchema));
541
+ };
542
+ };
543
+
544
+ //#endregion
545
+ //#region src/api/fragno-response.ts
546
+ /**
547
+ * Parse a Response object into a FragnoResponse discriminated union
548
+ */
549
+ async function parseFragnoResponse(response) {
550
+ const status = response.status;
551
+ const headers = response.headers;
552
+ if ((headers.get("content-type") || "").includes("application/x-ndjson")) return {
553
+ type: "jsonStream",
554
+ status,
555
+ headers,
556
+ stream: parseNDJSONStream(response)
557
+ };
558
+ const text = await response.text();
559
+ if (!text || text === "null") return {
560
+ type: "empty",
561
+ status,
562
+ headers
563
+ };
564
+ const data = JSON.parse(text);
565
+ if (data && typeof data === "object" && "code" in data) {
566
+ if ("message" in data) return {
567
+ type: "error",
568
+ status,
569
+ headers,
570
+ error: {
571
+ message: data.message,
572
+ code: data.code
573
+ }
574
+ };
575
+ if ("error" in data) return {
576
+ type: "error",
577
+ status,
578
+ headers,
579
+ error: {
580
+ message: data.error,
581
+ code: data.code
582
+ }
583
+ };
584
+ }
585
+ return {
586
+ type: "json",
587
+ status,
588
+ headers,
589
+ data
590
+ };
591
+ }
592
+ /**
593
+ * Parse an NDJSON stream into an async generator
594
+ */
595
+ async function* parseNDJSONStream(response) {
596
+ if (!response.body) return;
597
+ const reader = response.body.getReader();
598
+ const decoder = new TextDecoder();
599
+ let buffer = "";
600
+ try {
601
+ while (true) {
602
+ const { done, value } = await reader.read();
603
+ if (done) break;
604
+ buffer += decoder.decode(value, { stream: true });
605
+ const lines = buffer.split("\n");
606
+ buffer = lines.pop() || "";
607
+ for (const line of lines) if (line.trim()) yield JSON.parse(line);
608
+ }
609
+ if (buffer.trim()) yield JSON.parse(buffer);
610
+ } finally {
611
+ reader.releaseLock();
612
+ }
613
+ }
614
+
615
+ //#endregion
616
+ //#region src/api/fragment-instantiation.ts
617
+ const instantiatedFragmentFakeSymbol = "$fragno-instantiated-fragment";
618
+ function createFragment(fragmentBuilder, config, routesOrFactories, options, interfaceImplementations) {
619
+ const definition = fragmentBuilder.definition;
620
+ if (definition.usedServices) for (const [serviceName, serviceMeta] of Object.entries(definition.usedServices)) {
621
+ const implementation = interfaceImplementations?.[serviceName];
622
+ if (serviceMeta.required && !implementation) throw new Error(`Fragment '${definition.name}' requires service '${serviceMeta.name}' but it was not provided`);
623
+ }
624
+ const depsWithInterfaces = {
625
+ ...definition.dependencies?.(config, options) ?? {},
626
+ ...interfaceImplementations
627
+ };
628
+ const servicesFromWithServices = definition.services?.(config, options, depsWithInterfaces) ?? {};
629
+ let providedServicesResolved;
630
+ if (typeof definition.providedServices === "function") providedServicesResolved = definition.providedServices(config, options, depsWithInterfaces);
631
+ else if (definition.providedServices && typeof definition.providedServices === "object") {
632
+ providedServicesResolved = {};
633
+ for (const [serviceName, serviceOrFactory] of Object.entries(definition.providedServices)) if (typeof serviceOrFactory === "function") providedServicesResolved[serviceName] = serviceOrFactory(config, options, depsWithInterfaces);
634
+ else providedServicesResolved[serviceName] = serviceOrFactory;
635
+ }
636
+ const services = {
637
+ ...servicesFromWithServices,
638
+ ...providedServicesResolved,
639
+ ...interfaceImplementations
640
+ };
641
+ const routes = resolveRouteFactories({
642
+ config,
643
+ deps: depsWithInterfaces,
644
+ services
645
+ }, routesOrFactories);
646
+ const mountRoute = getMountRoute({
647
+ name: definition.name,
648
+ mountRoute: options.mountRoute
649
+ });
650
+ const router = createRouter();
651
+ let middlewareHandler;
652
+ const handlerWrapper = definition.createHandlerWrapper?.(options);
653
+ for (const routeConfig of routes) addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);
654
+ const fragment = {
655
+ [instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,
656
+ mountRoute,
657
+ config: {
658
+ name: definition.name,
659
+ routes
660
+ },
661
+ services,
662
+ deps: depsWithInterfaces,
663
+ additionalContext: {
664
+ ...definition.additionalContext,
665
+ ...options
666
+ },
667
+ withMiddleware: (handler) => {
668
+ if (middlewareHandler) throw new Error("Middleware already set");
669
+ middlewareHandler = handler;
670
+ return fragment;
671
+ },
672
+ callRoute: async (method, path, inputOptions) => {
673
+ return parseFragnoResponse(await fragment.callRouteRaw(method, path, inputOptions));
674
+ },
675
+ callRouteRaw: async (method, path, inputOptions) => {
676
+ const route = routes.find((r) => r.method === method && r.path === path);
677
+ if (!route) return Response.json({
678
+ error: `Route ${method} ${path} not found`,
679
+ code: "ROUTE_NOT_FOUND"
680
+ }, { status: 404 });
681
+ const { pathParams = {}, body, query, headers } = inputOptions || {};
682
+ const searchParams = query instanceof URLSearchParams ? query : query ? new URLSearchParams(query) : new URLSearchParams();
683
+ const requestHeaders = headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
684
+ const inputContext = new RequestInputContext({
685
+ path: route.path,
686
+ method: route.method,
687
+ pathParams,
688
+ searchParams,
689
+ headers: requestHeaders,
690
+ parsedBody: body,
691
+ inputSchema: route.inputSchema,
692
+ shouldValidateInput: true
693
+ });
694
+ const outputContext = new RequestOutputContext(route.outputSchema);
695
+ try {
696
+ let response;
697
+ const thisContext = {};
698
+ if (handlerWrapper) response = await handlerWrapper(route.handler).call(thisContext, inputContext, outputContext);
699
+ else response = await route.handler.call(thisContext, inputContext, outputContext);
700
+ return response;
701
+ } catch (error) {
702
+ console.error("Error in callRoute handler", error);
703
+ if (error instanceof FragnoApiError) return error.toResponse();
704
+ return Response.json({
705
+ error: "Internal server error",
706
+ code: "INTERNAL_SERVER_ERROR"
707
+ }, { status: 500 });
708
+ }
709
+ },
710
+ handlersFor: (framework) => {
711
+ const handler = fragment.handler;
712
+ if (framework === "h3" || framework === "nuxt") throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:
713
+ import { fromWebHandler } from "h3";
714
+ export default fromWebHandler(myFragment().handler);`);
715
+ return {
716
+ astro: { ALL: handler },
717
+ "react-router": {
718
+ loader: ({ request }) => handler(request),
719
+ action: ({ request }) => handler(request)
720
+ },
721
+ "next-js": {
722
+ GET: handler,
723
+ POST: handler,
724
+ PUT: handler,
725
+ DELETE: handler,
726
+ PATCH: handler,
727
+ HEAD: handler,
728
+ OPTIONS: handler
729
+ },
730
+ "svelte-kit": {
731
+ GET: handler,
732
+ POST: handler,
733
+ PUT: handler,
734
+ DELETE: handler,
735
+ PATCH: handler,
736
+ HEAD: handler,
737
+ OPTIONS: handler
738
+ },
739
+ "solid-start": {
740
+ GET: ({ request }) => handler(request),
741
+ POST: ({ request }) => handler(request),
742
+ PUT: ({ request }) => handler(request),
743
+ DELETE: ({ request }) => handler(request),
744
+ PATCH: ({ request }) => handler(request),
745
+ HEAD: ({ request }) => handler(request),
746
+ OPTIONS: ({ request }) => handler(request)
747
+ },
748
+ "tanstack-start": {
749
+ GET: ({ request }) => handler(request),
750
+ POST: ({ request }) => handler(request),
751
+ PUT: ({ request }) => handler(request),
752
+ DELETE: ({ request }) => handler(request),
753
+ PATCH: ({ request }) => handler(request),
754
+ HEAD: ({ request }) => handler(request),
755
+ OPTIONS: ({ request }) => handler(request)
756
+ }
757
+ }[framework];
758
+ },
759
+ handler: async (req) => {
760
+ const url = new URL(req.url);
761
+ const pathname = url.pathname;
762
+ const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;
763
+ if (matchRoute === null) return Response.json({
764
+ error: `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? Expecting: '${mountRoute}'.`,
765
+ code: "ROUTE_NOT_FOUND"
766
+ }, { status: 404 });
767
+ const route = findRoute(router, req.method, matchRoute);
768
+ if (!route) return Response.json({
769
+ error: `Fragno: Route for '${definition.name}' not found`,
770
+ code: "ROUTE_NOT_FOUND"
771
+ }, { status: 404 });
772
+ const { handler, inputSchema, outputSchema, path } = route.data;
773
+ const outputContext = new RequestOutputContext(outputSchema);
774
+ let requestBody = void 0;
775
+ let rawBody = void 0;
776
+ if (req.body instanceof ReadableStream) {
777
+ rawBody = await req.clone().text();
778
+ if (rawBody) try {
779
+ requestBody = JSON.parse(rawBody);
780
+ } catch {
781
+ requestBody = void 0;
782
+ }
783
+ }
784
+ const requestState = new MutableRequestState({
785
+ pathParams: route.params ?? {},
786
+ searchParams: url.searchParams,
787
+ body: requestBody,
788
+ headers: new Headers(req.headers)
789
+ });
790
+ if (middlewareHandler) {
791
+ const middlewareInputContext = new RequestMiddlewareInputContext(routes, {
792
+ method: req.method,
793
+ path,
794
+ request: req,
795
+ state: requestState
796
+ });
797
+ const middlewareOutputContext = new RequestMiddlewareOutputContext(depsWithInterfaces, services);
798
+ try {
799
+ const middlewareResult = await middlewareHandler(middlewareInputContext, middlewareOutputContext);
800
+ if (middlewareResult !== void 0) return middlewareResult;
801
+ } catch (error) {
802
+ console.error("Error in middleware", error);
803
+ if (error instanceof FragnoApiError) return error.toResponse();
804
+ return Response.json({
805
+ error: "Internal server error",
806
+ code: "INTERNAL_SERVER_ERROR"
807
+ }, { status: 500 });
808
+ }
809
+ }
810
+ const inputContext = await RequestInputContext.fromRequest({
811
+ request: req,
812
+ method: req.method,
813
+ path,
814
+ pathParams: route.params ?? {},
815
+ inputSchema,
816
+ state: requestState,
817
+ rawBody
818
+ });
819
+ try {
820
+ return await (handlerWrapper ? handlerWrapper(handler) : handler).call({}, inputContext, outputContext);
821
+ } catch (error) {
822
+ console.error("Error in handler", error);
823
+ if (error instanceof FragnoApiError) return error.toResponse();
824
+ return Response.json({
825
+ error: "Internal server error",
826
+ code: "INTERNAL_SERVER_ERROR"
827
+ }, { status: 500 });
828
+ }
829
+ }
830
+ };
831
+ return fragment;
832
+ }
833
+ /**
834
+ * Builder class for fluent fragment instantiation API
835
+ */
836
+ var FragmentInstantiationBuilder = class {
837
+ #fragmentBuilder;
838
+ #config;
839
+ #routes;
840
+ #options;
841
+ #services;
842
+ constructor(fragmentBuilder) {
843
+ this.#fragmentBuilder = fragmentBuilder;
844
+ }
845
+ /**
846
+ * Set the configuration for the fragment
847
+ */
848
+ withConfig(config) {
849
+ this.#config = config;
850
+ return this;
851
+ }
852
+ /**
853
+ * Set the routes for the fragment
854
+ */
855
+ withRoutes(routes) {
856
+ this.#routes = routes;
857
+ return this;
858
+ }
859
+ /**
860
+ * Set the options for the fragment (e.g., mountRoute, databaseAdapter)
861
+ */
862
+ withOptions(options) {
863
+ this.#options = options;
864
+ return this;
865
+ }
866
+ /**
867
+ * Provide implementations for services that this fragment uses
868
+ */
869
+ withServices(services) {
870
+ this.#services = services;
871
+ return this;
872
+ }
873
+ /**
874
+ * Build and return the instantiated fragment
875
+ */
876
+ build() {
877
+ return createFragment(this.#fragmentBuilder, this.#config ?? {}, this.#routes ?? [], this.#options ?? {}, this.#services);
878
+ }
879
+ };
880
+ /**
881
+ * Create a fluent builder for instantiating a fragment
882
+ *
883
+ * @example
884
+ * ```ts
885
+ * const fragment = instantiateFragment(myFragmentBuilder)
886
+ * .withConfig({ apiKey: "key" })
887
+ * .withRoutes([route1, route2])
888
+ * .withOptions({ mountRoute: "/api" })
889
+ * .build();
890
+ * ```
891
+ */
892
+ function instantiateFragment(fragmentBuilder) {
893
+ return new FragmentInstantiationBuilder(fragmentBuilder);
894
+ }
895
+
896
+ //#endregion
897
+ export { RequestOutputContext as a, addRoute$1 as c, instantiatedFragmentFakeSymbol as i, FragnoApiError as l, createFragment as n, RequestInputContext as o, instantiateFragment as r, getMountRoute as s, FragmentInstantiationBuilder as t, FragnoApiValidationError as u };
898
+ //# sourceMappingURL=fragment-instantiation-DUT-HLl1.js.map