@mark1russell7/service 0.5.6 → 0.5.8
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 +16 -3
- package/build/http.d.ts +19 -3
- package/build/http.js +32 -8
- package/build/http.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.js +2 -2
- package/build/limiter-DCDdzVwe.d.ts +45 -0
- package/build/limiter-pkBZUp-t.js +402 -0
- package/build/limiter-pkBZUp-t.js.map +1 -0
- package/build/limiter.d.ts +2 -1
- package/build/limiter.js +2 -2
- package/package.json +2 -2
- package/build/limiter-wv0uXDk9.js +0 -126
- package/build/limiter-wv0uXDk9.js.map +0 -1
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { a as limiterProblems, i as limitRule, n as freeAt, o as longestRetryAfter, t as decide } from "./policy-CgS4y94U.js";
|
|
2
|
-
import { convertExact, rationals } from "@mark1russell7/quantity";
|
|
3
|
-
//#region src/limiter/store.ts
|
|
4
|
-
const ms = (value) => ({
|
|
5
|
-
value,
|
|
6
|
-
unit: "ms"
|
|
7
|
-
});
|
|
8
|
-
/**
|
|
9
|
-
* This function makes the store of one replica. It reads time only through the clock. Thus a step
|
|
10
|
-
* of the wall clock does not move a count, and a fake clock gives exact decisions in a test.
|
|
11
|
-
* - The eviction is exact. A key whose bucket is full again, or whose window has no take, is equal
|
|
12
|
-
* to a new key. The store removes it at its next take, or at a sweep.
|
|
13
|
-
* - The count of keys has a bound. At the cap, a take of a new key sweeps the table, but only when
|
|
14
|
-
* a held key can be free. Each sweep reads each key one time. When no key is free, the store
|
|
15
|
-
* refuses the new key with the cause `cap`. Its `retryAfter` is at most the time until the
|
|
16
|
-
* earliest held key is free, and that time is at most `idleSpan` of the policy.
|
|
17
|
-
*/
|
|
18
|
-
const memoryStore = (clock) => {
|
|
19
|
-
const tables = /* @__PURE__ */ new Map();
|
|
20
|
-
const sweep = (table, rule, now) => {
|
|
21
|
-
let next = Number.POSITIVE_INFINITY;
|
|
22
|
-
for (const [key, state] of table.states) {
|
|
23
|
-
const free = freeAt(rule, state);
|
|
24
|
-
if (free <= now) table.states.delete(key);
|
|
25
|
-
else next = Math.min(next, free);
|
|
26
|
-
}
|
|
27
|
-
table.next = next;
|
|
28
|
-
};
|
|
29
|
-
return {
|
|
30
|
-
kind: "local",
|
|
31
|
-
take({ limiter, key, rule, maxKeys }) {
|
|
32
|
-
const now = clock.now();
|
|
33
|
-
let table = tables.get(limiter);
|
|
34
|
-
if (table === void 0) {
|
|
35
|
-
table = {
|
|
36
|
-
states: /* @__PURE__ */ new Map(),
|
|
37
|
-
next: Number.POSITIVE_INFINITY
|
|
38
|
-
};
|
|
39
|
-
tables.set(limiter, table);
|
|
40
|
-
}
|
|
41
|
-
let state = table.states.get(key);
|
|
42
|
-
if (state !== void 0 && freeAt(rule, state) <= now) {
|
|
43
|
-
table.states.delete(key);
|
|
44
|
-
state = void 0;
|
|
45
|
-
}
|
|
46
|
-
if (state === void 0 && table.states.size >= maxKeys) {
|
|
47
|
-
if (table.next <= now) sweep(table, rule, now);
|
|
48
|
-
if (table.states.size >= maxKeys) return {
|
|
49
|
-
allowed: false,
|
|
50
|
-
retryAfter: ms(table.next - now),
|
|
51
|
-
cause: "cap"
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const step = decide(rule, state, now);
|
|
55
|
-
if (state === void 0) {
|
|
56
|
-
table.states.set(key, step.state);
|
|
57
|
-
table.next = Math.min(table.next, freeAt(rule, step.state));
|
|
58
|
-
} else table.states.set(key, step.state);
|
|
59
|
-
return step.decision;
|
|
60
|
-
},
|
|
61
|
-
size(limiter) {
|
|
62
|
-
return tables.get(limiter)?.states.size ?? 0;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
/** This function gives the text of a failure. */
|
|
67
|
-
const failureOf = (error) => error instanceof Error ? error.message : String(error);
|
|
68
|
-
/**
|
|
69
|
-
* This function makes the runtime of a limiter from its spec and its store. It refuses a spec with a
|
|
70
|
-
* problem, because the limiter has no defaults. It also refuses a store of the wrong kind: a spec
|
|
71
|
-
* with a shared store needs a store that the replicas share.
|
|
72
|
-
* - A failure of a shared store gives the decision of `onStoreFailure`. With `open`, the take is
|
|
73
|
-
* permitted with `remaining` 0. With `closed`, it is refused with the cause `store`.
|
|
74
|
-
* - The `retryAfter` of the cause `store` is the longest `retryAfter` of the policy.
|
|
75
|
-
* - A failure of a local store rejects the take, because a local store has no `onStoreFailure`.
|
|
76
|
-
*/
|
|
77
|
-
const keyedLimiter = (name, spec, store) => {
|
|
78
|
-
const problems = limiterProblems(name, spec);
|
|
79
|
-
if (problems.length > 0) throw new Error(problems.join("; "));
|
|
80
|
-
if (store.kind !== spec.store.kind) throw new Error(`limiter ${name} declares a ${spec.store.kind} store, but the store that it got is ${store.kind}`);
|
|
81
|
-
const rule = limitRule(spec.policy);
|
|
82
|
-
const maxKeys = rationals.toNumber(convertExact(rationals.fromNumber(spec.evict.maxKeys.value), spec.evict.maxKeys.unit, "count"));
|
|
83
|
-
const onStoreFailure = spec.store.kind === "shared" ? spec.store.onStoreFailure : void 0;
|
|
84
|
-
const closedAfter = longestRetryAfter(spec.policy);
|
|
85
|
-
const take = async (key) => {
|
|
86
|
-
const request = {
|
|
87
|
-
limiter: name,
|
|
88
|
-
key,
|
|
89
|
-
rule,
|
|
90
|
-
maxKeys
|
|
91
|
-
};
|
|
92
|
-
if (onStoreFailure === void 0) return store.take(request);
|
|
93
|
-
try {
|
|
94
|
-
return await store.take(request);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
const failure = failureOf(error);
|
|
97
|
-
if (onStoreFailure === "open") return {
|
|
98
|
-
allowed: true,
|
|
99
|
-
remaining: 0,
|
|
100
|
-
failure
|
|
101
|
-
};
|
|
102
|
-
return {
|
|
103
|
-
allowed: false,
|
|
104
|
-
retryAfter: { ...closedAfter },
|
|
105
|
-
cause: "store",
|
|
106
|
-
failure
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
return {
|
|
111
|
-
name,
|
|
112
|
-
spec: {
|
|
113
|
-
key: spec.key,
|
|
114
|
-
policy: { ...spec.policy },
|
|
115
|
-
store: { ...spec.store },
|
|
116
|
-
evict: { maxKeys: { ...spec.evict.maxKeys } }
|
|
117
|
-
},
|
|
118
|
-
rule,
|
|
119
|
-
maxKeys,
|
|
120
|
-
take
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
//#endregion
|
|
124
|
-
export { memoryStore as n, keyedLimiter as t };
|
|
125
|
-
|
|
126
|
-
//# sourceMappingURL=limiter-wv0uXDk9.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"limiter-wv0uXDk9.js","names":["r"],"sources":["../src/limiter/store.ts"],"sourcesContent":["import type { Quantity } from \"@mark1russell7/quantity\";\nimport { convertExact, rationals as r } from \"@mark1russell7/quantity\";\nimport type { Clock } from \"../clock.ts\";\nimport type { Decision, KeyState, LimiterSpec, LimitRule } from \"./policy.ts\";\nimport { decide, freeAt, limitRule, limiterProblems, longestRetryAfter } from \"./policy.ts\";\n\n/** A take request is one take of one key, with the rule and the cap of its limiter in numbers. */\nexport interface TakeRequest {\n /** This name is the limiter. A store keeps the keys of each limiter apart. */\n readonly limiter: string;\n readonly key: string;\n readonly rule: LimitRule;\n /** This count is the most keys that the store holds for the limiter. */\n readonly maxKeys: number;\n}\n\n/**\n * A limiter store keeps the state of each key, and it decides each take in one atomic step, on its\n * own clock. Thus two takes of one key do not read the same state at any time. A store in this\n * process uses `decide`. A store that the replicas share does the same arithmetic in one step of its\n * own, for example one script of Redis that reads `TIME`.\n */\nexport interface LimiterStore {\n /** This kind tells where the counts are: in the memory of this replica, or in a store that the replicas share. */\n readonly kind: \"local\" | \"shared\";\n /**\n * This method decides one take. It removes a key only when its state is equal to the state of a\n * new key. It holds at most `maxKeys` keys for each limiter. A failure of a shared store rejects\n * the take, and the limiter then applies the `onStoreFailure` of its spec.\n */\n take(request: TakeRequest): Decision | Promise<Decision>;\n}\n\n/** A memory store is the store of one replica, on the injected monotonic clock. */\nexport interface MemoryStore extends LimiterStore {\n readonly kind: \"local\";\n /** This method gives the count of keys that the store holds for a limiter. */\n size(limiter: string): number;\n}\n\n/** A table is the keys of one limiter in a memory store. */\ninterface Table {\n readonly states: Map<string, KeyState>;\n /**\n * This time is at or before the earliest time at which a held key is equal to a new key. An\n * insert lowers it, and a sweep sets it again. A take of a held key only moves the time of that\n * key later, thus the bound stays true.\n */\n next: number;\n}\n\nconst ms = (value: number): Quantity => ({ value, unit: \"ms\" });\n\n/**\n * This function makes the store of one replica. It reads time only through the clock. Thus a step\n * of the wall clock does not move a count, and a fake clock gives exact decisions in a test.\n * - The eviction is exact. A key whose bucket is full again, or whose window has no take, is equal\n * to a new key. The store removes it at its next take, or at a sweep.\n * - The count of keys has a bound. At the cap, a take of a new key sweeps the table, but only when\n * a held key can be free. Each sweep reads each key one time. When no key is free, the store\n * refuses the new key with the cause `cap`. Its `retryAfter` is at most the time until the\n * earliest held key is free, and that time is at most `idleSpan` of the policy.\n */\nexport const memoryStore = (clock: Clock): MemoryStore => {\n const tables = new Map<string, Table>();\n const sweep = (table: Table, rule: LimitRule, now: number): void => {\n let next = Number.POSITIVE_INFINITY;\n for (const [key, state] of table.states) {\n const free = freeAt(rule, state);\n if (free <= now) table.states.delete(key);\n else next = Math.min(next, free);\n }\n table.next = next;\n };\n return {\n kind: \"local\",\n take({ limiter, key, rule, maxKeys }) {\n const now = clock.now();\n let table = tables.get(limiter);\n if (table === undefined) {\n table = { states: new Map(), next: Number.POSITIVE_INFINITY };\n tables.set(limiter, table);\n }\n let state = table.states.get(key);\n // a key that is equal to a new key goes: the decision is the same, and it frees its slot\n if (state !== undefined && freeAt(rule, state) <= now) {\n table.states.delete(key);\n state = undefined;\n }\n if (state === undefined && table.states.size >= maxKeys) {\n if (table.next <= now) sweep(table, rule, now);\n if (table.states.size >= maxKeys) return { allowed: false, retryAfter: ms(table.next - now), cause: \"cap\" };\n }\n const step = decide(rule, state, now);\n if (state === undefined) {\n // a new key is never refused by its policy: a full bucket, or an empty window\n table.states.set(key, step.state);\n table.next = Math.min(table.next, freeAt(rule, step.state));\n } else table.states.set(key, step.state);\n return step.decision;\n },\n size(limiter) {\n return tables.get(limiter)?.states.size ?? 0;\n },\n };\n};\n\n/** A keyed limiter is the runtime of one declared limiter: its spec, its rule in numbers, and its store. */\nexport interface KeyedLimiter {\n readonly name: string;\n /** This spec is the declaration, the same data that `svc.limiter(name, spec)` takes and `describe()` shows. */\n readonly spec: LimiterSpec;\n readonly rule: LimitRule;\n /** This count is the most keys that the store holds for the limiter. */\n readonly maxKeys: number;\n /**\n * This method takes one unit for a key, and gives the decision of the store. When a shared store\n * fails, it gives the decision of `onStoreFailure`, with the failure as text.\n */\n take(key: string): Promise<Decision>;\n}\n\n/** This function gives the text of a failure. */\nconst failureOf = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n\n/**\n * This function makes the runtime of a limiter from its spec and its store. It refuses a spec with a\n * problem, because the limiter has no defaults. It also refuses a store of the wrong kind: a spec\n * with a shared store needs a store that the replicas share.\n * - A failure of a shared store gives the decision of `onStoreFailure`. With `open`, the take is\n * permitted with `remaining` 0. With `closed`, it is refused with the cause `store`.\n * - The `retryAfter` of the cause `store` is the longest `retryAfter` of the policy.\n * - A failure of a local store rejects the take, because a local store has no `onStoreFailure`.\n */\nexport const keyedLimiter = (name: string, spec: LimiterSpec, store: LimiterStore): KeyedLimiter => {\n const problems = limiterProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n if (store.kind !== spec.store.kind)\n throw new Error(`limiter ${name} declares a ${spec.store.kind} store, but the store that it got is ${store.kind}`);\n const rule = limitRule(spec.policy);\n const maxKeys = r.toNumber(convertExact(r.fromNumber(spec.evict.maxKeys.value), spec.evict.maxKeys.unit, \"count\"));\n const onStoreFailure = spec.store.kind === \"shared\" ? spec.store.onStoreFailure : undefined;\n const closedAfter = longestRetryAfter(spec.policy);\n const take = async (key: string): Promise<Decision> => {\n const request: TakeRequest = { limiter: name, key, rule, maxKeys };\n if (onStoreFailure === undefined) return store.take(request);\n try {\n return await store.take(request);\n } catch (error) {\n const failure = failureOf(error);\n if (onStoreFailure === \"open\") return { allowed: true, remaining: 0, failure };\n return { allowed: false, retryAfter: { ...closedAfter }, cause: \"store\", failure };\n }\n };\n return {\n name,\n spec: {\n key: spec.key,\n policy: { ...spec.policy },\n store: { ...spec.store },\n evict: { maxKeys: { ...spec.evict.maxKeys } },\n },\n rule,\n maxKeys,\n take,\n };\n};\n"],"mappings":"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"}
|