silgi 0.0.14 → 0.1.0-beta.10
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/_fetch-adapter.d.mts +18 -0
- package/dist/adapters/_fetch-adapter.mjs +53 -0
- package/dist/adapters/astro.d.mts +15 -0
- package/dist/adapters/astro.mjs +31 -0
- package/dist/adapters/aws-lambda.d.mts +42 -0
- package/dist/adapters/aws-lambda.mjs +92 -0
- package/dist/adapters/express.d.mts +16 -0
- package/dist/adapters/express.mjs +120 -0
- package/dist/adapters/message-port.d.mts +42 -0
- package/dist/adapters/message-port.mjs +132 -0
- package/dist/adapters/nestjs.d.mts +25 -0
- package/dist/adapters/nestjs.mjs +75 -0
- package/dist/adapters/nextjs.d.mts +14 -0
- package/dist/adapters/nextjs.mjs +29 -0
- package/dist/adapters/peer.d.mts +27 -0
- package/dist/adapters/peer.mjs +36 -0
- package/dist/adapters/remix.d.mts +15 -0
- package/dist/adapters/remix.mjs +30 -0
- package/dist/adapters/solidstart.d.mts +12 -0
- package/dist/adapters/solidstart.mjs +29 -0
- package/dist/adapters/sveltekit.d.mts +14 -0
- package/dist/adapters/sveltekit.mjs +30 -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 +55 -0
- package/dist/builder.mjs +70 -0
- package/dist/callable.d.mts +19 -0
- package/dist/callable.mjs +42 -0
- package/dist/caller.mjs +90 -0
- package/dist/client/adapters/fetch/index.d.mts +19 -0
- package/dist/client/adapters/fetch/index.mjs +82 -0
- package/dist/client/adapters/ofetch/index.d.mts +57 -0
- package/dist/client/adapters/ofetch/index.mjs +100 -0
- package/dist/client/adapters/websocket/index.d.mts +20 -0
- package/dist/client/adapters/websocket/index.mjs +101 -0
- package/dist/client/client.d.mts +37 -0
- package/dist/client/client.mjs +80 -0
- package/dist/client/consume.d.mts +50 -0
- package/dist/client/consume.mjs +66 -0
- package/dist/client/dynamic-link.d.mts +16 -0
- package/dist/client/dynamic-link.mjs +19 -0
- package/dist/client/index.d.mts +6 -0
- package/dist/client/index.mjs +5 -0
- package/dist/client/interceptor.d.mts +31 -0
- package/dist/client/interceptor.mjs +34 -0
- package/dist/client/openapi.d.mts +29 -0
- package/dist/client/openapi.mjs +89 -0
- package/dist/client/plugins/batch.d.mts +26 -0
- package/dist/client/plugins/batch.mjs +64 -0
- package/dist/client/plugins/circuit-breaker.d.mts +24 -0
- package/dist/client/plugins/circuit-breaker.mjs +60 -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 +8 -0
- package/dist/client/plugins/index.mjs +8 -0
- package/dist/client/plugins/otel.d.mts +12 -0
- package/dist/client/plugins/otel.mjs +27 -0
- package/dist/client/plugins/retry.d.mts +34 -0
- package/dist/client/plugins/retry.mjs +79 -0
- package/dist/client/plugins/timeout.d.mts +10 -0
- package/dist/client/plugins/timeout.mjs +14 -0
- package/dist/client/server.d.mts +16 -0
- package/dist/client/server.mjs +62 -0
- package/dist/client/types.d.mts +29 -0
- package/dist/codec/devalue.d.mts +21 -0
- package/dist/codec/devalue.mjs +33 -0
- package/dist/codec/msgpack.d.mts +18 -0
- package/dist/codec/msgpack.mjs +45 -0
- package/dist/codec/sanitize.mjs +38 -0
- package/dist/compile.d.mts +46 -0
- package/dist/compile.mjs +332 -0
- package/dist/contract.d.mts +36 -0
- package/dist/contract.mjs +40 -0
- package/dist/core/codec.mjs +67 -0
- package/dist/core/dispatch.mjs +62 -0
- package/dist/core/error.d.mts +104 -0
- package/dist/core/error.mjs +139 -0
- package/dist/core/handler.d.mts +6 -0
- package/dist/core/handler.mjs +148 -0
- package/dist/core/input.mjs +49 -0
- package/dist/core/iterator.d.mts +17 -0
- package/dist/core/iterator.mjs +79 -0
- package/dist/core/router-utils.d.mts +25 -0
- package/dist/core/router-utils.mjs +98 -0
- package/dist/core/schema.d.mts +20 -0
- package/dist/core/schema.mjs +33 -0
- package/dist/core/serve.d.mts +51 -0
- package/dist/core/serve.mjs +76 -0
- package/dist/core/sse.d.mts +18 -0
- package/dist/core/sse.mjs +98 -0
- package/dist/core/storage.d.mts +20 -0
- package/dist/core/storage.mjs +61 -0
- package/dist/core/task.d.mts +62 -0
- package/dist/core/task.mjs +165 -0
- package/dist/core/utils.mjs +24 -0
- package/dist/index.d.mts +18 -37
- package/dist/index.mjs +15 -7
- package/dist/integrations/ai/index.d.mts +25 -0
- package/dist/integrations/ai/index.mjs +117 -0
- package/dist/integrations/better-auth/index.d.mts +61 -0
- package/dist/integrations/better-auth/index.mjs +332 -0
- package/dist/integrations/drizzle/index.d.mts +27 -0
- package/dist/integrations/drizzle/index.mjs +286 -0
- package/dist/integrations/hey-api/index.d.mts +2 -0
- package/dist/integrations/hey-api/index.mjs +2 -0
- package/dist/integrations/hey-api/to-client.d.mts +20 -0
- package/dist/integrations/hey-api/to-client.mjs +39 -0
- package/dist/integrations/pinia-colada/general-utils.d.mts +13 -0
- package/dist/integrations/pinia-colada/general-utils.mjs +9 -0
- package/dist/integrations/pinia-colada/index.d.mts +6 -0
- package/dist/integrations/pinia-colada/index.mjs +5 -0
- package/dist/integrations/pinia-colada/key.d.mts +11 -0
- package/dist/integrations/pinia-colada/key.mjs +11 -0
- package/dist/integrations/pinia-colada/procedure-utils.d.mts +25 -0
- package/dist/integrations/pinia-colada/procedure-utils.mjs +33 -0
- package/dist/integrations/pinia-colada/router-utils.d.mts +17 -0
- package/dist/integrations/pinia-colada/router-utils.mjs +30 -0
- package/dist/integrations/pinia-colada/types.d.mts +25 -0
- package/dist/integrations/react/index.d.mts +83 -0
- package/dist/integrations/react/index.mjs +196 -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 +60 -0
- package/dist/integrations/tanstack-query/ssr.mjs +102 -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 +22 -0
- package/dist/lazy.mjs +34 -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 +47 -0
- package/dist/plugins/analytics.d.mts +236 -0
- package/dist/plugins/analytics.mjs +816 -0
- package/dist/plugins/batch-server.d.mts +20 -0
- package/dist/plugins/batch-server.mjs +91 -0
- package/dist/plugins/body-limit.d.mts +19 -0
- package/dist/plugins/body-limit.mjs +49 -0
- package/dist/plugins/cache.d.mts +170 -0
- package/dist/plugins/cache.mjs +212 -0
- package/dist/plugins/coerce.d.mts +24 -0
- package/dist/plugins/coerce.mjs +70 -0
- package/dist/plugins/cookies.d.mts +14 -0
- package/dist/plugins/cookies.mjs +48 -0
- package/dist/plugins/cors.d.mts +43 -0
- package/dist/plugins/cors.mjs +62 -0
- package/dist/plugins/file-upload.d.mts +38 -0
- package/dist/plugins/file-upload.mjs +102 -0
- package/dist/plugins/index.d.mts +14 -0
- package/dist/plugins/index.mjs +14 -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 +53 -0
- package/dist/plugins/ratelimit.mjs +92 -0
- package/dist/plugins/signing.d.mts +41 -0
- package/dist/plugins/signing.mjs +118 -0
- package/dist/plugins/strict-get.d.mts +10 -0
- package/dist/plugins/strict-get.mjs +33 -0
- package/dist/scalar.d.mts +49 -0
- package/dist/scalar.mjs +311 -0
- package/dist/silgi.d.mts +144 -0
- package/dist/silgi.mjs +156 -0
- package/dist/trpc-interop.d.mts +22 -0
- package/dist/trpc-interop.mjs +68 -0
- package/dist/types.d.mts +108 -0
- package/dist/ws.d.mts +88 -0
- package/dist/ws.mjs +205 -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 +324 -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
package/dist/silgi.d.mts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { AnySchema, InferSchemaInput, InferSchemaOutput } from "./core/schema.mjs";
|
|
2
|
+
import { ErrorDef, GuardDef, GuardFn, InferClient, ProcedureDef, ResolveContext, RouterDef, WrapDef, WrapFn } from "./types.mjs";
|
|
3
|
+
import { ProcedureBuilder } from "./builder.mjs";
|
|
4
|
+
import { AnalyticsOptions } from "./plugins/analytics.mjs";
|
|
5
|
+
import { ScalarOptions } from "./scalar.mjs";
|
|
6
|
+
import { ServeOptions, SilgiServer } from "./core/serve.mjs";
|
|
7
|
+
import { StorageConfig, useStorage } from "./core/storage.mjs";
|
|
8
|
+
import { Hookable } from "hookable";
|
|
9
|
+
|
|
10
|
+
//#region src/silgi.d.ts
|
|
11
|
+
interface SilgiHooks {
|
|
12
|
+
/** Called before a request is processed */
|
|
13
|
+
request: (event: {
|
|
14
|
+
path: string;
|
|
15
|
+
input: unknown;
|
|
16
|
+
}) => void;
|
|
17
|
+
/** Called after a successful response */
|
|
18
|
+
response: (event: {
|
|
19
|
+
path: string;
|
|
20
|
+
output: unknown;
|
|
21
|
+
durationMs: number;
|
|
22
|
+
}) => void;
|
|
23
|
+
/** Called when an error occurs */
|
|
24
|
+
error: (event: {
|
|
25
|
+
path: string;
|
|
26
|
+
error: unknown;
|
|
27
|
+
}) => void;
|
|
28
|
+
/** Called when the server starts */
|
|
29
|
+
'serve:start': (event: {
|
|
30
|
+
url: string;
|
|
31
|
+
port: number;
|
|
32
|
+
hostname: string;
|
|
33
|
+
}) => void;
|
|
34
|
+
/** Called when the server is shutting down */
|
|
35
|
+
'serve:stop': (event: {
|
|
36
|
+
url: string;
|
|
37
|
+
port: number;
|
|
38
|
+
hostname: string;
|
|
39
|
+
}) => void;
|
|
40
|
+
}
|
|
41
|
+
interface SilgiConfig<TCtx extends Record<string, unknown>> {
|
|
42
|
+
context: (req: Request) => TCtx | Promise<TCtx>;
|
|
43
|
+
/** Register lifecycle hooks */
|
|
44
|
+
hooks?: Partial<{ [K in keyof SilgiHooks]: SilgiHooks[K] | SilgiHooks[K][] }>;
|
|
45
|
+
/**
|
|
46
|
+
* Storage configuration — mount drivers by path prefix.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* import redisDriver from 'unstorage/drivers/redis'
|
|
50
|
+
* import memoryDriver from 'unstorage/drivers/memory'
|
|
51
|
+
*
|
|
52
|
+
* storage: {
|
|
53
|
+
* cache: redisDriver({ url: 'redis://localhost' }),
|
|
54
|
+
* sessions: memoryDriver(),
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* Or pass a pre-built unstorage instance:
|
|
59
|
+
* ```ts
|
|
60
|
+
* storage: myStorageInstance
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
storage?: StorageConfig;
|
|
64
|
+
}
|
|
65
|
+
interface SilgiInstance<TBaseCtx extends Record<string, unknown>> {
|
|
66
|
+
/** Register a lifecycle hook */
|
|
67
|
+
hook: Hookable<SilgiHooks>['hook'];
|
|
68
|
+
/** Remove a lifecycle hook */
|
|
69
|
+
removeHook: Hookable<SilgiHooks>['removeHook'];
|
|
70
|
+
/** Access storage with optional prefix — uses configured mounts */
|
|
71
|
+
useStorage: typeof useStorage;
|
|
72
|
+
/** Create a guard middleware (flat, zero-closure) */
|
|
73
|
+
guard: GuardFactory<TBaseCtx>;
|
|
74
|
+
/** Create a wrap middleware (onion, before+after) */
|
|
75
|
+
wrap: (fn: WrapFn<TBaseCtx>) => WrapDef<TBaseCtx>;
|
|
76
|
+
/** Start a builder — resolve only */
|
|
77
|
+
$resolve: ProcedureBuilder<'query', TBaseCtx>['$resolve'];
|
|
78
|
+
/** Start a builder — set input schema */
|
|
79
|
+
$input: ProcedureBuilder<'query', TBaseCtx>['$input'];
|
|
80
|
+
/** Start a builder — add middleware */
|
|
81
|
+
$use: ProcedureBuilder<'query', TBaseCtx>['$use'];
|
|
82
|
+
/** Start a builder — set output schema */
|
|
83
|
+
$output: ProcedureBuilder<'query', TBaseCtx>['$output'];
|
|
84
|
+
/** Start a builder — set errors */
|
|
85
|
+
$errors: ProcedureBuilder<'query', TBaseCtx>['$errors'];
|
|
86
|
+
/** Start a builder — set route metadata */
|
|
87
|
+
$route: ProcedureBuilder<'query', TBaseCtx>['$route'];
|
|
88
|
+
/** Start a builder — set custom metadata */
|
|
89
|
+
$meta: ProcedureBuilder<'query', TBaseCtx>['$meta'];
|
|
90
|
+
/** Define a subscription (SSE stream) */
|
|
91
|
+
subscription: SubscriptionFactory<TBaseCtx>;
|
|
92
|
+
/** Start a builder — create a background task */
|
|
93
|
+
$task: ProcedureBuilder<'query', TBaseCtx>['$task'];
|
|
94
|
+
/** Assemble router and compile pipelines */
|
|
95
|
+
router: <T extends RouterDef>(def: T) => T;
|
|
96
|
+
/** Create a Fetch API handler: (Request) => Response */
|
|
97
|
+
handler: (router: RouterDef, options?: {
|
|
98
|
+
/** Enable Scalar API Reference UI at /reference and /openapi.json */scalar?: boolean | ScalarOptions; /** Enable analytics dashboard at /analytics */
|
|
99
|
+
analytics?: boolean | AnalyticsOptions;
|
|
100
|
+
}) => (request: Request) => Response | Promise<Response>;
|
|
101
|
+
/** Create a direct caller — call procedures without HTTP. For testing and server-side usage. */
|
|
102
|
+
createCaller: <T extends RouterDef>(router: T, options?: {
|
|
103
|
+
/** Override or extend the base context */contextOverride?: Record<string, unknown>; /** Mock request headers */
|
|
104
|
+
headers?: Record<string, string>; /** Default timeout in ms (default: 30000, null = no timeout) */
|
|
105
|
+
timeout?: number | null;
|
|
106
|
+
}) => InferClient<T>;
|
|
107
|
+
/** Create & start a Node.js HTTP server. Returns a handle to gracefully shut down. */
|
|
108
|
+
serve: (router: RouterDef, options?: ServeOptions) => Promise<SilgiServer>;
|
|
109
|
+
}
|
|
110
|
+
interface GuardConfig<TBaseCtx, TReturn extends Record<string, unknown> | void, TErrors extends ErrorDef> {
|
|
111
|
+
errors?: TErrors;
|
|
112
|
+
fn: GuardFn<TBaseCtx, TReturn>;
|
|
113
|
+
}
|
|
114
|
+
interface GuardFactory<TBaseCtx> {
|
|
115
|
+
/** Simple guard: guard(fn) */
|
|
116
|
+
<TReturn extends Record<string, unknown> | void>(fn: GuardFn<TBaseCtx, TReturn>): GuardDef<TBaseCtx, TReturn, {}>;
|
|
117
|
+
/** Guard with typed errors: guard({ errors, fn }) */
|
|
118
|
+
<TReturn extends Record<string, unknown> | void, TErrors extends ErrorDef>(config: GuardConfig<TBaseCtx, TReturn, TErrors>): GuardDef<TBaseCtx, TReturn, TErrors>;
|
|
119
|
+
}
|
|
120
|
+
interface SubscriptionFactory<TBaseCtx extends Record<string, unknown>> {
|
|
121
|
+
/** Builder: `subscription()` — returns chainable builder */
|
|
122
|
+
(): ProcedureBuilder<'subscription', TBaseCtx>;
|
|
123
|
+
/** Short: `subscription(resolve)` */
|
|
124
|
+
<TOutput>(resolve: (opts: ResolveContext<TBaseCtx, undefined, {}>) => AsyncIterableIterator<TOutput>): ProcedureDef<'subscription', undefined, TOutput, {}>;
|
|
125
|
+
/** Short: `subscription(input, resolve)` */
|
|
126
|
+
<TSchema extends AnySchema, TOutput>(input: TSchema, resolve: (opts: ResolveContext<TBaseCtx, InferSchemaOutput<TSchema>, {}>) => AsyncIterableIterator<TOutput>): ProcedureDef<'subscription', InferSchemaInput<TSchema>, TOutput, {}>;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Create a Silgi RPC instance with typed context.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const k = silgi({
|
|
134
|
+
* context: (req) => ({ db: getDB(), user: getUser(req) }),
|
|
135
|
+
* hooks: {
|
|
136
|
+
* request: ({ path }) => console.log(`-> ${path}`),
|
|
137
|
+
* },
|
|
138
|
+
* })
|
|
139
|
+
* const { query, mutation, guard, wrap, router, serve } = k
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function silgi<TBaseCtx extends Record<string, unknown>>(config: SilgiConfig<TBaseCtx>): SilgiInstance<TBaseCtx>;
|
|
143
|
+
//#endregion
|
|
144
|
+
export { SilgiConfig, SilgiInstance, silgi };
|
package/dist/silgi.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createTaskFromProcedure } from "./core/task.mjs";
|
|
2
|
+
import { createProcedureBuilder } from "./builder.mjs";
|
|
3
|
+
import { assignPaths, routerCache } from "./core/router-utils.mjs";
|
|
4
|
+
import { compileRouter } from "./compile.mjs";
|
|
5
|
+
import { createCaller } from "./caller.mjs";
|
|
6
|
+
import { createFetchHandler } from "./core/handler.mjs";
|
|
7
|
+
import { createHooks } from "hookable";
|
|
8
|
+
//#region src/silgi.ts
|
|
9
|
+
/**
|
|
10
|
+
* silgi() — the main entry point.
|
|
11
|
+
*
|
|
12
|
+
* Creates a Silgi instance with typed context.
|
|
13
|
+
* All procedure/middleware factories are methods on this instance,
|
|
14
|
+
* so context type flows automatically.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const k = silgi({ context: (req) => ({ db, headers }) })
|
|
18
|
+
* export const { query, mutation, guard, wrap, router, handler } = k
|
|
19
|
+
*/
|
|
20
|
+
function createProcedure(type, ...args) {
|
|
21
|
+
if (args.length === 0) return createProcedureBuilder(type);
|
|
22
|
+
if (args.length === 1 && typeof args[0] === "function") return {
|
|
23
|
+
type,
|
|
24
|
+
input: null,
|
|
25
|
+
output: null,
|
|
26
|
+
errors: null,
|
|
27
|
+
use: null,
|
|
28
|
+
resolve: args[0],
|
|
29
|
+
route: null,
|
|
30
|
+
meta: null
|
|
31
|
+
};
|
|
32
|
+
if (args.length === 2 && typeof args[1] === "function") return {
|
|
33
|
+
type,
|
|
34
|
+
input: args[0],
|
|
35
|
+
output: null,
|
|
36
|
+
errors: null,
|
|
37
|
+
use: null,
|
|
38
|
+
resolve: args[1],
|
|
39
|
+
route: null,
|
|
40
|
+
meta: null
|
|
41
|
+
};
|
|
42
|
+
throw new TypeError(`Invalid arguments for ${type}()`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a Silgi RPC instance with typed context.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const k = silgi({
|
|
50
|
+
* context: (req) => ({ db: getDB(), user: getUser(req) }),
|
|
51
|
+
* hooks: {
|
|
52
|
+
* request: ({ path }) => console.log(`-> ${path}`),
|
|
53
|
+
* },
|
|
54
|
+
* })
|
|
55
|
+
* const { query, mutation, guard, wrap, router, serve } = k
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
function silgi(config) {
|
|
59
|
+
const contextFactory = config.context;
|
|
60
|
+
const hooks = createHooks();
|
|
61
|
+
if (config.hooks) {
|
|
62
|
+
for (const [name, fn] of Object.entries(config.hooks)) if (Array.isArray(fn)) for (const f of fn) hooks.hook(name, f);
|
|
63
|
+
else if (fn) hooks.hook(name, fn);
|
|
64
|
+
}
|
|
65
|
+
if (config.storage) import("./core/storage.mjs").then((m) => m.initStorage(config.storage)).catch((e) => {
|
|
66
|
+
console.error(`[silgi] Failed to initialize storage: ${e instanceof Error ? e.message : e}`);
|
|
67
|
+
});
|
|
68
|
+
const ctxFactory = () => contextFactory(new Request("http://localhost"));
|
|
69
|
+
return {
|
|
70
|
+
hook: hooks.hook.bind(hooks),
|
|
71
|
+
removeHook: hooks.removeHook.bind(hooks),
|
|
72
|
+
useStorage: (...args) => {
|
|
73
|
+
return import("./core/storage.mjs").then((m) => m.useStorage(...args));
|
|
74
|
+
},
|
|
75
|
+
guard: (fnOrConfig) => {
|
|
76
|
+
if (typeof fnOrConfig === "function") return {
|
|
77
|
+
kind: "guard",
|
|
78
|
+
fn: fnOrConfig
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
kind: "guard",
|
|
82
|
+
fn: fnOrConfig.fn,
|
|
83
|
+
errors: fnOrConfig.errors
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
wrap: (fn) => ({
|
|
87
|
+
kind: "wrap",
|
|
88
|
+
fn
|
|
89
|
+
}),
|
|
90
|
+
$resolve: ((fn) => createProcedure("query", fn)),
|
|
91
|
+
$input: ((schema) => createProcedureBuilder("query", ctxFactory).$input(schema)),
|
|
92
|
+
$use: ((...middleware) => {
|
|
93
|
+
const b = createProcedureBuilder("query", ctxFactory);
|
|
94
|
+
for (const m of middleware) b.$use(m);
|
|
95
|
+
return b;
|
|
96
|
+
}),
|
|
97
|
+
$output: ((schema) => createProcedureBuilder("query", ctxFactory).$output(schema)),
|
|
98
|
+
$errors: ((errors) => createProcedureBuilder("query", ctxFactory).$errors(errors)),
|
|
99
|
+
$route: ((route) => createProcedureBuilder("query", ctxFactory).$route(route)),
|
|
100
|
+
$meta: ((meta) => createProcedureBuilder("query", ctxFactory).$meta(meta)),
|
|
101
|
+
subscription: ((...args) => createProcedure("subscription", ...args)),
|
|
102
|
+
$task: ((config) => {
|
|
103
|
+
return createTaskFromProcedure(config, config.resolve, null, null, () => contextFactory(new Request("http://localhost")));
|
|
104
|
+
}),
|
|
105
|
+
router: (def) => {
|
|
106
|
+
const assigned = assignPaths(def);
|
|
107
|
+
const flat = compileRouter(assigned);
|
|
108
|
+
routerCache.set(def, flat);
|
|
109
|
+
routerCache.set(assigned, flat);
|
|
110
|
+
return def;
|
|
111
|
+
},
|
|
112
|
+
createCaller: (routerDef, options) => {
|
|
113
|
+
return createCaller(routerDef, contextFactory, options);
|
|
114
|
+
},
|
|
115
|
+
handler: (routerDef, options) => {
|
|
116
|
+
let fetchHandler;
|
|
117
|
+
let initPromise;
|
|
118
|
+
async function init() {
|
|
119
|
+
let h = createFetchHandler(routerDef, contextFactory, hooks);
|
|
120
|
+
if (options?.scalar) {
|
|
121
|
+
const { wrapWithScalar } = await import("./scalar.mjs");
|
|
122
|
+
const scalarOpts = typeof options.scalar === "object" ? options.scalar : {};
|
|
123
|
+
h = wrapWithScalar(h, routerDef, scalarOpts);
|
|
124
|
+
}
|
|
125
|
+
if (options?.analytics) {
|
|
126
|
+
const { wrapWithAnalytics } = await import("./plugins/analytics.mjs");
|
|
127
|
+
const analyticsOpts = typeof options.analytics === "object" ? options.analytics : {};
|
|
128
|
+
h = wrapWithAnalytics(h, analyticsOpts);
|
|
129
|
+
}
|
|
130
|
+
fetchHandler = h;
|
|
131
|
+
}
|
|
132
|
+
return (request) => {
|
|
133
|
+
if (fetchHandler) return fetchHandler(request);
|
|
134
|
+
if (!options?.scalar && !options?.analytics) {
|
|
135
|
+
fetchHandler = createFetchHandler(routerDef, contextFactory, hooks);
|
|
136
|
+
return fetchHandler(request);
|
|
137
|
+
}
|
|
138
|
+
initPromise ??= init();
|
|
139
|
+
return initPromise.then(() => fetchHandler(request));
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
serve: async (routerDef, options) => {
|
|
143
|
+
const { createServeHandler } = await import("./core/serve.mjs");
|
|
144
|
+
const server = await createServeHandler(routerDef, contextFactory, hooks, options);
|
|
145
|
+
const { collectCronTasks, startCronJobs } = await import("./core/task.mjs");
|
|
146
|
+
const cronTasks = collectCronTasks(routerDef);
|
|
147
|
+
if (cronTasks.length > 0) {
|
|
148
|
+
await startCronJobs(cronTasks);
|
|
149
|
+
console.log(` ${cronTasks.length} cron task(s) scheduled`);
|
|
150
|
+
}
|
|
151
|
+
return server;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { silgi };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RouterDef } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/trpc-interop.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert a tRPC router to a Silgi RouterDef.
|
|
6
|
+
*
|
|
7
|
+
* Walks the tRPC router's `_def.procedures` and wraps each one as
|
|
8
|
+
* a Silgi ProcedureDef that calls the tRPC procedure's resolver.
|
|
9
|
+
*
|
|
10
|
+
* Supports:
|
|
11
|
+
* - tRPC v10 and v11 routers
|
|
12
|
+
* - Queries, mutations, and subscriptions
|
|
13
|
+
* - Input schemas (passed through as-is)
|
|
14
|
+
* - Middleware (runs inside tRPC, not Silgi's pipeline)
|
|
15
|
+
*
|
|
16
|
+
* Does NOT support:
|
|
17
|
+
* - Converting tRPC middleware to Silgi guards/wraps
|
|
18
|
+
* - tRPC context factories (use Silgi's context instead)
|
|
19
|
+
*/
|
|
20
|
+
declare function fromTRPC(trpcRouter: unknown): RouterDef;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { fromTRPC };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/trpc-interop.ts
|
|
2
|
+
/**
|
|
3
|
+
* Convert a tRPC router to a Silgi RouterDef.
|
|
4
|
+
*
|
|
5
|
+
* Walks the tRPC router's `_def.procedures` and wraps each one as
|
|
6
|
+
* a Silgi ProcedureDef that calls the tRPC procedure's resolver.
|
|
7
|
+
*
|
|
8
|
+
* Supports:
|
|
9
|
+
* - tRPC v10 and v11 routers
|
|
10
|
+
* - Queries, mutations, and subscriptions
|
|
11
|
+
* - Input schemas (passed through as-is)
|
|
12
|
+
* - Middleware (runs inside tRPC, not Silgi's pipeline)
|
|
13
|
+
*
|
|
14
|
+
* Does NOT support:
|
|
15
|
+
* - Converting tRPC middleware to Silgi guards/wraps
|
|
16
|
+
* - tRPC context factories (use Silgi's context instead)
|
|
17
|
+
*/
|
|
18
|
+
function fromTRPC(trpcRouter) {
|
|
19
|
+
if (!trpcRouter || typeof trpcRouter !== "object") throw new Error("fromTRPC: expected a tRPC router object");
|
|
20
|
+
const router = trpcRouter;
|
|
21
|
+
return walkTRPCRouter(router._def?.procedures ?? router);
|
|
22
|
+
}
|
|
23
|
+
function walkTRPCRouter(node) {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const [key, value] of Object.entries(node)) {
|
|
26
|
+
if (!value || typeof value !== "object") continue;
|
|
27
|
+
const proc = value;
|
|
28
|
+
if (isTRPCProcedure(proc)) result[key] = convertProcedure(proc);
|
|
29
|
+
else if (isTRPCRouter(proc)) result[key] = walkTRPCRouter(proc._def?.procedures ?? proc);
|
|
30
|
+
else result[key] = walkTRPCRouter(proc);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function isTRPCProcedure(value) {
|
|
35
|
+
const def = value._def;
|
|
36
|
+
if (!def) return false;
|
|
37
|
+
return def.type === "query" || def.type === "mutation" || def.type === "subscription";
|
|
38
|
+
}
|
|
39
|
+
function isTRPCRouter(value) {
|
|
40
|
+
return !!value._def?.procedures;
|
|
41
|
+
}
|
|
42
|
+
function convertProcedure(trpcProc) {
|
|
43
|
+
const def = trpcProc._def;
|
|
44
|
+
const type = def.type === "subscription" ? "subscription" : def.type === "mutation" ? "mutation" : "query";
|
|
45
|
+
return {
|
|
46
|
+
type,
|
|
47
|
+
input: def.inputs?.[0] ?? null,
|
|
48
|
+
output: null,
|
|
49
|
+
errors: null,
|
|
50
|
+
use: null,
|
|
51
|
+
resolve: async ({ input, ctx }) => {
|
|
52
|
+
if (typeof def.resolver === "function") return def.resolver({
|
|
53
|
+
input,
|
|
54
|
+
ctx,
|
|
55
|
+
type
|
|
56
|
+
});
|
|
57
|
+
if (typeof trpcProc.call === "function") return trpcProc.call({
|
|
58
|
+
input,
|
|
59
|
+
ctx
|
|
60
|
+
});
|
|
61
|
+
throw new Error(`Cannot resolve tRPC procedure: ${JSON.stringify(Object.keys(def))}`);
|
|
62
|
+
},
|
|
63
|
+
route: null,
|
|
64
|
+
meta: { __trpc: true }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { fromTRPC };
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AnySchema, InferSchemaInput } from "./core/schema.mjs";
|
|
2
|
+
import { TaskDef } from "./core/task.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
/** HTTP route metadata + OpenAPI operation metadata */
|
|
6
|
+
interface Route {
|
|
7
|
+
method?: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
/** Short summary of the operation (OpenAPI `summary`) */
|
|
10
|
+
summary?: string;
|
|
11
|
+
/** Detailed description of the operation (OpenAPI `description`, supports markdown) */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Tags for grouping operations in API docs */
|
|
14
|
+
tags?: string[];
|
|
15
|
+
/** Mark operation as deprecated in API docs */
|
|
16
|
+
deprecated?: boolean;
|
|
17
|
+
/** Custom operationId (default: router path joined with `_`) */
|
|
18
|
+
operationId?: string;
|
|
19
|
+
/** HTTP status code for successful responses (default: 200) */
|
|
20
|
+
successStatus?: number;
|
|
21
|
+
/** Description for the success response in API docs */
|
|
22
|
+
successDescription?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Per-procedure security override.
|
|
25
|
+
* - `false` — public endpoint (no auth), overrides global security
|
|
26
|
+
* - `string[]` — named security scheme(s) required
|
|
27
|
+
*/
|
|
28
|
+
security?: false | string[];
|
|
29
|
+
/**
|
|
30
|
+
* Override or extend the auto-generated OpenAPI operation object.
|
|
31
|
+
* - Object: merged over the generated operation
|
|
32
|
+
* - Function: receives the generated operation, returns the final one
|
|
33
|
+
*
|
|
34
|
+
* Use this for any OpenAPI metadata not covered by other Route fields
|
|
35
|
+
* (e.g. `externalDocs`, `x-` extensions, custom `parameters`).
|
|
36
|
+
*/
|
|
37
|
+
spec?: Record<string, unknown> | ((operation: Record<string, unknown>) => Record<string, unknown>);
|
|
38
|
+
/**
|
|
39
|
+
* Cache-Control header for query responses.
|
|
40
|
+
*
|
|
41
|
+
* - `number` — shorthand for `max-age=N` (seconds)
|
|
42
|
+
* - `string` — full Cache-Control value (e.g. `'public, max-age=60, stale-while-revalidate=30'`)
|
|
43
|
+
* - Only applies to query procedures (mutations and subscriptions are never cached)
|
|
44
|
+
*/
|
|
45
|
+
cache?: number | string;
|
|
46
|
+
/** Enable WebSocket RPC for this procedure */
|
|
47
|
+
ws?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Procedure metadata */
|
|
50
|
+
type Meta = Record<string, unknown>;
|
|
51
|
+
type ProcedureType = 'query' | 'mutation' | 'subscription';
|
|
52
|
+
/** Internal procedure representation — fixed shape for V8 optimization */
|
|
53
|
+
interface ProcedureDef<TType extends ProcedureType = ProcedureType, _TInput = unknown, _TOutput = unknown, TErrors extends ErrorDef = ErrorDef> {
|
|
54
|
+
readonly type: TType;
|
|
55
|
+
readonly input: AnySchema | null;
|
|
56
|
+
readonly output: AnySchema | null;
|
|
57
|
+
readonly errors: TErrors | null;
|
|
58
|
+
readonly use: readonly MiddlewareDef[] | null;
|
|
59
|
+
readonly resolve: Function;
|
|
60
|
+
readonly route: Route | null;
|
|
61
|
+
readonly meta: Meta | null;
|
|
62
|
+
}
|
|
63
|
+
/** Error definition: number shorthand or full config */
|
|
64
|
+
type ErrorDefItem = number | {
|
|
65
|
+
status: number;
|
|
66
|
+
message?: string;
|
|
67
|
+
data?: AnySchema;
|
|
68
|
+
};
|
|
69
|
+
type ErrorDef = Record<string, ErrorDefItem>;
|
|
70
|
+
/** Extract data schema from error def item */
|
|
71
|
+
type ErrorData<T extends ErrorDefItem> = T extends {
|
|
72
|
+
data: infer S extends AnySchema;
|
|
73
|
+
} ? InferSchemaInput<S> : undefined;
|
|
74
|
+
/** Typed fail() function — inferred from errors definition */
|
|
75
|
+
type FailFn<TErrors extends ErrorDef> = <K extends keyof TErrors & string>(code: K, ...args: ErrorData<TErrors[K]> extends undefined ? [data?: unknown] : [data: ErrorData<TErrors[K]>]) => never;
|
|
76
|
+
type GuardFn<TCtxIn, TReturn> = (ctx: TCtxIn) => TReturn | Promise<TReturn> | void | Promise<void>;
|
|
77
|
+
type WrapFn<TCtx> = (ctx: TCtx, next: () => Promise<unknown>) => Promise<unknown>;
|
|
78
|
+
interface GuardDef<TCtxIn = unknown, TReturn = unknown, TErrors extends ErrorDef = {}> {
|
|
79
|
+
readonly kind: 'guard';
|
|
80
|
+
readonly fn: GuardFn<TCtxIn, TReturn>;
|
|
81
|
+
readonly errors?: TErrors;
|
|
82
|
+
}
|
|
83
|
+
interface WrapDef<TCtx = unknown> {
|
|
84
|
+
readonly kind: 'wrap';
|
|
85
|
+
readonly fn: WrapFn<TCtx>;
|
|
86
|
+
}
|
|
87
|
+
type MiddlewareDef = GuardDef<any, any> | WrapDef<any>;
|
|
88
|
+
/** Extract the context additions from a single guard */
|
|
89
|
+
type InferGuardOutput<T> = T extends GuardDef<any, infer O> ? (O extends void | undefined ? {} : O extends Record<string, unknown> ? O : {}) : {};
|
|
90
|
+
/** Walk a middleware tuple, accumulate guard outputs into context */
|
|
91
|
+
type InferContextFromUse<T extends readonly MiddlewareDef[], TBase> = T extends readonly [infer Head, ...infer Tail extends readonly MiddlewareDef[]] ? InferContextFromUse<Tail, TBase & InferGuardOutput<Head>> : TBase;
|
|
92
|
+
interface ResolveContext<TCtx, TInput, TErrors extends ErrorDef> {
|
|
93
|
+
ctx: TCtx;
|
|
94
|
+
input: TInput;
|
|
95
|
+
fail: FailFn<TErrors>;
|
|
96
|
+
signal: AbortSignal;
|
|
97
|
+
/** URL params extracted from the route pattern (e.g. `/users/:id` → `{ id: '123' }`) */
|
|
98
|
+
params: Record<string, string>;
|
|
99
|
+
}
|
|
100
|
+
type RouterDef = {
|
|
101
|
+
[key: string]: ProcedureDef<any, any, any, any> | TaskDef<any, any> | RouterDef;
|
|
102
|
+
};
|
|
103
|
+
/** Return type wrapper — subscription yields, query/mutation returns Promise */
|
|
104
|
+
type ProcedureResult<TType extends ProcedureType, TOutput> = TType extends 'subscription' ? AsyncIterableIterator<TOutput> : Promise<TOutput>;
|
|
105
|
+
/** Infer client type from router */
|
|
106
|
+
type InferClient<T> = T extends ProcedureDef<infer TType, infer TInput, infer TOutput> ? undefined extends TInput ? () => ProcedureResult<TType, TOutput> : (input: TInput) => ProcedureResult<TType, TOutput> : T extends Record<string, unknown> ? { [K in keyof T]: InferClient<T[K]> } : never;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { ErrorDef, ErrorDefItem, FailFn, GuardDef, GuardFn, InferClient, InferContextFromUse, InferGuardOutput, Meta, MiddlewareDef, ProcedureDef, ProcedureType, ResolveContext, Route, RouterDef, WrapDef, WrapFn };
|
package/dist/ws.d.mts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RouterDef } from "./types.mjs";
|
|
2
|
+
import { Hooks, Peer } from "crossws";
|
|
3
|
+
import { Server } from "node:http";
|
|
4
|
+
|
|
5
|
+
//#region src/ws.d.ts
|
|
6
|
+
interface WSAdapterOptions<TCtx extends Record<string, unknown> = Record<string, unknown>> {
|
|
7
|
+
/**
|
|
8
|
+
* Wire protocol for WebSocket message encoding.
|
|
9
|
+
*
|
|
10
|
+
* - `'json'` — default, text frames with JSON
|
|
11
|
+
* - `'messagepack'` — binary frames with MessagePack
|
|
12
|
+
*
|
|
13
|
+
* @default 'json'
|
|
14
|
+
*/
|
|
15
|
+
protocol?: 'json' | 'messagepack';
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use `protocol: 'messagepack'` instead.
|
|
18
|
+
*/
|
|
19
|
+
binary?: boolean;
|
|
20
|
+
/** Context factory — receives the peer on each message */
|
|
21
|
+
context?: (peer: Peer) => TCtx | Promise<TCtx>;
|
|
22
|
+
/**
|
|
23
|
+
* Enable per-message-deflate compression.
|
|
24
|
+
*
|
|
25
|
+
* - `true`: enable with defaults
|
|
26
|
+
* - `object`: fine-tune zlib options (passed to ws `perMessageDeflate`)
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
compress?: boolean | {
|
|
31
|
+
threshold?: number;
|
|
32
|
+
serverNoContextTakeover?: boolean;
|
|
33
|
+
clientNoContextTakeover?: boolean;
|
|
34
|
+
serverMaxWindowBits?: number;
|
|
35
|
+
clientMaxWindowBits?: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Maximum allowed message size in bytes.
|
|
39
|
+
* Messages exceeding this limit will cause the connection to be closed.
|
|
40
|
+
*
|
|
41
|
+
* @default 1_048_576 (1 MB)
|
|
42
|
+
*/
|
|
43
|
+
maxPayload?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Keepalive ping interval in milliseconds.
|
|
46
|
+
* Server sends a ping frame at this interval; if the client
|
|
47
|
+
* does not respond with a pong before the next ping, the connection is terminated.
|
|
48
|
+
*
|
|
49
|
+
* Set to `0` or `false` to disable.
|
|
50
|
+
*
|
|
51
|
+
* @default 30_000 (30 seconds)
|
|
52
|
+
*/
|
|
53
|
+
keepalive?: number | false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create crossws-compatible hooks for Silgi RPC over WebSocket.
|
|
57
|
+
*
|
|
58
|
+
* Works with any crossws integration — Nitro, Deno, Bun, Cloudflare, etc.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // Nitro / Nuxt
|
|
63
|
+
* import { createWSHooks } from "silgi/ws";
|
|
64
|
+
* export default defineWebSocketHandler(createWSHooks(appRouter));
|
|
65
|
+
*
|
|
66
|
+
* // With context
|
|
67
|
+
* export default defineWebSocketHandler(createWSHooks(appRouter, {
|
|
68
|
+
* context: (peer) => ({ userId: peer.request?.headers.get('x-user-id') }),
|
|
69
|
+
* }));
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function createWSHooks<TCtx extends Record<string, unknown>>(routerDef: RouterDef, options?: WSAdapterOptions<TCtx>): Partial<Hooks>;
|
|
73
|
+
/**
|
|
74
|
+
* Attach WebSocket RPC handler to an existing Node.js HTTP server.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { createServer } from "node:http";
|
|
79
|
+
* import { attachWebSocket } from "silgi/ws";
|
|
80
|
+
*
|
|
81
|
+
* const server = createServer(httpHandler);
|
|
82
|
+
* attachWebSocket(server, appRouter);
|
|
83
|
+
* server.listen(3000);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
declare function attachWebSocket<TCtx extends Record<string, unknown>>(server: Server, routerDef: RouterDef, options?: WSAdapterOptions<TCtx>): Promise<void>;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { WSAdapterOptions, attachWebSocket, createWSHooks };
|