@fragno-dev/core 0.2.0 → 0.2.2

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 (146) hide show
  1. package/.turbo/turbo-build.log +72 -62
  2. package/CHANGELOG.md +28 -0
  3. package/dist/api/api.d.ts +3 -2
  4. package/dist/api/api.d.ts.map +1 -1
  5. package/dist/api/api.js +2 -1
  6. package/dist/api/api.js.map +1 -1
  7. package/dist/api/bind-services.d.ts +0 -1
  8. package/dist/api/bind-services.d.ts.map +1 -1
  9. package/dist/api/bind-services.js.map +1 -1
  10. package/dist/api/error.d.ts.map +1 -1
  11. package/dist/api/error.js.map +1 -1
  12. package/dist/api/fragment-definition-builder.d.ts +26 -44
  13. package/dist/api/fragment-definition-builder.d.ts.map +1 -1
  14. package/dist/api/fragment-definition-builder.js +15 -22
  15. package/dist/api/fragment-definition-builder.js.map +1 -1
  16. package/dist/api/fragment-instantiator.d.ts +51 -37
  17. package/dist/api/fragment-instantiator.d.ts.map +1 -1
  18. package/dist/api/fragment-instantiator.js +74 -69
  19. package/dist/api/fragment-instantiator.js.map +1 -1
  20. package/dist/api/request-context-storage.d.ts +4 -0
  21. package/dist/api/request-context-storage.d.ts.map +1 -1
  22. package/dist/api/request-context-storage.js +6 -0
  23. package/dist/api/request-context-storage.js.map +1 -1
  24. package/dist/api/request-input-context.d.ts.map +1 -1
  25. package/dist/api/request-input-context.js.map +1 -1
  26. package/dist/api/request-middleware.d.ts +1 -1
  27. package/dist/api/request-middleware.d.ts.map +1 -1
  28. package/dist/api/request-middleware.js.map +1 -1
  29. package/dist/api/request-output-context.d.ts +1 -1
  30. package/dist/api/request-output-context.d.ts.map +1 -1
  31. package/dist/api/request-output-context.js.map +1 -1
  32. package/dist/api/route-caller.d.ts +30 -0
  33. package/dist/api/route-caller.d.ts.map +1 -0
  34. package/dist/api/route-caller.js +63 -0
  35. package/dist/api/route-caller.js.map +1 -0
  36. package/dist/api/route-handler-input-options.d.ts.map +1 -1
  37. package/dist/api/route.d.ts +1 -1
  38. package/dist/api/route.d.ts.map +1 -1
  39. package/dist/api/route.js.map +1 -1
  40. package/dist/api/shared-types.d.ts.map +1 -1
  41. package/dist/client/client-error.d.ts.map +1 -1
  42. package/dist/client/client-error.js.map +1 -1
  43. package/dist/client/client.d.ts +91 -52
  44. package/dist/client/client.d.ts.map +1 -1
  45. package/dist/client/client.js +25 -9
  46. package/dist/client/client.js.map +1 -1
  47. package/dist/client/client.svelte.d.ts +6 -5
  48. package/dist/client/client.svelte.d.ts.map +1 -1
  49. package/dist/client/client.svelte.js +10 -2
  50. package/dist/client/client.svelte.js.map +1 -1
  51. package/dist/client/internal/ndjson-streaming.js.map +1 -1
  52. package/dist/client/react.d.ts +5 -4
  53. package/dist/client/react.d.ts.map +1 -1
  54. package/dist/client/react.js +104 -12
  55. package/dist/client/react.js.map +1 -1
  56. package/dist/client/solid.d.ts +7 -5
  57. package/dist/client/solid.d.ts.map +1 -1
  58. package/dist/client/solid.js +23 -9
  59. package/dist/client/solid.js.map +1 -1
  60. package/dist/client/vanilla.d.ts +16 -4
  61. package/dist/client/vanilla.d.ts.map +1 -1
  62. package/dist/client/vanilla.js +21 -1
  63. package/dist/client/vanilla.js.map +1 -1
  64. package/dist/client/vue.d.ts +7 -5
  65. package/dist/client/vue.d.ts.map +1 -1
  66. package/dist/client/vue.js +18 -10
  67. package/dist/client/vue.js.map +1 -1
  68. package/dist/id.d.ts +2 -0
  69. package/dist/id.js +3 -0
  70. package/dist/internal/cuid.d.ts +16 -0
  71. package/dist/internal/cuid.d.ts.map +1 -0
  72. package/dist/internal/cuid.js +82 -0
  73. package/dist/internal/cuid.js.map +1 -0
  74. package/dist/mod-client.d.ts +5 -4
  75. package/dist/mod-client.d.ts.map +1 -1
  76. package/dist/mod-client.js +7 -5
  77. package/dist/mod-client.js.map +1 -1
  78. package/dist/mod.d.ts +6 -5
  79. package/dist/mod.js +2 -1
  80. package/dist/runtime.js +1 -1
  81. package/dist/runtime.js.map +1 -1
  82. package/dist/test/test.d.ts +6 -6
  83. package/dist/test/test.d.ts.map +1 -1
  84. package/dist/test/test.js.map +1 -1
  85. package/dist/util/ssr.js.map +1 -1
  86. package/package.json +24 -40
  87. package/src/api/api.test.ts +3 -1
  88. package/src/api/api.ts +6 -0
  89. package/src/api/bind-services.ts +0 -5
  90. package/src/api/error.ts +1 -0
  91. package/src/api/fragment-definition-builder.extend.test.ts +2 -1
  92. package/src/api/fragment-definition-builder.test.ts +2 -1
  93. package/src/api/fragment-definition-builder.ts +49 -124
  94. package/src/api/fragment-instantiator.test.ts +92 -233
  95. package/src/api/fragment-instantiator.ts +228 -196
  96. package/src/api/fragment-services.test.ts +1 -0
  97. package/src/api/internal/path-runtime.test.ts +1 -0
  98. package/src/api/internal/path-type.test.ts +3 -1
  99. package/src/api/internal/route.test.ts +1 -0
  100. package/src/api/request-context-storage.ts +7 -0
  101. package/src/api/request-input-context.test.ts +4 -2
  102. package/src/api/request-input-context.ts +2 -1
  103. package/src/api/request-middleware.test.ts +9 -14
  104. package/src/api/request-middleware.ts +3 -2
  105. package/src/api/request-output-context.test.ts +3 -1
  106. package/src/api/request-output-context.ts +2 -1
  107. package/src/api/route-caller.test.ts +195 -0
  108. package/src/api/route-caller.ts +167 -0
  109. package/src/api/route-handler-input-options.ts +2 -1
  110. package/src/api/route.test.ts +4 -2
  111. package/src/api/route.ts +2 -1
  112. package/src/api/shared-types.ts +2 -1
  113. package/src/client/client-builder.test.ts +4 -2
  114. package/src/client/client-error.test.ts +2 -1
  115. package/src/client/client-error.ts +1 -1
  116. package/src/client/client-types.test.ts +19 -5
  117. package/src/client/client.ssr.test.ts +6 -4
  118. package/src/client/client.svelte.test.ts +18 -9
  119. package/src/client/client.svelte.ts +38 -13
  120. package/src/client/client.test.ts +49 -10
  121. package/src/client/client.ts +291 -141
  122. package/src/client/internal/ndjson-streaming.test.ts +6 -3
  123. package/src/client/internal/ndjson-streaming.ts +1 -0
  124. package/src/client/react.test.ts +176 -6
  125. package/src/client/react.ts +226 -31
  126. package/src/client/solid.test.ts +29 -5
  127. package/src/client/solid.ts +60 -22
  128. package/src/client/vanilla.test.ts +148 -6
  129. package/src/client/vanilla.ts +63 -9
  130. package/src/client/vue.test.ts +223 -84
  131. package/src/client/vue.ts +57 -30
  132. package/src/id.ts +1 -0
  133. package/src/internal/cuid.test.ts +164 -0
  134. package/src/internal/cuid.ts +133 -0
  135. package/src/mod-client.ts +4 -2
  136. package/src/mod.ts +3 -2
  137. package/src/runtime.ts +1 -1
  138. package/src/test/test.test.ts +4 -2
  139. package/src/test/test.ts +7 -9
  140. package/src/util/async.test.ts +1 -0
  141. package/src/util/content-type.test.ts +1 -0
  142. package/src/util/nanostores.test.ts +3 -1
  143. package/src/util/ssr.ts +1 -0
  144. package/tsconfig.json +1 -1
  145. package/tsdown.config.ts +1 -0
  146. package/vitest.config.ts +2 -1
