@spikard/node 0.12.0 → 0.15.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 CHANGED
@@ -1,66 +1,91 @@
1
- # Spikard for Node.js
1
+ # Spikard
2
+
3
+ <div align="center" style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin: 20px 0;">
4
+ <!-- Language Bindings -->
5
+ <a href="https://crates.io/crates/spikard">
6
+ <img src="https://img.shields.io/crates/v/spikard?label=Rust&color=007ec6" alt="Rust">
7
+ </a>
8
+ <a href="https://pypi.org/project/spikard/">
9
+ <img src="https://img.shields.io/pypi/v/spikard?label=Python&color=007ec6" alt="Python">
10
+ </a>
11
+ <a href="https://www.npmjs.com/package/@spikard/node">
12
+ <img src="https://img.shields.io/npm/v/@spikard/node?label=Node.js&color=007ec6" alt="Node.js">
13
+ </a>
14
+ <a href="https://www.npmjs.com/package/@spikard/wasm">
15
+ <img src="https://img.shields.io/npm/v/@spikard/wasm?label=WASM&color=007ec6" alt="WASM">
16
+ </a>
17
+ <a href="https://rubygems.org/gems/spikard">
18
+ <img src="https://img.shields.io/gem/v/spikard?label=Ruby&color=007ec6" alt="Ruby">
19
+ </a>
20
+ <a href="https://packagist.org/packages/spikard/spikard">
21
+ <img src="https://img.shields.io/packagist/v/spikard/spikard?label=PHP&color=007ec6" alt="PHP">
22
+ </a>
23
+ <a href="https://hex.pm/packages/spikard">
24
+ <img src="https://img.shields.io/hexpm/v/spikard?label=Elixir&color=007ec6" alt="Elixir">
25
+ </a>
26
+ <a href="https://central.sonatype.com/artifact/dev.spikard/spikard">
27
+ <img src="https://img.shields.io/maven-central/v/dev.spikard/spikard?label=Java&color=007ec6" alt="Java">
28
+ </a>
29
+ <a href="https://github.com/Goldziher/spikard/releases">
30
+ <img src="https://img.shields.io/github/v/tag/Goldziher/spikard?label=Go&color=007ec6" alt="Go">
31
+ </a>
32
+ <a href="https://www.nuget.org/packages/Spikard/">
33
+ <img src="https://img.shields.io/nuget/v/Spikard?label=C%23&color=007ec6" alt="C#">
34
+ </a>
35
+
36
+ <!-- Project Info -->
37
+ <a href="https://github.com/Goldziher/spikard/blob/main/LICENSE">
38
+ <img src="https://img.shields.io/badge/License-MIT-007ec6" alt="License">
39
+ </a>
40
+ <a href="https://github.com/Goldziher/spikard">
41
+ <img src="https://img.shields.io/badge/docs-GitHub-007ec6" alt="Documentation">
42
+ </a>
43
+ </div>
44
+
45
+ Rust-centric polyglot HTTP framework with OpenAPI/AsyncAPI/GraphQL/JSON-RPC codegen, tower-http middleware, and fixture-driven cross-language testing. Native NAPI-RS bindings for Node.js with TypeScript type definitions.
2
46
 
