@fragno-dev/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.turbo/turbo-build.log +61 -0
  2. package/.turbo/turbo-types$colon$check.log +2 -0
  3. package/dist/api/api.d.ts +2 -0
  4. package/dist/api/api.js +3 -0
  5. package/dist/api-CBDGZiLC.d.ts +278 -0
  6. package/dist/api-CBDGZiLC.d.ts.map +1 -0
  7. package/dist/api-DgHfYjq2.js +54 -0
  8. package/dist/api-DgHfYjq2.js.map +1 -0
  9. package/dist/client/client.d.ts +3 -0
  10. package/dist/client/client.js +6 -0
  11. package/dist/client/client.svelte.d.ts +33 -0
  12. package/dist/client/client.svelte.d.ts.map +1 -0
  13. package/dist/client/client.svelte.js +123 -0
  14. package/dist/client/client.svelte.js.map +1 -0
  15. package/dist/client/react.d.ts +58 -0
  16. package/dist/client/react.d.ts.map +1 -0
  17. package/dist/client/react.js +80 -0
  18. package/dist/client/react.js.map +1 -0
  19. package/dist/client/vanilla.d.ts +61 -0
  20. package/dist/client/vanilla.d.ts.map +1 -0
  21. package/dist/client/vanilla.js +136 -0
  22. package/dist/client/vanilla.js.map +1 -0
  23. package/dist/client/vue.d.ts +39 -0
  24. package/dist/client/vue.d.ts.map +1 -0
  25. package/dist/client/vue.js +108 -0
  26. package/dist/client/vue.js.map +1 -0
  27. package/dist/client-DWjxKDnE.js +703 -0
  28. package/dist/client-DWjxKDnE.js.map +1 -0
  29. package/dist/client-XFdAy-IQ.d.ts +287 -0
  30. package/dist/client-XFdAy-IQ.d.ts.map +1 -0
  31. package/dist/integrations/astro.d.ts +18 -0
  32. package/dist/integrations/astro.d.ts.map +1 -0
  33. package/dist/integrations/astro.js +16 -0
  34. package/dist/integrations/astro.js.map +1 -0
  35. package/dist/integrations/next-js.d.ts +15 -0
  36. package/dist/integrations/next-js.d.ts.map +1 -0
  37. package/dist/integrations/next-js.js +17 -0
  38. package/dist/integrations/next-js.js.map +1 -0
  39. package/dist/integrations/react-ssr.d.ts +19 -0
  40. package/dist/integrations/react-ssr.d.ts.map +1 -0
  41. package/dist/integrations/react-ssr.js +38 -0
  42. package/dist/integrations/react-ssr.js.map +1 -0
  43. package/dist/integrations/svelte-kit.d.ts +21 -0
  44. package/dist/integrations/svelte-kit.d.ts.map +1 -0
  45. package/dist/integrations/svelte-kit.js +18 -0
  46. package/dist/integrations/svelte-kit.js.map +1 -0
  47. package/dist/mod.d.ts +3 -0
  48. package/dist/mod.js +177 -0
  49. package/dist/mod.js.map +1 -0
  50. package/dist/route-Bp6eByhz.js +331 -0
  51. package/dist/route-Bp6eByhz.js.map +1 -0
  52. package/dist/ssr-tJHqcNSw.js +48 -0
  53. package/dist/ssr-tJHqcNSw.js.map +1 -0
  54. package/package.json +127 -0
  55. package/src/api/api.test.ts +140 -0
  56. package/src/api/api.ts +106 -0
  57. package/src/api/error.ts +47 -0
  58. package/src/api/fragment.test.ts +509 -0
  59. package/src/api/fragment.ts +277 -0
  60. package/src/api/internal/path-runtime.test.ts +121 -0
  61. package/src/api/internal/path-type.test.ts +602 -0
  62. package/src/api/internal/path.ts +322 -0
  63. package/src/api/internal/response-stream.ts +118 -0
  64. package/src/api/internal/route.test.ts +56 -0
  65. package/src/api/internal/route.ts +9 -0
  66. package/src/api/request-input-context.test.ts +437 -0
  67. package/src/api/request-input-context.ts +201 -0
  68. package/src/api/request-middleware.test.ts +544 -0
  69. package/src/api/request-middleware.ts +126 -0
  70. package/src/api/request-output-context.test.ts +626 -0
  71. package/src/api/request-output-context.ts +175 -0
  72. package/src/api/route.test.ts +176 -0
  73. package/src/api/route.ts +152 -0
  74. package/src/client/client-builder.test.ts +264 -0
  75. package/src/client/client-error.test.ts +15 -0
  76. package/src/client/client-error.ts +141 -0
  77. package/src/client/client-types.test.ts +493 -0
  78. package/src/client/client.ssr.test.ts +173 -0
  79. package/src/client/client.svelte.test.ts +837 -0
  80. package/src/client/client.svelte.ts +278 -0
  81. package/src/client/client.test.ts +1690 -0
  82. package/src/client/client.ts +1035 -0
  83. package/src/client/component.test.svelte +21 -0
  84. package/src/client/internal/ndjson-streaming.test.ts +457 -0
  85. package/src/client/internal/ndjson-streaming.ts +248 -0
  86. package/src/client/react.test.ts +947 -0
  87. package/src/client/react.ts +241 -0
  88. package/src/client/vanilla.test.ts +867 -0
  89. package/src/client/vanilla.ts +265 -0
  90. package/src/client/vue.test.ts +754 -0
  91. package/src/client/vue.ts +242 -0
  92. package/src/http/http-status.ts +60 -0
  93. package/src/integrations/astro.ts +17 -0
  94. package/src/integrations/next-js.ts +31 -0
  95. package/src/integrations/react-ssr.ts +40 -0
  96. package/src/integrations/svelte-kit.ts +41 -0
  97. package/src/mod.ts +20 -0
  98. package/src/util/async.test.ts +85 -0
  99. package/src/util/async.ts +96 -0
  100. package/src/util/content-type.test.ts +136 -0
  101. package/src/util/content-type.ts +84 -0
  102. package/src/util/nanostores.test.ts +28 -0
  103. package/src/util/nanostores.ts +65 -0
  104. package/src/util/ssr.ts +75 -0
  105. package/src/util/types-util.ts +16 -0
  106. package/tsconfig.json +10 -0
  107. package/tsdown.config.ts +21 -0
  108. package/vitest.config.ts +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-ssr.d.ts","names":[],"sources":["../../src/integrations/react-ssr.ts"],"sourcesContent":[],"mappings":";;AAoBA;AAUA;AAOA;;;;;;iBAjBsB,eAAA,CAAA,GAAmB;iBAUnB,cAAA,CAAA,GAAc;;;;iBAOd,gBAAA,CAAA,GAAgB"}
