@typokit/client-swr 0.1.4
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.
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
- package/src/env.d.ts +79 -0
- package/src/index.test.ts +56 -0
- package/src/index.ts +239 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RouteContract } from "@typokit/types";
|
|
2
|
+
import type { RouteMap, TypeSafeClient } from "@typokit/client";
|
|
3
|
+
import type { SWRResponse } from "swr";
|
|
4
|
+
import type { SWRMutationResponse } from "swr/mutation";
|
|
5
|
+
/** Build an SWR cache key from route path, params, and query */
|
|
6
|
+
export declare function buildSWRKey(path: string, params?: Record<string, string>, query?: Record<string, unknown>): readonly unknown[];
|
|
7
|
+
/** Extract GET route paths from a RouteMap */
|
|
8
|
+
type GetPaths<TRoutes extends RouteMap> = {
|
|
9
|
+
[P in keyof TRoutes]: "GET" extends keyof TRoutes[P] ? P : never;
|
|
10
|
+
}[keyof TRoutes] & string;
|
|
11
|
+
type MutationMethod = "POST" | "PUT" | "PATCH" | "DELETE";
|
|
12
|
+
/** Extract paths that have a given mutation method */
|
|
13
|
+
type MutationPaths<TRoutes extends RouteMap, M extends MutationMethod> = {
|
|
14
|
+
[P in keyof TRoutes]: M extends keyof TRoutes[P] ? P : never;
|
|
15
|
+
}[keyof TRoutes] & string;
|
|
16
|
+
/** Resolve the RouteContract for a given path and method */
|
|
17
|
+
type ContractFor<TRoutes extends RouteMap, P extends string, M extends string> = P extends keyof TRoutes ? M extends keyof TRoutes[P] ? TRoutes[P][M] extends RouteContract ? TRoutes[P][M] : RouteContract : RouteContract : RouteContract;
|
|
18
|
+
/** Options for useSWRGet: route-specific params/query + SWR config */
|
|
19
|
+
export interface UseSWRGetOptions<C extends RouteContract> {
|
|
20
|
+
params?: C["params"] extends void ? undefined : C["params"] & Record<string, string>;
|
|
21
|
+
query?: C["query"] extends void ? undefined : C["query"];
|
|
22
|
+
revalidateOnFocus?: boolean;
|
|
23
|
+
refreshInterval?: number;
|
|
24
|
+
dedupingInterval?: number;
|
|
25
|
+
suspense?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Variables passed to mutation hooks */
|
|
28
|
+
export interface MutationVariables<C extends RouteContract> {
|
|
29
|
+
params?: C["params"] extends void ? undefined : C["params"] & Record<string, string>;
|
|
30
|
+
body?: C["body"] extends void ? undefined : C["body"];
|
|
31
|
+
}
|
|
32
|
+
/** Mutation lifecycle callbacks */
|
|
33
|
+
interface MutationCallbacks<TData> {
|
|
34
|
+
onSuccess?: (data: TData) => void;
|
|
35
|
+
onError?: (error: Error) => void;
|
|
36
|
+
}
|
|
37
|
+
/** Type-safe SWR hooks generated from a RouteMap */
|
|
38
|
+
export interface SWRHooks<TRoutes extends RouteMap> {
|
|
39
|
+
/** useSWR wrapper for GET routes */
|
|
40
|
+
useGet<P extends GetPaths<TRoutes>>(path: P, options?: UseSWRGetOptions<ContractFor<TRoutes, P, "GET">>): SWRResponse<ContractFor<TRoutes, P, "GET">["response"], Error>;
|
|
41
|
+
/** useSWRMutation wrapper for POST routes */
|
|
42
|
+
usePost<P extends MutationPaths<TRoutes, "POST">>(path: P, options?: MutationCallbacks<ContractFor<TRoutes, P, "POST">["response"]>): SWRMutationResponse<ContractFor<TRoutes, P, "POST">["response"], Error, MutationVariables<ContractFor<TRoutes, P, "POST">>>;
|
|
43
|
+
/** useSWRMutation wrapper for PUT routes */
|
|
44
|
+
usePut<P extends MutationPaths<TRoutes, "PUT">>(path: P, options?: MutationCallbacks<ContractFor<TRoutes, P, "PUT">["response"]>): SWRMutationResponse<ContractFor<TRoutes, P, "PUT">["response"], Error, MutationVariables<ContractFor<TRoutes, P, "PUT">>>;
|
|
45
|
+
/** useSWRMutation wrapper for PATCH routes */
|
|
46
|
+
usePatch<P extends MutationPaths<TRoutes, "PATCH">>(path: P, options?: MutationCallbacks<ContractFor<TRoutes, P, "PATCH">["response"]>): SWRMutationResponse<ContractFor<TRoutes, P, "PATCH">["response"], Error, MutationVariables<ContractFor<TRoutes, P, "PATCH">>>;
|
|
47
|
+
/** useSWRMutation wrapper for DELETE routes */
|
|
48
|
+
useDelete<P extends MutationPaths<TRoutes, "DELETE">>(path: P, options?: MutationCallbacks<ContractFor<TRoutes, P, "DELETE">["response"]>): SWRMutationResponse<ContractFor<TRoutes, P, "DELETE">["response"], Error, MutationVariables<ContractFor<TRoutes, P, "DELETE">>>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create type-safe SWR hooks from a TypoKit client.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const hooks = createSWRHooks<MyRoutes>(client);
|
|
56
|
+
* // In a React component:
|
|
57
|
+
* const { data, isLoading } = hooks.useGet("/users", { query: { page: 1 } });
|
|
58
|
+
* const { trigger: createUser } = hooks.usePost("/users");
|
|
59
|
+
* createUser({ body: { name: "Alice" } });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function createSWRHooks<TRoutes extends RouteMap>(client: TypeSafeClient<TRoutes>): SWRHooks<TRoutes>;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,KAAK,CAAC;AACzD,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAMtB,gEAAgE;AAChE,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,SAAS,OAAO,EAAE,CASpB;AAID,8CAA8C;AAC9C,KAAK,QAAQ,CAAC,OAAO,SAAS,QAAQ,IAAI;KACvC,CAAC,IAAI,MAAM,OAAO,GAAG,KAAK,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CACjE,CAAC,MAAM,OAAO,CAAC,GACd,MAAM,CAAC;AAET,KAAK,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,sDAAsD;AACtD,KAAK,aAAa,CAAC,OAAO,SAAS,QAAQ,EAAE,CAAC,SAAS,cAAc,IAAI;KACtE,CAAC,IAAI,MAAM,OAAO,GAAG,CAAC,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CAC7D,CAAC,MAAM,OAAO,CAAC,GACd,MAAM,CAAC;AAET,4DAA4D;AAC5D,KAAK,WAAW,CACd,OAAO,SAAS,QAAQ,EACxB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,MAAM,IACd,CAAC,SAAS,MAAM,OAAO,GACvB,CAAC,SAAS,MAAM,OAAO,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,GACjC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GACb,aAAa,GACf,aAAa,GACf,aAAa,CAAC;AAIlB,sEAAsE;AACtE,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,aAAa;IACvD,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,GAC7B,SAAS,GACT,CAAC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IACzD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yCAAyC;AACzC,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,aAAa;IACxD,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,GAC7B,SAAS,GACT,CAAC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;CACvD;AAED,mCAAmC;AACnC,UAAU,iBAAiB,CAAC,KAAK;IAC/B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAID,oDAAoD;AACpD,MAAM,WAAW,QAAQ,CAAC,OAAO,SAAS,QAAQ;IAChD,oCAAoC;IACpC,MAAM,CAAC,CAAC,SAAS,QAAQ,CAAC,OAAO,CAAC,EAChC,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,gBAAgB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GACzD,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;IAElE,6CAA6C;IAC7C,OAAO,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,EAC9C,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,GACvE,mBAAmB,CACpB,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,EAC3C,KAAK,EACL,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CACnD,CAAC;IAEF,4CAA4C;IAC5C,MAAM,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,EAC5C,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,GACtE,mBAAmB,CACpB,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,EAC1C,KAAK,EACL,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAClD,CAAC;IAEF,8CAA8C;IAC9C,QAAQ,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,EAChD,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,GACxE,mBAAmB,CACpB,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,EAC5C,KAAK,EACL,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CACpD,CAAC;IAEF,+CAA+C;IAC/C,SAAS,CAAC,CAAC,SAAS,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,EAClD,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC,GACzE,mBAAmB,CACpB,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,EAC7C,KAAK,EACL,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CACrD,CAAC;CACH;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,QAAQ,EACrD,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,GAC9B,QAAQ,CAAC,OAAO,CAAC,CA0EnB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @typokit/client-swr — SWR Hooks
|
|
2
|
+
import useSWR from "swr";
|
|
3
|
+
import useSWRMutation from "swr/mutation";
|
|
4
|
+
// ─── SWR Key Builder ────────────────────────────────────────
|
|
5
|
+
/** Build an SWR cache key from route path, params, and query */
|
|
6
|
+
export function buildSWRKey(path, params, query) {
|
|
7
|
+
const key = [path];
|
|
8
|
+
if (params && Object.keys(params).length > 0) {
|
|
9
|
+
key.push(params);
|
|
10
|
+
}
|
|
11
|
+
if (query && Object.keys(query).length > 0) {
|
|
12
|
+
key.push(query);
|
|
13
|
+
}
|
|
14
|
+
return key;
|
|
15
|
+
}
|
|
16
|
+
// ─── Factory ────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Create type-safe SWR hooks from a TypoKit client.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const hooks = createSWRHooks<MyRoutes>(client);
|
|
23
|
+
* // In a React component:
|
|
24
|
+
* const { data, isLoading } = hooks.useGet("/users", { query: { page: 1 } });
|
|
25
|
+
* const { trigger: createUser } = hooks.usePost("/users");
|
|
26
|
+
* createUser({ body: { name: "Alice" } });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createSWRHooks(client) {
|
|
30
|
+
const c = client;
|
|
31
|
+
function makeGetHook(path, options) {
|
|
32
|
+
const key = buildSWRKey(path, options?.params, options?.query);
|
|
33
|
+
const config = {};
|
|
34
|
+
if (options?.revalidateOnFocus !== undefined)
|
|
35
|
+
config.revalidateOnFocus = options.revalidateOnFocus;
|
|
36
|
+
if (options?.refreshInterval !== undefined)
|
|
37
|
+
config.refreshInterval = options.refreshInterval;
|
|
38
|
+
if (options?.dedupingInterval !== undefined)
|
|
39
|
+
config.dedupingInterval = options.dedupingInterval;
|
|
40
|
+
if (options?.suspense !== undefined)
|
|
41
|
+
config.suspense = options.suspense;
|
|
42
|
+
return useSWR(key, () => c.get(path, { params: options?.params, query: options?.query }), config);
|
|
43
|
+
}
|
|
44
|
+
function makeMutationHook(method, path, options) {
|
|
45
|
+
const key = buildSWRKey(path);
|
|
46
|
+
const config = {};
|
|
47
|
+
if (options?.onSuccess)
|
|
48
|
+
config.onSuccess = options.onSuccess;
|
|
49
|
+
if (options?.onError)
|
|
50
|
+
config.onError = options.onError;
|
|
51
|
+
return useSWRMutation(key, (_key, { arg }) => c[method](path, { params: arg.params, body: arg.body }), config);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
useGet: (path, options) => makeGetHook(path, options),
|
|
55
|
+
usePost: (path, options) => makeMutationHook("post", path, options),
|
|
56
|
+
usePut: (path, options) => makeMutationHook("put", path, options),
|
|
57
|
+
usePatch: (path, options) => makeMutationHook("patch", path, options),
|
|
58
|
+
useDelete: (path, options) => makeMutationHook("delete", path, options),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AASlC,OAAO,MAAM,MAAM,KAAK,CAAC;AACzB,OAAO,cAAc,MAAM,cAAc,CAAC;AAE1C,+DAA+D;AAE/D,gEAAgE;AAChE,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,MAA+B,EAC/B,KAA+B;IAE/B,MAAM,GAAG,GAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAwHD,+DAA+D;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA+B;IAE/B,MAAM,CAAC,GAAG,MAAkC,CAAC;IAE7C,SAAS,WAAW,CAClB,IAAY,EACZ,OAOC;QAED,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAqC,EAAE,CAAC;QACpD,IAAI,OAAO,EAAE,iBAAiB,KAAK,SAAS;YAC1C,MAAM,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;QACvD,IAAI,OAAO,EAAE,eAAe,KAAK,SAAS;YACxC,MAAM,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QACnD,IAAI,OAAO,EAAE,gBAAgB,KAAK,SAAS;YACzC,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACrD,IAAI,OAAO,EAAE,QAAQ,KAAK,SAAS;YAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAExE,OAAO,MAAM,CACX,GAAG,EACH,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EACrE,MAAM,CACP,CAAC;IACJ,CAAC;IAED,SAAS,gBAAgB,CACvB,MAA2C,EAC3C,IAAY,EACZ,OAGC;QAMD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,MAAM,GAIR,EAAE,CAAC;QACP,IAAI,OAAO,EAAE,SAAS;YAAE,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QAC7D,IAAI,OAAO,EAAE,OAAO;YAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAEvD,OAAO,cAAc,CACnB,GAAG,EACH,CACE,IAAiC,EACjC,EAAE,GAAG,EAAgE,EACrE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,EAC5D,MAAM,CACP,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,CAAC,IAAY,EAAE,OAAiC,EAAE,EAAE,CAC1D,WAAW,CAAC,IAAI,EAAE,OAAkC,CAAC;QACvD,OAAO,EAAE,CAAC,IAAY,EAAE,OAAiC,EAAE,EAAE,CAC3D,gBAAgB,CAAC,MAAM,EAAE,IAAI,EAAE,OAAkC,CAAC;QACpE,MAAM,EAAE,CAAC,IAAY,EAAE,OAAiC,EAAE,EAAE,CAC1D,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAkC,CAAC;QACnE,QAAQ,EAAE,CAAC,IAAY,EAAE,OAAiC,EAAE,EAAE,CAC5D,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,OAAkC,CAAC;QACrE,SAAS,EAAE,CAAC,IAAY,EAAE,OAAiC,EAAE,EAAE,CAC7D,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAkC,CAAC;KAClD,CAAC;AACzB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typokit/client-swr",
|
|
3
|
+
"exports": {
|
|
4
|
+
".": {
|
|
5
|
+
"import": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.4",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@typokit/client": "0.1.4",
|
|
19
|
+
"@typokit/types": "0.1.4"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"swr": ">=2.0.0",
|
|
23
|
+
"react": ">=18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/KyleBastien/typokit",
|
|
28
|
+
"directory": "packages/client-swr"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "rstest run --passWithNoTests"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Ambient type declarations for swr (peer dependency)
|
|
2
|
+
// Consumers must install swr >= 2.0.0
|
|
3
|
+
|
|
4
|
+
declare module "swr" {
|
|
5
|
+
export type Key = string | readonly unknown[] | null | undefined | false;
|
|
6
|
+
|
|
7
|
+
export interface SWRConfiguration<TData = unknown, TError = Error> {
|
|
8
|
+
revalidateOnFocus?: boolean;
|
|
9
|
+
revalidateOnReconnect?: boolean;
|
|
10
|
+
refreshInterval?: number;
|
|
11
|
+
dedupingInterval?: number;
|
|
12
|
+
shouldRetryOnError?: boolean;
|
|
13
|
+
errorRetryCount?: number;
|
|
14
|
+
fallbackData?: TData;
|
|
15
|
+
suspense?: boolean;
|
|
16
|
+
onSuccess?: (data: TData) => void;
|
|
17
|
+
onError?: (error: TError) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SWRResponse<TData = unknown, TError = Error> {
|
|
21
|
+
data: TData | undefined;
|
|
22
|
+
error: TError | undefined;
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
isValidating: boolean;
|
|
25
|
+
mutate: (
|
|
26
|
+
data?:
|
|
27
|
+
| TData
|
|
28
|
+
| Promise<TData>
|
|
29
|
+
| ((current?: TData) => TData | Promise<TData>),
|
|
30
|
+
opts?: boolean | { revalidate?: boolean },
|
|
31
|
+
) => Promise<TData | undefined>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Fetcher<TData> = (...args: readonly unknown[]) => Promise<TData>;
|
|
35
|
+
|
|
36
|
+
export default function useSWR<TData = unknown, TError = Error>(
|
|
37
|
+
key: Key,
|
|
38
|
+
fetcher: Fetcher<TData> | null,
|
|
39
|
+
config?: SWRConfiguration<TData, TError>,
|
|
40
|
+
): SWRResponse<TData, TError>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare module "swr/mutation" {
|
|
44
|
+
export interface SWRMutationConfiguration<
|
|
45
|
+
TData = unknown,
|
|
46
|
+
TError = Error,
|
|
47
|
+
_TArg = never,
|
|
48
|
+
> {
|
|
49
|
+
onSuccess?: (data: TData) => void;
|
|
50
|
+
onError?: (error: TError) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SWRMutationResponse<
|
|
54
|
+
TData = unknown,
|
|
55
|
+
TError = Error,
|
|
56
|
+
TArg = never,
|
|
57
|
+
> {
|
|
58
|
+
trigger: (arg: TArg) => Promise<TData>;
|
|
59
|
+
data: TData | undefined;
|
|
60
|
+
error: TError | undefined;
|
|
61
|
+
isMutating: boolean;
|
|
62
|
+
reset: () => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type MutationFetcher<TData, TArg = never> = (
|
|
66
|
+
key: string | readonly unknown[],
|
|
67
|
+
options: { arg: TArg },
|
|
68
|
+
) => Promise<TData>;
|
|
69
|
+
|
|
70
|
+
export default function useSWRMutation<
|
|
71
|
+
TData = unknown,
|
|
72
|
+
TError = Error,
|
|
73
|
+
TArg = never,
|
|
74
|
+
>(
|
|
75
|
+
key: string | readonly unknown[],
|
|
76
|
+
fetcher: MutationFetcher<TData, TArg>,
|
|
77
|
+
options?: SWRMutationConfiguration<TData, TError, TArg>,
|
|
78
|
+
): SWRMutationResponse<TData, TError, TArg>;
|
|
79
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @typokit/client-swr — Tests
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from "@rstest/core";
|
|
4
|
+
import { buildSWRKey } from "./index.js";
|
|
5
|
+
|
|
6
|
+
// ─── buildSWRKey Tests ──────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
describe("buildSWRKey", () => {
|
|
9
|
+
it("returns path-only key when no params or query", () => {
|
|
10
|
+
const key = buildSWRKey("/users");
|
|
11
|
+
expect(key).toEqual(["/users"]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("includes params in key when provided", () => {
|
|
15
|
+
const key = buildSWRKey("/users/:id", { id: "123" });
|
|
16
|
+
expect(key).toEqual(["/users/:id", { id: "123" }]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("includes query in key when provided", () => {
|
|
20
|
+
const key = buildSWRKey("/users", undefined, { page: 1, limit: 10 });
|
|
21
|
+
expect(key).toEqual(["/users", { page: 1, limit: 10 }]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("includes both params and query when provided", () => {
|
|
25
|
+
const key = buildSWRKey("/users/:id/posts", { id: "42" }, { sort: "date" });
|
|
26
|
+
expect(key).toEqual(["/users/:id/posts", { id: "42" }, { sort: "date" }]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("omits empty params object", () => {
|
|
30
|
+
const key = buildSWRKey("/users", {});
|
|
31
|
+
expect(key).toEqual(["/users"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("omits empty query object", () => {
|
|
35
|
+
const key = buildSWRKey("/users", undefined, {});
|
|
36
|
+
expect(key).toEqual(["/users"]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("produces different keys for different params", () => {
|
|
40
|
+
const key1 = buildSWRKey("/users/:id", { id: "1" });
|
|
41
|
+
const key2 = buildSWRKey("/users/:id", { id: "2" });
|
|
42
|
+
expect(key1).not.toEqual(key2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("produces different keys for different queries", () => {
|
|
46
|
+
const key1 = buildSWRKey("/users", undefined, { page: 1 });
|
|
47
|
+
const key2 = buildSWRKey("/users", undefined, { page: 2 });
|
|
48
|
+
expect(key1).not.toEqual(key2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("produces different keys for different paths", () => {
|
|
52
|
+
const key1 = buildSWRKey("/users");
|
|
53
|
+
const key2 = buildSWRKey("/posts");
|
|
54
|
+
expect(key1).not.toEqual(key2);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// @typokit/client-swr — SWR Hooks
|
|
2
|
+
|
|
3
|
+
import type { RouteContract } from "@typokit/types";
|
|
4
|
+
import type { RouteMap, TypeSafeClient } from "@typokit/client";
|
|
5
|
+
import type { SWRConfiguration, SWRResponse } from "swr";
|
|
6
|
+
import type {
|
|
7
|
+
SWRMutationConfiguration,
|
|
8
|
+
SWRMutationResponse,
|
|
9
|
+
} from "swr/mutation";
|
|
10
|
+
import useSWR from "swr";
|
|
11
|
+
import useSWRMutation from "swr/mutation";
|
|
12
|
+
|
|
13
|
+
// ─── SWR Key Builder ────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/** Build an SWR cache key from route path, params, and query */
|
|
16
|
+
export function buildSWRKey(
|
|
17
|
+
path: string,
|
|
18
|
+
params?: Record<string, string>,
|
|
19
|
+
query?: Record<string, unknown>,
|
|
20
|
+
): readonly unknown[] {
|
|
21
|
+
const key: unknown[] = [path];
|
|
22
|
+
if (params && Object.keys(params).length > 0) {
|
|
23
|
+
key.push(params);
|
|
24
|
+
}
|
|
25
|
+
if (query && Object.keys(query).length > 0) {
|
|
26
|
+
key.push(query);
|
|
27
|
+
}
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Type-Level Utilities ───────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/** Extract GET route paths from a RouteMap */
|
|
34
|
+
type GetPaths<TRoutes extends RouteMap> = {
|
|
35
|
+
[P in keyof TRoutes]: "GET" extends keyof TRoutes[P] ? P : never;
|
|
36
|
+
}[keyof TRoutes] &
|
|
37
|
+
string;
|
|
38
|
+
|
|
39
|
+
type MutationMethod = "POST" | "PUT" | "PATCH" | "DELETE";
|
|
40
|
+
|
|
41
|
+
/** Extract paths that have a given mutation method */
|
|
42
|
+
type MutationPaths<TRoutes extends RouteMap, M extends MutationMethod> = {
|
|
43
|
+
[P in keyof TRoutes]: M extends keyof TRoutes[P] ? P : never;
|
|
44
|
+
}[keyof TRoutes] &
|
|
45
|
+
string;
|
|
46
|
+
|
|
47
|
+
/** Resolve the RouteContract for a given path and method */
|
|
48
|
+
type ContractFor<
|
|
49
|
+
TRoutes extends RouteMap,
|
|
50
|
+
P extends string,
|
|
51
|
+
M extends string,
|
|
52
|
+
> = P extends keyof TRoutes
|
|
53
|
+
? M extends keyof TRoutes[P]
|
|
54
|
+
? TRoutes[P][M] extends RouteContract
|
|
55
|
+
? TRoutes[P][M]
|
|
56
|
+
: RouteContract
|
|
57
|
+
: RouteContract
|
|
58
|
+
: RouteContract;
|
|
59
|
+
|
|
60
|
+
// ─── Hook Option / Variable Types ───────────────────────────
|
|
61
|
+
|
|
62
|
+
/** Options for useSWRGet: route-specific params/query + SWR config */
|
|
63
|
+
export interface UseSWRGetOptions<C extends RouteContract> {
|
|
64
|
+
params?: C["params"] extends void
|
|
65
|
+
? undefined
|
|
66
|
+
: C["params"] & Record<string, string>;
|
|
67
|
+
query?: C["query"] extends void ? undefined : C["query"];
|
|
68
|
+
revalidateOnFocus?: boolean;
|
|
69
|
+
refreshInterval?: number;
|
|
70
|
+
dedupingInterval?: number;
|
|
71
|
+
suspense?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Variables passed to mutation hooks */
|
|
75
|
+
export interface MutationVariables<C extends RouteContract> {
|
|
76
|
+
params?: C["params"] extends void
|
|
77
|
+
? undefined
|
|
78
|
+
: C["params"] & Record<string, string>;
|
|
79
|
+
body?: C["body"] extends void ? undefined : C["body"];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Mutation lifecycle callbacks */
|
|
83
|
+
interface MutationCallbacks<TData> {
|
|
84
|
+
onSuccess?: (data: TData) => void;
|
|
85
|
+
onError?: (error: Error) => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── SWRHooks Interface ─────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/** Type-safe SWR hooks generated from a RouteMap */
|
|
91
|
+
export interface SWRHooks<TRoutes extends RouteMap> {
|
|
92
|
+
/** useSWR wrapper for GET routes */
|
|
93
|
+
useGet<P extends GetPaths<TRoutes>>(
|
|
94
|
+
path: P,
|
|
95
|
+
options?: UseSWRGetOptions<ContractFor<TRoutes, P, "GET">>,
|
|
96
|
+
): SWRResponse<ContractFor<TRoutes, P, "GET">["response"], Error>;
|
|
97
|
+
|
|
98
|
+
/** useSWRMutation wrapper for POST routes */
|
|
99
|
+
usePost<P extends MutationPaths<TRoutes, "POST">>(
|
|
100
|
+
path: P,
|
|
101
|
+
options?: MutationCallbacks<ContractFor<TRoutes, P, "POST">["response"]>,
|
|
102
|
+
): SWRMutationResponse<
|
|
103
|
+
ContractFor<TRoutes, P, "POST">["response"],
|
|
104
|
+
Error,
|
|
105
|
+
MutationVariables<ContractFor<TRoutes, P, "POST">>
|
|
106
|
+
>;
|
|
107
|
+
|
|
108
|
+
/** useSWRMutation wrapper for PUT routes */
|
|
109
|
+
usePut<P extends MutationPaths<TRoutes, "PUT">>(
|
|
110
|
+
path: P,
|
|
111
|
+
options?: MutationCallbacks<ContractFor<TRoutes, P, "PUT">["response"]>,
|
|
112
|
+
): SWRMutationResponse<
|
|
113
|
+
ContractFor<TRoutes, P, "PUT">["response"],
|
|
114
|
+
Error,
|
|
115
|
+
MutationVariables<ContractFor<TRoutes, P, "PUT">>
|
|
116
|
+
>;
|
|
117
|
+
|
|
118
|
+
/** useSWRMutation wrapper for PATCH routes */
|
|
119
|
+
usePatch<P extends MutationPaths<TRoutes, "PATCH">>(
|
|
120
|
+
path: P,
|
|
121
|
+
options?: MutationCallbacks<ContractFor<TRoutes, P, "PATCH">["response"]>,
|
|
122
|
+
): SWRMutationResponse<
|
|
123
|
+
ContractFor<TRoutes, P, "PATCH">["response"],
|
|
124
|
+
Error,
|
|
125
|
+
MutationVariables<ContractFor<TRoutes, P, "PATCH">>
|
|
126
|
+
>;
|
|
127
|
+
|
|
128
|
+
/** useSWRMutation wrapper for DELETE routes */
|
|
129
|
+
useDelete<P extends MutationPaths<TRoutes, "DELETE">>(
|
|
130
|
+
path: P,
|
|
131
|
+
options?: MutationCallbacks<ContractFor<TRoutes, P, "DELETE">["response"]>,
|
|
132
|
+
): SWRMutationResponse<
|
|
133
|
+
ContractFor<TRoutes, P, "DELETE">["response"],
|
|
134
|
+
Error,
|
|
135
|
+
MutationVariables<ContractFor<TRoutes, P, "DELETE">>
|
|
136
|
+
>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Untyped client for internal use ────────────────────────
|
|
140
|
+
|
|
141
|
+
interface UntypedClient {
|
|
142
|
+
get(path: string, options?: unknown): Promise<unknown>;
|
|
143
|
+
post(path: string, options?: unknown): Promise<unknown>;
|
|
144
|
+
put(path: string, options?: unknown): Promise<unknown>;
|
|
145
|
+
patch(path: string, options?: unknown): Promise<unknown>;
|
|
146
|
+
delete(path: string, options?: unknown): Promise<unknown>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Factory ────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create type-safe SWR hooks from a TypoKit client.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* const hooks = createSWRHooks<MyRoutes>(client);
|
|
157
|
+
* // In a React component:
|
|
158
|
+
* const { data, isLoading } = hooks.useGet("/users", { query: { page: 1 } });
|
|
159
|
+
* const { trigger: createUser } = hooks.usePost("/users");
|
|
160
|
+
* createUser({ body: { name: "Alice" } });
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function createSWRHooks<TRoutes extends RouteMap>(
|
|
164
|
+
client: TypeSafeClient<TRoutes>,
|
|
165
|
+
): SWRHooks<TRoutes> {
|
|
166
|
+
const c = client as unknown as UntypedClient;
|
|
167
|
+
|
|
168
|
+
function makeGetHook(
|
|
169
|
+
path: string,
|
|
170
|
+
options?: {
|
|
171
|
+
params?: Record<string, string>;
|
|
172
|
+
query?: Record<string, unknown>;
|
|
173
|
+
revalidateOnFocus?: boolean;
|
|
174
|
+
refreshInterval?: number;
|
|
175
|
+
dedupingInterval?: number;
|
|
176
|
+
suspense?: boolean;
|
|
177
|
+
},
|
|
178
|
+
): SWRResponse<unknown, Error> {
|
|
179
|
+
const key = buildSWRKey(path, options?.params, options?.query);
|
|
180
|
+
const config: SWRConfiguration<unknown, Error> = {};
|
|
181
|
+
if (options?.revalidateOnFocus !== undefined)
|
|
182
|
+
config.revalidateOnFocus = options.revalidateOnFocus;
|
|
183
|
+
if (options?.refreshInterval !== undefined)
|
|
184
|
+
config.refreshInterval = options.refreshInterval;
|
|
185
|
+
if (options?.dedupingInterval !== undefined)
|
|
186
|
+
config.dedupingInterval = options.dedupingInterval;
|
|
187
|
+
if (options?.suspense !== undefined) config.suspense = options.suspense;
|
|
188
|
+
|
|
189
|
+
return useSWR(
|
|
190
|
+
key,
|
|
191
|
+
() => c.get(path, { params: options?.params, query: options?.query }),
|
|
192
|
+
config,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function makeMutationHook(
|
|
197
|
+
method: "post" | "put" | "patch" | "delete",
|
|
198
|
+
path: string,
|
|
199
|
+
options?: {
|
|
200
|
+
onSuccess?: (data: unknown) => void;
|
|
201
|
+
onError?: (error: Error) => void;
|
|
202
|
+
},
|
|
203
|
+
): SWRMutationResponse<
|
|
204
|
+
unknown,
|
|
205
|
+
Error,
|
|
206
|
+
{ params?: Record<string, string>; body?: unknown }
|
|
207
|
+
> {
|
|
208
|
+
const key = buildSWRKey(path);
|
|
209
|
+
const config: SWRMutationConfiguration<
|
|
210
|
+
unknown,
|
|
211
|
+
Error,
|
|
212
|
+
{ params?: Record<string, string>; body?: unknown }
|
|
213
|
+
> = {};
|
|
214
|
+
if (options?.onSuccess) config.onSuccess = options.onSuccess;
|
|
215
|
+
if (options?.onError) config.onError = options.onError;
|
|
216
|
+
|
|
217
|
+
return useSWRMutation(
|
|
218
|
+
key,
|
|
219
|
+
(
|
|
220
|
+
_key: string | readonly unknown[],
|
|
221
|
+
{ arg }: { arg: { params?: Record<string, string>; body?: unknown } },
|
|
222
|
+
) => c[method](path, { params: arg.params, body: arg.body }),
|
|
223
|
+
config,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
useGet: (path: string, options?: Record<string, unknown>) =>
|
|
229
|
+
makeGetHook(path, options as Record<string, unknown>),
|
|
230
|
+
usePost: (path: string, options?: Record<string, unknown>) =>
|
|
231
|
+
makeMutationHook("post", path, options as Record<string, unknown>),
|
|
232
|
+
usePut: (path: string, options?: Record<string, unknown>) =>
|
|
233
|
+
makeMutationHook("put", path, options as Record<string, unknown>),
|
|
234
|
+
usePatch: (path: string, options?: Record<string, unknown>) =>
|
|
235
|
+
makeMutationHook("patch", path, options as Record<string, unknown>),
|
|
236
|
+
useDelete: (path: string, options?: Record<string, unknown>) =>
|
|
237
|
+
makeMutationHook("delete", path, options as Record<string, unknown>),
|
|
238
|
+
} as SWRHooks<TRoutes>;
|
|
239
|
+
}
|