@fragno-dev/core 0.0.6 → 0.0.7

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 (53) hide show
  1. package/.turbo/turbo-build.log +41 -37
  2. package/.turbo/turbo-test.log +297 -0
  3. package/CHANGELOG.md +8 -0
  4. package/dist/api/api.d.ts +1 -1
  5. package/dist/api/api.js +1 -1
  6. package/dist/{api-CBDGZiLC.d.ts → api-CAPyac52.d.ts} +3 -3
  7. package/dist/api-CAPyac52.d.ts.map +1 -0
  8. package/dist/{api-DgHfYjq2.js → api-DuzjjCT4.js} +2 -2
  9. package/dist/{api-DgHfYjq2.js.map → api-DuzjjCT4.js.map} +1 -1
  10. package/dist/client/client.d.ts +2 -2
  11. package/dist/client/client.js +4 -4
  12. package/dist/client/client.svelte.d.ts +14 -14
  13. package/dist/client/client.svelte.d.ts.map +1 -1
  14. package/dist/client/client.svelte.js +4 -4
  15. package/dist/client/react.d.ts +12 -12
  16. package/dist/client/react.d.ts.map +1 -1
  17. package/dist/client/react.js +6 -8
  18. package/dist/client/react.js.map +1 -1
  19. package/dist/client/solid.d.ts +45 -0
  20. package/dist/client/solid.d.ts.map +1 -0
  21. package/dist/client/solid.js +110 -0
  22. package/dist/client/solid.js.map +1 -0
  23. package/dist/client/vanilla.d.ts +21 -21
  24. package/dist/client/vanilla.d.ts.map +1 -1
  25. package/dist/client/vanilla.js +4 -4
  26. package/dist/client/vue.d.ts +14 -14
  27. package/dist/client/vue.d.ts.map +1 -1
  28. package/dist/client/vue.js +4 -4
  29. package/dist/{client-DWjxKDnE.js → client-C_Oc8hpD.js} +7 -10
  30. package/dist/{client-DWjxKDnE.js.map → client-C_Oc8hpD.js.map} +1 -1
  31. package/dist/{client-B6s-lTFe.d.ts → client-DdpPMlXL.d.ts} +73 -50
  32. package/dist/client-DdpPMlXL.d.ts.map +1 -0
  33. package/dist/integrations/react-ssr.js +1 -1
  34. package/dist/mod.d.ts +2 -2
  35. package/dist/mod.js +16 -6
  36. package/dist/mod.js.map +1 -1
  37. package/dist/{route-Bp6eByhz.js → route-Dq62lXqB.js} +6 -6
  38. package/dist/route-Dq62lXqB.js.map +1 -0
  39. package/dist/{ssr-tJHqcNSw.js → ssr-BAhbA_3q.js} +2 -2
  40. package/dist/{ssr-tJHqcNSw.js.map → ssr-BAhbA_3q.js.map} +1 -1
  41. package/package.json +22 -5
  42. package/src/api/fragment.ts +31 -13
  43. package/src/api/request-middleware.test.ts +3 -3
  44. package/src/api/request-output-context.ts +3 -3
  45. package/src/api/route.ts +1 -8
  46. package/src/client/client-error.test.ts +17 -1
  47. package/src/client/solid.test.ts +840 -0
  48. package/src/client/solid.ts +261 -0
  49. package/tsdown.config.ts +1 -0
  50. package/vitest.config.ts +10 -7
  51. package/dist/api-CBDGZiLC.d.ts.map +0 -1
  52. package/dist/client-B6s-lTFe.d.ts.map +0 -1
  53. package/dist/route-Bp6eByhz.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-tJHqcNSw.js","names":["stores: FetcherStore[]","clientInitialData: Map<string, unknown> | undefined","stores"],"sources":["../src/util/ssr.ts"],"sourcesContent":["import { allTasks } from \"nanostores\";\nimport type { FetcherStore } from \"@nanostores/query\";\n\nlet stores: FetcherStore[] = [];\n\nexport const SSR_ENABLED = false;\n\nexport function getStores() {\n return stores;\n}\n\nexport function addStore(store: FetcherStore) {\n stores.push(store);\n}\n\nexport function cleanStores() {\n stores = [];\n}\n\n// Client side\ndeclare global {\n interface Window {\n __FRAGNO_INITIAL_DATA__?: [string, unknown][];\n }\n}\n\nlet clientInitialData: Map<string, unknown> | undefined;\n\nexport function hydrateFromWindow() {\n if (typeof window !== \"undefined\" && window.__FRAGNO_INITIAL_DATA__) {\n clientInitialData = new Map(window.__FRAGNO_INITIAL_DATA__);\n delete window.__FRAGNO_INITIAL_DATA__;\n console.warn(\"hydrateFromWindow\", {\n clientInitialData: Array.from(clientInitialData.entries()),\n });\n }\n}\n\nexport function getInitialData(key: string): unknown | undefined {\n if (clientInitialData?.has(key)) {\n const data = clientInitialData.get(key);\n clientInitialData.delete(key);\n return data;\n }\n return undefined;\n}\n\nfunction listenToStores(): void {\n for (const store of getStores()) {\n // By calling `listen`, we trigger the fetcher function of the store.\n // This will start the data fetching process on the server.\n // We don't need to do anything with the return value of `listen`, as we\n // are only interested in starting the data fetching.\n store.listen(() => {});\n }\n}\n\n// Server side\nexport async function getFinalStoreValues(): Promise<Map<string, unknown>> {\n listenToStores();\n await allTasks();\n\n const stores = getStores();\n const storesInitialValue = new Map<string, unknown>();\n\n for (const store of stores) {\n const value = store.get();\n if (!value || !store.key || value.loading) {\n continue;\n }\n storesInitialValue.set(store.key, value.data);\n }\n\n return storesInitialValue;\n}\n"],"mappings":";;;AAGA,IAAIA,SAAyB,EAAE;AAE/B,MAAa,cAAc;AAE3B,SAAgB,YAAY;AAC1B,QAAO;;AAGT,SAAgB,SAAS,OAAqB;AAC5C,QAAO,KAAK,MAAM;;AAGpB,SAAgB,cAAc;AAC5B,UAAS,EAAE;;AAUb,IAAIC;AAEJ,SAAgB,oBAAoB;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,yBAAyB;AACnE,sBAAoB,IAAI,IAAI,OAAO,wBAAwB;AAC3D,SAAO,OAAO;AACd,UAAQ,KAAK,qBAAqB,EAChC,mBAAmB,MAAM,KAAK,kBAAkB,SAAS,CAAC,EAC3D,CAAC;;;AAIN,SAAgB,eAAe,KAAkC;AAC/D,KAAI,mBAAmB,IAAI,IAAI,EAAE;EAC/B,MAAM,OAAO,kBAAkB,IAAI,IAAI;AACvC,oBAAkB,OAAO,IAAI;AAC7B,SAAO;;;AAKX,SAAS,iBAAuB;AAC9B,MAAK,MAAM,SAAS,WAAW,CAK7B,OAAM,aAAa,GAAG;;AAK1B,eAAsB,sBAAqD;AACzE,iBAAgB;AAChB,OAAM,UAAU;CAEhB,MAAMC,WAAS,WAAW;CAC1B,MAAM,qCAAqB,IAAI,KAAsB;AAErD,MAAK,MAAM,SAASA,UAAQ;EAC1B,MAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,CAAC,SAAS,CAAC,MAAM,OAAO,MAAM,QAChC;AAEF,qBAAmB,IAAI,MAAM,KAAK,MAAM,KAAK;;AAG/C,QAAO"}
