silgi 0.0.14 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +54 -0
- package/dist/compile.mjs +305 -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 +330 -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
package/README.md
CHANGED
|
@@ -1,2 +1,103 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br>
|
|
3
|
+
<img src=".github/assets/cover.png" alt="Silgi — Type-safe RPC framework for TypeScript" width="100%">
|
|
4
|
+
<br><br>
|
|
5
|
+
<a href="https://npmjs.com/package/silgi"><img src="https://img.shields.io/npm/v/silgi?style=flat&colorA=0a0908&colorB=edc462" alt="npm version"></a>
|
|
6
|
+
<a href="https://npmjs.com/package/silgi"><img src="https://img.shields.io/npm/dm/silgi?style=flat&colorA=0a0908&colorB=edc462" alt="npm downloads"></a>
|
|
7
|
+
<a href="https://github.com/productdevbook/silgi/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/productdevbook/silgi/ci.yml?style=flat&colorA=0a0908&colorB=edc462" alt="CI"></a>
|
|
8
|
+
<a href="https://github.com/productdevbook/silgi/blob/main/LICENSE"><img src="https://img.shields.io/github/license/productdevbook/silgi?style=flat&colorA=0a0908&colorB=edc462" alt="license"></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install silgi
|
|
15
|
+
```
|
|
16
|
+
|
|
1
17
|
```ts
|
|
2
|
-
|
|
18
|
+
import { silgi } from 'silgi'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
|
|
21
|
+
const s = silgi({ context: (req) => ({ db: getDB() }) })
|
|
22
|
+
|
|
23
|
+
const appRouter = s.router({
|
|
24
|
+
users: {
|
|
25
|
+
list: k
|
|
26
|
+
.$input(z.object({ limit: z.number().optional() }))
|
|
27
|
+
.$resolve(({ input, ctx }) => ctx.db.users.find({ take: input.limit })),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
s.serve(appRouter, { port: 3000, scalar: true })
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Single package** — server, client, 15 plugins, 14 adapters. One install.
|
|
37
|
+
- **Compiled pipeline** — guards unrolled, handlers pre-linked at startup.
|
|
38
|
+
- **Guard / Wrap** — guards enrich context (flat, sync fast-path). Wraps run before + after (onion).
|
|
39
|
+
- **Content negotiation** — JSON, MessagePack, devalue. Automatic from `Accept` header.
|
|
40
|
+
- **Contract-first** — define API shape, share types, implement separately.
|
|
41
|
+
- **Standard Schema** — Zod, Valibot, ArkType.
|
|
42
|
+
|
|
43
|
+
## Adapters
|
|
44
|
+
|
|
45
|
+
| | Import |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Standalone | `s.serve()` / `s.handler()` |
|
|
48
|
+
| Nitro v3 | `serverEntry` + `s.handler()` |
|
|
49
|
+
| Express | `silgi/express` |
|
|
50
|
+
| Fastify | `silgi/fastify` |
|
|
51
|
+
| Elysia | `silgi/elysia` |
|
|
52
|
+
| Next.js | `silgi/nextjs` |
|
|
53
|
+
| Nuxt | via Nitro `serverEntry` |
|
|
54
|
+
| SvelteKit | `silgi/sveltekit` |
|
|
55
|
+
| Remix | `silgi/remix` |
|
|
56
|
+
| Astro | `silgi/astro` |
|
|
57
|
+
| SolidStart | `silgi/solidstart` |
|
|
58
|
+
| NestJS | `silgi/nestjs` |
|
|
59
|
+
| AWS Lambda | `silgi/aws-lambda` |
|
|
60
|
+
| MessagePort | `silgi/message-port` |
|
|
61
|
+
|
|
62
|
+
## Ecosystem
|
|
63
|
+
|
|
64
|
+
Built-in re-exports — no extra dependencies needed:
|
|
65
|
+
|
|
66
|
+
| Import | Package | Use case |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `silgi/unstorage` | unstorage | Key-value storage (Redis, KV, S3) |
|
|
69
|
+
| `silgi/ocache` | ocache | Cached functions with TTL + SWR |
|
|
70
|
+
| `silgi/ofetch` | ofetch | Universal fetch with auto-retry |
|
|
71
|
+
| `silgi/srvx` | srvx | Universal server (Node, Deno, Bun) |
|
|
72
|
+
|
|
73
|
+
## Integrations
|
|
74
|
+
|
|
75
|
+
- **TanStack Query** — `queryOptions`, `mutationOptions`, `infiniteOptions`, `skipToken`
|
|
76
|
+
- **React Server Actions** — `createAction`, `useServerAction`, `useOptimisticServerAction`
|
|
77
|
+
- **AI SDK** — `routerToTools()` turns procedures into LLM tools
|
|
78
|
+
- **tRPC Interop** — `fromTRPC()` for incremental migration
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx giget@latest gh:productdevbook/silgi/examples/standalone my-app
|
|
84
|
+
npx giget@latest gh:productdevbook/silgi/examples/nextjs my-nextjs-app
|
|
85
|
+
npx giget@latest gh:productdevbook/silgi/examples/nuxt my-nuxt-app
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
10 examples: standalone, bun, express, elysia, nitro, nitro-h3, nextjs, nuxt, sveltekit, client-react.
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
[silgi.dev](https://silgi.dev)
|
|
93
|
+
|
|
94
|
+
## Credits
|
|
95
|
+
|
|
96
|
+
- [oRPC](https://github.com/unnoq/orpc) — Pipeline architecture, client proxy, error handling, contract-first workflow
|
|
97
|
+
- [tRPC](https://github.com/trpc/trpc) — Router/procedure model, end-to-end type inference
|
|
98
|
+
- [Elysia](https://github.com/elysiajs/elysia) — Sucrose-style static handler analysis
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/astro.d.ts
|
|
4
|
+
interface AstroAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
+
context?: (request: Request) => TCtx | Promise<TCtx>;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create an Astro API route handler.
|
|
10
|
+
* Astro passes { request: Request, params } — uses Silgi's handler().
|
|
11
|
+
*/
|
|
12
|
+
declare function silgiAstro<TCtx extends Record<string, unknown>>(router: RouterDef, options?: AstroAdapterOptions<TCtx>): (ctx: {
|
|
13
|
+
request: Request;
|
|
14
|
+
params: Record<string, string>;
|
|
15
|
+
}) => Promise<Response>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { AstroAdapterOptions, silgiAstro };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/adapters/astro.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create an Astro API route handler.
|
|
4
|
+
* Astro passes { request: Request, params } — uses Silgi's handler().
|
|
5
|
+
*/
|
|
6
|
+
function silgiAstro(router, options = {}) {
|
|
7
|
+
const prefix = options.prefix ?? "/api/rpc";
|
|
8
|
+
let _handler = null;
|
|
9
|
+
return async ({ request }) => {
|
|
10
|
+
if (!_handler) {
|
|
11
|
+
const { silgi } = await import("../silgi.mjs");
|
|
12
|
+
_handler = silgi({ context: options.context ?? (() => ({})) }).handler(router);
|
|
13
|
+
}
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
let pathname = url.pathname;
|
|
16
|
+
if (pathname.startsWith(prefix)) {
|
|
17
|
+
pathname = pathname.slice(prefix.length);
|
|
18
|
+
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
19
|
+
}
|
|
20
|
+
return _handler(new Request(new URL(pathname + url.search, url.origin), request));
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { silgiAstro };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/aws-lambda.d.ts
|
|
4
|
+
interface LambdaAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
+
/** Context factory — receives the Lambda event */
|
|
6
|
+
context?: (event: LambdaEvent) => TCtx | Promise<TCtx>;
|
|
7
|
+
/** Route prefix to strip. Default: none */
|
|
8
|
+
prefix?: string;
|
|
9
|
+
}
|
|
10
|
+
interface LambdaEvent {
|
|
11
|
+
httpMethod: string;
|
|
12
|
+
path: string;
|
|
13
|
+
body: string | null;
|
|
14
|
+
headers: Record<string, string>;
|
|
15
|
+
queryStringParameters: Record<string, string> | null;
|
|
16
|
+
requestContext?: Record<string, unknown>;
|
|
17
|
+
isBase64Encoded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface LambdaResponse {
|
|
20
|
+
statusCode: number;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create an AWS Lambda handler from a Silgi router.
|
|
26
|
+
*
|
|
27
|
+
* Supports API Gateway v1 (REST) and v2 (HTTP) event formats.
|
|
28
|
+
*/
|
|
29
|
+
declare function silgiLambda<TCtx extends Record<string, unknown>>(router: RouterDef, options?: LambdaAdapterOptions<TCtx>): (event: LambdaEvent) => Promise<LambdaResponse>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { LambdaAdapterOptions, silgiLambda };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
+
import { ValidationError } from "../core/schema.mjs";
|
|
3
|
+
import { compileRouter } from "../compile.mjs";
|
|
4
|
+
//#region src/adapters/aws-lambda.ts
|
|
5
|
+
/**
|
|
6
|
+
* AWS Lambda adapter — deploy Silgi as a Lambda function.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { silgiLambda } from "silgi/aws-lambda"
|
|
11
|
+
*
|
|
12
|
+
* export const handler = silgiLambda(appRouter, {
|
|
13
|
+
* context: (event) => ({ db: getDB(), userId: event.requestContext?.authorizer?.userId }),
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Create an AWS Lambda handler from a Silgi router.
|
|
19
|
+
*
|
|
20
|
+
* Supports API Gateway v1 (REST) and v2 (HTTP) event formats.
|
|
21
|
+
*/
|
|
22
|
+
function silgiLambda(router, options = {}) {
|
|
23
|
+
const flatRouter = compileRouter(router);
|
|
24
|
+
const prefix = options.prefix ?? "";
|
|
25
|
+
return async (event) => {
|
|
26
|
+
let pathname = event.path;
|
|
27
|
+
if (prefix && pathname.startsWith(prefix)) pathname = pathname.slice(prefix.length);
|
|
28
|
+
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
29
|
+
const match = flatRouter(event.httpMethod, "/" + pathname);
|
|
30
|
+
if (!match) return {
|
|
31
|
+
statusCode: 404,
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
code: "NOT_FOUND",
|
|
35
|
+
status: 404,
|
|
36
|
+
message: "Procedure not found"
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
const route = match.data;
|
|
40
|
+
try {
|
|
41
|
+
const ctx = Object.create(null);
|
|
42
|
+
if (match.params) ctx.params = match.params;
|
|
43
|
+
if (options.context) {
|
|
44
|
+
const baseCtx = await options.context(event);
|
|
45
|
+
const keys = Object.keys(baseCtx);
|
|
46
|
+
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
47
|
+
}
|
|
48
|
+
let input;
|
|
49
|
+
if (event.body) {
|
|
50
|
+
const body = event.isBase64Encoded ? Buffer.from(event.body, "base64").toString("utf-8") : event.body;
|
|
51
|
+
try {
|
|
52
|
+
input = JSON.parse(body);
|
|
53
|
+
} catch {}
|
|
54
|
+
} else if (event.queryStringParameters?.data) try {
|
|
55
|
+
input = JSON.parse(event.queryStringParameters.data);
|
|
56
|
+
} catch {}
|
|
57
|
+
const signal = AbortSignal.timeout(3e4);
|
|
58
|
+
const output = await route.handler(ctx, input, signal);
|
|
59
|
+
return {
|
|
60
|
+
statusCode: 200,
|
|
61
|
+
headers: { "content-type": "application/json" },
|
|
62
|
+
body: JSON.stringify(output)
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ValidationError) return {
|
|
66
|
+
statusCode: 400,
|
|
67
|
+
headers: { "content-type": "application/json" },
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
code: "BAD_REQUEST",
|
|
70
|
+
status: 400,
|
|
71
|
+
message: error.message,
|
|
72
|
+
data: { issues: error.issues }
|
|
73
|
+
})
|
|
74
|
+
};
|
|
75
|
+
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
76
|
+
return {
|
|
77
|
+
statusCode: e.status,
|
|
78
|
+
headers: { "content-type": "application/json" },
|
|
79
|
+
body: JSON.stringify(e.toJSON())
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { silgiLambda };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/elysia.d.ts
|
|
4
|
+
interface ElysiaAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
+
/** Context factory — receives the Elysia context */
|
|
6
|
+
context?: (ctx: any) => TCtx | Promise<TCtx>;
|
|
7
|
+
/** Route prefix. Default: "/rpc" */
|
|
8
|
+
prefix?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create an Elysia plugin that routes to Silgi procedures.
|
|
12
|
+
*
|
|
13
|
+
* Returns a function that can be passed to `app.use()`.
|
|
14
|
+
*/
|
|
15
|
+
declare function silgiElysia<TCtx extends Record<string, unknown>>(router: RouterDef, options?: ElysiaAdapterOptions<TCtx>): (app: any) => any;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ElysiaAdapterOptions, silgiElysia };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
+
import { ValidationError } from "../core/schema.mjs";
|
|
3
|
+
import { compileRouter } from "../compile.mjs";
|
|
4
|
+
//#region src/adapters/elysia.ts
|
|
5
|
+
/**
|
|
6
|
+
* Elysia adapter — use Silgi with Elysia on Bun.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { Elysia } from "elysia"
|
|
11
|
+
* import { silgiElysia } from "silgi/elysia"
|
|
12
|
+
*
|
|
13
|
+
* const app = new Elysia()
|
|
14
|
+
* .use(silgiElysia(appRouter, { prefix: "/rpc" }))
|
|
15
|
+
* .listen(3000)
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Create an Elysia plugin that routes to Silgi procedures.
|
|
20
|
+
*
|
|
21
|
+
* Returns a function that can be passed to `app.use()`.
|
|
22
|
+
*/
|
|
23
|
+
function silgiElysia(router, options = {}) {
|
|
24
|
+
const flatRouter = compileRouter(router);
|
|
25
|
+
const prefix = options.prefix ?? "/rpc";
|
|
26
|
+
return (app) => {
|
|
27
|
+
app.all(`${prefix}/*`, async (elysiaCtx) => {
|
|
28
|
+
let pathname = new URL(elysiaCtx.request.url).pathname;
|
|
29
|
+
if (pathname.startsWith(prefix)) pathname = pathname.slice(prefix.length);
|
|
30
|
+
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
31
|
+
const match = flatRouter(elysiaCtx.request.method, "/" + pathname);
|
|
32
|
+
if (!match) {
|
|
33
|
+
elysiaCtx.set.status = 404;
|
|
34
|
+
return {
|
|
35
|
+
code: "NOT_FOUND",
|
|
36
|
+
status: 404,
|
|
37
|
+
message: "Procedure not found"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const route = match.data;
|
|
41
|
+
try {
|
|
42
|
+
const ctx = Object.create(null);
|
|
43
|
+
if (match.params) ctx.params = match.params;
|
|
44
|
+
if (options.context) {
|
|
45
|
+
const baseCtx = await options.context(elysiaCtx);
|
|
46
|
+
const keys = Object.keys(baseCtx);
|
|
47
|
+
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
48
|
+
}
|
|
49
|
+
let input;
|
|
50
|
+
if (elysiaCtx.request.method === "POST" || elysiaCtx.request.method === "PUT") input = elysiaCtx.body;
|
|
51
|
+
else {
|
|
52
|
+
const data = elysiaCtx.query?.data;
|
|
53
|
+
if (data) input = typeof data === "string" ? JSON.parse(data) : data;
|
|
54
|
+
}
|
|
55
|
+
const signal = elysiaCtx.request.signal ?? new AbortController().signal;
|
|
56
|
+
return await route.handler(ctx, input, signal);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof ValidationError) {
|
|
59
|
+
elysiaCtx.set.status = 400;
|
|
60
|
+
return {
|
|
61
|
+
code: "BAD_REQUEST",
|
|
62
|
+
status: 400,
|
|
63
|
+
message: error.message,
|
|
64
|
+
data: { issues: error.issues }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
68
|
+
elysiaCtx.set.status = e.status;
|
|
69
|
+
return e.toJSON();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return app;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { silgiElysia };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/express.d.ts
|
|
4
|
+
interface ExpressAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
+
/** Context factory — receives the Express request */
|
|
6
|
+
context?: (req: any) => TCtx | Promise<TCtx>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create Express middleware that routes to Silgi procedures.
|
|
10
|
+
*
|
|
11
|
+
* Mount at a prefix — the remainder of the path is the procedure name.
|
|
12
|
+
* Requires `express.json()` middleware for POST body parsing.
|
|
13
|
+
*/
|
|
14
|
+
declare function silgiExpress<TCtx extends Record<string, unknown>>(router: RouterDef, options?: ExpressAdapterOptions<TCtx>): (req: any, res: any, next: any) => void;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ExpressAdapterOptions, silgiExpress };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
+
import { ValidationError } from "../core/schema.mjs";
|
|
3
|
+
import { compileRouter } from "../compile.mjs";
|
|
4
|
+
//#region src/adapters/express.ts
|
|
5
|
+
/**
|
|
6
|
+
* Express adapter — use Silgi as Express middleware.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import express from "express"
|
|
11
|
+
* import { silgiExpress } from "silgi/express"
|
|
12
|
+
*
|
|
13
|
+
* const app = express()
|
|
14
|
+
* app.use("/rpc", silgiExpress(appRouter, {
|
|
15
|
+
* context: (req) => ({ db: getDB(), user: req.user }),
|
|
16
|
+
* }))
|
|
17
|
+
* app.listen(3000)
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Create Express middleware that routes to Silgi procedures.
|
|
22
|
+
*
|
|
23
|
+
* Mount at a prefix — the remainder of the path is the procedure name.
|
|
24
|
+
* Requires `express.json()` middleware for POST body parsing.
|
|
25
|
+
*/
|
|
26
|
+
function silgiExpress(router, options = {}) {
|
|
27
|
+
const flatRouter = compileRouter(router);
|
|
28
|
+
return (req, res, next) => {
|
|
29
|
+
let pathname = req.path ?? req.url ?? "";
|
|
30
|
+
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
31
|
+
const match = flatRouter(req.method, "/" + pathname);
|
|
32
|
+
if (!match) return next();
|
|
33
|
+
const route = match.data;
|
|
34
|
+
const handle = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const ctx = Object.create(null);
|
|
37
|
+
if (match.params) ctx.params = match.params;
|
|
38
|
+
if (options.context) {
|
|
39
|
+
const baseCtx = await options.context(req);
|
|
40
|
+
const keys = Object.keys(baseCtx);
|
|
41
|
+
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
42
|
+
}
|
|
43
|
+
let input;
|
|
44
|
+
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") input = req.body;
|
|
45
|
+
else if (req.query?.data) if (typeof req.query.data === "string") try {
|
|
46
|
+
input = JSON.parse(req.query.data);
|
|
47
|
+
} catch {
|
|
48
|
+
res.status(400).json({
|
|
49
|
+
code: "BAD_REQUEST",
|
|
50
|
+
status: 400,
|
|
51
|
+
message: "Invalid JSON in data parameter"
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
else input = req.query.data;
|
|
56
|
+
const ac = new AbortController();
|
|
57
|
+
req.on("close", () => ac.abort());
|
|
58
|
+
const output = await route.handler(ctx, input, ac.signal);
|
|
59
|
+
res.json(output);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof ValidationError) {
|
|
62
|
+
res.status(400).json({
|
|
63
|
+
code: "BAD_REQUEST",
|
|
64
|
+
status: 400,
|
|
65
|
+
message: error.message,
|
|
66
|
+
data: { issues: error.issues }
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
71
|
+
res.status(e.status).json(e.toJSON());
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
handle();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { silgiExpress };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/fastify.d.ts
|
|
4
|
+
interface SilgiFastifyOptions {
|
|
5
|
+
/** Context factory — receives Fastify request */
|
|
6
|
+
context?: (req: any) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a Fastify plugin from a silgi router.
|
|
10
|
+
*
|
|
11
|
+
* Uses a single wildcard route with the compiled radix router for dispatch.
|
|
12
|
+
*/
|
|
13
|
+
declare function silgiFastify(routerDef: RouterDef, options?: SilgiFastifyOptions): (fastify: any) => Promise<void>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { SilgiFastifyOptions, silgiFastify };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
+
import { ValidationError } from "../core/schema.mjs";
|
|
3
|
+
import { compileRouter } from "../compile.mjs";
|
|
4
|
+
//#region src/adapters/fastify.ts
|
|
5
|
+
/**
|
|
6
|
+
* Fastify adapter — register silgi router as a Fastify plugin.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import Fastify from "fastify"
|
|
11
|
+
* import { silgiFastify } from "silgi/fastify"
|
|
12
|
+
*
|
|
13
|
+
* const app = Fastify()
|
|
14
|
+
* app.register(silgiFastify(appRouter), { prefix: "/rpc" })
|
|
15
|
+
* app.listen({ port: 3000 })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
let _msgpack;
|
|
19
|
+
let _devalue;
|
|
20
|
+
/**
|
|
21
|
+
* Create a Fastify plugin from a silgi router.
|
|
22
|
+
*
|
|
23
|
+
* Uses a single wildcard route with the compiled radix router for dispatch.
|
|
24
|
+
*/
|
|
25
|
+
function silgiFastify(routerDef, options = {}) {
|
|
26
|
+
const compiledRouter = compileRouter(routerDef);
|
|
27
|
+
const contextFactory = options.context ?? (() => ({}));
|
|
28
|
+
return async function plugin(fastify) {
|
|
29
|
+
fastify.all("/*", async (req, reply) => {
|
|
30
|
+
const pathname = "/" + (req.params?.["*"] ?? "");
|
|
31
|
+
const match = compiledRouter(req.method ?? "POST", pathname);
|
|
32
|
+
if (!match) return reply.status(404).send({
|
|
33
|
+
code: "NOT_FOUND",
|
|
34
|
+
status: 404,
|
|
35
|
+
message: "Not found"
|
|
36
|
+
});
|
|
37
|
+
const route = match.data;
|
|
38
|
+
const ctx = Object.create(null);
|
|
39
|
+
if (match.params) ctx.params = match.params;
|
|
40
|
+
try {
|
|
41
|
+
const baseCtx = await contextFactory(req);
|
|
42
|
+
Object.assign(ctx, baseCtx);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
const e = err instanceof SilgiError ? err : toSilgiError(err);
|
|
45
|
+
return reply.status(e.status).send(e.toJSON());
|
|
46
|
+
}
|
|
47
|
+
const rawInput = req.body && typeof req.body === "object" ? req.body : {};
|
|
48
|
+
try {
|
|
49
|
+
const ac = new AbortController();
|
|
50
|
+
req.raw?.on?.("close", () => ac.abort());
|
|
51
|
+
const result = route.handler(ctx, rawInput, ac.signal);
|
|
52
|
+
const output = result instanceof Promise ? await result : result;
|
|
53
|
+
if (route.cacheControl) reply.header("cache-control", route.cacheControl);
|
|
54
|
+
const accept = req.headers.accept;
|
|
55
|
+
if (accept?.includes("msgpack")) {
|
|
56
|
+
_msgpack ??= await import("../codec/msgpack.mjs");
|
|
57
|
+
return reply.header("content-type", _msgpack.MSGPACK_CONTENT_TYPE).send(Buffer.from(_msgpack.encode(output)));
|
|
58
|
+
}
|
|
59
|
+
if (accept?.includes("x-devalue")) {
|
|
60
|
+
_devalue ??= await import("../codec/devalue.mjs");
|
|
61
|
+
return reply.header("content-type", _devalue.DEVALUE_CONTENT_TYPE).send(_devalue.encode(output));
|
|
62
|
+
}
|
|
63
|
+
return reply.header("content-type", "application/json").send(route.stringify(output));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ValidationError) return reply.status(400).send({
|
|
66
|
+
code: "BAD_REQUEST",
|
|
67
|
+
status: 400,
|
|
68
|
+
message: error.message,
|
|
69
|
+
data: { issues: error.issues }
|
|
70
|
+
});
|
|
71
|
+
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
72
|
+
return reply.status(e.status).send(e.toJSON());
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { silgiFastify };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { ClientContext, ClientLink, ClientOptions } from "../client/types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/message-port.d.ts
|
|
5
|
+
interface MessagePortAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
6
|
+
context?: () => TCtx | Promise<TCtx>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Attach Silgi to a MessagePort (server side).
|
|
10
|
+
* Listens for RPC messages and responds with results.
|
|
11
|
+
* Returns a dispose function to stop listening.
|
|
12
|
+
*/
|
|
13
|
+
declare function silgiMessagePort<TCtx extends Record<string, unknown>>(router: RouterDef, port: {
|
|
14
|
+
postMessage(msg: unknown): void;
|
|
15
|
+
addEventListener(type: 'message', handler: (event: {
|
|
16
|
+
data: unknown;
|
|
17
|
+
}) => void): void;
|
|
18
|
+
removeEventListener(type: 'message', handler: (event: {
|
|
19
|
+
data: unknown;
|
|
20
|
+
}) => void): void;
|
|
21
|
+
}, options?: MessagePortAdapterOptions<TCtx>): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Client-side MessagePort link.
|
|
24
|
+
* Sends RPC messages and resolves promises when responses arrive.
|
|
25
|
+
*/
|
|
26
|
+
declare class MessagePortLink<TCtx extends ClientContext = ClientContext> implements ClientLink<TCtx> {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(port: {
|
|
29
|
+
postMessage(msg: unknown): void;
|
|
30
|
+
addEventListener(type: 'message', handler: (event: {
|
|
31
|
+
data: unknown;
|
|
32
|
+
}) => void): void;
|
|
33
|
+
});
|
|
34
|
+
call(path: readonly string[], input: unknown, _options: ClientOptions<TCtx>): Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { MessagePortAdapterOptions, MessagePortLink, silgiMessagePort };
|