@@ -7,12 +7,14 @@
7
7
  */
8
8
 
9
9
  import { describe, expect, test } from "vitest";
10
- import { type FragnoPublicClientConfig } from "./client";
11
- import { createClientBuilder } from "./client";
12
- import { defineRoute } from "../api/route";
13
- import { defineFragment } from "../api/fragment-definition-builder";
10
+
14
11
  import { z } from "zod";
12
+
13
+ import { defineFragment } from "../api/fragment-definition-builder";
14
+ import { defineRoute } from "../api/route";
15
15
  import { createAsyncIteratorFromCallback, waitForAsyncIterator } from "../util/async";
16
+ import { type FragnoPublicClientConfig } from "./client";
17
+ import { createClientBuilder } from "./client";
16
18
 
17
19
  describe("server side rendering", () => {
18
20
  const testFragmentDefinition = defineFragment("test-fragment").build();
@@ -1,16 +1,19 @@
1
1
  import { test, expect, describe, vi, beforeEach, afterEach, assert } from "vitest";
2
- import { type FragnoPublicClientConfig } from "./client";
3
- import { createClientBuilder } from "./client";
2
+
3
+ import { atom, computed } from "nanostores";
4
+ import { writable, readable, get, derived } from "svelte/store";
5
+ import { z } from "zod";
6
+
4
7
  import { render } from "@testing-library/svelte";
5
- import { defineRoute } from "../api/route";
8
+
6
9
  import { defineFragment } from "../api/fragment-definition-builder";
7
- import { z } from "zod";
8
- import { readableToAtom, useFragno } from "./client.svelte";
9
- import { writable, readable, get, derived } from "svelte/store";
10
+ import { RequestOutputContext } from "../api/request-output-context";
11
+ import { defineRoute } from "../api/route";
12
+ import { type FragnoPublicClientConfig } from "./client";
13
+ import { createClientBuilder } from "./client";
10
14
  import { FragnoClientUnknownApiError } from "./client-error";
15
+ import { readableToAtom, useFragno } from "./client.svelte";
11
16
  import TestComponent from "./component.test.svelte";
12
- import { atom, computed } from "nanostores";
13
- import { RequestOutputContext } from "../api/request-output-context";
14
17
 
15
18
  function renderHook(
16
19
  clientObj: Record<string, unknown>,
@@ -766,7 +769,13 @@ describe("useFragno", () => {
766
769
  expect((get(hook.data) as TestData | undefined)?.id).toBe(456);
767
770
  });
768
771
 
769
- expect(fetch).toHaveBeenCalledTimes(2);
772
+ const requestedIds = vi
773
+ .mocked(global.fetch)
774
+ .mock.calls.map(([input]) => String(input).match(/\/users\/([^/]+)/)?.[1])
775
+ .filter((id): id is string => id !== undefined);
776
+
777
+ expect(requestedIds).toContain("123");
778
+ expect(requestedIds).toContain("456");
770
779
  });
771
780
  });