1
+ {"version":3,"file":"ssr-BAhbA_3q.js","names":["stores: FetcherStore[]","clientInitialData: Map<string, unknown> | undefined","stores"],"sources":["../src/util/ssr.ts"],"sourcesContent":["import { allTasks } from \"nanostores\";\nimport type { FetcherStore } from \"@nanostores/query\";\n\nlet stores: FetcherStore[] = [];\n\nexport const SSR_ENABLED = false;\n\nexport function getStores() {\n return stores;\n}\n\nexport function addStore(store: FetcherStore) {\n stores.push(store);\n}\n\nexport function cleanStores() {\n stores = [];\n}\n\n// Client side\ndeclare global {\n interface Window {\n __FRAGNO_INITIAL_DATA__?: [string, unknown][];\n }\n}\n\nlet clientInitialData: Map<string, unknown> | undefined;\n\nexport function hydrateFromWindow() {\n if (typeof window !== \"undefined\" && window.__FRAGNO_INITIAL_DATA__) {\n clientInitialData = new Map(window.__FRAGNO_INITIAL_DATA__);\n delete window.__FRAGNO_INITIAL_DATA__;\n console.warn(\"hydrateFromWindow\", {\n clientInitialData: Array.from(clientInitialData.entries()),\n });\n }\n}\n\nexport function getInitialData(key: string): unknown | undefined {\n if (clientInitialData?.has(key)) {\n const data = clientInitialData.get(key);\n clientInitialData.delete(key);\n return data;\n }\n return undefined;\n}\n\nfunction listenToStores(): void {\n for (const store of getStores()) {\n // By calling `listen`, we trigger the fetcher function of the store.\n // This will start the data fetching process on the server.\n // We don't need to do anything with the return value of `listen`, as we\n // are only interested in starting the data fetching.\n store.listen(() => {});\n }\n}\n\n// Server side\nexport async function getFinalStoreValues(): Promise<Map<string, unknown>> {\n listenToStores();\n await allTasks();\n\n const stores = getStores();\n const storesInitialValue = new Map<string, unknown>();\n\n for (const store of stores) {\n const value = store.get();\n if (!value || !store.key || value.loading) {\n continue;\n }\n storesInitialValue.set(store.key, value.data);\n }\n\n return storesInitialValue;\n}\n"],"mappings":";;;AAGA,IAAIA,SAAyB,EAAE;AAE/B,MAAa,cAAc;AAE3B,SAAgB,YAAY;AAC1B,QAAO;;AAGT,SAAgB,SAAS,OAAqB;AAC5C,QAAO,KAAK,MAAM;;AAGpB,SAAgB,cAAc;AAC5B,UAAS,EAAE;;AAUb,IAAIC;AAEJ,SAAgB,oBAAoB;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,yBAAyB;AACnE,sBAAoB,IAAI,IAAI,OAAO,wBAAwB;AAC3D,SAAO,OAAO;AACd,UAAQ,KAAK,qBAAqB,EAChC,mBAAmB,MAAM,KAAK,kBAAkB,SAAS,CAAC,EAC3D,CAAC;;;AAIN,SAAgB,eAAe,KAAkC;AAC/D,KAAI,mBAAmB,IAAI,IAAI,EAAE;EAC/B,MAAM,OAAO,kBAAkB,IAAI,IAAI;AACvC,oBAAkB,OAAO,IAAI;AAC7B,SAAO;;;AAKX,SAAS,iBAAuB;AAC9B,MAAK,MAAM,SAAS,WAAW,CAK7B,OAAM,aAAa,GAAG;;AAK1B,eAAsB,sBAAqD;AACzE,iBAAgB;AAChB,OAAM,UAAU;CAEhB,MAAMC,WAAS,WAAW;CAC1B,MAAM,qCAAqB,IAAI,KAAsB;AAErD,MAAK,MAAM,SAASA,UAAQ;EAC1B,MAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,CAAC,SAAS,CAAC,MAAM,OAAO,MAAM,QAChC;AAEF,qBAAmB,IAAI,MAAM,KAAK,MAAM,KAAK;;AAG/C,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/core",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "exports": {
5
5
  ".": {
6
6
  "bun": "./src/mod.ts",
@@ -21,7 +21,7 @@
21
21
  "default": "./dist/client/client.js"
22
22
  },
23
23
  "./react": {
24
- "bun": "./src/client/react.tsx",
24
+ "bun": "./src/client/react.ts",
25
25
  "development": "./src/client/react.ts",
26
26
  "types": "./dist/client/react.d.ts",
27
27
  "default": "./dist/client/react.js"
@@ -44,6 +44,12 @@
44
44
  "types": "./dist/client/client.svelte.d.ts",
45
45
  "default": "./dist/client/client.svelte.js"
46
46
  },
47
+ "./solid": {
48
+ "bun": "./src/client/solid.ts",
49
+ "development": "./src/client/solid.ts",
50
+ "types": "./dist/client/solid.d.ts",
51
+ "default": "./dist/client/solid.js"
52
+ },
47
53
  "./react-ssr": {
48
54
  "bun": "./src/integrations/react-ssr.ts",
49
55
  "development": "./src/integrations/react-ssr.ts",
@@ -58,17 +64,21 @@
58
64
  "scripts": {
59
65
  "build": "tsdown",
60
66
  "build:watch": "tsdown --watch",
61
- "types:check": "tsc --noEmit"
67
+ "types:check": "tsc --noEmit",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest --watch"
62
70
  },
63
71
  "type": "module",
64
72
  "dependencies": {
65
73
  "@nanostores/query": "^0.3.4",
74
+ "@nanostores/solid": "^1.1.1",
66
75
  "@standard-schema/spec": "^1.0.0",
67
76
  "nanostores": "^1.0.1",
68
77
  "rou3": "^0.7.3"
69
78
  },
70
79
  "devDependencies": {
71
80
  "@fragno-private/typescript-config": "0.0.1",
81
+ "@fragno-private/vitest-config": "0.0.0",
72
82
  "@sveltejs/vite-plugin-svelte": "^6.2.0",
73
83
  "@testing-library/react": "^16.3.0",
74
84
  "@testing-library/svelte": "^5.2.8",
@@ -76,17 +86,21 @@
76
86
  "@types/node": "^22",
77
87
  "@types/react": "^19.0.0",
78
88
  "@types/react-dom": "^19.0.0",
79
- "happy-dom": "^18.0.1",
89
+ "@vitest/coverage-istanbul": "^3.2.4",
90
+ "happy-dom": "^20.0.0",
80
91
  "react": "^19.0.0",
81
92
  "react-dom": "^19.0.0",
82
93
  "svelte": "^5.1.0",
94
+ "solid-js": "^1.9.3",
95
+ "vitest": "^3.2.4",
83
96
  "vue": "^3",
84
97
  "zod": "^4.0.5"
85
98
  },
86
99
  "peerDependencies": {
87
100
  "react": "^19.0.0",
88
101
  "vue": "^3",
89
- "svelte": "^5"
102
+ "svelte": "^5",
103
+ "solid-js": "^1.0.0"
90
104
  },
91
105
  "peerDependenciesMeta": {
92
106
  "react": {
@@ -97,6 +111,9 @@
97
111
  },
98
112
  "svelte": {
99
113
  "optional": true
114
+ },
115
+ "solid-js": {
116
+ "optional": true
100
117
  }
101
118
  },
102
119
  "repository": {
@@ -7,7 +7,6 @@ import { RequestInputContext } from "./request-input-context";
7
7
  import type { ExtractPathParams } from "./internal/path";
8
8
  import { RequestOutputContext } from "./request-output-context";
9
9
  import {
10
- type EmptyObject,
11
10
  type AnyFragnoRouteConfig,
12
11
  type AnyRouteOrFactory,
13
12
  type FlattenRouteFactories,
@@ -37,6 +36,16 @@ type ReactRouterHandlers = {
37
36
  action: (args: { request: Request }) => Promise<Response>;
38
37
  };
39
38
 
39
+ type SolidStartHandlers = {
40
+ GET: (args: { request: Request }) => Promise<Response>;
41
+ POST: (args: { request: Request }) => Promise<Response>;
42
+ PUT: (args: { request: Request }) => Promise<Response>;
43
+ DELETE: (args: { request: Request }) => Promise<Response>;
44
+ PATCH: (args: { request: Request }) => Promise<Response>;
45
+ HEAD: (args: { request: Request }) => Promise<Response>;
46
+ OPTIONS: (args: { request: Request }) => Promise<Response>;
47
+ };
48
+
40
49
  type StandardHandlers = {
41
50
  GET: (req: Request) => Promise<Response>;
42
51
  POST: (req: Request) => Promise<Response>;
@@ -52,13 +61,14 @@ type HandlersByFramework = {
52
61
  "react-router": ReactRouterHandlers;
53
62
  "next-js": StandardHandlers;
54
63
  "svelte-kit": StandardHandlers;
64
+ "solid-start": SolidStartHandlers;
55
65
  };
56
66
 
57
67
  type FullstackFrameworks = keyof HandlersByFramework;
58
68
 
59
69
  export interface FragnoInstantiatedFragment<
60
70
  TRoutes extends readonly AnyFragnoRouteConfig[] = [],
61
- TDeps = EmptyObject,
71
+ TDeps = {},
62
72
  TServices extends Record<string, unknown> = Record<string, unknown>,
63
73
  > {
64
74
  config: FragnoFragmentSharedConfig<TRoutes>;
@@ -90,21 +100,13 @@ export type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<
90
100
  readonly AnyFragnoRouteConfig[]
91
101
  >;
92
102
 
93
- interface FragmentDefinition<
94
- TConfig,
95
- TDeps = EmptyObject,
96
- TServices extends Record<string, unknown> = EmptyObject,
97
- > {
103
+ interface FragmentDefinition<TConfig, TDeps = {}, TServices extends Record<string, unknown> = {}> {
98
104
  name: string;
99
105
  dependencies?: (config: TConfig) => TDeps;
100
106
  services?: (config: TConfig, deps: TDeps) => TServices;
101
107
  }
102
108
 
103
- export class FragmentBuilder<
104
- TConfig,
105
- TDeps = EmptyObject,
106
- TServices extends Record<string, unknown> = EmptyObject,
107
- > {
109
+ export class FragmentBuilder<TConfig, TDeps = {}, TServices extends Record<string, unknown> = {}> {
108
110
  #definition: FragmentDefinition<TConfig, TDeps, TServices>;
109
111
 
110
112
  constructor(definition: FragmentDefinition<TConfig, TDeps, TServices>) {
@@ -134,7 +136,6 @@ export class FragmentBuilder<
134
136
  }
135
137
  }
136
138
 
137
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
138
139
  export function defineFragment<TConfig = {}>(name: string): FragmentBuilder<TConfig> {
139
140
  return new FragmentBuilder({
140
141
  name,
@@ -210,6 +211,14 @@ export function createFragment<
210
211
  },
211
212
  handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {
212
213
  const handler = fragment.handler;
214
+
215
+ // LLMs hallucinate these values sometimes, solution isn't obvious so we throw this error
216
+ // @ts-expect-error TS2367
217
+ if (framework === "h3" || framework === "nuxt") {
218
+ throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:
219
+ import { fromWebHandler } from "h3";
220
+ export default fromWebHandler(myFragment().handler);`);
221
+ }
213
222
  const allHandlers = {
214
223
  astro: { ALL: handler },
215
224
  "react-router": {
@@ -234,6 +243,15 @@ export function createFragment<
234
243
  HEAD: handler,
235
244
  OPTIONS: handler,
236
245
  },
246
+ "solid-start": {
247
+ GET: ({ request }: { request: Request }) => handler(request),
248
+ POST: ({ request }: { request: Request }) => handler(request),
249
+ PUT: ({ request }: { request: Request }) => handler(request),
250
+ DELETE: ({ request }: { request: Request }) => handler(request),
251
+ PATCH: ({ request }: { request: Request }) => handler(request),
252
+ HEAD: ({ request }: { request: Request }) => handler(request),
253
+ OPTIONS: ({ request }: { request: Request }) => handler(request),
254
+ },
237
255
  } satisfies HandlersByFramework;
238
256
 
239
257
  return allHandlers[framework];
@@ -45,7 +45,7 @@ describe("Request Middleware", () => {
45
45
  expect(unauthorizedRes.status).toBe(401);
46
46
  const unauthorizedBody = await unauthorizedRes.json();
47
47
  expect(unauthorizedBody).toEqual({
48
- error: "Unauthorized",
48
+ message: "Unauthorized",
49
49
  code: "UNAUTHORIZED",
50
50
  });
51
51
 
@@ -134,7 +134,7 @@ describe("Request Middleware", () => {
134
134
  expect(res.status).toBe(403);
135
135
 
136
136
  expect(await res.json()).toEqual({
137
- error: "Creating users has been disabled.",
137
+ message: "Creating users has been disabled.",
138
138
  code: "CREATE_USERS_DISABLED",
139
139
  });
140
140
 
@@ -439,7 +439,7 @@ describe("Request Middleware", () => {
439
439
 
440
440
  const body = await res.json();
441
441
  expect(body).toEqual({
442
- error: "Request validation failed in middleware",
442
+ message: "Request validation failed in middleware",
443
443
  code: "MIDDLEWARE_VALIDATION_ERROR",
444
444
  });
445
445
  });
@@ -51,16 +51,16 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
51
51
  headers?: HeadersInit,
52
52
  ): Response {
53
53
  if (typeof initOrStatus === "undefined") {
54
- return Response.json({ error: message, code }, { status: 500, headers });
54
+ return Response.json({ message: message, code }, { status: 500, headers });
55
55
  }
56
56
 
57
57
  if (typeof initOrStatus === "number") {
58
- return Response.json({ error: message, code }, { status: initOrStatus, headers });
58
+ return Response.json({ message: message, code }, { status: initOrStatus, headers });
59
59
  }
60
60
 
61
61
  const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
62
62
  return Response.json(
63
- { error: message, code },
63
+ { message: message, code },
64
64
  { status: initOrStatus.status, headers: mergedHeaders },
65
65
  );
66
66
  }
package/src/api/route.ts CHANGED
@@ -125,14 +125,7 @@ export function defineRoute<
125
125
  return config;
126
126
  }
127
127
 
128
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
129
- export type EmptyObject = {}; //Record<string, never>;
130
-
131
- export function defineRoutes<
132
- TConfig = EmptyObject,
133
- TDeps = EmptyObject,
134
- TServices = EmptyObject,
135
- >() {
128
+ export function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>() {
136
129
  return {
137
130
  create: <
138
131
  const TRoutes extends readonly FragnoRouteConfig<
@@ -1,6 +1,7 @@
1
1
  import { test, expect, describe } from "vitest";
2
- import { FragnoClientApiError } from "./client-error";
2
+ import { FragnoClientApiError, FragnoClientUnknownApiError } from "./client-error";
3
3
  import { FragnoApiError } from "../api/error";
4
+ import { RequestOutputContext } from "../api/request-output-context";
4
5
 
5
6
  describe("Error Conversion", () => {
6
7
  test("should convert API error to client error", async () => {
@@ -12,4 +13,19 @@ describe("Error Conversion", () => {
12
13
  expect(clientError.code).toBe("API_ERROR");
13
14
  expect(clientError.status).toBe(500);
14
15
  });
16
+
17
+ test("error() should never result in an unknown error", async () => {
18
+ const ctx = new RequestOutputContext();
19
+ const response = ctx.error({ message: "test", code: "MY_TEST_ERROR" }, { status: 400 });
20
+
21
+ expect(response).toBeInstanceOf(Response);
22
+ expect(response.status).toBe(400);
23
+
24
+ const clientError = await FragnoClientApiError.fromResponse(response);
25
+ expect(clientError).toBeInstanceOf(FragnoClientApiError);
26
+ expect(clientError).not.toBeInstanceOf(FragnoClientUnknownApiError);
27
+ expect(clientError.message).toBe("test");
28
+ expect(clientError.code).toBe("MY_TEST_ERROR");
29
+ expect(clientError.status).toBe(400);
30
+ });
15
31
  });