@daloyjs/core 0.4.0 → 0.5.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 +13 -5
- package/bin/daloy.mjs +52 -0
- package/dist/app.d.ts +14 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +33 -12
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +39 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +257 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/multipart.d.ts +129 -0
- package/dist/multipart.d.ts.map +1 -0
- package/dist/multipart.js +201 -0
- package/dist/multipart.js.map +1 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +53 -6
- package/dist/openapi.js.map +1 -1
- package/dist/rate-limit-redis.d.ts +101 -0
- package/dist/rate-limit-redis.d.ts.map +1 -0
- package/dist/rate-limit-redis.js +117 -0
- package/dist/rate-limit-redis.js.map +1 -0
- package/package.json +18 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-redis.d.ts","sourceRoot":"","sources":["../src/rate-limit-redis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED,uDAAuD;AACvD,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,WAAW,GAAG,aAAa,CAAC;CACzD;AA+BD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,cAAc,CAoBpF;AAID;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnF;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAMjE;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CACF,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAC/C,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAMrE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-backed {@link RateLimitStore} for {@link rateLimit}.
|
|
3
|
+
*
|
|
4
|
+
* The default in-process `MemoryStore` is per-instance and therefore unsafe
|
|
5
|
+
* behind more than one server replica. This store keeps the same token-bucket
|
|
6
|
+
* semantics (fixed window of `windowMs`) but stores the counter in Redis so
|
|
7
|
+
* every replica observes the same value.
|
|
8
|
+
*
|
|
9
|
+
* We avoid taking a hard dependency on any specific Redis client. Instead the
|
|
10
|
+
* store accepts a tiny {@link RedisCommands} contract: a single `eval`
|
|
11
|
+
* method. It also ships small adapters for the two most common clients
|
|
12
|
+
* ({@link ioredisAdapter}, {@link nodeRedisAdapter}). That keeps installs
|
|
13
|
+
* lightweight and means new clients can be plugged in with ~5 lines of glue
|
|
14
|
+
* code, which matches DaloyJS's "no magic, no global patching" rule.
|
|
15
|
+
*
|
|
16
|
+
* @example Using ioredis
|
|
17
|
+
* ```ts
|
|
18
|
+
* import IORedis from "ioredis";
|
|
19
|
+
* import { rateLimit } from "@daloyjs/core";
|
|
20
|
+
* import { redisRateLimitStore, ioredisAdapter } from "@daloyjs/core/rate-limit-redis";
|
|
21
|
+
*
|
|
22
|
+
* const redis = new IORedis(process.env.REDIS_URL!);
|
|
23
|
+
* app.use(rateLimit({
|
|
24
|
+
* windowMs: 60_000,
|
|
25
|
+
* max: 120,
|
|
26
|
+
* store: redisRateLimitStore({ client: ioredisAdapter(redis) }),
|
|
27
|
+
* }));
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Using node-redis v4+
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { createClient } from "redis";
|
|
33
|
+
* import { redisRateLimitStore, nodeRedisAdapter } from "@daloyjs/core/rate-limit-redis";
|
|
34
|
+
*
|
|
35
|
+
* const redis = createClient({ url: process.env.REDIS_URL });
|
|
36
|
+
* await redis.connect();
|
|
37
|
+
* app.use(rateLimit({
|
|
38
|
+
* windowMs: 60_000,
|
|
39
|
+
* max: 120,
|
|
40
|
+
* store: redisRateLimitStore({ client: nodeRedisAdapter(redis) }),
|
|
41
|
+
* }));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Atomic INCR + PEXPIRE script.
|
|
46
|
+
*
|
|
47
|
+
* Returns `{count, ttlMs}` so the caller can compute `resetMs` without a
|
|
48
|
+
* second round trip. We set the TTL only on the very first INCR so a busy
|
|
49
|
+
* key keeps its original window and is not perpetually extended.
|
|
50
|
+
*/
|
|
51
|
+
const SCRIPT = `local current = redis.call('INCR', KEYS[1])
|
|
52
|
+
if current == 1 then
|
|
53
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[1])
|
|
54
|
+
return {current, tonumber(ARGV[1])}
|
|
55
|
+
end
|
|
56
|
+
local ttl = redis.call('PTTL', KEYS[1])
|
|
57
|
+
if ttl < 0 then
|
|
58
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[1])
|
|
59
|
+
ttl = tonumber(ARGV[1])
|
|
60
|
+
end
|
|
61
|
+
return {current, ttl}`;
|
|
62
|
+
function toNumber(value) {
|
|
63
|
+
if (typeof value === "number")
|
|
64
|
+
return value;
|
|
65
|
+
if (typeof value === "bigint")
|
|
66
|
+
return Number(value);
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
const n = Number(value);
|
|
69
|
+
return Number.isFinite(n) ? n : 0;
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build a {@link RateLimitStore} that persists counters in Redis.
|
|
75
|
+
*
|
|
76
|
+
* The returned store is safe to share between requests and replicas. Errors
|
|
77
|
+
* from Redis are fail-open by default (see {@link RedisRateLimitStoreOptions.onError});
|
|
78
|
+
* pass a custom handler to fail-closed (return `"fail-closed"`).
|
|
79
|
+
*/
|
|
80
|
+
export function redisRateLimitStore(opts) {
|
|
81
|
+
const prefix = opts.prefix ?? "daloy:rl:";
|
|
82
|
+
const onError = opts.onError;
|
|
83
|
+
return {
|
|
84
|
+
async hit(key, windowMs) {
|
|
85
|
+
const fullKey = prefix + key;
|
|
86
|
+
try {
|
|
87
|
+
const result = (await opts.client.eval(SCRIPT, [fullKey], [String(windowMs)]));
|
|
88
|
+
const count = toNumber(result?.[0]);
|
|
89
|
+
const ttl = toNumber(result?.[1]);
|
|
90
|
+
return { count, resetMs: Date.now() + ttl };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const decision = onError ? onError(err) : "fail-open";
|
|
94
|
+
if (decision === "fail-closed")
|
|
95
|
+
throw err;
|
|
96
|
+
return { count: 1, resetMs: Date.now() + windowMs };
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Wrap an [`ioredis`](https://github.com/redis/ioredis) client. */
|
|
102
|
+
export function ioredisAdapter(client) {
|
|
103
|
+
return {
|
|
104
|
+
eval(script, keys, args) {
|
|
105
|
+
return client.eval(script, keys.length, ...keys, ...args);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Wrap a [`node-redis`](https://github.com/redis/node-redis) v4+ client. */
|
|
110
|
+
export function nodeRedisAdapter(client) {
|
|
111
|
+
return {
|
|
112
|
+
eval(script, keys, args) {
|
|
113
|
+
return client.eval(script, { keys, arguments: args });
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=rate-limit-redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-redis.js","sourceRoot":"","sources":["../src/rate-limit-redis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAiCH;;;;;;GAMG;AACH,MAAM,MAAM,GAAG;;;;;;;;;;sBAUO,CAAC;AAEvB,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,QAAgB;YACrC,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAEvD,CAAC;gBACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAClC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;YAC9C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;gBACtD,IAAI,QAAQ,KAAK,aAAa;oBAAE,MAAM,GAAG,CAAC;gBAC1C,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YACtD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAYD,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI;YACrB,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;QAC5D,CAAC;KACF,CAAC;AACJ,CAAC;AAaD,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,MAAqB;IACpD,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI;YACrB,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daloyjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"author": "DaloyJS",
|
|
18
18
|
"main": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
|
+
"bin": {
|
|
21
|
+
"daloy": "bin/daloy.mjs"
|
|
22
|
+
},
|
|
20
23
|
"engines": {
|
|
21
24
|
"node": ">=20.10.0",
|
|
22
25
|
"pnpm": ">=9.0.0"
|
|
@@ -69,10 +72,23 @@
|
|
|
69
72
|
"./tracing": {
|
|
70
73
|
"types": "./dist/tracing.d.ts",
|
|
71
74
|
"import": "./dist/tracing.js"
|
|
75
|
+
},
|
|
76
|
+
"./multipart": {
|
|
77
|
+
"types": "./dist/multipart.d.ts",
|
|
78
|
+
"import": "./dist/multipart.js"
|
|
79
|
+
},
|
|
80
|
+
"./rate-limit-redis": {
|
|
81
|
+
"types": "./dist/rate-limit-redis.d.ts",
|
|
82
|
+
"import": "./dist/rate-limit-redis.js"
|
|
83
|
+
},
|
|
84
|
+
"./cli": {
|
|
85
|
+
"types": "./dist/cli.d.ts",
|
|
86
|
+
"import": "./dist/cli.js"
|
|
72
87
|
}
|
|
73
88
|
},
|
|
74
89
|
"files": [
|
|
75
90
|
"dist",
|
|
91
|
+
"bin",
|
|
76
92
|
"README.md"
|
|
77
93
|
],
|
|
78
94
|
"keywords": [
|
|
@@ -110,6 +126,7 @@
|
|
|
110
126
|
"gen:openapi": "node --import tsx scripts/dump-openapi.ts",
|
|
111
127
|
"gen:client": "openapi-ts",
|
|
112
128
|
"gen": "pnpm gen:openapi && pnpm gen:client",
|
|
129
|
+
"verify:lockfile": "node --import tsx scripts/verify-lockfile-sources.ts",
|
|
113
130
|
"audit": "pnpm audit --prod"
|
|
114
131
|
}
|
|
115
132
|
}
|