silgi 0.0.13 → 0.1.0-beta.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.
- package/README.md +102 -1
- package/dist/_virtual/_rolldown/runtime.mjs +5 -0
- package/dist/adapters/astro.d.mts +17 -0
- package/dist/adapters/astro.mjs +24 -0
- package/dist/adapters/aws-lambda.d.mts +31 -0
- package/dist/adapters/aws-lambda.mjs +85 -0
- package/dist/adapters/elysia.d.mts +17 -0
- package/dist/adapters/elysia.mjs +76 -0
- package/dist/adapters/express.d.mts +16 -0
- package/dist/adapters/express.mjs +78 -0
- package/dist/adapters/fastify.d.mts +15 -0
- package/dist/adapters/fastify.mjs +78 -0
- package/dist/adapters/message-port.d.mts +37 -0
- package/dist/adapters/message-port.mjs +129 -0
- package/dist/adapters/nestjs.d.mts +25 -0
- package/dist/adapters/nestjs.mjs +91 -0
- package/dist/adapters/nextjs.d.mts +21 -0
- package/dist/adapters/nextjs.mjs +30 -0
- package/dist/adapters/peer.d.mts +27 -0
- package/dist/adapters/peer.mjs +36 -0
- package/dist/adapters/remix.d.mts +17 -0
- package/dist/adapters/remix.mjs +24 -0
- package/dist/adapters/solidstart.d.mts +14 -0
- package/dist/adapters/solidstart.mjs +30 -0
- package/dist/adapters/sveltekit.d.mts +18 -0
- package/dist/adapters/sveltekit.mjs +33 -0
- package/dist/analyze.mjs +26 -0
- package/dist/broker/index.d.mts +62 -0
- package/dist/broker/index.mjs +153 -0
- package/dist/broker/nats.d.mts +33 -0
- package/dist/broker/nats.mjs +31 -0
- package/dist/broker/redis.d.mts +51 -0
- package/dist/broker/redis.mjs +92 -0
- package/dist/builder.d.mts +36 -0
- package/dist/builder.mjs +51 -0
- package/dist/callable.d.mts +17 -0
- package/dist/callable.mjs +42 -0
- package/dist/client/adapters/fetch/index.d.mts +17 -0
- package/dist/client/adapters/fetch/index.mjs +61 -0
- package/dist/client/adapters/ofetch/index.d.mts +41 -0
- package/dist/client/adapters/ofetch/index.mjs +92 -0
- package/dist/client/client.d.mts +29 -0
- package/dist/client/client.mjs +54 -0
- package/dist/client/dynamic-link.d.mts +15 -0
- package/dist/client/dynamic-link.mjs +16 -0
- package/dist/client/index.d.mts +7 -0
- package/dist/client/index.mjs +6 -0
- package/dist/client/interceptor.d.mts +31 -0
- package/dist/client/interceptor.mjs +34 -0
- package/dist/client/merge.d.mts +28 -0
- package/dist/client/merge.mjs +30 -0
- package/dist/client/openapi.d.mts +29 -0
- package/dist/client/openapi.mjs +89 -0
- package/dist/client/plugins/batch.d.mts +20 -0
- package/dist/client/plugins/batch.mjs +64 -0
- package/dist/client/plugins/csrf.d.mts +13 -0
- package/dist/client/plugins/csrf.mjs +20 -0
- package/dist/client/plugins/dedupe.d.mts +10 -0
- package/dist/client/plugins/dedupe.mjs +28 -0
- package/dist/client/plugins/index.d.mts +5 -0
- package/dist/client/plugins/index.mjs +5 -0
- package/dist/client/plugins/retry.d.mts +11 -0
- package/dist/client/plugins/retry.mjs +21 -0
- package/dist/client/server.d.mts +16 -0
- package/dist/client/server.mjs +60 -0
- package/dist/client/types.d.mts +29 -0
- package/dist/codec/devalue.d.mts +21 -0
- package/dist/codec/devalue.mjs +32 -0
- package/dist/codec/msgpack.d.mts +21 -0
- package/dist/codec/msgpack.mjs +59 -0
- package/dist/compile.d.mts +52 -0
- package/dist/compile.mjs +304 -0
- package/dist/contract.d.mts +36 -0
- package/dist/contract.mjs +40 -0
- package/dist/core/error.d.mts +104 -0
- package/dist/core/error.mjs +139 -0
- package/dist/core/handler.mjs +546 -0
- package/dist/core/iterator.d.mts +17 -0
- package/dist/core/iterator.mjs +79 -0
- package/dist/core/router-utils.mjs +16 -0
- package/dist/core/schema.d.mts +19 -0
- package/dist/core/schema.mjs +26 -0
- package/dist/core/serve.mjs +38 -0
- package/dist/core/sse.d.mts +16 -0
- package/dist/core/sse.mjs +95 -0
- package/dist/core/storage.d.mts +21 -0
- package/dist/core/storage.mjs +63 -0
- package/dist/core/utils.mjs +21 -0
- package/dist/fast-stringify.mjs +125 -0
- package/dist/index.d.mts +15 -37
- package/dist/index.mjs +13 -7
- package/dist/integrations/ai/index.d.mts +25 -0
- package/dist/integrations/ai/index.mjs +116 -0
- package/dist/integrations/react/index.d.mts +83 -0
- package/dist/integrations/react/index.mjs +197 -0
- package/dist/integrations/tanstack-query/index.d.mts +120 -0
- package/dist/integrations/tanstack-query/index.mjs +100 -0
- package/dist/integrations/tanstack-query/ssr.d.mts +51 -0
- package/dist/integrations/tanstack-query/ssr.mjs +89 -0
- package/dist/integrations/zod/converter.d.mts +75 -0
- package/dist/integrations/zod/converter.mjs +345 -0
- package/dist/integrations/zod/index.d.mts +2 -0
- package/dist/integrations/zod/index.mjs +2 -0
- package/dist/lazy.d.mts +24 -0
- package/dist/lazy.mjs +27 -0
- package/dist/lifecycle.d.mts +36 -0
- package/dist/lifecycle.mjs +46 -0
- package/dist/map-input.d.mts +17 -0
- package/dist/map-input.mjs +24 -0
- package/dist/plugins/analytics.d.mts +168 -0
- package/dist/plugins/analytics.mjs +459 -0
- package/dist/plugins/batch-server.d.mts +20 -0
- package/dist/plugins/batch-server.mjs +86 -0
- package/dist/plugins/body-limit.d.mts +16 -0
- package/dist/plugins/body-limit.mjs +44 -0
- package/dist/plugins/cache.d.mts +170 -0
- package/dist/plugins/cache.mjs +200 -0
- package/dist/plugins/coerce.d.mts +21 -0
- package/dist/plugins/coerce.mjs +46 -0
- package/dist/plugins/compression.d.mts +19 -0
- package/dist/plugins/compression.mjs +23 -0
- package/dist/plugins/cookies.d.mts +44 -0
- package/dist/plugins/cookies.mjs +67 -0
- package/dist/plugins/cors.d.mts +39 -0
- package/dist/plugins/cors.mjs +56 -0
- package/dist/plugins/custom-serializer.d.mts +57 -0
- package/dist/plugins/custom-serializer.mjs +40 -0
- package/dist/plugins/file-upload.d.mts +38 -0
- package/dist/plugins/file-upload.mjs +100 -0
- package/dist/plugins/index.d.mts +16 -0
- package/dist/plugins/index.mjs +16 -0
- package/dist/plugins/otel.d.mts +35 -0
- package/dist/plugins/otel.mjs +40 -0
- package/dist/plugins/pino.d.mts +60 -0
- package/dist/plugins/pino.mjs +42 -0
- package/dist/plugins/pubsub.d.mts +50 -0
- package/dist/plugins/pubsub.mjs +53 -0
- package/dist/plugins/ratelimit.d.mts +51 -0
- package/dist/plugins/ratelimit.mjs +81 -0
- package/dist/plugins/signing.d.mts +41 -0
- package/dist/plugins/signing.mjs +115 -0
- package/dist/plugins/strict-get.d.mts +10 -0
- package/dist/plugins/strict-get.mjs +33 -0
- package/dist/route/add.mjs +240 -0
- package/dist/route/compiler.mjs +373 -0
- package/dist/route/context.mjs +12 -0
- package/dist/route/types.d.mts +11 -0
- package/dist/route/utils.mjs +17 -0
- package/dist/scalar.d.mts +53 -0
- package/dist/scalar.mjs +315 -0
- package/dist/silgi.d.mts +139 -0
- package/dist/silgi.mjs +113 -0
- package/dist/trpc-interop.d.mts +22 -0
- package/dist/trpc-interop.mjs +68 -0
- package/dist/types.d.mts +82 -0
- package/dist/ws.d.mts +42 -0
- package/dist/ws.mjs +137 -0
- package/lib/dashboard/index.html +123 -0
- package/lib/ocache.d.mts +1 -0
- package/lib/ocache.mjs +1 -0
- package/lib/ofetch.d.mts +1 -0
- package/lib/ofetch.mjs +1 -0
- package/lib/srvx.d.mts +1 -0
- package/lib/srvx.mjs +1 -0
- package/lib/unstorage.d.mts +1 -0
- package/lib/unstorage.mjs +1 -0
- package/package.json +291 -65
- package/bin/silgi.mjs +0 -3
- package/dist/chunks/generate.mjs +0 -933
- package/dist/chunks/init.mjs +0 -21
- package/dist/cli/config.d.mts +0 -19
- package/dist/cli/config.d.ts +0 -19
- package/dist/cli/config.mjs +0 -5
- package/dist/cli/index.d.mts +0 -2
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +0 -119
- package/dist/index.d.ts +0 -37
- package/dist/plugins/openapi.d.mts +0 -138
- package/dist/plugins/openapi.d.ts +0 -138
- package/dist/plugins/openapi.mjs +0 -204
- package/dist/plugins/scalar.d.mts +0 -14
- package/dist/plugins/scalar.d.ts +0 -14
- package/dist/plugins/scalar.mjs +0 -66
- package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
- package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
- package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ProcedureDef, RouterDef } from "../../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/integrations/react/index.d.ts
|
|
4
|
+
type ActionResult<TOutput> = [error: null, data: TOutput] | [error: {
|
|
5
|
+
code: string;
|
|
6
|
+
status: number;
|
|
7
|
+
message: string;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
}, data: undefined];
|
|
10
|
+
/**
|
|
11
|
+
* Create a server action from a v2 ProcedureDef.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // app/actions.ts
|
|
16
|
+
* "use server"
|
|
17
|
+
* import { createAction } from "silgi/react"
|
|
18
|
+
*
|
|
19
|
+
* export const createUser = createAction(appRouter.users.create)
|
|
20
|
+
*
|
|
21
|
+
* // app/page.tsx
|
|
22
|
+
* const [error, user] = await createUser({ name: "Alice" })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function createAction<TInput = unknown, TOutput = unknown>(procedure: ProcedureDef): (input: TInput) => Promise<ActionResult<TOutput>>;
|
|
26
|
+
/**
|
|
27
|
+
* Create a FormData-accepting server action from a v2 procedure.
|
|
28
|
+
*/
|
|
29
|
+
declare function createFormAction<TOutput = unknown>(procedure: ProcedureDef, options?: {
|
|
30
|
+
parseFormData?: (fd: FormData) => unknown;
|
|
31
|
+
}): (formData: FormData) => Promise<ActionResult<TOutput>>;
|
|
32
|
+
/**
|
|
33
|
+
* Create actions for all procedures in a router.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const actions = createActions(appRouter)
|
|
38
|
+
* const [error, users] = await actions.users.list({ limit: 10 })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function createActions<T extends RouterDef>(router: T): ActionRouter<T>;
|
|
42
|
+
/**
|
|
43
|
+
* React hook for calling server actions with loading/error state.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const { execute, data, error, isPending } = useServerAction(createUser)
|
|
48
|
+
*
|
|
49
|
+
* <button onClick={() => execute({ name: "Alice" })} disabled={isPending}>
|
|
50
|
+
* {isPending ? "Creating..." : "Create User"}
|
|
51
|
+
* </button>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function useServerAction<TInput, TOutput>(action: (input: TInput) => Promise<ActionResult<TOutput>>): {
|
|
55
|
+
execute: any;
|
|
56
|
+
data: any;
|
|
57
|
+
error: any;
|
|
58
|
+
isPending: any;
|
|
59
|
+
reset: any;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* React hook for server actions with optimistic UI updates.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* const { execute, data, optimisticData, isPending } = useOptimisticServerAction(updateUser, {
|
|
67
|
+
* optimistic: (input) => ({ ...currentUser, ...input }),
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare function useOptimisticServerAction<TInput, TOutput>(action: (input: TInput) => Promise<ActionResult<TOutput>>, options: {
|
|
72
|
+
optimistic: (input: TInput) => TOutput;
|
|
73
|
+
}): {
|
|
74
|
+
execute: any; /** Confirmed server data */
|
|
75
|
+
data: any; /** Optimistic data (shown while pending, cleared on settle) */
|
|
76
|
+
optimisticData: any; /** The value to display: optimistic while pending, confirmed otherwise */
|
|
77
|
+
displayData: any;
|
|
78
|
+
error: any;
|
|
79
|
+
isPending: any;
|
|
80
|
+
};
|
|
81
|
+
type ActionRouter<T extends RouterDef> = { [K in keyof T]: T[K] extends ProcedureDef<any, infer TInput, infer TOutput> ? (input: TInput extends undefined ? void : TInput) => Promise<ActionResult<TOutput>> : T[K] extends RouterDef ? ActionRouter<T[K]> : never };
|
|
82
|
+
//#endregion
|
|
83
|
+
export { ActionResult, createAction, createActions, createFormAction, useOptimisticServerAction, useServerAction };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { __require } from "../../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { SilgiError, toSilgiError } from "../../core/error.mjs";
|
|
3
|
+
import { compileProcedure } from "../../compile.mjs";
|
|
4
|
+
//#region src/integrations/react/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* React Server Actions — v2 integration.
|
|
7
|
+
*
|
|
8
|
+
* Creates type-safe server actions from v2 procedures.
|
|
9
|
+
* Returns [error, data] tuples instead of throwing.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Create a server action from a v2 ProcedureDef.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // app/actions.ts
|
|
17
|
+
* "use server"
|
|
18
|
+
* import { createAction } from "silgi/react"
|
|
19
|
+
*
|
|
20
|
+
* export const createUser = createAction(appRouter.users.create)
|
|
21
|
+
*
|
|
22
|
+
* // app/page.tsx
|
|
23
|
+
* const [error, user] = await createUser({ name: "Alice" })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function createAction(procedure) {
|
|
27
|
+
const handler = compileProcedure(procedure);
|
|
28
|
+
const signal = new AbortController().signal;
|
|
29
|
+
return async (input) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = handler(Object.create(null), input, signal);
|
|
32
|
+
return [null, result instanceof Promise ? await result : result];
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (isFrameworkError(error)) throw error;
|
|
35
|
+
return [(error instanceof SilgiError ? error : toSilgiError(error)).toJSON(), void 0];
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a FormData-accepting server action from a v2 procedure.
|
|
41
|
+
*/
|
|
42
|
+
function createFormAction(procedure, options) {
|
|
43
|
+
const action = createAction(procedure);
|
|
44
|
+
const parse = options?.parseFormData ?? defaultFormDataParser;
|
|
45
|
+
return (formData) => action(parse(formData));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create actions for all procedures in a router.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const actions = createActions(appRouter)
|
|
53
|
+
* const [error, users] = await actions.users.list({ limit: 10 })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function createActions(router) {
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const [key, value] of Object.entries(router)) if (isProcedureDef(value)) result[key] = createAction(value);
|
|
59
|
+
else if (typeof value === "object" && value !== null) result[key] = createActions(value);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* React hook for calling server actions with loading/error state.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const { execute, data, error, isPending } = useServerAction(createUser)
|
|
68
|
+
*
|
|
69
|
+
* <button onClick={() => execute({ name: "Alice" })} disabled={isPending}>
|
|
70
|
+
* {isPending ? "Creating..." : "Create User"}
|
|
71
|
+
* </button>
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
function useServerAction(action) {
|
|
75
|
+
const { useState, useCallback, useRef } = __require("react");
|
|
76
|
+
const [data, setData] = useState(void 0);
|
|
77
|
+
const [error, setError] = useState(null);
|
|
78
|
+
const [isPending, setIsPending] = useState(false);
|
|
79
|
+
const mountedRef = useRef(true);
|
|
80
|
+
const { useEffect } = __require("react");
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
mountedRef.current = true;
|
|
83
|
+
return () => {
|
|
84
|
+
mountedRef.current = false;
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
87
|
+
return {
|
|
88
|
+
execute: useCallback(async (input) => {
|
|
89
|
+
setIsPending(true);
|
|
90
|
+
setError(null);
|
|
91
|
+
try {
|
|
92
|
+
const [err, result] = await action(input);
|
|
93
|
+
if (!mountedRef.current) return;
|
|
94
|
+
if (err) {
|
|
95
|
+
setError(err);
|
|
96
|
+
setData(void 0);
|
|
97
|
+
} else {
|
|
98
|
+
setData(result);
|
|
99
|
+
setError(null);
|
|
100
|
+
}
|
|
101
|
+
return [err, result];
|
|
102
|
+
} finally {
|
|
103
|
+
if (mountedRef.current) setIsPending(false);
|
|
104
|
+
}
|
|
105
|
+
}, [action]),
|
|
106
|
+
data,
|
|
107
|
+
error,
|
|
108
|
+
isPending,
|
|
109
|
+
reset: useCallback(() => {
|
|
110
|
+
setData(void 0);
|
|
111
|
+
setError(null);
|
|
112
|
+
setIsPending(false);
|
|
113
|
+
}, [])
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* React hook for server actions with optimistic UI updates.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* const { execute, data, optimisticData, isPending } = useOptimisticServerAction(updateUser, {
|
|
122
|
+
* optimistic: (input) => ({ ...currentUser, ...input }),
|
|
123
|
+
* })
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function useOptimisticServerAction(action, options) {
|
|
127
|
+
const { useState, useCallback, useRef } = __require("react");
|
|
128
|
+
const { useEffect } = __require("react");
|
|
129
|
+
const [data, setData] = useState(void 0);
|
|
130
|
+
const [optimisticData, setOptimisticData] = useState(void 0);
|
|
131
|
+
const [error, setError] = useState(null);
|
|
132
|
+
const [isPending, setIsPending] = useState(false);
|
|
133
|
+
const mountedRef = useRef(true);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
mountedRef.current = true;
|
|
136
|
+
return () => {
|
|
137
|
+
mountedRef.current = false;
|
|
138
|
+
};
|
|
139
|
+
}, []);
|
|
140
|
+
return {
|
|
141
|
+
execute: useCallback(async (input) => {
|
|
142
|
+
setIsPending(true);
|
|
143
|
+
setError(null);
|
|
144
|
+
setOptimisticData(options.optimistic(input));
|
|
145
|
+
try {
|
|
146
|
+
const [err, result] = await action(input);
|
|
147
|
+
if (!mountedRef.current) return;
|
|
148
|
+
if (err) {
|
|
149
|
+
setError(err);
|
|
150
|
+
setOptimisticData(void 0);
|
|
151
|
+
} else {
|
|
152
|
+
setData(result);
|
|
153
|
+
setOptimisticData(void 0);
|
|
154
|
+
}
|
|
155
|
+
return [err, result];
|
|
156
|
+
} catch (e) {
|
|
157
|
+
if (mountedRef.current) setOptimisticData(void 0);
|
|
158
|
+
throw e;
|
|
159
|
+
} finally {
|
|
160
|
+
if (mountedRef.current) setIsPending(false);
|
|
161
|
+
}
|
|
162
|
+
}, [action, options.optimistic]),
|
|
163
|
+
data,
|
|
164
|
+
optimisticData,
|
|
165
|
+
displayData: optimisticData ?? data,
|
|
166
|
+
error,
|
|
167
|
+
isPending
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function isProcedureDef(v) {
|
|
171
|
+
return typeof v === "object" && v !== null && "type" in v && "resolve" in v;
|
|
172
|
+
}
|
|
173
|
+
function isFrameworkError(error) {
|
|
174
|
+
if (typeof error !== "object" || error === null) return false;
|
|
175
|
+
if (typeof error.digest === "string" && error.digest.startsWith("NEXT_")) return true;
|
|
176
|
+
if (error.isNotFound === true) return true;
|
|
177
|
+
if (error instanceof Response) return true;
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
function defaultFormDataParser(formData) {
|
|
181
|
+
const result = {};
|
|
182
|
+
for (const [key, value] of formData.entries()) setNestedValue(result, key, value instanceof File && value.size === 0 ? void 0 : value);
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
function setNestedValue(obj, path, value) {
|
|
186
|
+
const keys = path.split("[").map((k) => k.replace("]", ""));
|
|
187
|
+
let current = obj;
|
|
188
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
189
|
+
const k = keys[i];
|
|
190
|
+
const next = keys[i + 1];
|
|
191
|
+
if (!(k in current)) current[k] = /^\d+$/.test(next) ? [] : {};
|
|
192
|
+
current = current[k];
|
|
193
|
+
}
|
|
194
|
+
current[keys[keys.length - 1]] = value;
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { createAction, createActions, createFormAction, useOptimisticServerAction, useServerAction };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Client, NestedClient } from "../../client/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/integrations/tanstack-query/index.d.ts
|
|
4
|
+
type OperationType = 'query' | 'infinite' | 'mutation';
|
|
5
|
+
type OperationKey = [path: readonly string[], options: {
|
|
6
|
+
type?: OperationType;
|
|
7
|
+
input?: unknown;
|
|
8
|
+
}];
|
|
9
|
+
declare function generateKey(path: readonly string[], options?: {
|
|
10
|
+
type?: OperationType;
|
|
11
|
+
input?: unknown;
|
|
12
|
+
}): OperationKey;
|
|
13
|
+
interface QueryOptionsIn<TInput, TOutput, _TError> {
|
|
14
|
+
input: TInput;
|
|
15
|
+
queryKey?: unknown[];
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
staleTime?: number;
|
|
18
|
+
gcTime?: number;
|
|
19
|
+
refetchInterval?: number | false;
|
|
20
|
+
retry?: boolean | number;
|
|
21
|
+
select?: (data: TOutput) => unknown;
|
|
22
|
+
}
|
|
23
|
+
interface MutationOptionsIn<TInput, TOutput, TError> {
|
|
24
|
+
onSuccess?: (data: TOutput, input: TInput) => void;
|
|
25
|
+
onError?: (error: TError, input: TInput) => void;
|
|
26
|
+
onSettled?: (data: TOutput | undefined, error: TError | null, input: TInput) => void;
|
|
27
|
+
retry?: boolean | number;
|
|
28
|
+
}
|
|
29
|
+
/** Sentinel value to disable a query in a type-safe way. */
|
|
30
|
+
declare const skipToken: unique symbol;
|
|
31
|
+
type SkipToken = typeof skipToken;
|
|
32
|
+
interface InfiniteQueryOptionsIn<TInput, TOutput, _TError, TPageParam> {
|
|
33
|
+
input: (pageParam: TPageParam) => TInput;
|
|
34
|
+
initialPageParam: TPageParam;
|
|
35
|
+
getNextPageParam: (lastPage: TOutput, allPages: TOutput[], lastPageParam: TPageParam) => TPageParam | undefined;
|
|
36
|
+
queryKey?: unknown[];
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
staleTime?: number;
|
|
39
|
+
gcTime?: number;
|
|
40
|
+
}
|
|
41
|
+
interface ProcedureQueryUtils<TInput, TOutput, TError> {
|
|
42
|
+
/** Direct call to the procedure */
|
|
43
|
+
call: (input: TInput, options?: {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}) => Promise<TOutput>;
|
|
46
|
+
/** Generate a query key for this procedure */
|
|
47
|
+
queryKey: (input?: TInput) => OperationKey;
|
|
48
|
+
/** Generate full query options for useQuery. Pass `skipToken` as input to disable. */
|
|
49
|
+
queryOptions: (options: QueryOptionsIn<TInput | SkipToken, TOutput, TError>) => {
|
|
50
|
+
queryKey: OperationKey;
|
|
51
|
+
queryFn: (ctx: {
|
|
52
|
+
signal: AbortSignal;
|
|
53
|
+
}) => Promise<TOutput>;
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
staleTime?: number;
|
|
56
|
+
gcTime?: number;
|
|
57
|
+
refetchInterval?: number | false;
|
|
58
|
+
retry?: boolean | number;
|
|
59
|
+
select?: (data: TOutput) => unknown;
|
|
60
|
+
};
|
|
61
|
+
/** Generate infinite query options for useInfiniteQuery */
|
|
62
|
+
infiniteOptions: <TPageParam = number>(options: InfiniteQueryOptionsIn<TInput, TOutput, TError, TPageParam>) => {
|
|
63
|
+
queryKey: OperationKey;
|
|
64
|
+
queryFn: (ctx: {
|
|
65
|
+
signal: AbortSignal;
|
|
66
|
+
pageParam: TPageParam;
|
|
67
|
+
}) => Promise<TOutput>;
|
|
68
|
+
initialPageParam: TPageParam;
|
|
69
|
+
getNextPageParam: (lastPage: TOutput, allPages: TOutput[], lastPageParam: TPageParam) => TPageParam | undefined;
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
staleTime?: number;
|
|
72
|
+
gcTime?: number;
|
|
73
|
+
};
|
|
74
|
+
/** Generate streamed query options — data appends to array as events arrive */
|
|
75
|
+
streamedOptions: (options: QueryOptionsIn<TInput, TOutput[], TError>) => {
|
|
76
|
+
queryKey: OperationKey;
|
|
77
|
+
queryFn: (ctx: {
|
|
78
|
+
signal: AbortSignal;
|
|
79
|
+
}) => Promise<TOutput[]>;
|
|
80
|
+
enabled?: boolean;
|
|
81
|
+
staleTime?: number;
|
|
82
|
+
gcTime?: number;
|
|
83
|
+
};
|
|
84
|
+
/** Generate live query options — latest event replaces previous data */
|
|
85
|
+
liveOptions: (options: QueryOptionsIn<TInput, TOutput, TError>) => {
|
|
86
|
+
queryKey: OperationKey;
|
|
87
|
+
queryFn: (ctx: {
|
|
88
|
+
signal: AbortSignal;
|
|
89
|
+
}) => Promise<TOutput>;
|
|
90
|
+
refetchInterval: number;
|
|
91
|
+
enabled?: boolean;
|
|
92
|
+
staleTime?: number;
|
|
93
|
+
gcTime?: number;
|
|
94
|
+
};
|
|
95
|
+
/** Generate a mutation key */
|
|
96
|
+
mutationKey: () => OperationKey;
|
|
97
|
+
/** Generate full mutation options for useMutation */
|
|
98
|
+
mutationOptions: (options?: MutationOptionsIn<TInput, TOutput, TError>) => {
|
|
99
|
+
mutationKey: OperationKey;
|
|
100
|
+
mutationFn: (input: TInput) => Promise<TOutput>;
|
|
101
|
+
onSuccess?: (data: TOutput, input: TInput) => void;
|
|
102
|
+
onError?: (error: TError, input: TInput) => void;
|
|
103
|
+
onSettled?: (data: TOutput | undefined, error: TError | null, input: TInput) => void;
|
|
104
|
+
retry?: boolean | number;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
interface GeneralUtils {
|
|
108
|
+
/** Generate a key prefix for bulk invalidation */
|
|
109
|
+
key: (input?: unknown) => OperationKey;
|
|
110
|
+
}
|
|
111
|
+
type QueryUtils<T extends NestedClient> = T extends Client<any, infer TInput, infer TOutput, infer TError> ? ProcedureQueryUtils<TInput, TOutput, TError> & GeneralUtils : T extends Record<string, NestedClient> ? { [K in keyof T]: QueryUtils<T[K]> } & GeneralUtils : GeneralUtils;
|
|
112
|
+
/**
|
|
113
|
+
* Create TanStack Query utilities from a Silgi client.
|
|
114
|
+
*
|
|
115
|
+
* Returns a recursive proxy that mirrors the client structure,
|
|
116
|
+
* with `.queryOptions()`, `.mutationOptions()`, `.queryKey()` at each level.
|
|
117
|
+
*/
|
|
118
|
+
declare function createQueryUtils<T extends NestedClient>(client: T, path?: readonly string[]): QueryUtils<T>;
|
|
119
|
+
//#endregion
|
|
120
|
+
export { GeneralUtils, InfiniteQueryOptionsIn, MutationOptionsIn, OperationKey, OperationType, ProcedureQueryUtils, QueryOptionsIn, QueryUtils, SkipToken, createQueryUtils, generateKey, skipToken };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//#region src/integrations/tanstack-query/index.ts
|
|
2
|
+
function generateKey(path, options) {
|
|
3
|
+
const keyOptions = {};
|
|
4
|
+
if (options?.type) keyOptions.type = options.type;
|
|
5
|
+
if (options?.input !== void 0) keyOptions.input = options.input;
|
|
6
|
+
return [path, keyOptions];
|
|
7
|
+
}
|
|
8
|
+
/** Sentinel value to disable a query in a type-safe way. */
|
|
9
|
+
const skipToken = Symbol.for("silgi.skipToken");
|
|
10
|
+
function createProcedureUtils(client, path) {
|
|
11
|
+
return {
|
|
12
|
+
call: (input, options) => client(input, options),
|
|
13
|
+
queryKey: (input) => generateKey(path, {
|
|
14
|
+
type: "query",
|
|
15
|
+
input
|
|
16
|
+
}),
|
|
17
|
+
queryOptions: (options) => {
|
|
18
|
+
const isSkipped = options.input === skipToken;
|
|
19
|
+
return {
|
|
20
|
+
queryKey: options.queryKey ? options.queryKey : generateKey(path, {
|
|
21
|
+
type: "query",
|
|
22
|
+
input: isSkipped ? void 0 : options.input
|
|
23
|
+
}),
|
|
24
|
+
queryFn: ({ signal }) => client(options.input, { signal }),
|
|
25
|
+
...isSkipped && { enabled: false },
|
|
26
|
+
...options.enabled !== void 0 && { enabled: options.enabled },
|
|
27
|
+
...options.staleTime !== void 0 && { staleTime: options.staleTime },
|
|
28
|
+
...options.gcTime !== void 0 && { gcTime: options.gcTime },
|
|
29
|
+
...options.refetchInterval !== void 0 && { refetchInterval: options.refetchInterval },
|
|
30
|
+
...options.retry !== void 0 && { retry: options.retry },
|
|
31
|
+
...options.select !== void 0 && { select: options.select }
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
infiniteOptions: (options) => ({
|
|
35
|
+
queryKey: options.queryKey ? options.queryKey : generateKey(path, { type: "infinite" }),
|
|
36
|
+
queryFn: ({ signal, pageParam }) => client(options.input(pageParam), { signal }),
|
|
37
|
+
initialPageParam: options.initialPageParam,
|
|
38
|
+
getNextPageParam: options.getNextPageParam,
|
|
39
|
+
...options.enabled !== void 0 && { enabled: options.enabled },
|
|
40
|
+
...options.staleTime !== void 0 && { staleTime: options.staleTime },
|
|
41
|
+
...options.gcTime !== void 0 && { gcTime: options.gcTime }
|
|
42
|
+
}),
|
|
43
|
+
streamedOptions: (options) => ({
|
|
44
|
+
queryKey: options.queryKey ? options.queryKey : generateKey(path, {
|
|
45
|
+
type: "query",
|
|
46
|
+
input: options.input
|
|
47
|
+
}),
|
|
48
|
+
queryFn: async ({ signal }) => {
|
|
49
|
+
const result = await client(options.input, { signal });
|
|
50
|
+
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
51
|
+
const items = [];
|
|
52
|
+
for await (const item of result) items.push(item);
|
|
53
|
+
return items;
|
|
54
|
+
}
|
|
55
|
+
return Array.isArray(result) ? result : [result];
|
|
56
|
+
},
|
|
57
|
+
...options.enabled !== void 0 && { enabled: options.enabled },
|
|
58
|
+
...options.staleTime !== void 0 && { staleTime: options.staleTime },
|
|
59
|
+
...options.gcTime !== void 0 && { gcTime: options.gcTime }
|
|
60
|
+
}),
|
|
61
|
+
liveOptions: (options) => ({
|
|
62
|
+
queryKey: options.queryKey ? options.queryKey : generateKey(path, {
|
|
63
|
+
type: "query",
|
|
64
|
+
input: options.input
|
|
65
|
+
}),
|
|
66
|
+
queryFn: ({ signal }) => client(options.input, { signal }),
|
|
67
|
+
refetchInterval: options.refetchInterval ?? 1e3,
|
|
68
|
+
...options.enabled !== void 0 && { enabled: options.enabled },
|
|
69
|
+
...options.staleTime !== void 0 && { staleTime: options.staleTime },
|
|
70
|
+
...options.gcTime !== void 0 && { gcTime: options.gcTime }
|
|
71
|
+
}),
|
|
72
|
+
mutationKey: () => generateKey(path, { type: "mutation" }),
|
|
73
|
+
mutationOptions: (options) => ({
|
|
74
|
+
mutationKey: generateKey(path, { type: "mutation" }),
|
|
75
|
+
mutationFn: (input) => client(input),
|
|
76
|
+
...options
|
|
77
|
+
})
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create TanStack Query utilities from a Silgi client.
|
|
82
|
+
*
|
|
83
|
+
* Returns a recursive proxy that mirrors the client structure,
|
|
84
|
+
* with `.queryOptions()`, `.mutationOptions()`, `.queryKey()` at each level.
|
|
85
|
+
*/
|
|
86
|
+
function createQueryUtils(client, path = []) {
|
|
87
|
+
const generalUtils = { key: (input) => generateKey(path, input !== void 0 ? { input } : void 0) };
|
|
88
|
+
const procedureUtils = typeof client === "function" ? createProcedureUtils(client, path) : {};
|
|
89
|
+
return new Proxy({}, { get(_target, prop) {
|
|
90
|
+
if (prop === "then") return void 0;
|
|
91
|
+
if (prop === "key") return generalUtils.key;
|
|
92
|
+
if (typeof prop === "string" && prop in procedureUtils) return procedureUtils[prop];
|
|
93
|
+
if (typeof prop === "string") {
|
|
94
|
+
const child = client[prop];
|
|
95
|
+
if (child) return createQueryUtils(child, [...path, prop]);
|
|
96
|
+
}
|
|
97
|
+
} });
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { createQueryUtils, generateKey, skipToken };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/integrations/tanstack-query/ssr.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* SSR Hydration for TanStack Query — prevent refetch waterfalls.
|
|
4
|
+
*
|
|
5
|
+
* Prefetch queries on the server and dehydrate them for the client.
|
|
6
|
+
* The client hydrates without refetching — zero waterfall.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // Server (SSR)
|
|
11
|
+
* import { prefetchQueries, dehydrate } from "silgi/tanstack-query/ssr"
|
|
12
|
+
*
|
|
13
|
+
* const queryClient = new QueryClient()
|
|
14
|
+
* await prefetchQueries(queryClient, utils, [
|
|
15
|
+
* utils.users.list.queryOptions({ input: { limit: 10 } }),
|
|
16
|
+
* utils.health.queryOptions({ input: undefined }),
|
|
17
|
+
* ])
|
|
18
|
+
* const dehydratedState = dehydrate(queryClient)
|
|
19
|
+
*
|
|
20
|
+
* // Client
|
|
21
|
+
* <HydrationBoundary state={dehydratedState}>
|
|
22
|
+
* <App />
|
|
23
|
+
* </HydrationBoundary>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Prefetch multiple queries on the server.
|
|
28
|
+
* Pass the same options you'd use with `useQuery`.
|
|
29
|
+
*/
|
|
30
|
+
declare function prefetchQueries(queryClient: any, ...optionsArray: Array<{
|
|
31
|
+
queryKey: unknown;
|
|
32
|
+
queryFn: Function;
|
|
33
|
+
} | Array<{
|
|
34
|
+
queryKey: unknown;
|
|
35
|
+
queryFn: Function;
|
|
36
|
+
}>>): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Dehydrate the query client for SSR transfer.
|
|
39
|
+
* Wraps TanStack Query's dehydrate with Silgi-aware serialization.
|
|
40
|
+
*/
|
|
41
|
+
declare function dehydrate(queryClient: any): unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Create a custom serializer for SSR hydration that handles
|
|
44
|
+
* Silgi-specific types (Date, Map, etc.) during dehydration.
|
|
45
|
+
*/
|
|
46
|
+
declare function createSSRSerializer(): {
|
|
47
|
+
serialize: (value: unknown) => string;
|
|
48
|
+
deserialize: (text: string) => unknown;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { createSSRSerializer, dehydrate, prefetchQueries };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//#region src/integrations/tanstack-query/ssr.ts
|
|
2
|
+
/**
|
|
3
|
+
* SSR Hydration for TanStack Query — prevent refetch waterfalls.
|
|
4
|
+
*
|
|
5
|
+
* Prefetch queries on the server and dehydrate them for the client.
|
|
6
|
+
* The client hydrates without refetching — zero waterfall.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // Server (SSR)
|
|
11
|
+
* import { prefetchQueries, dehydrate } from "silgi/tanstack-query/ssr"
|
|
12
|
+
*
|
|
13
|
+
* const queryClient = new QueryClient()
|
|
14
|
+
* await prefetchQueries(queryClient, utils, [
|
|
15
|
+
* utils.users.list.queryOptions({ input: { limit: 10 } }),
|
|
16
|
+
* utils.health.queryOptions({ input: undefined }),
|
|
17
|
+
* ])
|
|
18
|
+
* const dehydratedState = dehydrate(queryClient)
|
|
19
|
+
*
|
|
20
|
+
* // Client
|
|
21
|
+
* <HydrationBoundary state={dehydratedState}>
|
|
22
|
+
* <App />
|
|
23
|
+
* </HydrationBoundary>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Prefetch multiple queries on the server.
|
|
28
|
+
* Pass the same options you'd use with `useQuery`.
|
|
29
|
+
*/
|
|
30
|
+
async function prefetchQueries(queryClient, ...optionsArray) {
|
|
31
|
+
const flat = optionsArray.flat();
|
|
32
|
+
await Promise.all(flat.map((opts) => queryClient.prefetchQuery(opts)));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Dehydrate the query client for SSR transfer.
|
|
36
|
+
* Wraps TanStack Query's dehydrate with Silgi-aware serialization.
|
|
37
|
+
*/
|
|
38
|
+
function dehydrate(queryClient) {
|
|
39
|
+
if (typeof queryClient.dehydrate === "function") return queryClient.dehydrate();
|
|
40
|
+
const cache = queryClient.getQueryCache?.();
|
|
41
|
+
if (!cache) return {};
|
|
42
|
+
return { queries: cache.getAll().map((query) => ({
|
|
43
|
+
queryKey: query.queryKey,
|
|
44
|
+
state: query.state
|
|
45
|
+
})) };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a custom serializer for SSR hydration that handles
|
|
49
|
+
* Silgi-specific types (Date, Map, etc.) during dehydration.
|
|
50
|
+
*/
|
|
51
|
+
function createSSRSerializer() {
|
|
52
|
+
return {
|
|
53
|
+
serialize: (value) => {
|
|
54
|
+
return JSON.stringify(value, function(_key, val) {
|
|
55
|
+
const original = this[_key];
|
|
56
|
+
if (original instanceof Date) return {
|
|
57
|
+
__type: "Date",
|
|
58
|
+
value: original.toISOString()
|
|
59
|
+
};
|
|
60
|
+
if (original instanceof Map) return {
|
|
61
|
+
__type: "Map",
|
|
62
|
+
value: Array.from(original.entries())
|
|
63
|
+
};
|
|
64
|
+
if (original instanceof Set) return {
|
|
65
|
+
__type: "Set",
|
|
66
|
+
value: Array.from(original)
|
|
67
|
+
};
|
|
68
|
+
if (typeof original === "bigint") return {
|
|
69
|
+
__type: "BigInt",
|
|
70
|
+
value: original.toString()
|
|
71
|
+
};
|
|
72
|
+
return val;
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
deserialize: (text) => {
|
|
76
|
+
return JSON.parse(text, (_key, val) => {
|
|
77
|
+
if (val && typeof val === "object" && "__type" in val) switch (val.__type) {
|
|
78
|
+
case "Date": return new Date(val.value);
|
|
79
|
+
case "Map": return new Map(val.value);
|
|
80
|
+
case "Set": return new Set(val.value);
|
|
81
|
+
case "BigInt": return BigInt(val.value);
|
|
82
|
+
}
|
|
83
|
+
return val;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { createSSRSerializer, dehydrate, prefetchQueries };
|