@mark1russell7/service 0.5.2 → 0.5.4
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 +68 -0
- package/build/{builder-BzjhWk2E.d.ts → builder-DyDB7zM9.d.ts} +9 -2
- package/build/http.d.ts +9 -1
- package/build/http.js +25 -1
- package/build/http.js.map +1 -1
- package/build/index.d.ts +31 -8
- package/build/index.js +117 -5
- package/build/index.js.map +1 -1
- package/build/{lifecycle-DjEpgpRk.d.ts → lifecycle-Chu9Pri2.d.ts} +7 -1
- package/build/limiter-wv0uXDk9.js +126 -0
- package/build/limiter-wv0uXDk9.js.map +1 -0
- package/build/limiter.d.ts +3 -0
- package/build/limiter.js +3 -0
- package/build/policy-CgS4y94U.js +187 -0
- package/build/policy-CgS4y94U.js.map +1 -0
- package/build/policy-G4QJ1ZZq.d.ts +176 -0
- package/build/redis.d.ts +43 -3
- package/build/redis.js +154 -1
- package/build/redis.js.map +1 -1
- package/build/store-DzTyosou.d.ts +72 -0
- package/build/testing.d.ts +2 -2
- package/package.json +9 -4
package/build/index.js
CHANGED
|
@@ -2,7 +2,9 @@ import { platformClock, realClock } from "./clock.js";
|
|
|
2
2
|
import { detectionTime, heartbeat } from "./heartbeat.js";
|
|
3
3
|
import { a as recoverSpan, i as heartbeatDetection, n as asWindow, r as failSpan, t as asDebounce } from "./timing-DGRi3XCI.js";
|
|
4
4
|
import { n as operationProblems, t as mayReissue } from "./operation-DL5RxFds.js";
|
|
5
|
+
import { a as limiterProblems, i as limitRule, n as freeAt, o as longestRetryAfter, r as idleSpan, s as retryAfterSeconds, t as decide } from "./policy-CgS4y94U.js";
|
|
5
6
|
import { backoffBand, backoffDelay, backoffProblems } from "./backoff.js";
|
|
7
|
+
import { n as memoryStore, t as keyedLimiter } from "./limiter-wv0uXDk9.js";
|
|
6
8
|
import { n as countOf, r as millisecondsOf, t as boundedMillisecondsOf } from "./knobs-BxG-c7Zb.js";
|
|
7
9
|
import { convertExact, convertible, quantity, rationals, valueIn } from "@mark1russell7/quantity";
|
|
8
10
|
import { replay } from "@mark1russell7/algebra/replay";
|
|
@@ -1427,6 +1429,7 @@ const service = (name) => {
|
|
|
1427
1429
|
const choices = /* @__PURE__ */ new Map();
|
|
1428
1430
|
const checks = [];
|
|
1429
1431
|
const runs = /* @__PURE__ */ new Map();
|
|
1432
|
+
const limiters = [];
|
|
1430
1433
|
const couplings = [];
|
|
1431
1434
|
const phases = [];
|
|
1432
1435
|
let readiness;
|
|
@@ -1587,6 +1590,19 @@ const service = (name) => {
|
|
|
1587
1590
|
}
|
|
1588
1591
|
return builder;
|
|
1589
1592
|
},
|
|
1593
|
+
limiter(entry, spec) {
|
|
1594
|
+
composing(`the limiter ${entry}`);
|
|
1595
|
+
once(limiters, entry, "limiter");
|
|
1596
|
+
const given = spec ?? {};
|
|
1597
|
+
limiters.push({
|
|
1598
|
+
name: entry,
|
|
1599
|
+
...given,
|
|
1600
|
+
...given.policy === void 0 ? {} : { policy: { ...given.policy } },
|
|
1601
|
+
...given.store === void 0 ? {} : { store: { ...given.store } },
|
|
1602
|
+
...given.evict === void 0 ? {} : { evict: { ...given.evict } }
|
|
1603
|
+
});
|
|
1604
|
+
return builder;
|
|
1605
|
+
},
|
|
1590
1606
|
healthServer(server) {
|
|
1591
1607
|
composing("the health server");
|
|
1592
1608
|
if (probes !== null) throw new Error(`${name} already has a health server`);
|
|
@@ -1716,6 +1732,11 @@ const service = (name) => {
|
|
|
1716
1732
|
for (const [route, operation] of Object.entries(controller.routes)) problems.push(...operationProblems(`controller ${controller.name}`, route, operation));
|
|
1717
1733
|
for (const declared of controller.patterns) problems.push(...routePatternProblems(`controller ${controller.name}`, declared));
|
|
1718
1734
|
}
|
|
1735
|
+
for (const limiter of limiters) {
|
|
1736
|
+
const { name: entry, ...spec } = limiter;
|
|
1737
|
+
problems.push(...limiterProblems(entry, spec));
|
|
1738
|
+
if (limiter.store?.kind === "shared" && typeof limiter.store.dependency === "string" && !known.has(limiter.store.dependency)) problems.push(`limiter ${entry} keeps its counts in ${limiter.store.dependency}, which is not a dependency`);
|
|
1739
|
+
}
|
|
1719
1740
|
for (const dependency of dependencies) for (const [name, operation] of Object.entries(dependency.operations)) problems.push(...operationProblems(`dependency ${dependency.name}`, name, operation));
|
|
1720
1741
|
for (const phase of phases) if (!isTime$1(phase.cap)) problems.push(`phase ${phase.name}'s cap is not a time`);
|
|
1721
1742
|
problems.push(...shutdownProblems(floor, repeat, triggers, exitCodes, messages));
|
|
@@ -1749,6 +1770,12 @@ const service = (name) => {
|
|
|
1749
1770
|
...entry,
|
|
1750
1771
|
poll: { ...entry.poll }
|
|
1751
1772
|
})),
|
|
1773
|
+
...limiters.length === 0 ? {} : { limiters: limiters.map((entry) => ({
|
|
1774
|
+
...entry,
|
|
1775
|
+
policy: { ...entry.policy },
|
|
1776
|
+
store: { ...entry.store },
|
|
1777
|
+
evict: { maxKeys: { ...entry.evict.maxKeys } }
|
|
1778
|
+
})) },
|
|
1752
1779
|
policies: {
|
|
1753
1780
|
readiness,
|
|
1754
1781
|
liveness,
|
|
@@ -1910,8 +1937,74 @@ const SERVICE_RULES = [
|
|
|
1910
1937
|
"O21",
|
|
1911
1938
|
"O22",
|
|
1912
1939
|
"O23",
|
|
1913
|
-
"O27"
|
|
1940
|
+
"O27",
|
|
1941
|
+
"O29",
|
|
1942
|
+
"O30"
|
|
1914
1943
|
];
|
|
1944
|
+
/**
|
|
1945
|
+
* These are the two rules of the law for a keyed limiter, as data. The caller of `lint()` gives
|
|
1946
|
+
* them, as it gives each other rule. Each one applies one time for each limiter.
|
|
1947
|
+
* - O29: the longest `Retry-After` of a limiter is at most the retry window of its caller. Its
|
|
1948
|
+
* second inequality applies the same bound to a new key at the cap of keys.
|
|
1949
|
+
* Otherwise the caller stops before the limit lets its call in, and a refusal becomes a failure.
|
|
1950
|
+
* - O30: the least delay of a caller before it sends a refused call again is at least the longest
|
|
1951
|
+
* `Retry-After` of the policy. Otherwise a caller that does not read the header sends again too
|
|
1952
|
+
* early, and the limiter refuses each early call again.
|
|
1953
|
+
*
|
|
1954
|
+
* The identifiers O29 and O30 are provisional. The corpus of the law in jarvis (R3, track B) has 28
|
|
1955
|
+
* rules, O01 to O28. It does not have these two rules yet, thus they take the next two identifiers.
|
|
1956
|
+
*/
|
|
1957
|
+
const LIMITER_RULES = [{
|
|
1958
|
+
id: "O29",
|
|
1959
|
+
rule: "the longest Retry-After of a limiter, at its cap of keys too, is at most the retry window of its caller",
|
|
1960
|
+
window: "the caller stops before the limit lets its call in, thus a refusal becomes a failure",
|
|
1961
|
+
expr: {
|
|
1962
|
+
lhs: { terms: [{
|
|
1963
|
+
knob: "retry_after",
|
|
1964
|
+
coef: 1,
|
|
1965
|
+
at: "limiter"
|
|
1966
|
+
}] },
|
|
1967
|
+
op: "<=",
|
|
1968
|
+
rhs: { terms: [{
|
|
1969
|
+
knob: "client_retry_window",
|
|
1970
|
+
coef: 1,
|
|
1971
|
+
at: "caller"
|
|
1972
|
+
}] },
|
|
1973
|
+
margin: 0
|
|
1974
|
+
},
|
|
1975
|
+
expr_more: [{
|
|
1976
|
+
lhs: { terms: [{
|
|
1977
|
+
knob: "retry_after_at_cap",
|
|
1978
|
+
coef: 1,
|
|
1979
|
+
at: "limiter"
|
|
1980
|
+
}] },
|
|
1981
|
+
op: "<=",
|
|
1982
|
+
rhs: { terms: [{
|
|
1983
|
+
knob: "client_retry_window",
|
|
1984
|
+
coef: 1,
|
|
1985
|
+
at: "caller"
|
|
1986
|
+
}] },
|
|
1987
|
+
margin: 0
|
|
1988
|
+
}]
|
|
1989
|
+
}, {
|
|
1990
|
+
id: "O30",
|
|
1991
|
+
rule: "the least delay of a caller before it sends a refused call again is at least the longest Retry-After of the limiter",
|
|
1992
|
+
window: "a caller that does not read Retry-After sends again too early, and the limiter refuses each early call again",
|
|
1993
|
+
expr: {
|
|
1994
|
+
lhs: { terms: [{
|
|
1995
|
+
knob: "retry_delay",
|
|
1996
|
+
coef: 1,
|
|
1997
|
+
at: "caller"
|
|
1998
|
+
}] },
|
|
1999
|
+
op: ">=",
|
|
2000
|
+
rhs: { terms: [{
|
|
2001
|
+
knob: "retry_after",
|
|
2002
|
+
coef: 1,
|
|
2003
|
+
at: "limiter"
|
|
2004
|
+
}] },
|
|
2005
|
+
margin: 0
|
|
2006
|
+
}
|
|
2007
|
+
}];
|
|
1915
2008
|
/** This function gives a finite amount in seconds. */
|
|
1916
2009
|
const secondsOf = (amount) => rationals.toNumber(convertExact(rationals.fromNumber(amount.value), amount.unit, "s"));
|
|
1917
2010
|
/** This function gives an amount, or no bound, as the law binds it. */
|
|
@@ -1987,8 +2080,24 @@ const transportBindings = (transport, hop, scenario) => {
|
|
|
1987
2080
|
for (const [symbol, amount] of Object.entries(scenario.transports?.[transport.name] ?? {})) bind[symbol] = boundOf(amount);
|
|
1988
2081
|
return bind;
|
|
1989
2082
|
};
|
|
2083
|
+
/**
|
|
2084
|
+
* This function gives the symbols of a limiter, and the values of the scenario for its callers.
|
|
2085
|
+
* - `retry_after@limiter` is the longest `Retry-After` of a refusal by the policy, in whole seconds.
|
|
2086
|
+
* It is one interval of the bucket, or one window, as the HTTP edge sends it.
|
|
2087
|
+
* - `retry_after_at_cap@limiter` is the longest `Retry-After` of a new key at the cap of keys. It is
|
|
2088
|
+
* the idle span, `burst / rate` or one window.
|
|
2089
|
+
*/
|
|
2090
|
+
const limiterBindings = (limiter, scenario) => {
|
|
2091
|
+
const bind = {
|
|
2092
|
+
"retry_after@limiter": retryAfterSeconds(longestRetryAfter(limiter.policy)),
|
|
2093
|
+
"retry_after_at_cap@limiter": retryAfterSeconds(idleSpan(limiter.policy))
|
|
2094
|
+
};
|
|
2095
|
+
for (const [symbol, amount] of Object.entries(scenario.limiters?.[limiter.name] ?? {})) bind[symbol.includes("@") ? symbol : `${symbol}@caller`] = boundOf(amount);
|
|
2096
|
+
return bind;
|
|
2097
|
+
};
|
|
1990
2098
|
const SCOPED = /@(client|dependency|server)$/;
|
|
1991
2099
|
const TRANSPORT_SCOPED = /@consumer$/;
|
|
2100
|
+
const LIMITER_SCOPED = /@(limiter|caller)$/;
|
|
1992
2101
|
/** This identifier is the rule of the findings about a field that the declaration does not give. */
|
|
1993
2102
|
const DECLARATION_RULE = "declaration";
|
|
1994
2103
|
/**
|
|
@@ -2007,8 +2116,8 @@ const declarationFindings = (svc) => svc.shutdown.onRepeatSignal === void 0 ? [{
|
|
|
2007
2116
|
}] : [];
|
|
2008
2117
|
/**
|
|
2009
2118
|
* This function applies each service rule to a declaration and a scenario. A rule applies one time
|
|
2010
|
-
* for the service, for each client,
|
|
2011
|
-
* finding for each necessary field that the description does not give (`DECLARATION_RULE`).
|
|
2119
|
+
* for the service, for each client, for each consumer and worker, or for each limiter. The function
|
|
2120
|
+
* also gives a finding for each necessary field that the description does not give (`DECLARATION_RULE`).
|
|
2012
2121
|
*/
|
|
2013
2122
|
const lint = (svc, scenario, rules) => {
|
|
2014
2123
|
const service = serviceBindings(svc, scenario).bind;
|
|
@@ -2016,7 +2125,10 @@ const lint = (svc, scenario, rules) => {
|
|
|
2016
2125
|
for (const rule of rules) {
|
|
2017
2126
|
if (!SERVICE_RULES.includes(rule.id)) continue;
|
|
2018
2127
|
const symbols = symbolsOf(rule);
|
|
2019
|
-
const scopes = symbols.some((symbol) =>
|
|
2128
|
+
const scopes = symbols.some((symbol) => LIMITER_SCOPED.test(symbol)) ? (svc.limiters ?? []).map((limiter) => [limiter.name, {
|
|
2129
|
+
...service,
|
|
2130
|
+
...limiterBindings(limiter, scenario)
|
|
2131
|
+
}]) : symbols.some((symbol) => TRANSPORT_SCOPED.test(symbol)) ? svc.transports.flatMap((transport) => {
|
|
2020
2132
|
const hop = TRANSPORT_HOPS[transport.kind];
|
|
2021
2133
|
return hop === null ? [] : [[transport.name, {
|
|
2022
2134
|
...service,
|
|
@@ -3252,6 +3364,6 @@ const MACHINES = {
|
|
|
3252
3364
|
watchdog: WATCHDOG_MACHINE
|
|
3253
3365
|
};
|
|
3254
3366
|
//#endregion
|
|
3255
|
-
export { DECLARATION_RULE, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, SERVICE_RULES, STAGES, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, heartbeat, heartbeatDetection, inequalities, isExitCode, isExitSignal, isMessageShape, kubernetes, lint, localDev, matchesMessage, mayReissue, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3367
|
+
export { DECLARATION_RULE, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, SERVICE_RULES, STAGES, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterProblems, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3256
3368
|
|
|
3257
3369
|
//# sourceMappingURL=index.js.map
|