3
- [![Documentation](https://img.shields.io/badge/docs-spikard.dev-blue)](https://spikard.dev)
4
- [![Crates.io](https://img.shields.io/crates/v/spikard.svg?color=blue)](https://crates.io/crates/spikard)
5
- [![PyPI](https://img.shields.io/pypi/v/spikard.svg?color=blue)](https://pypi.org/project/spikard/)
6
- [![npm](https://img.shields.io/npm/v/@spikard/node.svg?color=blue)](https://www.npmjs.com/package/@spikard/node)
7
- [![Gem](https://img.shields.io/gem/v/spikard.svg?color=blue)](https://rubygems.org/gems/spikard)
8
- [![Packagist](https://img.shields.io/packagist/v/spikard/spikard.svg?color=blue)](https://packagist.org/packages/spikard/spikard)
9
- [![Hex.pm](https://img.shields.io/hexpm/v/spikard.svg?color=blue)](https://hex.pm/packages/spikard)
10
- [![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
47
+ ## Installation
11
48
 
12
- High-performance HTTP framework for Node.js powered by a Rust core. Provides type-safe routing, validation, middleware, and testing via **napi-rs** bindings with zero-copy JSON conversion.
49
+ **npm:**
13
50
 
14
- ## Features
51
+ ```bash
52
+ npm install @spikard/node
53
+ ```
15
54
 
16
- - **Rust-Powered Performance**: Native speed via Tokio with dedicated thread pool
17
- - **Full TypeScript Support**: Auto-generated types from napi-rs FFI bindings
18
- - **Zero-Copy JSON**: Direct conversion without serialization overhead
19
- - **Tower-HTTP Middleware**: Compression, rate limiting, timeouts, auth, CORS, request IDs
20
- - **Schema Validation**: Zod integration for request/response validation
21
- - **Lifecycle Hooks**: onRequest, preValidation, preHandler, onResponse, onError
22
- - **Testing**: TestClient for HTTP, WebSocket, and SSE assertions
23
-
24
- ## Installation
55
+ **pnpm:**
25
56
 
26
57
  ```bash
27
- npm install @spikard/node
28
- # or
29
58
  pnpm add @spikard/node
30
59
  ```
31
60
 
32
- **Requirements:** Node.js 20+
61
+ **yarn:**
62
+
63
+ ```bash
64
+ yarn add @spikard/node
65
+ ```
33
66
 
34
- For building from source, see the [main README](../../README.md#development).
67
+ ### System Requirements
68
+
69
+ - **Node.js 18+** required (NAPI-RS native bindings)
70
+ - Pre-built binaries for Linux (x86_64), macOS (arm64, x86_64), Windows (x86_64)
35
71
 
36
72
  ## Quick Start
37
73
 
38
74
  ```typescript
39
- import { Spikard, type Request } from "@spikard/node";
75
+ import { Spikard, type Request } from "spikard";
40
76
  import { z } from "zod";
41
77
 
42
- const UserSchema = z.object({
43
- id: z.number(),
44
- name: z.string(),
45
- email: z.string().email(),
46
- });
47
-
78
+ const UserSchema = z.object({ id: z.number(), name: z.string() });
48
79
  type User = z.infer<typeof UserSchema>;
49
80
 
50
81
  const app = new Spikard();
51
82
 
52
- const getUser = async (req: Request): Promise<User> => {
53
- const id = Number(req.params["id"] ?? 0);
54
- return { id, name: "Alice", email: "alice@example.com" };
55
- };
56
-
57
- const createUser = async (req: Request): Promise<User> => {
58
- return UserSchema.parse(req.json());
59
- };
60
-
61
83
  app.addRoute(
62
84
  { method: "GET", path: "/users/:id", handler_name: "getUser", is_async: true },
63
- getUser,
85
+ async (req: Request): Promise<User> => {
86
+ const id = Number(req.params["id"] ?? 0);
87
+ return { id, name: "Alice" };
88
+ },
64
89
  );
65
90
 
66
91
  app.addRoute(
@@ -72,32 +97,42 @@ app.addRoute(
72
97
  response_schema: UserSchema,
73
98
  is_async: true,
74
99
  },
75
- createUser,
100
+ async (req: Request): Promise<User> => UserSchema.parse(req.json()),
76
101
  );
77
102
 
78
- app.run({ port: 8000 });
103
+ if (require.main === module) {
104
+ app.run({ port: 8000 });
105
+ }
79
106
  ```
80
107
 
81
- ## Routing & Schemas
108
+ ## Features
109
+
110
+ - **HTTP routing** — type-safe route definitions with path, query, and body parameter validation
111
+ - **OpenAPI / AsyncAPI / GraphQL / JSON-RPC** — code generation and spec parsing built in
112
+ - **Tower middleware** — compression, rate limiting, timeouts, auth (JWT/API key), static files
113
+ - **Lifecycle hooks** — `onRequest`, `preValidation`, `preHandler`, `onResponse`, `onError`
114
+ - **Fixture-driven testing** — shared JSON fixtures drive tests across all language bindings
115
+ - **Polyglot** — single Rust core, thin bindings for Python, Node.js, Ruby, PHP, Elixir, Go, Java, C#, Kotlin, Dart, Gleam, WASM, Swift, Zig, and C FFI
82
116
 
83
- Routes support Zod validation (recommended) or raw JSON Schema:
117
+ ## Routing
84
118
 
85
119
  ```typescript
86
- import { Spikard, type Request } from "@spikard/node";
120
+ import { Spikard, type Request } from "spikard";
87
121
  import { z } from "zod";
88
122
 
123
+ const UserSchema = z.object({ id: z.number(), name: z.string() });
124
+ type User = z.infer<typeof UserSchema>;
125
+
89
126
  const app = new Spikard();
90
127
 
91
- const UserSchema = z.object({
92
- name: z.string().min(1),
93
- email: z.string().email(),
94
- });
128
+ const health = async (): Promise<{ status: string }> => ({ status: "ok" });
95
129
 
96
- const createUser = async (req: Request) => {
97
- const user = req.json();
98
- return { id: 1, ...user };
130
+ const createUser = async (req: Request): Promise<User> => {
131
+ return UserSchema.parse(req.json());
99
132
  };
100
133
 
134
+ app.addRoute({ method: "GET", path: "/health", handler_name: "health", is_async: true }, health);
135
+
101
136
  app.addRoute(
102
137
  {
103
138
  method: "POST",
@@ -111,175 +146,60 @@ app.addRoute(
111
146
  );
112
147
  ```
113
148
 
114
- Supported HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE.
149
+ ## Validation
115
150
 
116
- ## Dependency Injection
151
+ ```typescript
152
+ import { Spikard, type Request } from "spikard";
153
+ import { z } from "zod";
117
154
 
118
- Register values or factories and access them via `request.dependencies`:
155
+ const PaymentSchema = z.object({
156
+ id: z.string().uuid(),
157
+ amount: z.number().positive(),
158
+ });
159
+ type Payment = z.infer<typeof PaymentSchema>;
119
160
 
120
- ```typescript
121
161
  const app = new Spikard();
122
162
 
123
- app.provide("config", { dbUrl: "postgresql://localhost/app" });
124
- app.provide(
125
- "dbPool",
126
- async ({ config }) => ({ url: config.dbUrl, driver: "pool" }),
127
- { dependsOn: ["config"], singleton: true },
128
- );
163
+ const createPayment = async (req: Request): Promise<Payment> => {
164
+ return PaymentSchema.parse(req.json());
165
+ };
129
166
 
130
167
  app.addRoute(
131
- { method: "GET", path: "/stats", handler_name: "stats", is_async: true },
132
- async (req) => {
133
- const deps = req.dependencies ?? {};
134
- return { db: deps.dbPool?.url, env: deps.config?.dbUrl };
168
+ {
169
+ method: "POST",
170
+ path: "/payments",
171
+ handler_name: "createPayment",
172
+ request_schema: PaymentSchema,
173
+ response_schema: PaymentSchema,
174
+ is_async: true,
135
175
  },
176
+ createPayment,
136
177
  );
137
178
  ```
138
179
 
139
- ## Request Handling
140
-
141
- Access query, path params, headers, cookies, and body:
142
-
143
- ```typescript
144
- get("/search")(async (req) => {
145
- const q = req.query.q;
146
- const id = req.params.id;
147
- const auth = req.headers.authorization;
148
- const session = req.cookies.session_id;
149
- const body = req.json<{ name: string }>();
150
- const form = req.form();
151
- return { query: q, id };
152
- });
153
- ```
154
-
155
- ## Advanced Features
180
+ ## Middleware
156
181
 
157
- **File Uploads:**
158
182
  ```typescript
159
- post("/upload")(async (req) => {
160
- const body = req.json<{ file: UploadFile }>();
161
- return { filename: body.file.filename, size: body.file.size };
162
- });
163
- ```
164
-
165
- **Streaming Responses:**
166
- ```typescript
167
- get("/stream")(async function* () {
168
- for (let i = 0; i < 10; i++) {
169
- yield JSON.stringify({ count: i }) + "\n";
170
- await new Promise(r => setTimeout(r, 100));
171
- }
172
- });
173
- ```
174
-
175
- ## Configuration
176
-
177
- Configure middleware, compression, rate limiting, and authentication:
183
+ import { Spikard, type Request } from "spikard";
178
184
 
179
- ```typescript
180
- const config: ServerConfig = {
181
- port: 8080,
182
- workers: 4,
183
- maxBodySize: 10 * 1024 * 1024,
184
- requestTimeout: 30,
185
- compression: { gzip: true, brotli: true, minSize: 1024 },
186
- rateLimit: { perSecond: 100, burst: 200 },
187
- jwtAuth: { secret: "key", algorithm: "HS256" },
188
- };
189
-
190
- app.run(config);
191
- ```
192
-
193
- See [ServerConfig](../../docs/adr/0002-runtime-and-middleware.md) for all options.
194
-
195
- ## Lifecycle Hooks
196
-
197
- Execute code at key request/response stages:
185
+ const app = new Spikard();
198
186
 
199
- ```typescript
200
- app.onRequest(async (request) => {
187
+ app.onRequest(async (request: Request): Promise<Request> => {
201
188
  console.log(`${request.method} ${request.path}`);
202
189
  return request;
203
190
  });
204
-
205
- app.preValidation(async (request) => {
206
- if (!request.headers["authorization"]) {
207
- return { status: 401, body: { error: "Unauthorized" } };
208
- }
209
- return request;
210
- });
211
-
212
- app.onResponse(async (response) => {
213
- response.headers["X-Frame-Options"] = "DENY";
214
- return response;
215
- });
216
191
  ```
217
192
 
218
- ## Testing
219
-
220
- Use TestClient for HTTP, WebSocket, and SSE testing:
221
-
222
- ```typescript
223
- import { TestClient } from "@spikard/node";
224
- import { expect } from "vitest";
225
-
226
- const client = new TestClient(app);
227
-
228
- // HTTP testing
229
- const response = await client.get("/users/123");
230
- expect(response.statusCode).toBe(200);
231
-
232
- // WebSocket testing
233
- const ws = await client.websocketConnect("/ws");
234
- await ws.sendJson({ message: "hello" });
235
-
236
- // SSE testing
237
- const sse = await client.get("/events");
238
- ```
239
-
240
- ## Performance
241
-
242
- Benchmarked across 34 workloads at 100 concurrency ([methodology](../../docs/benchmarks/methodology.md)):
243
-
244
- | Framework | Avg RPS | P50 (ms) | P99 (ms) |
245
- |-----------|--------:|----------:|----------:|
246
- | **spikard (Bun)** | 49,460 | 2.18 | 4.21 |
247
- | **spikard (Node)** | 46,160 | 2.18 | 3.35 |
248
- | elysia | 44,326 | 2.41 | 4.68 |
249
- | kito | 36,958 | 4.94 | 12.86 |
250
- | fastify | 19,167 | 6.74 | 14.76 |
251
- | morojs | 14,196 | 6.44 | 12.61 |
252
- | hono | 10,928 | 10.91 | 18.62 |
253
-
254
- Spikard Node is **1.2x faster** than Kito and **2.4x faster** than Fastify.
255
-
256
- Key optimizations:
257
- - **napi-rs** zero-copy FFI bindings
258
- - **Dedicated Tokio runtime** without blocking Node event loop
259
- - **Zero-copy JSON** conversion (30-40% faster than JSON.parse)
260
- - **ThreadsafeFunction** for async JavaScript callbacks
261
-
262
- ## Examples
263
-
264
- See [examples/](../../examples/) for runnable projects. Code generation is supported for OpenAPI, GraphQL, AsyncAPI, and JSON-RPC specifications.
265
-
266
193
  ## Documentation
267
194
 
268
- Full documentation at [spikard.dev](https://spikard.dev). See also [CONTRIBUTING.md](../../CONTRIBUTING.md).
269
-
270
- ## Ecosystem
195
+ - **[Repository](https://github.com/Goldziher/spikard)** source code, examples, and contributing guide
196
+ - **[Examples](https://github.com/Goldziher/spikard/tree/main/examples)** — working examples per language
197
+ - **[Issues](https://github.com/Goldziher/spikard/issues)** — bug reports and feature requests
271
198
 
272
- Spikard is available across multiple languages:
199
+ ## Contributing
273
200
 
274
- | Platform | Package | Status |
275
- |----------|---------|--------|
276
- | **Node.js** | [@spikard/node](https://www.npmjs.com/package/@spikard/node) | Stable |
277
- | **Python** | [spikard](https://pypi.org/project/spikard/) | Stable |
278
- | **Rust** | [spikard](https://crates.io/crates/spikard) | Stable |
279
- | **Ruby** | [spikard](https://rubygems.org/gems/spikard) | Stable |
280
- | **PHP** | [spikard/spikard](https://packagist.org/packages/spikard/spikard) | Stable |
281
- | **Elixir** | [spikard](https://hex.pm/packages/spikard) | Stable |
201
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/Goldziher/spikard/blob/main/CONTRIBUTING.md).
282
202
 
283
203
  ## License
284
204
 
285
- MIT - See [LICENSE](../../LICENSE) for details
205
+ MIT License see [LICENSE](https://github.com/Goldziher/spikard/blob/main/LICENSE) for details.