@@ -0,0 +1,38 @@
1
+ import { cleanStores, getFinalStoreValues } from "../ssr-tJHqcNSw.js";
2
+
3
+ //#region src/integrations/react-ssr.ts
4
+ /**
5
+ * Advice from https://pragmaticwebsecurity.com/articles/spasecurity/json-stringify-xss.html
6
+ * @param obj
7
+ * @returns
8
+ */
9
+ function javascriptEscaped(obj) {
10
+ return JSON.stringify(obj).replace(/</g, "\\u003c");
11
+ }
12
+ /**
13
+ * This method should be called after a first render pass is finished.
14
+ * It gets all values from the stores and embeds them in a script tag in the HTML body.
15
+ *
16
+ * On the client side, this script tag is used to hydrate the stores.
17
+ * Be sure to also call finishServerLoad when the page is rendered, to reset the stores for the next request.
18
+ *
19
+ * @returns A string to be embedded in a script tag in the HTML body
20
+ */
21
+ async function startServerLoad() {
22
+ const initialStoreValues = await getFinalStoreValues();
23
+ console.log("initialStoreValues", initialStoreValues);
24
+ return `window.__FRAGNO_INITIAL_DATA__ = ${javascriptEscaped(Array.from(initialStoreValues.entries()))}`;
25
+ }
26
+ async function initServerLoad() {
27
+ cleanStores();
28
+ }
29
+ /**
30
+ * Reset the stores for the next request.
31
+ */
32
+ async function finishServerLoad() {
33
+ cleanStores();
34
+ }
35
+
36
+ //#endregion
37
+ export { finishServerLoad, initServerLoad, startServerLoad };
38
+ //# sourceMappingURL=react-ssr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-ssr.js","names":[],"sources":["../../src/integrations/react-ssr.ts"],"sourcesContent":["import { cleanStores, getFinalStoreValues } from \"../util/ssr\";\n\n/**\n * Advice from https://pragmaticwebsecurity.com/articles/spasecurity/json-stringify-xss.html\n * @param obj\n * @returns\n */\nfunction javascriptEscaped(obj: unknown) {\n return JSON.stringify(obj).replace(/</g, \"\\\\u003c\");\n}\n\n/**\n * This method should be called after a first render pass is finished.\n * It gets all values from the stores and embeds them in a script tag in the HTML body.\n *\n * On the client side, this script tag is used to hydrate the stores.\n * Be sure to also call finishServerLoad when the page is rendered, to reset the stores for the next request.\n *\n * @returns A string to be embedded in a script tag in the HTML body\n */\nexport async function startServerLoad(): Promise<string> {\n const initialStoreValues = await getFinalStoreValues();\n\n console.log(\"initialStoreValues\", initialStoreValues);\n\n return `window.__FRAGNO_INITIAL_DATA__ = ${javascriptEscaped(\n Array.from(initialStoreValues.entries()),\n )}`;\n}\n\nexport async function initServerLoad() {\n cleanStores();\n}\n\n/**\n * Reset the stores for the next request.\n */\nexport async function finishServerLoad() {\n cleanStores();\n}\n"],"mappings":";;;;;;;;AAOA,SAAS,kBAAkB,KAAc;AACvC,QAAO,KAAK,UAAU,IAAI,CAAC,QAAQ,MAAM,UAAU;;;;;;;;;;;AAYrD,eAAsB,kBAAmC;CACvD,MAAM,qBAAqB,MAAM,qBAAqB;AAEtD,SAAQ,IAAI,sBAAsB,mBAAmB;AAErD,QAAO,oCAAoC,kBACzC,MAAM,KAAK,mBAAmB,SAAS,CAAC,CACzC;;AAGH,eAAsB,iBAAiB;AACrC,cAAa;;;;;AAMf,eAAsB,mBAAmB;AACvC,cAAa"}
@@ -0,0 +1,21 @@
1
+ //#region src/integrations/svelte-kit.d.ts
2
+ type MaybePromise<T> = T | Promise<T>;
3
+ type SvelteKitRequestEvent = {
4
+ request: Request;
5
+ };
6
+ type SvelteKitRequestHandler = (event: SvelteKitRequestEvent) => MaybePromise<Response>;
7
+ interface SvelteKitHandlers {
8
+ GET: SvelteKitRequestHandler;
9
+ POST: SvelteKitRequestHandler;
10
+ PUT: SvelteKitRequestHandler;
11
+ PATCH: SvelteKitRequestHandler;
12
+ DELETE: SvelteKitRequestHandler;
13
+ OPTIONS: SvelteKitRequestHandler;
14
+ }
15
+ declare function toSvelteHandler<T extends {
16
+ handler: (req: Request) => Promise<Response>;
17
+ }>(fragment: T): SvelteKitHandlers;
18
+ declare function toSvelteHandler(handler: (req: Request) => Promise<Response>): SvelteKitHandlers;
19
+ //#endregion
20
+ export { SvelteKitHandlers, SvelteKitRequestHandler, toSvelteHandler };
21
+ //# sourceMappingURL=svelte-kit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte-kit.d.ts","names":[],"sources":["../../src/integrations/svelte-kit.ts"],"sourcesContent":[],"mappings":";KAAK,kBAAkB,IAAI,QAAQ;KAE9B,qBAAA,GAFY;EAAA,OAAA,EAGN,OAHM;;AAAkB,KAMvB,uBAAA,GANuB,CAAA,KAAA,EAMW,qBANX,EAAA,GAMqC,YANrC,CAMkD,QANlD,CAAA;AAAR,UAQV,iBAAA,CARU;EAAO,GAAA,EAS3B,uBAT2B;EAE7B,IAAA,EAQG,uBARkB;EAId,GAAA,EAKL,uBAL4B;EAAA,KAAA,EAM1B,uBAN0B;QAAW,EAOpC,uBAPoC;SAAuC,EAQ1E,uBAR0E;;AAAD,iBAWpE,eAXoE,CAAA,UAAA;EAEnE,OAAA,EAAA,CAAA,GAAA,EAS0C,OATzB,EAAA,GASqC,OATrC,CAS6C,QAT7C,CAAA;CAAA,CAAA,CAAA,QAAA,EAUtB,CAVsB,CAAA,EAW/B,iBAX+B;AAC3B,iBAWS,eAAA,CAXT,OAAA,EAAA,CAAA,GAAA,EAWwC,OAXxC,EAAA,GAWoD,OAXpD,CAW4D,QAX5D,CAAA,CAAA,EAWwE,iBAXxE"}
@@ -0,0 +1,18 @@
1
+ //#region src/integrations/svelte-kit.ts
2
+ function toSvelteHandler(fragmentOrHandler) {
3
+ const requestHandler = async ({ request }) => {
4
+ return "handler" in fragmentOrHandler ? fragmentOrHandler.handler(request) : fragmentOrHandler(request);
5
+ };
6
+ return {
7
+ GET: requestHandler,
8
+ POST: requestHandler,
9
+ PUT: requestHandler,
10
+ PATCH: requestHandler,
11
+ DELETE: requestHandler,
12
+ OPTIONS: requestHandler
13
+ };
14
+ }
15
+
16
+ //#endregion
17
+ export { toSvelteHandler };
18
+ //# sourceMappingURL=svelte-kit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte-kit.js","names":["requestHandler: SvelteKitRequestHandler"],"sources":["../../src/integrations/svelte-kit.ts"],"sourcesContent":["type MaybePromise<T> = T | Promise<T>;\n\ntype SvelteKitRequestEvent = {\n request: Request;\n};\n\nexport type SvelteKitRequestHandler = (event: SvelteKitRequestEvent) => MaybePromise<Response>;\n\nexport interface SvelteKitHandlers {\n GET: SvelteKitRequestHandler;\n POST: SvelteKitRequestHandler;\n PUT: SvelteKitRequestHandler;\n PATCH: SvelteKitRequestHandler;\n DELETE: SvelteKitRequestHandler;\n OPTIONS: SvelteKitRequestHandler;\n}\n\nexport function toSvelteHandler<T extends { handler: (req: Request) => Promise<Response> }>(\n fragment: T,\n): SvelteKitHandlers;\nexport function toSvelteHandler(handler: (req: Request) => Promise<Response>): SvelteKitHandlers;\nexport function toSvelteHandler(\n fragmentOrHandler:\n | { handler: (req: Request) => Promise<Response> }\n | ((req: Request) => Promise<Response>),\n): SvelteKitHandlers {\n const requestHandler: SvelteKitRequestHandler = async ({ request }) => {\n return \"handler\" in fragmentOrHandler\n ? fragmentOrHandler.handler(request)\n : fragmentOrHandler(request);\n };\n\n return {\n GET: requestHandler,\n POST: requestHandler,\n PUT: requestHandler,\n PATCH: requestHandler,\n DELETE: requestHandler,\n OPTIONS: requestHandler,\n };\n}\n"],"mappings":";AAqBA,SAAgB,gBACd,mBAGmB;CACnB,MAAMA,iBAA0C,OAAO,EAAE,cAAc;AACrE,SAAO,aAAa,oBAChB,kBAAkB,QAAQ,QAAQ,GAClC,kBAAkB,QAAQ;;AAGhC,QAAO;EACL,KAAK;EACL,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;EACR,SAAS;EACV"}
package/dist/mod.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { FragnoRouteConfig } from "./api-CBDGZiLC.js";
2
+ import { AnyRouteOrFactory, FlattenRouteFactories, FragmentBuilder, FragnoFragmentSharedConfig, FragnoInstantiatedFragment, FragnoPublicClientConfig, FragnoPublicConfig, RouteFactory, RouteFactoryContext, createFragment, defineFragment, defineRoute, defineRoutes } from "./client-XFdAy-IQ.js";
3
+ export { type AnyRouteOrFactory, type FlattenRouteFactories, FragmentBuilder, type FragnoFragmentSharedConfig, type FragnoInstantiatedFragment, type FragnoPublicClientConfig, type FragnoPublicConfig, type FragnoRouteConfig, type RouteFactory, type RouteFactoryContext, createFragment, defineFragment, defineRoute, defineRoutes };
package/dist/mod.js ADDED
@@ -0,0 +1,177 @@
1
+ import { FragnoApiError } from "./api-DgHfYjq2.js";
2
+ import { OutputContext, RequestInputContext, RequestOutputContext, defineRoute, defineRoutes, getMountRoute, resolveRouteFactories } from "./route-Bp6eByhz.js";
3
+ import { addRoute, createRouter, findRoute } from "rou3";
4
+
5
+ //#region src/api/request-middleware.ts
6
+ var RequestMiddlewareOutputContext = class extends OutputContext {
7
+ #deps;
8
+ #services;
9
+ constructor(deps, services) {
10
+ super();
11
+ this.#deps = deps;
12
+ this.#services = services;
13
+ }
14
+ get deps() {
15
+ return this.#deps;
16
+ }
17
+ get services() {
18
+ return this.#services;
19
+ }
20
+ };
21
+ var RequestMiddlewareInputContext = class {
22
+ #options;
23
+ #route;
24
+ constructor(routes, options) {
25
+ this.#options = options;
26
+ const route = routes.find((route$1) => route$1.path === options.path && route$1.method === options.method);
27
+ if (!route) throw new Error(`Route not found: ${options.path} ${options.method}`);
28
+ this.#route = route;
29
+ }
30
+ get path() {
31
+ return this.#options.path;
32
+ }
33
+ get method() {
34
+ return this.#options.method;
35
+ }
36
+ get pathParams() {
37
+ return this.#options.pathParams ?? {};
38
+ }
39
+ get queryParams() {
40
+ return this.#options.searchParams;
41
+ }
42
+ get inputSchema() {
43
+ return this.#route.inputSchema;
44
+ }
45
+ get outputSchema() {
46
+ return this.#route.outputSchema;
47
+ }
48
+ ifMatchesRoute = async (method, path, handler) => {
49
+ if (this.path !== path || this.method !== method) return;
50
+ const inputContext = await RequestInputContext.fromRequest({
51
+ request: this.#options.request,
52
+ method: this.#options.method,
53
+ path,
54
+ pathParams: this.pathParams,
55
+ inputSchema: this.#route.inputSchema
56
+ });
57
+ const outputContext = new RequestOutputContext(this.#route.outputSchema);
58
+ return handler(inputContext, outputContext);
59
+ };
60
+ };
61
+
62
+ //#endregion
63
+ //#region src/api/fragment.ts
64
+ var FragmentBuilder = class FragmentBuilder {
65
+ #definition;
66
+ constructor(definition) {
67
+ this.#definition = definition;
68
+ }
69
+ get definition() {
70
+ return this.#definition;
71
+ }
72
+ withDependencies(fn) {
73
+ return new FragmentBuilder({
74
+ ...this.#definition,
75
+ dependencies: fn
76
+ });
77
+ }
78
+ withServices(fn) {
79
+ return new FragmentBuilder({
80
+ ...this.#definition,
81
+ services: fn
82
+ });
83
+ }
84
+ };
85
+ function defineFragment(name) {
86
+ return new FragmentBuilder({ name });
87
+ }
88
+ function createFragment(fragmentDefinition, config, routesOrFactories, fragnoConfig = {}) {
89
+ const definition = fragmentDefinition.definition;
90
+ const dependencies = definition.dependencies ? definition.dependencies(config) : {};
91
+ const services = definition.services ? definition.services(config, dependencies) : {};
92
+ const routes = resolveRouteFactories({
93
+ config,
94
+ deps: dependencies,
95
+ services
96
+ }, routesOrFactories);
97
+ const mountRoute = getMountRoute({
98
+ name: definition.name,
99
+ mountRoute: fragnoConfig.mountRoute
100
+ });
101
+ const router = createRouter();
102
+ let middlewareHandler;
103
+ for (const routeConfig of routes) addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);
104
+ const fragment = {
105
+ mountRoute,
106
+ config: {
107
+ name: definition.name,
108
+ routes
109
+ },
110
+ services,
111
+ deps: dependencies,
112
+ withMiddleware: (handler) => {
113
+ if (middlewareHandler) throw new Error("Middleware already set");
114
+ middlewareHandler = handler;
115
+ return fragment;
116
+ },
117
+ handler: async (req) => {
118
+ const pathname = new URL(req.url).pathname;
119
+ const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;
120
+ if (matchRoute === null) return Response.json({
121
+ error: `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? Expecting: '${mountRoute}'.`,
122
+ code: "ROUTE_NOT_FOUND"
123
+ }, { status: 404 });
124
+ const route = findRoute(router, req.method, matchRoute);
125
+ if (!route) return Response.json({
126
+ error: `Fragno: Route for '${definition.name}' not found`,
127
+ code: "ROUTE_NOT_FOUND"
128
+ }, { status: 404 });
129
+ const { handler, inputSchema, outputSchema, path } = route.data;
130
+ const outputContext = new RequestOutputContext(outputSchema);
131
+ if (middlewareHandler) {
132
+ const middlewareInputContext = new RequestMiddlewareInputContext(routes, {
133
+ method: req.method,
134
+ path,
135
+ pathParams: route.params,
136
+ searchParams: new URL(req.url).searchParams,
137
+ body: req.body,
138
+ request: req
139
+ });
140
+ const middlewareOutputContext = new RequestMiddlewareOutputContext(dependencies, services);
141
+ try {
142
+ const middlewareResult = await middlewareHandler(middlewareInputContext, middlewareOutputContext);
143
+ if (middlewareResult !== void 0) return middlewareResult;
144
+ } catch (error) {
145
+ console.error("Error in middleware", error);
146
+ if (error instanceof FragnoApiError) return error.toResponse();
147
+ return Response.json({
148
+ error: "Internal server error",
149
+ code: "INTERNAL_SERVER_ERROR"
150
+ }, { status: 500 });
151
+ }
152
+ }
153
+ const inputContext = await RequestInputContext.fromRequest({
154
+ request: req,
155
+ method: req.method,
156
+ path,
157
+ pathParams: route.params ?? {},
158
+ inputSchema
159
+ });
160
+ try {
161
+ return await handler(inputContext, outputContext);
162
+ } catch (error) {
163
+ console.error("Error in handler", error);
164
+ if (error instanceof FragnoApiError) return error.toResponse();
165
+ return Response.json({
166
+ error: "Internal server error",
167
+ code: "INTERNAL_SERVER_ERROR"
168
+ }, { status: 500 });
169
+ }
170
+ }
171
+ };
172
+ return fragment;
173
+ }
174
+
175
+ //#endregion
176
+ export { FragmentBuilder, createFragment, defineFragment, defineRoute, defineRoutes };
177
+ //# sourceMappingURL=mod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.js","names":["#deps","#services","#options","#route","route","#definition","middlewareHandler:\n | FragnoMiddlewareCallback<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices>\n | undefined","fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n TServices\n >"],"sources":["../src/api/request-middleware.ts","../src/api/fragment.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { HTTPMethod } from \"./api\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport type { RequestBodyType } from \"./request-input-context\";\nimport type { AnyFragnoRouteConfig } from \"./route\";\nimport { RequestInputContext } from \"./request-input-context\";\nimport { OutputContext, RequestOutputContext } from \"./request-output-context\";\n\nexport type FragnoMiddlewareCallback<\n TRoutes extends readonly AnyFragnoRouteConfig[],\n TDeps,\n TServices extends Record<string, unknown>,\n> = (\n inputContext: RequestMiddlewareInputContext<TRoutes>,\n outputContext: RequestMiddlewareOutputContext<TDeps, TServices>,\n) => Promise<Response | undefined> | Response | undefined;\n\nexport interface RequestMiddlewareOptions {\n path: string;\n method: HTTPMethod;\n pathParams?: Record<string, string>;\n searchParams: URLSearchParams;\n body: RequestBodyType;\n request: Request;\n}\n\nexport class RequestMiddlewareOutputContext<\n const TDeps,\n const TServices extends Record<string, unknown>,\n> extends OutputContext<unknown, string> {\n readonly #deps: TDeps;\n readonly #services: TServices;\n\n constructor(deps: TDeps, services: TServices) {\n super();\n this.#deps = deps;\n this.#services = services;\n }\n\n get deps(): TDeps {\n return this.#deps;\n }\n\n get services(): TServices {\n return this.#services;\n }\n}\n\nexport class RequestMiddlewareInputContext<const TRoutes extends readonly AnyFragnoRouteConfig[]> {\n readonly #options: RequestMiddlewareOptions;\n readonly #route: TRoutes[number];\n\n constructor(routes: TRoutes, options: RequestMiddlewareOptions) {\n this.#options = options;\n\n const route = routes.find(\n (route) => route.path === options.path && route.method === options.method,\n );\n\n if (!route) {\n throw new Error(`Route not found: ${options.path} ${options.method}`);\n }\n\n this.#route = route;\n }\n\n get path(): string {\n return this.#options.path;\n }\n\n get method(): HTTPMethod {\n return this.#options.method;\n }\n\n get pathParams(): Record<string, string> {\n return this.#options.pathParams ?? {};\n }\n\n get queryParams(): URLSearchParams {\n return this.#options.searchParams;\n }\n\n get inputSchema(): StandardSchemaV1 | undefined {\n return this.#route.inputSchema;\n }\n\n get outputSchema(): StandardSchemaV1 | undefined {\n return this.#route.outputSchema;\n }\n\n // Defined as a field so that `this` reference stays in tact when destructuring\n ifMatchesRoute = async <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes>,\n const TRoute extends ExtractRouteByPath<TRoutes, TPath, TMethod> = ExtractRouteByPath<\n TRoutes,\n TPath,\n TMethod\n >,\n >(\n method: TMethod,\n path: TPath,\n handler: (\n ...args: Parameters<TRoute[\"handler\"]>\n ) => Promise<Response | undefined | void> | Response | undefined | void,\n ): Promise<Response | undefined> => {\n if (this.path !== path || this.method !== method) {\n return undefined;\n }\n\n // TODO(Wilco): We should support reading/modifying headers here.\n const inputContext = await RequestInputContext.fromRequest({\n request: this.#options.request,\n method: this.#options.method,\n path: path,\n pathParams: this.pathParams as ExtractPathParams<TPath>,\n inputSchema: this.#route.inputSchema,\n });\n\n const outputContext = new RequestOutputContext(this.#route.outputSchema);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (handler as any)(inputContext, outputContext);\n };\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { type FragnoRouteConfig, type HTTPMethod } from \"./api\";\nimport { FragnoApiError } from \"./error\";\nimport { getMountRoute } from \"./internal/route\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { RequestInputContext } from \"./request-input-context\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport { RequestOutputContext } from \"./request-output-context\";\nimport {\n type EmptyObject,\n type AnyFragnoRouteConfig,\n type AnyRouteOrFactory,\n type FlattenRouteFactories,\n resolveRouteFactories,\n} from \"./route\";\nimport {\n RequestMiddlewareInputContext,\n RequestMiddlewareOutputContext,\n type FragnoMiddlewareCallback,\n} from \"./request-middleware\";\n\nexport interface FragnoPublicConfig {\n mountRoute?: string;\n}\n\nexport interface FragnoPublicClientConfig {\n mountRoute?: string;\n baseUrl?: string;\n}\n\nexport interface FragnoInstantiatedFragment<\n TRoutes extends readonly AnyFragnoRouteConfig[] = [],\n TDeps = EmptyObject,\n TServices extends Record<string, unknown> = Record<string, unknown>,\n> {\n config: FragnoFragmentSharedConfig<TRoutes>;\n deps: TDeps;\n services: TServices;\n handler: (req: Request) => Promise<Response>;\n mountRoute: string;\n withMiddleware: (\n handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,\n ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices>;\n}\n\nexport interface FragnoFragmentSharedConfig<\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n name: string;\n routes: TRoutes;\n}\n\nexport type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<\n readonly AnyFragnoRouteConfig[]\n>;\n\ninterface FragmentDefinition<\n TConfig,\n TDeps = EmptyObject,\n TServices extends Record<string, unknown> = EmptyObject,\n> {\n name: string;\n dependencies?: (config: TConfig) => TDeps;\n services?: (config: TConfig, deps: TDeps) => TServices;\n}\n\nexport class FragmentBuilder<\n TConfig,\n TDeps = EmptyObject,\n TServices extends Record<string, unknown> = EmptyObject,\n> {\n #definition: FragmentDefinition<TConfig, TDeps, TServices>;\n\n constructor(definition: FragmentDefinition<TConfig, TDeps, TServices>) {\n this.#definition = definition;\n }\n\n get definition() {\n return this.#definition;\n }\n\n withDependencies<TNewDeps>(\n fn: (config: TConfig) => TNewDeps,\n ): FragmentBuilder<TConfig, TNewDeps, TServices> {\n return new FragmentBuilder<TConfig, TNewDeps, TServices>({\n ...this.#definition,\n dependencies: fn,\n } as FragmentDefinition<TConfig, TNewDeps, TServices>);\n }\n\n withServices<TNewServices extends Record<string, unknown>>(\n fn: (config: TConfig, deps: TDeps) => TNewServices,\n ): FragmentBuilder<TConfig, TDeps, TNewServices> {\n return new FragmentBuilder<TConfig, TDeps, TNewServices>({\n ...this.#definition,\n services: fn,\n } as FragmentDefinition<TConfig, TDeps, TNewServices>);\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function defineFragment<TConfig = {}>(name: string): FragmentBuilder<TConfig> {\n return new FragmentBuilder({\n name,\n });\n}\n\nexport function createFragment<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n fragmentDefinition: FragmentBuilder<TConfig, TDeps, TServices>,\n config: TConfig,\n routesOrFactories: TRoutesOrFactories,\n fragnoConfig: FragnoPublicConfig = {},\n): FragnoInstantiatedFragment<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices> {\n const definition = fragmentDefinition.definition;\n\n const dependencies = definition.dependencies ? definition.dependencies(config) : ({} as TDeps);\n const services = definition.services\n ? definition.services(config, dependencies)\n : ({} as TServices);\n\n const context = { config, deps: dependencies, services };\n const routes = resolveRouteFactories(context, routesOrFactories);\n\n const mountRoute = getMountRoute({\n name: definition.name,\n mountRoute: fragnoConfig.mountRoute,\n });\n\n const router =\n createRouter<\n FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >\n >();\n\n let middlewareHandler:\n | FragnoMiddlewareCallback<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices>\n | undefined;\n\n for (const routeConfig of routes) {\n addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);\n }\n\n const fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n TServices\n > = {\n mountRoute,\n config: {\n name: definition.name,\n routes,\n },\n services,\n deps: dependencies,\n withMiddleware: (handler) => {\n if (middlewareHandler) {\n throw new Error(\"Middleware already set\");\n }\n\n middlewareHandler = handler;\n\n return fragment;\n },\n handler: async (req: Request) => {\n const url = new URL(req.url);\n const pathname = url.pathname;\n\n const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;\n\n if (matchRoute === null) {\n return Response.json(\n {\n error:\n `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? ` +\n `Expecting: '${mountRoute}'.`,\n code: \"ROUTE_NOT_FOUND\",\n },\n { status: 404 },\n );\n }\n\n const route = findRoute(router, req.method, matchRoute);\n\n if (!route) {\n return Response.json(\n { error: `Fragno: Route for '${definition.name}' not found`, code: \"ROUTE_NOT_FOUND\" },\n { status: 404 },\n );\n }\n\n const { handler, inputSchema, outputSchema, path } = route.data;\n\n const outputContext = new RequestOutputContext(outputSchema);\n\n if (middlewareHandler) {\n const middlewareInputContext = new RequestMiddlewareInputContext(routes, {\n method: req.method as HTTPMethod,\n path,\n pathParams: route.params,\n searchParams: new URL(req.url).searchParams,\n body: req.body,\n request: req,\n });\n\n const middlewareOutputContext = new RequestMiddlewareOutputContext(dependencies, services);\n\n try {\n const middlewareResult = await middlewareHandler(\n middlewareInputContext,\n middlewareOutputContext,\n );\n if (middlewareResult !== undefined) {\n return middlewareResult;\n }\n } catch (error) {\n console.error(\"Error in middleware\", error);\n\n if (error instanceof FragnoApiError) {\n // TODO: If a validation error occurs in middleware (when calling `await input.valid()`)\n // the processing is short-circuited and a potential `catch` block around the call\n // to `input.valid()` in the actual handler will not be executed.\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n }\n\n const inputContext = await RequestInputContext.fromRequest({\n request: req,\n method: req.method,\n path,\n pathParams: (route.params ?? {}) as ExtractPathParams<typeof path>,\n inputSchema,\n });\n\n try {\n const result = await handler(inputContext, outputContext);\n return result;\n } catch (error) {\n console.error(\"Error in handler\", error);\n\n if (error instanceof FragnoApiError) {\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n },\n };\n\n return fragment;\n}\n"],"mappings":"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"}
@@ -0,0 +1,331 @@
1
+ import { FragnoApiValidationError } from "./api-DgHfYjq2.js";
2
+
3
+ //#region src/api/internal/route.ts
4
+ function getMountRoute(opts) {
5
+ const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
6
+ if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
7
+ return mountRoute;
8
+ }
9
+
10
+ //#endregion
11
+ //#region src/api/request-input-context.ts
12
+ var RequestInputContext = class RequestInputContext {
13
+ #path;
14
+ #method;
15
+ #pathParams;
16
+ #searchParams;
17
+ #body;
18
+ #inputSchema;
19
+ #shouldValidateInput;
20
+ constructor(config) {
21
+ this.#path = config.path;
22
+ this.#method = config.method;
23
+ this.#pathParams = config.pathParams;
24
+ this.#searchParams = config.searchParams;
25
+ this.#body = config.body;
26
+ this.#inputSchema = config.inputSchema;
27
+ this.#shouldValidateInput = config.shouldValidateInput ?? true;
28
+ }
29
+ /**
30
+ * Create a RequestContext from a Request object for server-side handling
31
+ */
32
+ static async fromRequest(config) {
33
+ const url = new URL(config.request.url);
34
+ const request = config.request.clone();
35
+ const json = request.body instanceof ReadableStream ? await request.json() : void 0;
36
+ return new RequestInputContext({
37
+ method: config.method,
38
+ path: config.path,
39
+ pathParams: config.pathParams,
40
+ searchParams: url.searchParams,
41
+ body: json,
42
+ inputSchema: config.inputSchema,
43
+ shouldValidateInput: config.shouldValidateInput
44
+ });
45
+ }
46
+ /**
47
+ * Create a RequestContext for server-side rendering contexts (no Request object)
48
+ */
49
+ static fromSSRContext(config) {
50
+ return new RequestInputContext({
51
+ method: config.method,
52
+ path: config.path,
53
+ pathParams: config.pathParams,
54
+ searchParams: config.searchParams ?? new URLSearchParams(),
55
+ body: "body" in config ? config.body : void 0,
56
+ inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
57
+ shouldValidateInput: false
58
+ });
59
+ }
60
+ /**
61
+ * The HTTP method as string (e.g., `GET`, `POST`)
62
+ */
63
+ get method() {
64
+ return this.#method;
65
+ }
66
+ /**
67
+ * The matched route path (e.g., `/users/:id`)
68
+ * @remarks `string`
69
+ */
70
+ get path() {
71
+ return this.#path;
72
+ }
73
+ /**
74
+ * Extracted path parameters as object (e.g., `{ id: '123' }`)
75
+ * @remarks `Record<string, string>`
76
+ */
77
+ get pathParams() {
78
+ return this.#pathParams;
79
+ }
80
+ /**
81
+ * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters
82
+ * @remarks `URLSearchParams`
83
+ */
84
+ get query() {
85
+ return this.#searchParams;
86
+ }
87
+ /**
88
+ * @internal
89
+ */
90
+ get rawBody() {
91
+ return this.#body;
92
+ }
93
+ /**
94
+ * Input validation context (only if inputSchema is defined)
95
+ * @remarks `InputContext`
96
+ */
97
+ get input() {
98
+ if (!this.#inputSchema) return;
99
+ return {
100
+ schema: this.#inputSchema,
101
+ valid: async () => {
102
+ if (!this.#shouldValidateInput) return this.#body;
103
+ return this.#validateInput();
104
+ }
105
+ };
106
+ }
107
+ async #validateInput() {
108
+ if (!this.#inputSchema) throw new Error("No input schema defined for this route");
109
+ if (this.#body instanceof FormData || this.#body instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
110
+ const result = await this.#inputSchema["~standard"].validate(this.#body);
111
+ if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
112
+ return result.value;
113
+ }
114
+ };
115
+
116
+ //#endregion
117
+ //#region src/api/internal/response-stream.ts
118
+ var ResponseStream = class {
119
+ #writer;
120
+ #encoder;
121
+ #abortSubscribers = [];
122
+ #responseReadable;
123
+ #aborted = false;
124
+ #closed = false;
125
+ /**
126
+ * Whether the stream has been aborted.
127
+ */
128
+ get aborted() {
129
+ return this.#aborted;
130
+ }
131
+ /**
132
+ * Whether the stream has been closed normally.
133
+ */
134
+ get closed() {
135
+ return this.#closed;
136
+ }
137
+ /**
138
+ * The readable stream that the response is piped to.
139
+ */
140
+ get responseReadable() {
141
+ return this.#responseReadable;
142
+ }
143
+ constructor(writable, readable) {
144
+ this.#writer = writable.getWriter();
145
+ this.#encoder = new TextEncoder();
146
+ const reader = readable.getReader();
147
+ this.#abortSubscribers.push(async () => {
148
+ await reader.cancel();
149
+ });
150
+ this.#responseReadable = new ReadableStream({
151
+ async pull(controller) {
152
+ const { done, value } = await reader.read();
153
+ if (done) controller.close();
154
+ else controller.enqueue(value);
155
+ },
156
+ cancel: () => {
157
+ this.abort();
158
+ }
159
+ });
160
+ }
161
+ async writeRaw(input) {
162
+ try {
163
+ if (typeof input === "string") input = this.#encoder.encode(input);
164
+ await this.#writer.write(input);
165
+ } catch {}
166
+ }
167
+ write(input) {
168
+ return this.writeRaw(JSON.stringify(input) + "\n");
169
+ }
170
+ sleep(ms) {
171
+ return new Promise((res) => setTimeout(res, ms));
172
+ }
173
+ async close() {
174
+ try {
175
+ await this.#writer.close();
176
+ } catch {} finally {
177
+ this.#closed = true;
178
+ }
179
+ }
180
+ onAbort(listener) {
181
+ this.#abortSubscribers.push(listener);
182
+ }
183
+ /**
184
+ * Abort the stream.
185
+ * You can call this method when stream is aborted by external event.
186
+ */
187
+ abort() {
188
+ if (!this.aborted) {
189
+ this.#aborted = true;
190
+ this.#abortSubscribers.forEach((subscriber) => subscriber());
191
+ }
192
+ }
193
+ };
194
+
195
+ //#endregion
196
+ //#region src/api/request-output-context.ts
197
+ /**
198
+ * Utility function to merge headers from multiple sources.
199
+ * Later headers override earlier ones.
200
+ */
201
+ function mergeHeaders(...headerSources) {
202
+ const mergedHeaders = new Headers();
203
+ for (const headerSource of headerSources) {
204
+ if (!headerSource) continue;
205
+ if (headerSource instanceof Headers) for (const [key, value] of headerSource.entries()) mergedHeaders.set(key, value);
206
+ else if (Array.isArray(headerSource)) for (const [key, value] of headerSource) mergedHeaders.set(key, value);
207
+ else for (const [key, value] of Object.entries(headerSource)) mergedHeaders.set(key, value);
208
+ }
209
+ return mergedHeaders;
210
+ }
211
+ var OutputContext = class {
212
+ /**
213
+ * Creates an error response.
214
+ *
215
+ * Shortcut for `throw new FragnoApiError(...)`
216
+ */
217
+ error({ message, code }, initOrStatus, headers) {
218
+ if (typeof initOrStatus === "undefined") return Response.json({
219
+ error: message,
220
+ code
221
+ }, {
222
+ status: 500,
223
+ headers
224
+ });
225
+ if (typeof initOrStatus === "number") return Response.json({
226
+ error: message,
227
+ code
228
+ }, {
229
+ status: initOrStatus,
230
+ headers
231
+ });
232
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
233
+ return Response.json({
234
+ error: message,
235
+ code
236
+ }, {
237
+ status: initOrStatus.status,
238
+ headers: mergedHeaders
239
+ });
240
+ }
241
+ empty(initOrStatus, headers) {
242
+ const defaultHeaders = {};
243
+ if (typeof initOrStatus === "undefined") {
244
+ const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
245
+ return Response.json(null, {
246
+ status: 201,
247
+ headers: mergedHeaders$1
248
+ });
249
+ }
250
+ if (typeof initOrStatus === "number") {
251
+ const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
252
+ return Response.json(null, {
253
+ status: initOrStatus,
254
+ headers: mergedHeaders$1
255
+ });
256
+ }
257
+ const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
258
+ return Response.json(null, {
259
+ status: initOrStatus.status,
260
+ headers: mergedHeaders
261
+ });
262
+ }
263
+ json(object, initOrStatus, headers) {
264
+ if (typeof initOrStatus === "undefined") return Response.json(object, {
265
+ status: 200,
266
+ headers
267
+ });
268
+ if (typeof initOrStatus === "number") return Response.json(object, {
269
+ status: initOrStatus,
270
+ headers
271
+ });
272
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
273
+ return Response.json(object, {
274
+ status: initOrStatus.status,
275
+ headers: mergedHeaders
276
+ });
277
+ }
278
+ jsonStream = (cb, { onError, headers } = {}) => {
279
+ const defaultHeaders = {
280
+ "content-type": "application/x-ndjson; charset=utf-8",
281
+ "transfer-encoding": "chunked",
282
+ "cache-control": "no-cache"
283
+ };
284
+ const { readable, writable } = new TransformStream();
285
+ const stream = new ResponseStream(writable, readable);
286
+ (async () => {
287
+ try {
288
+ await cb(stream);
289
+ } catch (e) {
290
+ if (e === void 0) {} else if (e instanceof Error && onError) await onError(e, stream);
291
+ else console.error(e);
292
+ } finally {
293
+ stream.close();
294
+ }
295
+ })();
296
+ return new Response(stream.responseReadable, {
297
+ status: 200,
298
+ headers: mergeHeaders(defaultHeaders, headers)
299
+ });
300
+ };
301
+ };
302
+ var RequestOutputContext = class extends OutputContext {
303
+ #outputSchema;
304
+ constructor(outputSchema) {
305
+ super();
306
+ this.#outputSchema = outputSchema;
307
+ }
308
+ };
309
+
310
+ //#endregion
311
+ //#region src/api/route.ts
312
+ function resolveRouteFactories(context, routesOrFactories) {
313
+ const routes = [];
314
+ for (const item of routesOrFactories) if (typeof item === "function") {
315
+ const factoryRoutes = item(context);
316
+ routes.push(...factoryRoutes);
317
+ } else routes.push(item);
318
+ return routes;
319
+ }
320
+ function defineRoute(config) {
321
+ return config;
322
+ }
323
+ function defineRoutes() {
324
+ return { create: (fn) => {
325
+ return fn;
326
+ } };
327
+ }
328
+
329
+ //#endregion
330
+ export { OutputContext, RequestInputContext, RequestOutputContext, defineRoute, defineRoutes, getMountRoute, resolveRouteFactories };
331
+ //# sourceMappingURL=route-Bp6eByhz.js.map