772
781
 
@@ -1,6 +1,12 @@
1
- import type { StandardSchemaV1 } from "@standard-schema/spec";
2
1
  import { atom, type ReadableAtom } from "nanostores";
2
+ import { onDestroy } from "svelte";
3
+ import { writable, type Readable, get } from "svelte/store";
4
+
5
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
6
+
3
7
  import type { NonGetHTTPMethod } from "../api/api";
8
+ import type { MaybeExtractPathParamsOrWiden, QueryParamsHint } from "../api/internal/path";
9
+ import type { InferOr } from "../util/types-util";
4
10
  import {
5
11
  isGetHook,
6
12
  isMutatorHook,
@@ -8,13 +14,10 @@ import {
8
14
  type FragnoClientHookData,
9
15
  type FragnoClientMutatorData,
10
16
  type FragnoStoreData,
17
+ type FragnoStoreFactoryData,
18
+ type FragnoStoreObjectData,
11
19
  } from "./client";
12
20
  import type { FragnoClientError } from "./client-error";
13
- import type { InferOr } from "../util/types-util";
14
- import type { MaybeExtractPathParamsOrWiden, QueryParamsHint } from "../api/internal/path";
15
-
16
- import { writable, type Readable, get } from "svelte/store";
17
- import { onDestroy } from "svelte";
18
21
 
19
22
  export type FragnoSvelteHook<
20
23
  _TMethod extends "GET",
@@ -223,10 +226,30 @@ function createSvelteMutator<
223
226
  };
