sermon-js 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +239 -2
- package/dist/api/index.cjs +174 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +77 -0
- package/dist/api/index.d.ts +77 -0
- package/dist/api/index.js +171 -0
- package/dist/api/index.js.map +1 -0
- package/dist/chunk-2M4UBCDM.js +178 -0
- package/dist/chunk-2M4UBCDM.js.map +1 -0
- package/dist/chunk-AH372HLN.js +98 -0
- package/dist/chunk-AH372HLN.js.map +1 -0
- package/dist/chunk-APC2HVSI.js +154 -0
- package/dist/chunk-APC2HVSI.js.map +1 -0
- package/dist/chunk-D7BBJTYU.cjs +335 -0
- package/dist/chunk-D7BBJTYU.cjs.map +1 -0
- package/dist/chunk-ELV4ZU6G.cjs +51 -0
- package/dist/chunk-ELV4ZU6G.cjs.map +1 -0
- package/dist/chunk-EMP7XVKC.js +334 -0
- package/dist/chunk-EMP7XVKC.js.map +1 -0
- package/dist/chunk-I3346MUL.js +49 -0
- package/dist/chunk-I3346MUL.js.map +1 -0
- package/dist/chunk-NP3YYVRZ.cjs +163 -0
- package/dist/chunk-NP3YYVRZ.cjs.map +1 -0
- package/dist/chunk-PARE6EUZ.cjs +184 -0
- package/dist/chunk-PARE6EUZ.cjs.map +1 -0
- package/dist/chunk-XHXEKB3Q.cjs +241 -0
- package/dist/chunk-XHXEKB3Q.cjs.map +1 -0
- package/dist/chunk-XPXIDJUU.js +229 -0
- package/dist/chunk-XPXIDJUU.js.map +1 -0
- package/dist/chunk-ZI442ZA5.cjs +100 -0
- package/dist/chunk-ZI442ZA5.cjs.map +1 -0
- package/dist/config-BEbP-uLf.d.cts +125 -0
- package/dist/config-w1tm1bdu.d.ts +125 -0
- package/dist/generic-BJmlgtUM.d.ts +31 -0
- package/dist/generic-lmrGxMDC.d.cts +31 -0
- package/dist/index.cjs +407 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/index.cjs +1012 -0
- package/dist/presets/index.cjs.map +1 -0
- package/dist/presets/index.d.cts +516 -0
- package/dist/presets/index.d.ts +516 -0
- package/dist/presets/index.js +971 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/react/heroui/index.cjs +822 -0
- package/dist/react/heroui/index.cjs.map +1 -0
- package/dist/react/heroui/index.d.cts +122 -0
- package/dist/react/heroui/index.d.ts +122 -0
- package/dist/react/heroui/index.js +821 -0
- package/dist/react/heroui/index.js.map +1 -0
- package/dist/react/heroui/styles.css +11 -0
- package/dist/react/index.cjs +71 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +53 -0
- package/dist/react/index.d.ts +53 -0
- package/dist/react/index.js +70 -0
- package/dist/react/index.js.map +1 -0
- package/dist/status-DsWzgb9Q.d.cts +146 -0
- package/dist/status-zd04ZQ98.d.ts +146 -0
- package/dist/store-DybhDphS.d.cts +77 -0
- package/dist/store-_GuzSP0z.d.ts +77 -0
- package/dist/stores/mongodb.cjs +99 -0
- package/dist/stores/mongodb.cjs.map +1 -0
- package/dist/stores/mongodb.d.cts +40 -0
- package/dist/stores/mongodb.d.ts +40 -0
- package/dist/stores/mongodb.js +97 -0
- package/dist/stores/mongodb.js.map +1 -0
- package/dist/stores/redis.cjs +23 -0
- package/dist/stores/redis.cjs.map +1 -0
- package/dist/stores/redis.d.cts +31 -0
- package/dist/stores/redis.d.ts +31 -0
- package/dist/stores/redis.js +21 -0
- package/dist/stores/redis.js.map +1 -0
- package/dist/stores/sql.cjs +164 -0
- package/dist/stores/sql.cjs.map +1 -0
- package/dist/stores/sql.d.cts +39 -0
- package/dist/stores/sql.d.ts +39 -0
- package/dist/stores/sql.js +162 -0
- package/dist/stores/sql.js.map +1 -0
- package/dist/stores/upstash.cjs +27 -0
- package/dist/stores/upstash.cjs.map +1 -0
- package/dist/stores/upstash.d.cts +19 -0
- package/dist/stores/upstash.d.ts +19 -0
- package/dist/stores/upstash.js +25 -0
- package/dist/stores/upstash.js.map +1 -0
- package/dist/testing/index.cjs +209 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +66 -0
- package/dist/testing/index.d.ts +66 -0
- package/dist/testing/index.js +205 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tree-BXbXfjF1.d.cts +236 -0
- package/dist/tree-BXbXfjF1.d.ts +236 -0
- package/dist/types-CctvUAs2.d.cts +192 -0
- package/dist/types-CctvUAs2.d.ts +192 -0
- package/package.json +233 -3
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkXHXEKB3Q_cjs = require('./chunk-XHXEKB3Q.cjs');
|
|
4
|
+
|
|
5
|
+
// src/sanitize.ts
|
|
6
|
+
var MAX_LENGTH = 300;
|
|
7
|
+
var MIN_SECRET_LENGTH = 6;
|
|
8
|
+
var KEY_PATTERNS = [
|
|
9
|
+
// PEM blocks (private keys, certificates), whole or cut short
|
|
10
|
+
/-----BEGIN [A-Z0-9 ]+-----[\s\S]*?(-----END [A-Z0-9 ]+-----|$)/g,
|
|
11
|
+
// JSON Web Tokens (OAuth access and identity tokens)
|
|
12
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(\.[A-Za-z0-9_-]*)?/g,
|
|
13
|
+
// Prefixed API keys: OpenAI/Anthropic/Stripe (sk-, pk_, rk_), GitHub, Slack, Fly, MailerSend.
|
|
14
|
+
// "*" is included because providers echo keys back partly masked (sk-proj-****abcd).
|
|
15
|
+
/\b(sk|pk|rk)[-_][A-Za-z0-9_*.-]{12,}/g,
|
|
16
|
+
/\b(gh[pousr]_|github_pat_)[A-Za-z0-9_*]{12,}/g,
|
|
17
|
+
/\bxox[abeoprs]-[A-Za-z0-9*-]{10,}/g,
|
|
18
|
+
/\b(FlyV1 |fm[12]_|fo1_)[A-Za-z0-9+/=_,*-]{12,}/g,
|
|
19
|
+
/\bmlsn\.[A-Za-z0-9*]{12,}/g,
|
|
20
|
+
// AWS access key ids, Google API keys, Google OAuth tokens
|
|
21
|
+
/\b(AKIA|ASIA)[0-9A-Z*]{12,}/g,
|
|
22
|
+
/\bAIza[0-9A-Za-z_*-]{20,}/g,
|
|
23
|
+
/\bya29\.[0-9A-Za-z_*.-]{12,}/g
|
|
24
|
+
];
|
|
25
|
+
function collectSecretValues(secrets) {
|
|
26
|
+
const values = /* @__PURE__ */ new Set();
|
|
27
|
+
const addOne = (value) => {
|
|
28
|
+
if (value.length >= MIN_SECRET_LENGTH) values.add(value);
|
|
29
|
+
};
|
|
30
|
+
const add = (value) => {
|
|
31
|
+
if (typeof value !== "string" || value.length < MIN_SECRET_LENGTH) return;
|
|
32
|
+
addOne(value);
|
|
33
|
+
try {
|
|
34
|
+
addOne(encodeURIComponent(value));
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
addOne(JSON.stringify(value).slice(1, -1));
|
|
38
|
+
try {
|
|
39
|
+
addOne(btoa(value).replace(/=+$/, ""));
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const walk = (value) => {
|
|
44
|
+
if (typeof value === "string") add(value);
|
|
45
|
+
else if (Array.isArray(value)) value.forEach(walk);
|
|
46
|
+
else if (value && typeof value === "object") Object.values(value).forEach(walk);
|
|
47
|
+
};
|
|
48
|
+
for (const secret of secrets) {
|
|
49
|
+
if (!secret) continue;
|
|
50
|
+
add(secret);
|
|
51
|
+
if (secret.trimStart().startsWith("{")) {
|
|
52
|
+
try {
|
|
53
|
+
walk(JSON.parse(secret));
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const credentials = /^[a-z][a-z0-9+.-]*:\/\/([^\s/@:]*):([^\s/@]*)@/i.exec(secret);
|
|
58
|
+
for (const part of credentials?.slice(1) ?? []) {
|
|
59
|
+
add(part);
|
|
60
|
+
try {
|
|
61
|
+
add(decodeURIComponent(part));
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
67
|
+
}
|
|
68
|
+
function redactText({ text, secretValues }) {
|
|
69
|
+
let out = text;
|
|
70
|
+
for (const secret of secretValues) out = out.split(secret).join("***");
|
|
71
|
+
out = out.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1***@").replace(/(\b(?:https?|wss?):\/\/[^\s?]+)\?[^\s"')]*/gi, "$1?***").replace(/\b(Bearer|Token|Basic|DeepL-Auth-Key)\s+[A-Za-z0-9._~+/=*-]{8,}/gi, "$1 ***");
|
|
72
|
+
for (const pattern of KEY_PATTERNS) out = out.replace(pattern, "***");
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function sanitizeError({
|
|
76
|
+
message,
|
|
77
|
+
secretValues
|
|
78
|
+
}) {
|
|
79
|
+
if (!message) return void 0;
|
|
80
|
+
const out = redactText({ text: message, secretValues });
|
|
81
|
+
return out.length > MAX_LENGTH ? `${out.slice(0, MAX_LENGTH)}\u2026` : out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/runner.ts
|
|
85
|
+
var GRACE_MS = 1e3;
|
|
86
|
+
async function runService({
|
|
87
|
+
service,
|
|
88
|
+
secretValues,
|
|
89
|
+
cache,
|
|
90
|
+
now = () => Date.now(),
|
|
91
|
+
fetch: fetchImpl,
|
|
92
|
+
hideResponseBodies
|
|
93
|
+
}) {
|
|
94
|
+
const checkedAt = now();
|
|
95
|
+
if (service.missing.length > 0) {
|
|
96
|
+
return { key: service.key, status: "unconfigured", checkedAt, probed: false };
|
|
97
|
+
}
|
|
98
|
+
const redact = (text) => redactText({ text, secretValues });
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const ctx = {
|
|
101
|
+
secrets: service.secrets,
|
|
102
|
+
http: chunkXHXEKB3Q_cjs.createHttp({ timeoutMs: service.timeoutMs, signal: controller.signal, fetch: fetchImpl, redact, hideResponseBodies }),
|
|
103
|
+
measure: chunkXHXEKB3Q_cjs.measure,
|
|
104
|
+
combine: chunkXHXEKB3Q_cjs.combine,
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
timeoutMs: service.timeoutMs,
|
|
107
|
+
slowMs: service.slowMs,
|
|
108
|
+
cache,
|
|
109
|
+
now: checkedAt
|
|
110
|
+
};
|
|
111
|
+
const start = performance.now();
|
|
112
|
+
let outcome;
|
|
113
|
+
let timer;
|
|
114
|
+
try {
|
|
115
|
+
const returned = await Promise.race([
|
|
116
|
+
Promise.resolve().then(() => service.check(ctx)),
|
|
117
|
+
new Promise((_, reject) => {
|
|
118
|
+
timer = setTimeout(() => {
|
|
119
|
+
controller.abort(new DOMException("Timed out", "TimeoutError"));
|
|
120
|
+
reject(new DOMException("Timed out", "TimeoutError"));
|
|
121
|
+
}, service.timeoutMs + GRACE_MS);
|
|
122
|
+
})
|
|
123
|
+
]);
|
|
124
|
+
outcome = chunkXHXEKB3Q_cjs.normalizeReturn(returned);
|
|
125
|
+
if (outcome.latencyMs === void 0 && outcome.probed !== false && outcome.status !== "sleeping") {
|
|
126
|
+
outcome = { ...outcome, latencyMs: Math.round(performance.now() - start) };
|
|
127
|
+
}
|
|
128
|
+
} catch (error2) {
|
|
129
|
+
outcome = { status: "down", error: chunkXHXEKB3Q_cjs.errorMessage(error2) };
|
|
130
|
+
} finally {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
}
|
|
133
|
+
let { status, error } = outcome;
|
|
134
|
+
if (status === "up" && outcome.latencyMs !== void 0 && outcome.latencyMs > service.slowMs) {
|
|
135
|
+
status = "degraded";
|
|
136
|
+
error ??= `Slow response: ${outcome.latencyMs} ms (threshold ${service.slowMs} ms)`;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
key: service.key,
|
|
140
|
+
status,
|
|
141
|
+
checkedAt,
|
|
142
|
+
probed: outcome.probed ?? true,
|
|
143
|
+
latencyMs: outcome.latencyMs,
|
|
144
|
+
httpStatus: outcome.httpStatus,
|
|
145
|
+
error: sanitizeError({ message: error, secretValues }),
|
|
146
|
+
// Everything below is public too: no text leaves the runner without being redacted
|
|
147
|
+
metrics: outcome.metrics ? Object.fromEntries(
|
|
148
|
+
Object.entries(outcome.metrics).map(([name, value]) => [
|
|
149
|
+
redact(name),
|
|
150
|
+
typeof value === "string" ? redact(value) : value
|
|
151
|
+
])
|
|
152
|
+
) : void 0,
|
|
153
|
+
usage: outcome.usage?.length ? outcome.usage.map((usage) => ({
|
|
154
|
+
...usage,
|
|
155
|
+
label: redact(usage.label),
|
|
156
|
+
unit: usage.unit === void 0 ? void 0 : redact(usage.unit)
|
|
157
|
+
})) : void 0,
|
|
158
|
+
instances: outcome.instances?.length ? outcome.instances.map((instance) => ({
|
|
159
|
+
...instance,
|
|
160
|
+
key: redact(instance.key),
|
|
161
|
+
region: instance.region === void 0 ? void 0 : redact(instance.region),
|
|
162
|
+
id: instance.id === void 0 ? void 0 : redact(instance.id),
|
|
163
|
+
state: instance.state === void 0 ? void 0 : redact(instance.state),
|
|
164
|
+
error: sanitizeError({ message: instance.error, secretValues })
|
|
165
|
+
})) : void 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function runServices({
|
|
169
|
+
services,
|
|
170
|
+
cacheFor,
|
|
171
|
+
...rest
|
|
172
|
+
}) {
|
|
173
|
+
return Promise.all(
|
|
174
|
+
services.map((service) => runService({ service, cache: cacheFor(service.key), ...rest }))
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
exports.collectSecretValues = collectSecretValues;
|
|
179
|
+
exports.redactText = redactText;
|
|
180
|
+
exports.runService = runService;
|
|
181
|
+
exports.runServices = runServices;
|
|
182
|
+
exports.sanitizeError = sanitizeError;
|
|
183
|
+
//# sourceMappingURL=chunk-PARE6EUZ.cjs.map
|
|
184
|
+
//# sourceMappingURL=chunk-PARE6EUZ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sanitize.ts","../src/runner.ts"],"names":["createHttp","measure","combine","normalizeReturn","error","errorMessage"],"mappings":"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","file":"chunk-PARE6EUZ.cjs","sourcesContent":["const MAX_LENGTH = 300;\nconst MIN_SECRET_LENGTH = 6;\n\n/** Shapes of well-known credentials, redacted even when the value was never declared as a secret */\nconst KEY_PATTERNS: RegExp[] = [\n // PEM blocks (private keys, certificates), whole or cut short\n /-----BEGIN [A-Z0-9 ]+-----[\\s\\S]*?(-----END [A-Z0-9 ]+-----|$)/g,\n // JSON Web Tokens (OAuth access and identity tokens)\n /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}(\\.[A-Za-z0-9_-]*)?/g,\n // Prefixed API keys: OpenAI/Anthropic/Stripe (sk-, pk_, rk_), GitHub, Slack, Fly, MailerSend.\n // \"*\" is included because providers echo keys back partly masked (sk-proj-****abcd).\n /\\b(sk|pk|rk)[-_][A-Za-z0-9_*.-]{12,}/g,\n /\\b(gh[pousr]_|github_pat_)[A-Za-z0-9_*]{12,}/g,\n /\\bxox[abeoprs]-[A-Za-z0-9*-]{10,}/g,\n /\\b(FlyV1 |fm[12]_|fo1_)[A-Za-z0-9+/=_,*-]{12,}/g,\n /\\bmlsn\\.[A-Za-z0-9*]{12,}/g,\n // AWS access key ids, Google API keys, Google OAuth tokens\n /\\b(AKIA|ASIA)[0-9A-Z*]{12,}/g,\n /\\bAIza[0-9A-Za-z_*-]{20,}/g,\n /\\bya29\\.[0-9A-Za-z_*.-]{12,}/g,\n];\n\n/**\n * Every value worth redacting from a set of secrets: the values themselves, the string fields\n * of JSON secrets (e.g. the private key inside a service-account JSON), the user and password\n * of connection strings, and the encoded forms a value takes in URLs, JSON and Basic auth.\n */\nexport function collectSecretValues(secrets: Iterable<string | undefined>): string[] {\n const values = new Set<string>();\n const addOne = (value: string) => {\n if (value.length >= MIN_SECRET_LENGTH) values.add(value);\n };\n const add = (value: unknown) => {\n if (typeof value !== \"string\" || value.length < MIN_SECRET_LENGTH) return;\n addOne(value);\n // The same value as it appears in a URL, inside JSON, and base64-encoded\n try {\n addOne(encodeURIComponent(value));\n } catch {\n // Lone surrogates can't be encoded: the raw value is still covered\n }\n addOne(JSON.stringify(value).slice(1, -1));\n try {\n addOne(btoa(value).replace(/=+$/, \"\"));\n } catch {\n // Not Latin-1: no base64 form to worry about\n }\n };\n const walk = (value: unknown) => {\n if (typeof value === \"string\") add(value);\n else if (Array.isArray(value)) value.forEach(walk);\n else if (value && typeof value === \"object\") Object.values(value).forEach(walk);\n };\n for (const secret of secrets) {\n if (!secret) continue;\n add(secret);\n if (secret.trimStart().startsWith(\"{\")) {\n try {\n walk(JSON.parse(secret));\n } catch {\n // Not JSON: the whole value is already covered\n }\n }\n // Connection strings: drivers report the user or the password on their own\n const credentials = /^[a-z][a-z0-9+.-]*:\\/\\/([^\\s/@:]*):([^\\s/@]*)@/i.exec(secret);\n for (const part of credentials?.slice(1) ?? []) {\n add(part);\n try {\n add(decodeURIComponent(part));\n } catch {\n // Not percent-encoded\n }\n }\n }\n // Longest first, so a secret containing another one is fully redacted\n return [...values].sort((a, b) => b.length - a.length);\n}\n\n/**\n * Strip secret values, URL credentials, query strings, bearer-like tokens and anything shaped\n * like a well-known credential. Safe for text of any length: nothing is cut here, so a secret\n * can never be half-removed.\n */\nexport function redactText({ text, secretValues }: { text: string; secretValues: string[] }): string {\n let out = text;\n for (const secret of secretValues) out = out.split(secret).join(\"***\");\n out = out\n // user:pass@host\n .replace(/(\\b[a-z][a-z0-9+.-]*:\\/\\/)[^\\s/@]+@/gi, \"$1***@\")\n // query strings\n .replace(/(\\b(?:https?|wss?):\\/\\/[^\\s?]+)\\?[^\\s\"')]*/gi, \"$1?***\")\n // bearer-ish tokens that slipped through\n .replace(/\\b(Bearer|Token|Basic|DeepL-Auth-Key)\\s+[A-Za-z0-9._~+/=*-]{8,}/gi, \"$1 ***\");\n for (const pattern of KEY_PATTERNS) out = out.replace(pattern, \"***\");\n return out;\n}\n\n/** Errors are shown on public pages: redact, then truncate (in that order) */\nexport function sanitizeError({\n message,\n secretValues,\n}: {\n message: string | undefined;\n secretValues: string[];\n}): string | undefined {\n if (!message) return undefined;\n const out = redactText({ text: message, secretValues });\n return out.length > MAX_LENGTH ? `${out.slice(0, MAX_LENGTH)}…` : out;\n}\n","import type { ResolvedService } from \"./config\";\nimport { combine, createHttp, errorMessage, measure, normalizeReturn } from \"./probe\";\nimport { redactText, sanitizeError } from \"./sanitize\";\nimport type { CheckCache, CheckContext, CheckOutcome, CheckResult, SecretsInput } from \"./types\";\n\n/** Extra time after `timeoutMs` before a check that ignores its abort signal is given up on */\nconst GRACE_MS = 1000;\n\nexport type RunServiceOptions = {\n service: ResolvedService;\n /** Every secret value of the whole configuration, for redaction */\n secretValues: string[];\n cache: CheckCache;\n now?: () => number;\n fetch?: typeof fetch;\n /** Report failed HTTP answers by status code only */\n hideResponseBodies?: boolean;\n};\n\n/**\n * Run one service and apply the rules shared by all of them:\n * - missing required secrets/fields → \"unconfigured\", the check is never called\n * - a throw or a timeout → down\n * - latency above `slowMs` turns \"up\" into \"degraded\"\n * - errors and every other text of the result are redacted before they leave the runner\n */\nexport async function runService({\n service,\n secretValues,\n cache,\n now = () => Date.now(),\n fetch: fetchImpl,\n hideResponseBodies,\n}: RunServiceOptions): Promise<CheckResult> {\n const checkedAt = now();\n if (service.missing.length > 0) {\n return { key: service.key, status: \"unconfigured\", checkedAt, probed: false };\n }\n\n const redact = (text: string) => redactText({ text, secretValues });\n const controller = new AbortController();\n const ctx: CheckContext<SecretsInput> = {\n secrets: service.secrets as Record<string, string>,\n http: createHttp({ timeoutMs: service.timeoutMs, signal: controller.signal, fetch: fetchImpl, redact, hideResponseBodies }),\n measure,\n combine,\n signal: controller.signal,\n timeoutMs: service.timeoutMs,\n slowMs: service.slowMs,\n cache,\n now: checkedAt,\n };\n\n const start = performance.now();\n let outcome: CheckOutcome;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const returned = await Promise.race([\n Promise.resolve().then(() => service.check(ctx)),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort(new DOMException(\"Timed out\", \"TimeoutError\"));\n reject(new DOMException(\"Timed out\", \"TimeoutError\"));\n }, service.timeoutMs + GRACE_MS);\n }),\n ]);\n outcome = normalizeReturn(returned);\n if (outcome.latencyMs === undefined && outcome.probed !== false && outcome.status !== \"sleeping\") {\n // Checks that don't measure themselves get the wall time of the whole check\n outcome = { ...outcome, latencyMs: Math.round(performance.now() - start) };\n }\n } catch (error) {\n outcome = { status: \"down\", error: errorMessage(error) };\n } finally {\n clearTimeout(timer);\n }\n\n let { status, error } = outcome;\n if (status === \"up\" && outcome.latencyMs !== undefined && outcome.latencyMs > service.slowMs) {\n status = \"degraded\";\n error ??= `Slow response: ${outcome.latencyMs} ms (threshold ${service.slowMs} ms)`;\n }\n\n return {\n key: service.key,\n status,\n checkedAt,\n probed: outcome.probed ?? true,\n latencyMs: outcome.latencyMs,\n httpStatus: outcome.httpStatus,\n error: sanitizeError({ message: error, secretValues }),\n // Everything below is public too: no text leaves the runner without being redacted\n metrics: outcome.metrics\n ? Object.fromEntries(\n Object.entries(outcome.metrics).map(([name, value]) => [\n redact(name),\n typeof value === \"string\" ? redact(value) : value,\n ])\n )\n : undefined,\n usage: outcome.usage?.length\n ? outcome.usage.map((usage) => ({\n ...usage,\n label: redact(usage.label),\n unit: usage.unit === undefined ? undefined : redact(usage.unit),\n }))\n : undefined,\n instances: outcome.instances?.length\n ? outcome.instances.map((instance) => ({\n ...instance,\n key: redact(instance.key),\n region: instance.region === undefined ? undefined : redact(instance.region),\n id: instance.id === undefined ? undefined : redact(instance.id),\n state: instance.state === undefined ? undefined : redact(instance.state),\n error: sanitizeError({ message: instance.error, secretValues }),\n }))\n : undefined,\n };\n}\n\nexport async function runServices({\n services,\n cacheFor,\n ...rest\n}: Omit<RunServiceOptions, \"service\" | \"cache\"> & {\n services: ResolvedService[];\n /** Each service gets its own cache namespace */\n cacheFor: (key: string) => CheckCache;\n}): Promise<CheckResult[]> {\n return Promise.all(\n services.map((service) => runService({ service, cache: cacheFor(service.key), ...rest }))\n );\n}\n"]}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/config.ts
|
|
4
|
+
var DEFAULT_SLOW_MS = 2e3;
|
|
5
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
6
|
+
function group(options) {
|
|
7
|
+
if (!options.title?.trim()) throw new Error("sermon-js: group() needs a title");
|
|
8
|
+
return {
|
|
9
|
+
node: "group",
|
|
10
|
+
title: options.title,
|
|
11
|
+
description: options.description,
|
|
12
|
+
services: options.services ?? []
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function hideSecrets(service, { secrets, sensitive }) {
|
|
16
|
+
Object.defineProperty(service, "secrets", { value: secrets, enumerable: false, writable: false, configurable: true });
|
|
17
|
+
Object.defineProperty(service, "sensitive", {
|
|
18
|
+
value: sensitive ?? [],
|
|
19
|
+
enumerable: false,
|
|
20
|
+
writable: false,
|
|
21
|
+
configurable: true
|
|
22
|
+
});
|
|
23
|
+
return service;
|
|
24
|
+
}
|
|
25
|
+
var isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
26
|
+
function custom(options) {
|
|
27
|
+
if (!options.name?.trim()) throw new Error("sermon-js: custom() needs a name");
|
|
28
|
+
if (typeof options.check !== "function") {
|
|
29
|
+
throw new Error(`sermon-js: custom service "${options.name}" needs a check function`);
|
|
30
|
+
}
|
|
31
|
+
const secrets = options.secrets ?? {};
|
|
32
|
+
const missing = Object.keys(secrets).filter((name) => isEmpty(secrets[name])).map((name) => `secret "${name}"`);
|
|
33
|
+
if (options.enabled === false) missing.push("disabled");
|
|
34
|
+
const service = {
|
|
35
|
+
node: "service",
|
|
36
|
+
kind: "custom",
|
|
37
|
+
name: options.name,
|
|
38
|
+
description: options.description,
|
|
39
|
+
critical: options.critical ?? false,
|
|
40
|
+
slowMs: options.slowMs ?? DEFAULT_SLOW_MS,
|
|
41
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
42
|
+
secrets,
|
|
43
|
+
missing,
|
|
44
|
+
check: options.check
|
|
45
|
+
};
|
|
46
|
+
return hideSecrets(service, { secrets });
|
|
47
|
+
}
|
|
48
|
+
function definePreset() {
|
|
49
|
+
return function(spec) {
|
|
50
|
+
return function preset(options) {
|
|
51
|
+
const derived = spec.derive(options);
|
|
52
|
+
const secrets = options.secrets ?? {};
|
|
53
|
+
const missing = [
|
|
54
|
+
...(spec.requiredFields ?? []).filter((field) => isEmpty(options[field])).map((field) => `field "${field}"`),
|
|
55
|
+
...(spec.requiredSecrets === "all" ? Object.keys(secrets) : spec.requiredSecrets ?? []).filter((name2) => isEmpty(secrets[name2])).map((name2) => `secret "${name2}"`)
|
|
56
|
+
];
|
|
57
|
+
const name = options.name?.trim() || derived.name?.trim() || spec.kind;
|
|
58
|
+
const service = {
|
|
59
|
+
node: "service",
|
|
60
|
+
kind: spec.kind,
|
|
61
|
+
name,
|
|
62
|
+
description: options.description ?? derived.description,
|
|
63
|
+
critical: options.critical ?? false,
|
|
64
|
+
slowMs: options.slowMs ?? spec.defaults?.slowMs ?? DEFAULT_SLOW_MS,
|
|
65
|
+
timeoutMs: options.timeoutMs ?? spec.defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
66
|
+
identity: derived.identity,
|
|
67
|
+
secrets,
|
|
68
|
+
missing,
|
|
69
|
+
check: (ctx) => spec.check({ ...ctx, options })
|
|
70
|
+
};
|
|
71
|
+
const sensitive = (spec.sensitive?.(options) ?? []).filter((value) => !!value);
|
|
72
|
+
return hideSecrets(service, { secrets, sensitive });
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function slug(text) {
|
|
77
|
+
return text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "x";
|
|
78
|
+
}
|
|
79
|
+
function identityKey(identity) {
|
|
80
|
+
return identity.toLowerCase().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "") || "x";
|
|
81
|
+
}
|
|
82
|
+
function unique(base, taken) {
|
|
83
|
+
let key = base;
|
|
84
|
+
for (let n = 2; taken.has(key); n++) key = `${base}-${n}`;
|
|
85
|
+
taken.add(key);
|
|
86
|
+
return key;
|
|
87
|
+
}
|
|
88
|
+
function resolveTree(nodes) {
|
|
89
|
+
const services = /* @__PURE__ */ new Map();
|
|
90
|
+
const serviceKeys = /* @__PURE__ */ new Set();
|
|
91
|
+
const groupKeys = /* @__PURE__ */ new Set();
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const walk = (list, path) => list.map((node) => {
|
|
94
|
+
if (!node || typeof node !== "object" || !("node" in node)) {
|
|
95
|
+
throw new Error("sermon-js: services must be built with group(), custom() or a preset function");
|
|
96
|
+
}
|
|
97
|
+
if (node.node === "group") {
|
|
98
|
+
const segments = [...path, slug(node.title)];
|
|
99
|
+
const key2 = unique(segments.join(":"), groupKeys);
|
|
100
|
+
return {
|
|
101
|
+
type: "group",
|
|
102
|
+
key: key2,
|
|
103
|
+
title: node.title,
|
|
104
|
+
description: node.description,
|
|
105
|
+
children: walk(node.services, segments)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const base = node.identity ? identityKey(node.identity) : [...path, slug(node.name)].join(":");
|
|
109
|
+
const key = unique(base, serviceKeys);
|
|
110
|
+
if (key !== base) {
|
|
111
|
+
warnings.push(
|
|
112
|
+
`Two services resolve to "${base}" (${node.kind} "${node.name}"): the second one is tracked as "${key}".`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
services.set(key, hideSecrets({ ...node, key }, { secrets: node.secrets, sensitive: node.sensitive }));
|
|
116
|
+
return { type: "service", key };
|
|
117
|
+
});
|
|
118
|
+
const tree = walk(nodes, []);
|
|
119
|
+
return { tree, services, warnings };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/probe.ts
|
|
123
|
+
var SEVERITY = { up: 0, sleeping: 0, unconfigured: 0, degraded: 1, down: 2 };
|
|
124
|
+
function errorMessage(error) {
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
if (error.name === "TimeoutError" || error.name === "AbortError") return "Timed out";
|
|
127
|
+
const cause = error.cause;
|
|
128
|
+
const detail = cause?.code ?? cause?.errors?.[0]?.code ?? cause?.message;
|
|
129
|
+
return detail ? `${error.message}: ${detail}` : error.message;
|
|
130
|
+
}
|
|
131
|
+
return String(error);
|
|
132
|
+
}
|
|
133
|
+
function snippet(text) {
|
|
134
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
135
|
+
return trimmed.length > 200 ? `${trimmed.slice(0, 200)}\u2026` : trimmed;
|
|
136
|
+
}
|
|
137
|
+
function httpError({ status, text }) {
|
|
138
|
+
if (status === 401 || status === 407) return `HTTP ${status}: credentials rejected`;
|
|
139
|
+
if (status === 403) return "HTTP 403: access denied";
|
|
140
|
+
return `HTTP ${status}${text ? `: ${snippet(text)}` : ""}`;
|
|
141
|
+
}
|
|
142
|
+
function normalizeReturn(value) {
|
|
143
|
+
if (value === void 0 || value === null || value === true) return { status: "up" };
|
|
144
|
+
if (value === false) return { status: "down" };
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
function createHttp({
|
|
148
|
+
timeoutMs,
|
|
149
|
+
signal,
|
|
150
|
+
fetch: fetchImpl = fetch,
|
|
151
|
+
redact = (text) => text,
|
|
152
|
+
hideResponseBodies = false
|
|
153
|
+
}) {
|
|
154
|
+
return async function http(url, init = {}) {
|
|
155
|
+
const { okStatuses, ...requestInit } = init;
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const timer = setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), timeoutMs);
|
|
158
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
159
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
160
|
+
if (signal?.aborted) onAbort();
|
|
161
|
+
const start = performance.now();
|
|
162
|
+
const elapsed = () => Math.round(performance.now() - start);
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetchImpl(url, {
|
|
165
|
+
redirect: "manual",
|
|
166
|
+
...requestInit,
|
|
167
|
+
signal: controller.signal
|
|
168
|
+
});
|
|
169
|
+
const text = await res.text();
|
|
170
|
+
const latencyMs = elapsed();
|
|
171
|
+
let json;
|
|
172
|
+
try {
|
|
173
|
+
json = text ? JSON.parse(text) : void 0;
|
|
174
|
+
} catch {
|
|
175
|
+
json = void 0;
|
|
176
|
+
}
|
|
177
|
+
const ok = res.status < 400 || !!okStatuses?.includes(res.status);
|
|
178
|
+
const outcome = ok ? { status: "up", latencyMs, httpStatus: res.status } : {
|
|
179
|
+
status: res.status === 429 ? "degraded" : "down",
|
|
180
|
+
latencyMs,
|
|
181
|
+
httpStatus: res.status,
|
|
182
|
+
error: httpError({ status: res.status, text: hideResponseBodies ? "" : redact(text) })
|
|
183
|
+
};
|
|
184
|
+
return { ok, status: res.status, latencyMs, text, json, headers: res.headers, outcome };
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const latencyMs = elapsed();
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
latencyMs,
|
|
190
|
+
text: "",
|
|
191
|
+
json: void 0,
|
|
192
|
+
outcome: { status: "down", error: errorMessage(error) }
|
|
193
|
+
};
|
|
194
|
+
} finally {
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
signal?.removeEventListener("abort", onAbort);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function measure(fn) {
|
|
201
|
+
const start = performance.now();
|
|
202
|
+
try {
|
|
203
|
+
const outcome = normalizeReturn(await fn());
|
|
204
|
+
return { ...outcome, latencyMs: outcome.latencyMs ?? Math.round(performance.now() - start) };
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return { status: "down", latencyMs: Math.round(performance.now() - start), error: errorMessage(error) };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function combine(outcomes) {
|
|
210
|
+
if (outcomes.length === 0) return { status: "up" };
|
|
211
|
+
const worst = outcomes.reduce((a, b) => SEVERITY[b.status] > SEVERITY[a.status] ? b : a);
|
|
212
|
+
const latencies = outcomes.map((o) => o.latencyMs).filter((l) => l !== void 0);
|
|
213
|
+
const errors = outcomes.map((o) => o.error).filter((e) => !!e);
|
|
214
|
+
const usage = outcomes.flatMap((o) => o.usage ?? []);
|
|
215
|
+
const instances = outcomes.flatMap((o) => o.instances ?? []);
|
|
216
|
+
const metrics = Object.assign({}, ...outcomes.map((o) => o.metrics ?? {}));
|
|
217
|
+
return {
|
|
218
|
+
status: worst.status,
|
|
219
|
+
latencyMs: latencies.length ? Math.max(...latencies) : void 0,
|
|
220
|
+
httpStatus: worst.httpStatus,
|
|
221
|
+
error: errors.length ? errors.join("; ") : void 0,
|
|
222
|
+
metrics: metrics && Object.keys(metrics).length ? metrics : void 0,
|
|
223
|
+
usage: usage.length ? usage : void 0,
|
|
224
|
+
instances: instances.length ? instances : void 0,
|
|
225
|
+
probed: outcomes.some((o) => o.probed !== false)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
exports.DEFAULT_SLOW_MS = DEFAULT_SLOW_MS;
|
|
230
|
+
exports.DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
|
231
|
+
exports.combine = combine;
|
|
232
|
+
exports.createHttp = createHttp;
|
|
233
|
+
exports.custom = custom;
|
|
234
|
+
exports.definePreset = definePreset;
|
|
235
|
+
exports.errorMessage = errorMessage;
|
|
236
|
+
exports.group = group;
|
|
237
|
+
exports.measure = measure;
|
|
238
|
+
exports.normalizeReturn = normalizeReturn;
|
|
239
|
+
exports.resolveTree = resolveTree;
|
|
240
|
+
//# sourceMappingURL=chunk-XHXEKB3Q.cjs.map
|
|
241
|
+
//# sourceMappingURL=chunk-XHXEKB3Q.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/probe.ts"],"names":["name","key"],"mappings":"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","file":"chunk-XHXEKB3Q.cjs","sourcesContent":["import type { CheckContext, CheckReturn, CommonOptions, SecretsInput } from \"./types\";\n\nexport const DEFAULT_SLOW_MS = 2000;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n\ntype AnyCheck = (ctx: CheckContext<SecretsInput>) => Promise<CheckReturn> | CheckReturn;\n\n/** A service ready to be checked. Built by `custom()` or a preset; not meant to be written by hand. */\nexport type ServiceDefinition = {\n node: \"service\";\n /** The preset kind: \"custom\", \"generic.http\", \"fly.app\"… */\n kind: string;\n name: string;\n description?: string;\n critical: boolean;\n slowMs: number;\n timeoutMs: number;\n /** Natural identity (e.g. `fly.app:my-app`): keeps history across renames and moves */\n identity?: string;\n /**\n * Hidden from `console.log`, `JSON.stringify` and object spreads (the property is not\n * enumerable), so a logged or serialized configuration never prints credentials.\n */\n secrets: SecretsInput;\n /** Other values to redact from public text (e.g. credentials given as headers). Hidden like `secrets`. */\n sensitive?: string[];\n /** Required secrets/fields that are empty: the service is shown as \"not configured\" and never runs */\n missing: string[];\n check: AnyCheck;\n};\n\nexport type GroupDefinition = {\n node: \"group\";\n title: string;\n description?: string;\n services: ServiceNode[];\n};\n\nexport type ServiceNode = ServiceDefinition | GroupDefinition;\n\n/** A titled set of services. Groups can contain other groups. */\nexport function group(options: { title: string; description?: string; services: ServiceNode[] }): GroupDefinition {\n if (!options.title?.trim()) throw new Error(\"sermon-js: group() needs a title\");\n return {\n node: \"group\",\n title: options.title,\n description: options.description,\n services: options.services ?? [],\n };\n}\n\n/**\n * Keep credentials out of anything that prints or serializes a service: loggers, error\n * reporters and `util.inspect` only walk enumerable properties.\n */\nexport function hideSecrets<T extends { secrets: SecretsInput; sensitive?: string[] }>(\n service: T,\n { secrets, sensitive }: { secrets: SecretsInput; sensitive?: string[] }\n): T {\n Object.defineProperty(service, \"secrets\", { value: secrets, enumerable: false, writable: false, configurable: true });\n Object.defineProperty(service, \"sensitive\", {\n value: sensitive ?? [],\n enumerable: false,\n writable: false,\n configurable: true,\n });\n return service;\n}\n\nconst isEmpty = (value: unknown) => value === undefined || value === null || value === \"\";\n\nexport type CustomOptions<Secrets extends SecretsInput> = CommonOptions & {\n /** How to call it (required for custom services) */\n name: string;\n /** Values the check needs. They're redacted from public errors; if one is empty the service is \"not configured\". */\n secrets?: Secrets;\n /** Set to false to list the service as \"not configured\" without running it */\n enabled?: boolean;\n /** Return `true`/nothing for up, `false` for down, or a full outcome. Throwing means down. */\n check: (ctx: CheckContext<Secrets>) => Promise<CheckReturn> | CheckReturn;\n};\n\n/** Any check you can write yourself: the escape hatch for services without a preset */\nexport function custom<Secrets extends SecretsInput = Record<never, never>>(\n options: CustomOptions<Secrets>\n): ServiceDefinition {\n if (!options.name?.trim()) throw new Error(\"sermon-js: custom() needs a name\");\n if (typeof options.check !== \"function\") {\n throw new Error(`sermon-js: custom service \"${options.name}\" needs a check function`);\n }\n const secrets = (options.secrets ?? {}) as SecretsInput;\n const missing = Object.keys(secrets)\n .filter((name) => isEmpty(secrets[name]))\n .map((name) => `secret \"${name}\"`);\n if (options.enabled === false) missing.push(\"disabled\");\n const service: ServiceDefinition = {\n node: \"service\",\n kind: \"custom\",\n name: options.name,\n description: options.description,\n critical: options.critical ?? false,\n slowMs: options.slowMs ?? DEFAULT_SLOW_MS,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n secrets,\n missing,\n check: options.check as AnyCheck,\n };\n return hideSecrets(service, { secrets });\n}\n\n/**\n * What every preset accepts on top of its own fields. Declare a preset's options as\n * `PresetOptions & { …fields; secrets: { … }; track?: { … } }`.\n */\nexport type PresetOptions = CommonOptions;\n\n/** The shape a preset's options must fit (kept separate so `secrets` and `track` stay exact per preset) */\ntype PresetOptionsShape = CommonOptions & {\n secrets?: SecretsInput;\n track?: unknown;\n};\n\ntype SecretsOf<O> = O extends { secrets?: infer S } ? (NonNullable<S> extends SecretsInput ? NonNullable<S> : never) : never;\n\n/** Inside a preset's check: required secrets are strings, optional ones may be missing */\nexport type PresetContext<O extends PresetOptionsShape, Required extends string> = Omit<\n CheckContext<SecretsInput>,\n \"secrets\"\n> & {\n // Keys are mapped through Extract so the type isn't homomorphic: `-?` would strip the `undefined` we add\n secrets: { [K in Extract<keyof SecretsOf<O>, string>]: K extends Required ? string : string | undefined };\n /** The options the user passed to the preset */\n options: O;\n};\n\nexport type PresetSpec<O extends PresetOptionsShape, Required extends string> = {\n /** Namespaced kind, e.g. \"fly.app\" */\n kind: string;\n /** Secrets that must be non-empty for the service to run; \"all\" requires every secret given */\n requiredSecrets?: readonly Required[] | \"all\";\n /** Options that must be non-empty at runtime (e.g. a URL read from the environment) */\n requiredFields?: readonly (keyof O & string)[];\n defaults?: { slowMs?: number; timeoutMs?: number };\n /** Name, natural identity and description derived from the options */\n derive: (options: O) => { name: string; identity?: string; description?: string };\n /** Values outside `secrets` that must never show in public text (e.g. credentials in headers) */\n sensitive?: (options: O) => Array<string | undefined>;\n // NoInfer: the required set is inferred from `requiredSecrets` only, never from this parameter\n check: (ctx: PresetContext<O, NoInfer<Required>>) => Promise<CheckReturn> | CheckReturn;\n};\n\n/**\n * Build a preset: a typed function that turns a few options into a service.\n *\n * ```ts\n * type Options = PresetOptions & { app: string; secrets: { token: string | undefined } };\n * export const app = definePreset<Options>()({\n * kind: \"fly.app\",\n * requiredSecrets: [\"token\"],\n * derive: (o) => ({ name: o.name ?? o.app, identity: `fly.app:${o.app}` }),\n * check: ({ options, secrets, http }) => …,\n * });\n * ```\n */\nexport function definePreset<O extends PresetOptionsShape>() {\n return function <const Required extends keyof SecretsOf<O> & string = never>(\n spec: PresetSpec<O, Required>\n ): (options: O) => ServiceDefinition {\n return function preset(options: O): ServiceDefinition {\n const derived = spec.derive(options);\n const secrets = (options.secrets ?? {}) as SecretsInput;\n const missing = [\n ...(spec.requiredFields ?? [])\n .filter((field) => isEmpty(options[field]))\n .map((field) => `field \"${field}\"`),\n ...(spec.requiredSecrets === \"all\" ? Object.keys(secrets) : (spec.requiredSecrets ?? []))\n .filter((name) => isEmpty(secrets[name]))\n .map((name) => `secret \"${name}\"`),\n ];\n // A name derived from an empty field (e.g. an unset env var) falls back to the kind:\n // the service is still listed, as \"not configured\"\n const name = options.name?.trim() || derived.name?.trim() || spec.kind;\n const service: ServiceDefinition = {\n node: \"service\",\n kind: spec.kind,\n name,\n description: options.description ?? derived.description,\n critical: options.critical ?? false,\n slowMs: options.slowMs ?? spec.defaults?.slowMs ?? DEFAULT_SLOW_MS,\n timeoutMs: options.timeoutMs ?? spec.defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n identity: derived.identity,\n secrets,\n missing,\n check: (ctx) => spec.check({ ...ctx, options } as unknown as PresetContext<O, Required>),\n };\n const sensitive = (spec.sensitive?.(options) ?? []).filter((value): value is string => !!value);\n return hideSecrets(service, { secrets, sensitive });\n };\n };\n}\n\n/** A service with its derived key */\nexport type ResolvedService = ServiceDefinition & { key: string };\n\nexport type ResolvedNode =\n | { type: \"group\"; key: string; title: string; description?: string; children: ResolvedNode[] }\n | { type: \"service\"; key: string };\n\nexport type ResolvedTree = {\n tree: ResolvedNode[];\n /** Every service by key, in declaration order */\n services: Map<string, ResolvedService>;\n warnings: string[];\n};\n\nfunction slug(text: string) {\n return (\n text\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"x\"\n );\n}\n\n/** Identities keep their dots and colons (`fly.app:my-app`), everything else is slugged */\nfunction identityKey(identity: string) {\n return (\n identity\n .toLowerCase()\n .replace(/[^a-z0-9._:-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"x\"\n );\n}\n\nfunction unique(base: string, taken: Set<string>) {\n let key = base;\n for (let n = 2; taken.has(key); n++) key = `${base}-${n}`;\n taken.add(key);\n return key;\n}\n\n/**\n * Walk the services tree and give every group and service an internal key: unique, stable,\n * and safe in URLs. Services with a natural identity keep the same key wherever they sit;\n * the others are keyed by their group path and name.\n */\nexport function resolveTree(nodes: ServiceNode[]): ResolvedTree {\n const services = new Map<string, ResolvedService>();\n const serviceKeys = new Set<string>();\n const groupKeys = new Set<string>();\n const warnings: string[] = [];\n\n const walk = (list: ServiceNode[], path: string[]): ResolvedNode[] =>\n list.map((node) => {\n if (!node || typeof node !== \"object\" || !(\"node\" in node)) {\n throw new Error(\"sermon-js: services must be built with group(), custom() or a preset function\");\n }\n if (node.node === \"group\") {\n const segments = [...path, slug(node.title)];\n const key = unique(segments.join(\":\"), groupKeys);\n return {\n type: \"group\",\n key,\n title: node.title,\n description: node.description,\n children: walk(node.services, segments),\n };\n }\n const base = node.identity ? identityKey(node.identity) : [...path, slug(node.name)].join(\":\");\n const key = unique(base, serviceKeys);\n if (key !== base) {\n warnings.push(\n `Two services resolve to \"${base}\" (${node.kind} \"${node.name}\"): the second one is tracked as \"${key}\".`\n );\n }\n // The spread drops the hidden properties: put them back, still hidden\n services.set(key, hideSecrets({ ...node, key }, { secrets: node.secrets, sensitive: node.sensitive }));\n return { type: \"service\", key };\n });\n\n const tree = walk(nodes, []);\n return { tree, services, warnings };\n}\n","import type { CheckOutcome, CheckReturn, HttpInit, HttpResult, OutcomeStatus, Status } from \"./types\";\n\nconst SEVERITY: Record<Status, number> = { up: 0, sleeping: 0, unconfigured: 0, degraded: 1, down: 2 };\n\n/** Human message for anything thrown, surfacing the real network reason when there is one */\nexport function errorMessage(error: unknown): string {\n if (error instanceof Error) {\n if (error.name === \"TimeoutError\" || error.name === \"AbortError\") return \"Timed out\";\n // fetch implementations hide the reason (ECONNREFUSED, ENOTFOUND, TLS…) in `cause`\n const cause = (error as Error & { cause?: unknown }).cause as\n | (Error & { code?: string; errors?: Array<Error & { code?: string }> })\n | undefined;\n const detail = cause?.code ?? cause?.errors?.[0]?.code ?? cause?.message;\n return detail ? `${error.message}: ${detail}` : error.message;\n }\n return String(error);\n}\n\nfunction snippet(text: string) {\n const trimmed = text.replace(/\\s+/g, \" \").trim();\n return trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed;\n}\n\n/**\n * The public error for a failed HTTP answer. Bodies of rejected credentials are left out:\n * providers echo the key back there, often only partly masked.\n */\nfunction httpError({ status, text }: { status: number; text: string }) {\n if (status === 401 || status === 407) return `HTTP ${status}: credentials rejected`;\n if (status === 403) return \"HTTP 403: access denied\";\n return `HTTP ${status}${text ? `: ${snippet(text)}` : \"\"}`;\n}\n\n/** `true`/nothing → up, `false` → down, outcomes pass through */\nexport function normalizeReturn(value: CheckReturn): CheckOutcome {\n if (value === undefined || value === null || value === true) return { status: \"up\" };\n if (value === false) return { status: \"down\" };\n return value;\n}\n\n/**\n * Timed `fetch` that never throws. Redirects are not followed by default, so a 3xx\n * counts as a healthy answer from the target itself.\n */\nexport function createHttp({\n timeoutMs,\n signal,\n fetch: fetchImpl = fetch,\n redact = (text) => text,\n hideResponseBodies = false,\n}: {\n timeoutMs: number;\n signal?: AbortSignal;\n fetch?: typeof fetch;\n /** Removes secrets from response bodies before they are cut into error messages */\n redact?: (text: string) => string;\n /** Report failed answers by status code only, never quoting what the target replied */\n hideResponseBodies?: boolean;\n}) {\n return async function http(url: string, init: HttpInit = {}): Promise<HttpResult> {\n const { okStatuses, ...requestInit } = init;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(new DOMException(\"Timed out\", \"TimeoutError\")), timeoutMs);\n const onAbort = () => controller.abort(signal?.reason);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (signal?.aborted) onAbort();\n\n const start = performance.now();\n const elapsed = () => Math.round(performance.now() - start);\n try {\n const res = await fetchImpl(url, {\n redirect: \"manual\",\n ...requestInit,\n signal: controller.signal,\n });\n const text = await res.text();\n const latencyMs = elapsed();\n let json: unknown;\n try {\n json = text ? JSON.parse(text) : undefined;\n } catch {\n json = undefined;\n }\n const ok = res.status < 400 || !!okStatuses?.includes(res.status);\n const outcome: CheckOutcome = ok\n ? { status: \"up\", latencyMs, httpStatus: res.status }\n : {\n status: res.status === 429 ? \"degraded\" : \"down\",\n latencyMs,\n httpStatus: res.status,\n error: httpError({ status: res.status, text: hideResponseBodies ? \"\" : redact(text) }),\n };\n return { ok, status: res.status, latencyMs, text, json, headers: res.headers, outcome };\n } catch (error) {\n const latencyMs = elapsed();\n return {\n ok: false,\n latencyMs,\n text: \"\",\n json: undefined,\n outcome: { status: \"down\", error: errorMessage(error) },\n };\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n }\n };\n}\n\n/** Times any async call; a throw becomes a down outcome */\nexport async function measure(fn: () => Promise<CheckReturn>): Promise<CheckOutcome> {\n const start = performance.now();\n try {\n const outcome = normalizeReturn(await fn());\n return { ...outcome, latencyMs: outcome.latencyMs ?? Math.round(performance.now() - start) };\n } catch (error) {\n return { status: \"down\", latencyMs: Math.round(performance.now() - start), error: errorMessage(error) };\n }\n}\n\n/**\n * Merges several probes of the same service run in parallel:\n * worst status wins, latency is the slowest call, metrics/usage/errors are merged.\n */\nexport function combine(outcomes: CheckOutcome[]): CheckOutcome {\n if (outcomes.length === 0) return { status: \"up\" };\n const worst = outcomes.reduce((a, b) => (SEVERITY[b.status] > SEVERITY[a.status] ? b : a));\n const latencies = outcomes.map((o) => o.latencyMs).filter((l): l is number => l !== undefined);\n const errors = outcomes.map((o) => o.error).filter((e): e is string => !!e);\n const usage = outcomes.flatMap((o) => o.usage ?? []);\n const instances = outcomes.flatMap((o) => o.instances ?? []);\n const metrics = Object.assign({}, ...outcomes.map((o) => o.metrics ?? {})) as CheckOutcome[\"metrics\"];\n return {\n status: worst.status as OutcomeStatus,\n latencyMs: latencies.length ? Math.max(...latencies) : undefined,\n httpStatus: worst.httpStatus,\n error: errors.length ? errors.join(\"; \") : undefined,\n metrics: metrics && Object.keys(metrics).length ? metrics : undefined,\n usage: usage.length ? usage : undefined,\n instances: instances.length ? instances : undefined,\n probed: outcomes.some((o) => o.probed !== false),\n };\n}\n"]}
|