halt-rate 0.3.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 +9 -1
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/fastify.d.mts +29 -0
- package/dist/adapters/fastify.d.ts +29 -0
- package/dist/adapters/fastify.js +38 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/fastify.mjs +35 -0
- package/dist/adapters/fastify.mjs.map +1 -0
- package/dist/adapters/graphql.d.mts +22 -0
- package/dist/adapters/graphql.d.ts +22 -0
- package/dist/adapters/graphql.js +51 -0
- package/dist/adapters/graphql.js.map +1 -0
- package/dist/adapters/graphql.mjs +49 -0
- package/dist/adapters/graphql.mjs.map +1 -0
- package/dist/adapters/hono.d.mts +30 -0
- package/dist/adapters/hono.d.ts +30 -0
- package/dist/adapters/hono.js +56 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/hono.mjs +54 -0
- package/dist/adapters/hono.mjs.map +1 -0
- package/dist/adapters/next.d.mts +1 -1
- package/dist/adapters/next.d.ts +1 -1
- package/dist/adapters/next.js +8 -3
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/next.mjs +8 -3
- package/dist/adapters/next.mjs.map +1 -1
- package/dist/index.d.mts +51 -3
- package/dist/index.d.ts +51 -3
- package/dist/index.js +74 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{limiter-DqVVE0Kl.d.mts → limiter-C_Yf7DTg.d.mts} +2 -0
- package/dist/{limiter-DqVVE0Kl.d.ts → limiter-C_Yf7DTg.d.ts} +2 -0
- package/package.json +66 -11
package/dist/index.mjs
CHANGED
|
@@ -28,6 +28,7 @@ function normalizePolicy(policy) {
|
|
|
28
28
|
algorithm: policy.algorithm ?? "token_bucket" /* TOKEN_BUCKET */,
|
|
29
29
|
keyStrategy: policy.keyStrategy ?? "ip" /* IP */,
|
|
30
30
|
burst: policy.burst ?? Math.floor(policy.limit * 1.2),
|
|
31
|
+
slidingPrecision: policy.slidingPrecision ?? 10,
|
|
31
32
|
cost: policy.cost ?? 1,
|
|
32
33
|
blockDuration: policy.blockDuration ?? void 0,
|
|
33
34
|
keyExtractor: policy.keyExtractor ?? void 0,
|
|
@@ -43,6 +44,9 @@ function normalizePolicy(policy) {
|
|
|
43
44
|
if (normalized.cost <= 0) {
|
|
44
45
|
throw new Error("cost must be positive");
|
|
45
46
|
}
|
|
47
|
+
if (!Number.isInteger(normalized.slidingPrecision) || normalized.slidingPrecision <= 0) {
|
|
48
|
+
throw new Error("slidingPrecision must be a positive integer");
|
|
49
|
+
}
|
|
46
50
|
if (normalized.burst < normalized.limit) {
|
|
47
51
|
throw new Error("burst must be >= limit");
|
|
48
52
|
}
|
|
@@ -484,21 +488,22 @@ var RateLimiter = class {
|
|
|
484
488
|
return decision2;
|
|
485
489
|
}
|
|
486
490
|
const store = this.store;
|
|
487
|
-
|
|
491
|
+
const cacheKey = `${policy.name}|${policy.algorithm}|${policy.limit}|${policy.window}|${policy.burst}|${policy.slidingPrecision}`;
|
|
492
|
+
let algorithm = this.algorithmCache.get(cacheKey);
|
|
488
493
|
if (!algorithm) {
|
|
489
494
|
if (policy.algorithm === "token_bucket" /* TOKEN_BUCKET */) {
|
|
490
495
|
algorithm = new TokenBucket(policy.burst, policy.limit, policy.window);
|
|
491
496
|
} else if (policy.algorithm === "fixed_window" /* FIXED_WINDOW */) {
|
|
492
497
|
algorithm = new FixedWindow(policy.limit, policy.window);
|
|
493
498
|
} else if (policy.algorithm === "sliding_window" /* SLIDING_WINDOW */) {
|
|
494
|
-
algorithm = new SlidingWindow(policy.limit, policy.window);
|
|
499
|
+
algorithm = new SlidingWindow(policy.limit, policy.window, policy.slidingPrecision);
|
|
495
500
|
} else if (policy.algorithm === "leaky_bucket" /* LEAKY_BUCKET */) {
|
|
496
501
|
const leakRate = policy.limit / policy.window;
|
|
497
502
|
algorithm = new LeakyBucket(policy.burst, leakRate, policy.window);
|
|
498
503
|
} else {
|
|
499
504
|
throw new Error(`Algorithm ${policy.algorithm} not implemented`);
|
|
500
505
|
}
|
|
501
|
-
this.algorithmCache.set(
|
|
506
|
+
this.algorithmCache.set(cacheKey, algorithm);
|
|
502
507
|
}
|
|
503
508
|
const span = this.otelTracer?.startSpan?.("halt.check", { attributes: { policy: policy.name, key } });
|
|
504
509
|
const state = store.get(storageKey);
|
|
@@ -1093,6 +1098,70 @@ function getPlanPolicy(planName) {
|
|
|
1093
1098
|
return PLAN_TIERS[normalized];
|
|
1094
1099
|
}
|
|
1095
1100
|
|
|
1101
|
+
// src/core/registry.ts
|
|
1102
|
+
var PolicyRegistry = class {
|
|
1103
|
+
constructor(initial = []) {
|
|
1104
|
+
this.policies = /* @__PURE__ */ new Map();
|
|
1105
|
+
for (const p of initial) this.register(p);
|
|
1106
|
+
}
|
|
1107
|
+
/** Add or replace a policy (keyed by `policy.name`). */
|
|
1108
|
+
register(policy) {
|
|
1109
|
+
this.policies.set(policy.name, policy);
|
|
1110
|
+
return this;
|
|
1111
|
+
}
|
|
1112
|
+
get(name) {
|
|
1113
|
+
return this.policies.get(name);
|
|
1114
|
+
}
|
|
1115
|
+
has(name) {
|
|
1116
|
+
return this.policies.has(name);
|
|
1117
|
+
}
|
|
1118
|
+
/** Mutate fields of an existing policy at runtime (e.g. `{ limit: 500 }`). */
|
|
1119
|
+
update(name, patch) {
|
|
1120
|
+
const existing = this.policies.get(name);
|
|
1121
|
+
if (!existing) throw new Error(`Unknown policy: ${name}`);
|
|
1122
|
+
const updated = { ...existing, ...patch, name: existing.name };
|
|
1123
|
+
if (patch.limit !== void 0 && patch.burst === void 0) {
|
|
1124
|
+
delete updated.burst;
|
|
1125
|
+
}
|
|
1126
|
+
this.policies.set(name, updated);
|
|
1127
|
+
return updated;
|
|
1128
|
+
}
|
|
1129
|
+
remove(name) {
|
|
1130
|
+
return this.policies.delete(name);
|
|
1131
|
+
}
|
|
1132
|
+
list() {
|
|
1133
|
+
return [...this.policies.values()];
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Build a resolver for the limiter's `policy` option.
|
|
1137
|
+
* @param selector maps a request to a registered policy name.
|
|
1138
|
+
*/
|
|
1139
|
+
resolver(selector) {
|
|
1140
|
+
return (request) => {
|
|
1141
|
+
const name = selector(request);
|
|
1142
|
+
const policy = this.policies.get(name);
|
|
1143
|
+
if (!policy) throw new Error(`Unknown policy: ${name}`);
|
|
1144
|
+
return policy;
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
function cachedPolicyResolver(loader, options = {}) {
|
|
1149
|
+
const ttlMs = options.ttlMs ?? 5e3;
|
|
1150
|
+
const keyFn = options.key ?? (() => "__default__");
|
|
1151
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1152
|
+
return async (request) => {
|
|
1153
|
+
const key = keyFn(request);
|
|
1154
|
+
const now = Date.now();
|
|
1155
|
+
const hit = cache.get(key);
|
|
1156
|
+
if (hit && hit.expires > now) {
|
|
1157
|
+
return hit.policy;
|
|
1158
|
+
}
|
|
1159
|
+
const policy = await loader(request);
|
|
1160
|
+
cache.set(key, { policy, expires: now + ttlMs });
|
|
1161
|
+
return policy;
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1096
1165
|
// src/core/quota.ts
|
|
1097
1166
|
var QuotaPeriod = /* @__PURE__ */ ((QuotaPeriod2) => {
|
|
1098
1167
|
QuotaPeriod2["HOURLY"] = "hourly";
|
|
@@ -1611,6 +1680,6 @@ var OpenTelemetryMetrics = class {
|
|
|
1611
1680
|
}
|
|
1612
1681
|
};
|
|
1613
1682
|
|
|
1614
|
-
export { Algorithm, CompositeTelemetry, InMemoryStore, KeyStrategy, LoggingTelemetry, MetricsTelemetry, OpenTelemetryMetrics, PENALTY_LENIENT, PENALTY_MODERATE, PENALTY_STRICT, PenaltyManager, QUOTA_ENTERPRISE_MONTHLY, QUOTA_FREE_DAILY, QUOTA_FREE_MONTHLY, QUOTA_PRO_DAILY, QUOTA_PRO_MONTHLY, QuotaManager, QuotaPeriod, RateLimiter, RedisStore, StatsCollector, extractors_exports as extractors, isAtomicStore, normalizePolicy, presets_exports as presets, toHeaders };
|
|
1683
|
+
export { Algorithm, CompositeTelemetry, InMemoryStore, KeyStrategy, LoggingTelemetry, MetricsTelemetry, OpenTelemetryMetrics, PENALTY_LENIENT, PENALTY_MODERATE, PENALTY_STRICT, PenaltyManager, PolicyRegistry, QUOTA_ENTERPRISE_MONTHLY, QUOTA_FREE_DAILY, QUOTA_FREE_MONTHLY, QUOTA_PRO_DAILY, QUOTA_PRO_MONTHLY, QuotaManager, QuotaPeriod, RateLimiter, RedisStore, StatsCollector, cachedPolicyResolver, extractors_exports as extractors, isAtomicStore, normalizePolicy, presets_exports as presets, toHeaders };
|
|
1615
1684
|
//# sourceMappingURL=index.mjs.map
|
|
1616
1685
|
//# sourceMappingURL=index.mjs.map
|