tachyon-rs 0.2.16 → 0.3.0
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 +58 -29
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/helper.d.ts +3 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/request.d.ts +2 -0
- package/dist/request.d.ts.map +1 -1
- package/dist/tachyon.d.ts +1 -7
- package/dist/tachyon.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,28 +15,60 @@ bun add tachyon-rs
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import { Tachyon, TachyonResponse } from 'tachyon-rs'
|
|
18
|
+
import { Tachyon, TachyonResponse, status } from 'tachyon-rs'
|
|
19
19
|
|
|
20
20
|
new Tachyon()
|
|
21
21
|
.get('/', 'Hello Tachyon!')
|
|
22
22
|
.get('/json', { message: 'fast' })
|
|
23
|
-
.get('/dynamic', (req) => {
|
|
24
|
-
|
|
25
|
-
})
|
|
23
|
+
.get('/dynamic', (req) => status(200, { path: req.path }))
|
|
24
|
+
.post('/echo', (req) => status(200, req.body ?? '{}'))
|
|
26
25
|
.listen(3000)
|
|
27
26
|
```
|
|
28
27
|
|
|
28
|
+
## Responses
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { TachyonResponse, status } from 'tachyon-rs'
|
|
32
|
+
|
|
33
|
+
// status(code, body) — shorthand for any status code
|
|
34
|
+
status(200, { message: 'ok' })
|
|
35
|
+
status(201, { id: 42 })
|
|
36
|
+
status(400, { error: 'bad request' })
|
|
37
|
+
|
|
38
|
+
// TachyonResponse — when you need headers or content-type control
|
|
39
|
+
new TachyonResponse(200, 'Hello!')
|
|
40
|
+
.text() // send as text/plain
|
|
41
|
+
.header('Cache-Control', 'max-age=3600')
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Request
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
app.get('/users', (req) => {
|
|
48
|
+
req.method // "GET"
|
|
49
|
+
req.path // "/users"
|
|
50
|
+
req.body // string | undefined
|
|
51
|
+
req.header('x-api-key') // string | undefined (lazy parsed, zero-cost if unused)
|
|
52
|
+
req.headers // ReadonlyMap<string, string>
|
|
53
|
+
return status(200, [])
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
29
57
|
## Plugins
|
|
30
58
|
|
|
31
59
|
Lifecycle hooks: `pre` (before handler) and `pos` (after handler).
|
|
32
60
|
|
|
61
|
+
- `pre` returning a `TachyonResponse` short-circuits the request (e.g., 401).
|
|
62
|
+
- `pos` returning a `TachyonResponse` replaces the original response.
|
|
63
|
+
- Returning `void` from either continues normally.
|
|
64
|
+
|
|
33
65
|
```typescript
|
|
34
|
-
import { Tachyon, TachyonResponse, type Plugin } from 'tachyon-rs'
|
|
66
|
+
import { Tachyon, TachyonResponse, status, type Plugin } from 'tachyon-rs'
|
|
35
67
|
|
|
36
68
|
const auth: Plugin = {
|
|
37
69
|
pre: (req) => {
|
|
38
70
|
if (!req.header('authorization')) {
|
|
39
|
-
return
|
|
71
|
+
return status(401, { error: 'Unauthorized' })
|
|
40
72
|
}
|
|
41
73
|
}
|
|
42
74
|
}
|
|
@@ -50,22 +82,18 @@ const cors: Plugin = {
|
|
|
50
82
|
.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
51
83
|
}
|
|
52
84
|
},
|
|
53
|
-
pos: (_req, res) =>
|
|
54
|
-
return res.header('Access-Control-Allow-Origin', '*')
|
|
55
|
-
}
|
|
85
|
+
pos: (_req, res) => res.header('Access-Control-Allow-Origin', '*')
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
const logger: Plugin = {
|
|
59
|
-
pos: (req, res) => {
|
|
60
|
-
console.log(`${req.method} ${req.path} -> ${res.status}`)
|
|
61
|
-
}
|
|
89
|
+
pos: (req, res) => console.log(`${req.method} ${req.path} -> ${res.status}`)
|
|
62
90
|
}
|
|
63
91
|
|
|
64
92
|
new Tachyon({ security: 'strict' })
|
|
65
93
|
.use(cors)
|
|
66
94
|
.use(auth)
|
|
67
95
|
.use(logger)
|
|
68
|
-
.get('/api/users', () =>
|
|
96
|
+
.get('/api/users', () => status(200, []))
|
|
69
97
|
.listen(3000)
|
|
70
98
|
```
|
|
71
99
|
|
|
@@ -73,38 +101,39 @@ new Tachyon({ security: 'strict' })
|
|
|
73
101
|
|
|
74
102
|
```typescript
|
|
75
103
|
new Tachyon({
|
|
76
|
-
workers: 4,
|
|
104
|
+
workers: 4, // worker threads (default: CPU count)
|
|
77
105
|
security: 'basic', // 'none' | 'basic' | 'strict'
|
|
78
|
-
compressionThreshold: 1024, // bytes, 0 = all, -1 = disabled
|
|
106
|
+
compressionThreshold: 1024, // bytes, 0 = compress all, -1 = disabled
|
|
79
107
|
})
|
|
80
108
|
```
|
|
81
109
|
|
|
82
|
-
### Security
|
|
110
|
+
### Security presets
|
|
83
111
|
|
|
84
|
-
| Preset | Headers |
|
|
85
|
-
|
|
112
|
+
| Preset | Headers added |
|
|
113
|
+
|--------|---------------|
|
|
86
114
|
| `none` | None |
|
|
87
115
|
| `basic` | `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN` |
|
|
88
116
|
| `strict` | All from basic + `X-XSS-Protection`, `Referrer-Policy`, `Permissions-Policy`, `COOP`, `CORP` |
|
|
89
117
|
|
|
90
118
|
### Compression
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
Responses are automatically gzip-compressed when the client supports it (`Accept-Encoding: gzip`). Compression runs in Rust — zero JS overhead.
|
|
93
121
|
|
|
94
122
|
```typescript
|
|
95
|
-
new Tachyon()
|
|
96
|
-
new Tachyon({ compressionThreshold: 0 })
|
|
97
|
-
new Tachyon({ compressionThreshold: 4096 })// compress bodies >= 4KB
|
|
98
|
-
new Tachyon({ compressionThreshold: -1 })
|
|
123
|
+
new Tachyon() // default: compress bodies >= 1KB
|
|
124
|
+
new Tachyon({ compressionThreshold: 0 }) // compress everything
|
|
125
|
+
new Tachyon({ compressionThreshold: 4096 }) // compress bodies >= 4KB
|
|
126
|
+
new Tachyon({ compressionThreshold: -1 }) // disable compression
|
|
99
127
|
```
|
|
100
128
|
|
|
101
|
-
##
|
|
129
|
+
## How it works
|
|
102
130
|
|
|
103
|
-
- **Rust server** —
|
|
104
|
-
- **SIMD parser** — HTTP scanning with SSE4.2/AVX2/NEON, 68-90% faster
|
|
105
|
-
- **
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
131
|
+
- **Rust server** — TCP listener, buffer pool, HTTP parsing, and I/O run in Rust on Tokio workers
|
|
132
|
+
- **SIMD parser** — HTTP scanning with SSE4.2/AVX2/NEON (zero-copy, 68-90% faster than byte-by-byte)
|
|
133
|
+
- **Rust routing** — each route is dispatched via `HashMap<method, HashMap<path, handler>>` in Rust — O(1), no JS call for 404
|
|
134
|
+
- **Flat headers** — headers passed as a single string (1 allocation); parsed lazily in JS only if accessed
|
|
135
|
+
- **Native gzip** — compression in Rust, transparent to your handler
|
|
136
|
+
- **Non-blocking bridge** — JS callbacks use `rx.await` instead of blocking Tokio workers, so concurrency scales without thread explosion
|
|
108
137
|
|
|
109
138
|
## Platforms
|
|
110
139
|
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SecurityPreset = 'none' | 'basic' | 'strict';
|
|
2
|
+
export interface TachyonConfig {
|
|
3
|
+
security?: SecurityPreset;
|
|
4
|
+
/** Minimum body size in bytes to trigger gzip compression. 0 = compress all, -1 = disabled. Default: 1024 */
|
|
5
|
+
compressionThreshold?: number;
|
|
6
|
+
/** Catch panics in handlers. Disable for max performance in controlled environments. Default: true */
|
|
7
|
+
catchPanics?: boolean;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,6GAA6G;IAC7G,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,sGAAsG;IACtG,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB"}
|
package/dist/helper.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,QAAQ,MAAM,EAAE,UAAU,CAAC,KAAG,eAEvD,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { status } from "./helper";
|
|
1
2
|
export { Tachyon } from "./tachyon";
|
|
2
3
|
export { TachyonRequest } from "./request";
|
|
3
4
|
export { TachyonResponse } from "./response";
|
|
4
|
-
export type {
|
|
5
|
+
export type { OnRequestHook, OnResponseHook } from "./tachyon";
|
|
6
|
+
export type { SecurityPreset, TachyonConfig } from "./config";
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
function c(i){if(i===null||i===void 0)return{value:"null",valueType:"null"};switch(typeof i){case"string":return{value:i};case"number":return{value:String(i),valueType:"number"};case"boolean":return{value:String(i),valueType:"bool"};case"object":if(Array.isArray(i))return{valueType:"array",children:i.map((t)=>c(t))};return{valueType:"object",children:Object.entries(i).map(([t,d])=>({key:t,...c(d)}))};default:return{value:String(i)}}}class R{status;body;headers=[];_contentType="json";constructor(i,t){this.status=i;this.body=t}header(i,t){return this.headers.push({name:i,value:t}),this}text(){return this._contentType="text",this}convertToRustJson(){if(typeof this.body!=="object"||this.body===null)return;if(Array.isArray(this.body))return{array:this.body.map((i)=>c(i))};return{json:Object.entries(this.body).map(([i,t])=>({key:i,...c(t)}))}}toRaw(){let i=this.convertToRustJson();return{status:this.status,body:typeof this.body==="string"?this.body:void 0,contentType:typeof this.body==="string"?"text":this._contentType,headers:this.headers.length>0?this.headers:void 0,json:i?.json,array:i?.array}}}var x=(i,t)=>{return new R(i,typeof t==="string"?t:JSON.stringify(t))};import{TachyonRawServer as w}from"@tachyon-rs/server";class b{method;path;body;_headersRaw;_headers;constructor(i){this.method=i.method,this.path=i.path,this.body=i.body,this._headersRaw=i.headers}header(i){if(!this._headers)this._parseHeaders();return this._headers.get(i.toLowerCase())}get headers(){if(!this._headers)this._parseHeaders();return this._headers}_parseHeaders(){this._headers=new Map;let i=this._headersRaw,t=0;while(t<i.length){let d=i.indexOf("\t",t);if(d===-1)break;let f=i.indexOf(`
|
|
2
|
+
`,d+1);if(f===-1)break;let m=i.slice(t,d).toLowerCase(),g=i.slice(d+1,f);this._headers.set(m,g),t=f+1}}}var T=["GET","POST","PUT","DELETE"];class y{routes;plugins=[];config;constructor(i){this.routes=new Map,this.config=i??{}}use(i){return this.plugins.push(i),this}transformToResponse(i){return typeof i==="function"?i:()=>x(200,i)}get(i,t){return this.routes.set("0@"+i,this.transformToResponse(t)),this}post(i,t){return this.routes.set("1@"+i,this.transformToResponse(t)),this}put(i,t){return this.routes.set("2@"+i,this.transformToResponse(t)),this}delete(i,t){return this.routes.set("3@"+i,this.transformToResponse(t)),this}listen(i){let t=new w({bindAddr:"0.0.0.0:"+i,security:this.config.security??"basic",compressionThreshold:this.config.compressionThreshold,catchPanics:this.config.catchPanics}),d=this.plugins;for(let[f,m]of this.routes){let g=f.indexOf("@"),H=parseInt(f.slice(0,g)),O=f.slice(g+1);t.route(T[H]??"GET",O,(s)=>{let o=new b(s);for(let r of d){let _=r.pre?.(o);if(_)return _.toRaw()}let h=m(o);for(let r of d){let _=r.pos?.(o,h);if(_)h=_}return typeof h.toRaw==="function"?h.toRaw():h})}t.listen()}}export{x as status,R as TachyonResponse,b as TachyonRequest,y as Tachyon};
|
package/dist/request.d.ts
CHANGED
|
@@ -3,10 +3,12 @@ declare class TachyonRequest {
|
|
|
3
3
|
method: string;
|
|
4
4
|
path: string;
|
|
5
5
|
body: string | undefined;
|
|
6
|
+
private _headersRaw;
|
|
6
7
|
private _headers;
|
|
7
8
|
constructor(raw: TachyonRawRequest);
|
|
8
9
|
header(name: string): string | undefined;
|
|
9
10
|
get headers(): ReadonlyMap<string, string>;
|
|
11
|
+
private _parseHeaders;
|
|
10
12
|
}
|
|
11
13
|
export { TachyonRequest };
|
|
12
14
|
//# sourceMappingURL=request.d.ts.map
|
package/dist/request.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,cAAM,cAAc;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,cAAM,cAAc;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAiC;gBAErC,GAAG,EAAE,iBAAiB;IAOlC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKxC,IAAI,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAGzC;IAED,OAAO,CAAC,aAAa;CAetB;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/tachyon.d.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { TachyonRequest } from "./request";
|
|
2
2
|
import { TachyonResponse } from "./response";
|
|
3
|
-
|
|
4
|
-
export interface TachyonConfig {
|
|
5
|
-
workers?: number;
|
|
6
|
-
security?: SecurityPreset;
|
|
7
|
-
/** Minimum body size in bytes to trigger gzip compression. 0 = compress all, -1 = disabled. Default: 1024 */
|
|
8
|
-
compressionThreshold?: number;
|
|
9
|
-
}
|
|
3
|
+
import type { TachyonConfig } from "./config";
|
|
10
4
|
/**
|
|
11
5
|
* Pre-request hook. Runs before the route handler.
|
|
12
6
|
* - Return a `TachyonResponse` to short-circuit (e.g., 401 Unauthorized).
|
package/dist/tachyon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tachyon.d.ts","sourceRoot":"","sources":["../src/tachyon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"tachyon.d.ts","sourceRoot":"","sources":["../src/tachyon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,GAAG,IAAI,CAAA;AAE3E;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,eAAe,GAAG,IAAI,CAAA;AAElG,MAAM,MAAM,MAAM,GAAG;IACnB,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB,CAAA;AAED,cAAM,OAAO;IAEX,OAAO,CAAC,MAAM,CAAwD;IACtE,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,CAAC,EAAE,aAAa;IAK3B,GAAG,CAAC,MAAM,EAAE,MAAM;IAKzB,OAAO,CAAC,mBAAmB;IAIpB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKzG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAK1G,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKzG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAK5G,MAAM,CAAC,IAAI,EAAE,MAAM;CA0C3B;AAED,OAAO,EAAE,OAAO,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"build": "bun build src/index.ts --outdir dist --format esm --minify --external @tachyon-rs/server && tsc"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@tachyon-rs/server": "0.
|
|
20
|
+
"@tachyon-rs/server": "0.3.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/bun": "latest",
|
|
24
24
|
"typescript": "^5"
|
|
25
25
|
},
|
|
26
|
-
"version": "0.
|
|
26
|
+
"version": "0.3.0"
|
|
27
27
|
}
|