224
227
  }
225
228
 
226
- export function createSvelteStore<T extends object>(hook: FragnoStoreData<T>): T {
227
- // Since nanostores already implement Svelte's store contract,
228
- // we can return the store object directly for use with $ syntax
229
- return hook.obj;
229
+ export type FragnoSvelteStore<T extends object, TArgs extends unknown[] = []> = TArgs extends []
230
+ ? T
231
+ : (...args: TArgs) => T;
232
+
233
+ export function createSvelteStore<T extends object, TArgs extends unknown[]>(
234
+ hook: FragnoStoreData<T, TArgs>,
235
+ ): FragnoSvelteStore<T, TArgs> {
236
+ if ("obj" in hook) {
237
+ // Since nanostores already implement Svelte's store contract,
238
+ // we can return the store object directly for use with $ syntax
239
+ return hook.obj as FragnoSvelteStore<T, TArgs>;
240
+ }
241
+
242
+ return ((...args: TArgs) => {
243
+ const value = hook.factory(...args);
244
+ const disposer = value[Symbol.dispose as keyof typeof value];
245
+ if (typeof disposer === "function") {
246
+ onDestroy(() => {
247
+ disposer.call(value);
248
+ });
249
+ }
250
+
251
+ return value;
252
+ }) as FragnoSvelteStore<T, TArgs>;
230
253
  }
231
254
 
232
255
  export function useFragno<T extends Record<string, unknown>>(
@@ -249,9 +272,11 @@ export function useFragno<T extends Record<string, unknown>>(
249
272
  infer TQueryParameters
250
273
  >
251
274
  ? FragnoSvelteMutator<M, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>
252
- : T[K] extends FragnoStoreData<infer TStoreObj>
253
- ? TStoreObj
254
- : T[K];
275
+ : T[K] extends FragnoStoreObjectData<infer TStoreObj>
276
+ ? FragnoSvelteStore<TStoreObj, []>
277
+ : T[K] extends FragnoStoreFactoryData<infer TStoreObj, infer TStoreArgs>
278
+ ? FragnoSvelteStore<TStoreObj, TStoreArgs>
279
+ : T[K];
255
280
  } {
256
281
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
257
282
  const result = {} as any;
@@ -1,14 +1,16 @@
1
1
  import { afterEach, assert, beforeEach, describe, expect, test, vi } from "vitest";
2
+
3
+ import { atom, computed, effect } from "nanostores";
2
4
  import { z } from "zod";
5
+
6
+ import { defineFragment } from "../api/fragment-definition-builder";
7
+ import { RequestOutputContext } from "../api/request-output-context";
3
8
  import { defineRoute } from "../api/route";
4
- import { buildUrl, createClientBuilder, getCacheKey, isGetHook, isMutatorHook } from "./client";
5
- import { useFragno } from "./vanilla";
6
9
  import { createAsyncIteratorFromCallback, waitForAsyncIterator } from "../util/async";
10
+ import { buildUrl, createClientBuilder, getCacheKey, isGetHook, isMutatorHook } from "./client";
7
11
  import type { FragnoPublicClientConfig } from "./client";
8
- import { atom, computed, effect } from "nanostores";
9
- import { defineFragment } from "../api/fragment-definition-builder";
10
- import { RequestOutputContext } from "../api/request-output-context";
11
12
  import { FragnoClientUnknownApiError } from "./client-error";
13
+ import { useFragno } from "./vanilla";
12
14
 
13
15
  // Mock fetch globally
14
16
  global.fetch = vi.fn();
@@ -1325,6 +1327,32 @@ describe("createMutator", () => {
1325
1327
  expect(result).toBeUndefined();
1326
1328
  });
1327
1329
 
1330
+ test("body is optional when inputSchema allows undefined", async () => {
1331
+ const testFragment = defineFragment("test-fragment").build();
1332
+ const testRoutes = [
1333
+ defineRoute({
1334
+ method: "POST",
1335
+ path: "/sign-out",
1336
+ inputSchema: z.object({ sessionId: z.string().optional() }).optional(),
1337
+ outputSchema: z.object({ success: z.boolean() }),
1338
+ handler: async (_ctx, { empty }) => empty(),
1339
+ }),
1340
+ ] as const;
1341
+
1342
+ vi.mocked(global.fetch).mockImplementation(async () => {
1343
+ return new Response(null, { status: 204 });
1344
+ });
1345
+
1346
+ const cb = createClientBuilder(testFragment, clientConfig, testRoutes);
1347
+ const signOut = cb.createMutator("POST", "/sign-out");
1348
+
1349
+ const result = await signOut.mutateQuery({});
1350
+ expect(result).toBeUndefined();
1351
+
1352
+ const storeResult = await signOut.mutatorStore.mutate({});
1353
+ expect(storeResult).toBeUndefined();
1354
+ });
1355
+
1328
1356
  test("should send octet-stream body without wrapping", async () => {
1329
1357
  const testFragment = defineFragment("test-fragment").build();
1330
1358
  const testRoutes = [
@@ -2208,7 +2236,7 @@ describe("Custom Fetcher Configuration", () => {
2208
2236
  expect(defaultOptions).toBeUndefined();
2209
2237
  });
2210
2238
 
2211
- test("getFetcher returns default fetch and options", () => {
2239
+ test("getFetcher returns a bound default fetch and options", async () => {
2212
2240
  const client = createClientBuilder(
2213
2241
  testFragment,
2214
2242
  {
@@ -2218,9 +2246,20 @@ describe("Custom Fetcher Configuration", () => {
2218
2246
  testRoutes,
2219
2247
  );
2220
2248
 
2221
- const { fetcher, defaultOptions } = client.getFetcher();
2222
- expect(fetcher).toBe(fetch);
2223
- expect(defaultOptions).toBeDefined();
2224
- expect(defaultOptions?.credentials).toBe("include");
2249
+ const originalFetch = globalThis.fetch;
2250
+ const fetchSpy = vi.fn<typeof fetch>().mockResolvedValue(new Response(null, { status: 204 }));
2251
+ globalThis.fetch = fetchSpy;
2252
+
2253
+ try {
2254
+ const { fetcher, defaultOptions } = client.getFetcher();
2255
+ expect(fetcher).not.toBe(globalThis.fetch);
2256
+ expect(defaultOptions).toBeDefined();
2257
+ expect(defaultOptions?.credentials).toBe("include");
2258
+
2259
+ await fetcher("https://example.com");
2260
+ expect(fetchSpy).toHaveBeenCalledWith("https://example.com");
2261
+ } finally {
2262
+ globalThis.fetch = originalFetch;
2263
+ }
2225
2264
  });
2226
2265
  });