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,229 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var DEFAULT_SLOW_MS = 2e3;
|
|
3
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
4
|
+
function group(options) {
|
|
5
|
+
if (!options.title?.trim()) throw new Error("sermon-js: group() needs a title");
|
|
6
|
+
return {
|
|
7
|
+
node: "group",
|
|
8
|
+
title: options.title,
|
|
9
|
+
description: options.description,
|
|
10
|
+
services: options.services ?? []
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function hideSecrets(service, { secrets, sensitive }) {
|
|
14
|
+
Object.defineProperty(service, "secrets", { value: secrets, enumerable: false, writable: false, configurable: true });
|
|
15
|
+
Object.defineProperty(service, "sensitive", {
|
|
16
|
+
value: sensitive ?? [],
|
|
17
|
+
enumerable: false,
|
|
18
|
+
writable: false,
|
|
19
|
+
configurable: true
|
|
20
|
+
});
|
|
21
|
+
return service;
|
|
22
|
+
}
|
|
23
|
+
var isEmpty = (value) => value === void 0 || value === null || value === "";
|
|
24
|
+
function custom(options) {
|
|
25
|
+
if (!options.name?.trim()) throw new Error("sermon-js: custom() needs a name");
|
|
26
|
+
if (typeof options.check !== "function") {
|
|
27
|
+
throw new Error(`sermon-js: custom service "${options.name}" needs a check function`);
|
|
28
|
+
}
|
|
29
|
+
const secrets = options.secrets ?? {};
|
|
30
|
+
const missing = Object.keys(secrets).filter((name) => isEmpty(secrets[name])).map((name) => `secret "${name}"`);
|
|
31
|
+
if (options.enabled === false) missing.push("disabled");
|
|
32
|
+
const service = {
|
|
33
|
+
node: "service",
|
|
34
|
+
kind: "custom",
|
|
35
|
+
name: options.name,
|
|
36
|
+
description: options.description,
|
|
37
|
+
critical: options.critical ?? false,
|
|
38
|
+
slowMs: options.slowMs ?? DEFAULT_SLOW_MS,
|
|
39
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
40
|
+
secrets,
|
|
41
|
+
missing,
|
|
42
|
+
check: options.check
|
|
43
|
+
};
|
|
44
|
+
return hideSecrets(service, { secrets });
|
|
45
|
+
}
|
|
46
|
+
function definePreset() {
|
|
47
|
+
return function(spec) {
|
|
48
|
+
return function preset(options) {
|
|
49
|
+
const derived = spec.derive(options);
|
|
50
|
+
const secrets = options.secrets ?? {};
|
|
51
|
+
const missing = [
|
|
52
|
+
...(spec.requiredFields ?? []).filter((field) => isEmpty(options[field])).map((field) => `field "${field}"`),
|
|
53
|
+
...(spec.requiredSecrets === "all" ? Object.keys(secrets) : spec.requiredSecrets ?? []).filter((name2) => isEmpty(secrets[name2])).map((name2) => `secret "${name2}"`)
|
|
54
|
+
];
|
|
55
|
+
const name = options.name?.trim() || derived.name?.trim() || spec.kind;
|
|
56
|
+
const service = {
|
|
57
|
+
node: "service",
|
|
58
|
+
kind: spec.kind,
|
|
59
|
+
name,
|
|
60
|
+
description: options.description ?? derived.description,
|
|
61
|
+
critical: options.critical ?? false,
|
|
62
|
+
slowMs: options.slowMs ?? spec.defaults?.slowMs ?? DEFAULT_SLOW_MS,
|
|
63
|
+
timeoutMs: options.timeoutMs ?? spec.defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
64
|
+
identity: derived.identity,
|
|
65
|
+
secrets,
|
|
66
|
+
missing,
|
|
67
|
+
check: (ctx) => spec.check({ ...ctx, options })
|
|
68
|
+
};
|
|
69
|
+
const sensitive = (spec.sensitive?.(options) ?? []).filter((value) => !!value);
|
|
70
|
+
return hideSecrets(service, { secrets, sensitive });
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function slug(text) {
|
|
75
|
+
return text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "x";
|
|
76
|
+
}
|
|
77
|
+
function identityKey(identity) {
|
|
78
|
+
return identity.toLowerCase().replace(/[^a-z0-9._:-]+/g, "-").replace(/^-+|-+$/g, "") || "x";
|
|
79
|
+
}
|
|
80
|
+
function unique(base, taken) {
|
|
81
|
+
let key = base;
|
|
82
|
+
for (let n = 2; taken.has(key); n++) key = `${base}-${n}`;
|
|
83
|
+
taken.add(key);
|
|
84
|
+
return key;
|
|
85
|
+
}
|
|
86
|
+
function resolveTree(nodes) {
|
|
87
|
+
const services = /* @__PURE__ */ new Map();
|
|
88
|
+
const serviceKeys = /* @__PURE__ */ new Set();
|
|
89
|
+
const groupKeys = /* @__PURE__ */ new Set();
|
|
90
|
+
const warnings = [];
|
|
91
|
+
const walk = (list, path) => list.map((node) => {
|
|
92
|
+
if (!node || typeof node !== "object" || !("node" in node)) {
|
|
93
|
+
throw new Error("sermon-js: services must be built with group(), custom() or a preset function");
|
|
94
|
+
}
|
|
95
|
+
if (node.node === "group") {
|
|
96
|
+
const segments = [...path, slug(node.title)];
|
|
97
|
+
const key2 = unique(segments.join(":"), groupKeys);
|
|
98
|
+
return {
|
|
99
|
+
type: "group",
|
|
100
|
+
key: key2,
|
|
101
|
+
title: node.title,
|
|
102
|
+
description: node.description,
|
|
103
|
+
children: walk(node.services, segments)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const base = node.identity ? identityKey(node.identity) : [...path, slug(node.name)].join(":");
|
|
107
|
+
const key = unique(base, serviceKeys);
|
|
108
|
+
if (key !== base) {
|
|
109
|
+
warnings.push(
|
|
110
|
+
`Two services resolve to "${base}" (${node.kind} "${node.name}"): the second one is tracked as "${key}".`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
services.set(key, hideSecrets({ ...node, key }, { secrets: node.secrets, sensitive: node.sensitive }));
|
|
114
|
+
return { type: "service", key };
|
|
115
|
+
});
|
|
116
|
+
const tree = walk(nodes, []);
|
|
117
|
+
return { tree, services, warnings };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/probe.ts
|
|
121
|
+
var SEVERITY = { up: 0, sleeping: 0, unconfigured: 0, degraded: 1, down: 2 };
|
|
122
|
+
function errorMessage(error) {
|
|
123
|
+
if (error instanceof Error) {
|
|
124
|
+
if (error.name === "TimeoutError" || error.name === "AbortError") return "Timed out";
|
|
125
|
+
const cause = error.cause;
|
|
126
|
+
const detail = cause?.code ?? cause?.errors?.[0]?.code ?? cause?.message;
|
|
127
|
+
return detail ? `${error.message}: ${detail}` : error.message;
|
|
128
|
+
}
|
|
129
|
+
return String(error);
|
|
130
|
+
}
|
|
131
|
+
function snippet(text) {
|
|
132
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
133
|
+
return trimmed.length > 200 ? `${trimmed.slice(0, 200)}\u2026` : trimmed;
|
|
134
|
+
}
|
|
135
|
+
function httpError({ status, text }) {
|
|
136
|
+
if (status === 401 || status === 407) return `HTTP ${status}: credentials rejected`;
|
|
137
|
+
if (status === 403) return "HTTP 403: access denied";
|
|
138
|
+
return `HTTP ${status}${text ? `: ${snippet(text)}` : ""}`;
|
|
139
|
+
}
|
|
140
|
+
function normalizeReturn(value) {
|
|
141
|
+
if (value === void 0 || value === null || value === true) return { status: "up" };
|
|
142
|
+
if (value === false) return { status: "down" };
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
function createHttp({
|
|
146
|
+
timeoutMs,
|
|
147
|
+
signal,
|
|
148
|
+
fetch: fetchImpl = fetch,
|
|
149
|
+
redact = (text) => text,
|
|
150
|
+
hideResponseBodies = false
|
|
151
|
+
}) {
|
|
152
|
+
return async function http(url, init = {}) {
|
|
153
|
+
const { okStatuses, ...requestInit } = init;
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const timer = setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), timeoutMs);
|
|
156
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
157
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
158
|
+
if (signal?.aborted) onAbort();
|
|
159
|
+
const start = performance.now();
|
|
160
|
+
const elapsed = () => Math.round(performance.now() - start);
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetchImpl(url, {
|
|
163
|
+
redirect: "manual",
|
|
164
|
+
...requestInit,
|
|
165
|
+
signal: controller.signal
|
|
166
|
+
});
|
|
167
|
+
const text = await res.text();
|
|
168
|
+
const latencyMs = elapsed();
|
|
169
|
+
let json;
|
|
170
|
+
try {
|
|
171
|
+
json = text ? JSON.parse(text) : void 0;
|
|
172
|
+
} catch {
|
|
173
|
+
json = void 0;
|
|
174
|
+
}
|
|
175
|
+
const ok = res.status < 400 || !!okStatuses?.includes(res.status);
|
|
176
|
+
const outcome = ok ? { status: "up", latencyMs, httpStatus: res.status } : {
|
|
177
|
+
status: res.status === 429 ? "degraded" : "down",
|
|
178
|
+
latencyMs,
|
|
179
|
+
httpStatus: res.status,
|
|
180
|
+
error: httpError({ status: res.status, text: hideResponseBodies ? "" : redact(text) })
|
|
181
|
+
};
|
|
182
|
+
return { ok, status: res.status, latencyMs, text, json, headers: res.headers, outcome };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const latencyMs = elapsed();
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
latencyMs,
|
|
188
|
+
text: "",
|
|
189
|
+
json: void 0,
|
|
190
|
+
outcome: { status: "down", error: errorMessage(error) }
|
|
191
|
+
};
|
|
192
|
+
} finally {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
signal?.removeEventListener("abort", onAbort);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async function measure(fn) {
|
|
199
|
+
const start = performance.now();
|
|
200
|
+
try {
|
|
201
|
+
const outcome = normalizeReturn(await fn());
|
|
202
|
+
return { ...outcome, latencyMs: outcome.latencyMs ?? Math.round(performance.now() - start) };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return { status: "down", latencyMs: Math.round(performance.now() - start), error: errorMessage(error) };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function combine(outcomes) {
|
|
208
|
+
if (outcomes.length === 0) return { status: "up" };
|
|
209
|
+
const worst = outcomes.reduce((a, b) => SEVERITY[b.status] > SEVERITY[a.status] ? b : a);
|
|
210
|
+
const latencies = outcomes.map((o) => o.latencyMs).filter((l) => l !== void 0);
|
|
211
|
+
const errors = outcomes.map((o) => o.error).filter((e) => !!e);
|
|
212
|
+
const usage = outcomes.flatMap((o) => o.usage ?? []);
|
|
213
|
+
const instances = outcomes.flatMap((o) => o.instances ?? []);
|
|
214
|
+
const metrics = Object.assign({}, ...outcomes.map((o) => o.metrics ?? {}));
|
|
215
|
+
return {
|
|
216
|
+
status: worst.status,
|
|
217
|
+
latencyMs: latencies.length ? Math.max(...latencies) : void 0,
|
|
218
|
+
httpStatus: worst.httpStatus,
|
|
219
|
+
error: errors.length ? errors.join("; ") : void 0,
|
|
220
|
+
metrics: metrics && Object.keys(metrics).length ? metrics : void 0,
|
|
221
|
+
usage: usage.length ? usage : void 0,
|
|
222
|
+
instances: instances.length ? instances : void 0,
|
|
223
|
+
probed: outcomes.some((o) => o.probed !== false)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { DEFAULT_SLOW_MS, DEFAULT_TIMEOUT_MS, combine, createHttp, custom, definePreset, errorMessage, group, measure, normalizeReturn, resolveTree };
|
|
228
|
+
//# sourceMappingURL=chunk-XPXIDJUU.js.map
|
|
229
|
+
//# sourceMappingURL=chunk-XPXIDJUU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/probe.ts"],"names":["name","key"],"mappings":"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","file":"chunk-XPXIDJUU.js","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"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/stores/redis-core.ts
|
|
4
|
+
function parse(value) {
|
|
5
|
+
if (typeof value !== "string") return value && typeof value === "object" ? value : void 0;
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(value);
|
|
8
|
+
} catch {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function toRecord(reply) {
|
|
13
|
+
if (!reply) return {};
|
|
14
|
+
if (Array.isArray(reply)) {
|
|
15
|
+
const record = {};
|
|
16
|
+
for (let i = 0; i + 1 < reply.length; i += 2) record[String(reply[i])] = reply[i + 1];
|
|
17
|
+
return record;
|
|
18
|
+
}
|
|
19
|
+
if (reply instanceof Map) return Object.fromEntries(reply);
|
|
20
|
+
return reply;
|
|
21
|
+
}
|
|
22
|
+
var list = (reply) => Array.isArray(reply) ? reply : [];
|
|
23
|
+
function createRedisStore({ exec, prefix = "sermon" }) {
|
|
24
|
+
const k = {
|
|
25
|
+
latest: `${prefix}:latest`,
|
|
26
|
+
runs: `${prefix}:runs`,
|
|
27
|
+
samples: (key) => `${prefix}:samples:${key}`,
|
|
28
|
+
lock: (name) => `${prefix}:lock:${name}`,
|
|
29
|
+
cache: (key) => `${prefix}:cache:${key}`
|
|
30
|
+
};
|
|
31
|
+
const one = async (command) => (await exec([command]))[0];
|
|
32
|
+
return {
|
|
33
|
+
async saveRun({ summary, results, samples }) {
|
|
34
|
+
const commands = [];
|
|
35
|
+
if (results.length) {
|
|
36
|
+
commands.push(["HSET", k.latest, ...results.flatMap((result) => [result.key, JSON.stringify(result)])]);
|
|
37
|
+
}
|
|
38
|
+
for (const sample of samples) {
|
|
39
|
+
commands.push(["ZADD", k.samples(sample.key), sample.at, JSON.stringify(sample)]);
|
|
40
|
+
}
|
|
41
|
+
commands.push(["ZADD", k.runs, summary.at, JSON.stringify(summary)]);
|
|
42
|
+
await exec(commands);
|
|
43
|
+
},
|
|
44
|
+
async latest() {
|
|
45
|
+
const record = toRecord(await one(["HGETALL", k.latest]));
|
|
46
|
+
const latest = {};
|
|
47
|
+
for (const [key, value] of Object.entries(record)) {
|
|
48
|
+
const result = parse(value);
|
|
49
|
+
if (result) latest[key] = result;
|
|
50
|
+
}
|
|
51
|
+
return latest;
|
|
52
|
+
},
|
|
53
|
+
async runs({ since, until }) {
|
|
54
|
+
const reply = await one(["ZRANGEBYSCORE", k.runs, since, until]);
|
|
55
|
+
return list(reply).map((member) => parse(member)).filter((run) => !!run);
|
|
56
|
+
},
|
|
57
|
+
async samples(key, { since, until }) {
|
|
58
|
+
const reply = await one(["ZRANGEBYSCORE", k.samples(key), since, until]);
|
|
59
|
+
return list(reply).map((member) => parse(member)).filter((sample) => !!sample);
|
|
60
|
+
},
|
|
61
|
+
async prune({ before, keepKeys }) {
|
|
62
|
+
const keys = list(await one(["HKEYS", k.latest])).map(String);
|
|
63
|
+
const commands = [
|
|
64
|
+
["ZREMRANGEBYSCORE", k.runs, "-inf", `(${before}`],
|
|
65
|
+
...keys.map((key) => ["ZREMRANGEBYSCORE", k.samples(key), "-inf", `(${before}`])
|
|
66
|
+
];
|
|
67
|
+
if (keepKeys) {
|
|
68
|
+
const keep = new Set(keepKeys);
|
|
69
|
+
const removed = keys.filter((key) => !keep.has(key));
|
|
70
|
+
if (removed.length) {
|
|
71
|
+
commands.push(["HDEL", k.latest, ...removed], ["DEL", ...removed.map(k.samples)]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
await exec(commands);
|
|
75
|
+
},
|
|
76
|
+
async lock(name, ttlMs) {
|
|
77
|
+
const ttl = Math.max(1, Math.round(ttlMs));
|
|
78
|
+
const acquired = await one(["SET", k.lock(name), "1", "NX", "PX", ttl]);
|
|
79
|
+
if (acquired) return { ok: true };
|
|
80
|
+
const remaining = Number(await one(["PTTL", k.lock(name)]));
|
|
81
|
+
return { ok: false, retryAfterMs: remaining > 0 ? remaining : ttl };
|
|
82
|
+
},
|
|
83
|
+
async unlock(name) {
|
|
84
|
+
await one(["DEL", k.lock(name)]);
|
|
85
|
+
},
|
|
86
|
+
cache: {
|
|
87
|
+
async get(key) {
|
|
88
|
+
const value = await one(["GET", k.cache(key)]);
|
|
89
|
+
return typeof value === "string" ? value : value === null || value === void 0 ? null : JSON.stringify(value);
|
|
90
|
+
},
|
|
91
|
+
async set(key, value, ttlMs) {
|
|
92
|
+
await one(["SET", k.cache(key), value, "PX", Math.max(1, Math.round(ttlMs))]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
exports.createRedisStore = createRedisStore;
|
|
99
|
+
//# sourceMappingURL=chunk-ZI442ZA5.cjs.map
|
|
100
|
+
//# sourceMappingURL=chunk-ZI442ZA5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stores/redis-core.ts"],"names":[],"mappings":";;;AAQA,SAAS,MAAS,KAAA,EAA+B;AAC/C,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU,OAAO,SAAS,OAAO,KAAA,KAAU,WAAY,KAAA,GAAc,MAAA;AAC1F,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,SAAS,KAAA,EAAyC;AACzD,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,MAAM,SAAkC,EAAC;AACzC,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,IAAK,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAC,CAAA,GAAI,KAAA,CAAM,IAAI,CAAC,CAAA;AACpF,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,YAAiB,GAAA,EAAK,OAAO,MAAA,CAAO,YAAY,KAAK,CAAA;AACzD,EAAA,OAAO,KAAA;AACT;AAEA,IAAM,IAAA,GAAO,CAAC,KAAA,KAA+B,KAAA,CAAM,QAAQ,KAAK,CAAA,GAAI,QAAQ,EAAC;AAStE,SAAS,gBAAA,CAAiB,EAAE,IAAA,EAAM,MAAA,GAAS,UAAS,EAAsD;AAC/G,EAAA,MAAM,CAAA,GAAI;AAAA,IACR,MAAA,EAAQ,GAAG,MAAM,CAAA,OAAA,CAAA;AAAA,IACjB,IAAA,EAAM,GAAG,MAAM,CAAA,KAAA,CAAA;AAAA,IACf,SAAS,CAAC,GAAA,KAAgB,CAAA,EAAG,MAAM,YAAY,GAAG,CAAA,CAAA;AAAA,IAClD,MAAM,CAAC,IAAA,KAAiB,CAAA,EAAG,MAAM,SAAS,IAAI,CAAA,CAAA;AAAA,IAC9C,OAAO,CAAC,GAAA,KAAgB,CAAA,EAAG,MAAM,UAAU,GAAG,CAAA;AAAA,GAChD;AACA,EAAA,MAAM,GAAA,GAAM,OAAO,OAAA,KAAA,CAA2B,MAAM,KAAK,CAAC,OAAO,CAAC,CAAA,EAAG,CAAC,CAAA;AAEtE,EAAA,OAAO;AAAA,IACL,MAAM,OAAA,CAAQ,EAAE,OAAA,EAAS,OAAA,EAAS,SAAQ,EAAG;AAC3C,MAAA,MAAM,WAA2B,EAAC;AAClC,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,QAAA,CAAS,KAAK,CAAC,MAAA,EAAQ,EAAE,MAAA,EAAQ,GAAG,QAAQ,OAAA,CAAQ,CAAC,WAAW,CAAC,MAAA,CAAO,KAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,MACxG;AACA,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,QAAA,CAAS,IAAA,CAAK,CAAC,MAAA,EAAQ,CAAA,CAAE,QAAQ,MAAA,CAAO,GAAG,CAAA,EAAG,MAAA,CAAO,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAC,CAAA;AAAA,MAClF;AACA,MAAA,QAAA,CAAS,IAAA,CAAK,CAAC,MAAA,EAAQ,CAAA,CAAE,IAAA,EAAM,OAAA,CAAQ,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAC,CAAA;AACnE,MAAA,MAAM,KAAK,QAAQ,CAAA;AAAA,IACrB,CAAA;AAAA,IAEA,MAAM,MAAA,GAAS;AACb,MAAA,MAAM,MAAA,GAAS,SAAS,MAAM,GAAA,CAAI,CAAC,SAAA,EAAW,CAAA,CAAE,MAAM,CAAC,CAAC,CAAA;AACxD,MAAA,MAAM,SAAsC,EAAC;AAC7C,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,QAAA,MAAM,MAAA,GAAS,MAAmB,KAAK,CAAA;AACvC,QAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA;AAAA,MAC5B;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IAEA,MAAM,IAAA,CAAK,EAAE,KAAA,EAAO,OAAM,EAAG;AAC3B,MAAA,MAAM,KAAA,GAAQ,MAAM,GAAA,CAAI,CAAC,iBAAiB,CAAA,CAAE,IAAA,EAAM,KAAA,EAAO,KAAK,CAAC,CAAA;AAC/D,MAAA,OAAO,IAAA,CAAK,KAAK,CAAA,CACd,GAAA,CAAI,CAAC,MAAA,KAAW,KAAA,CAAkB,MAAM,CAAC,EACzC,MAAA,CAAO,CAAC,GAAA,KAA2B,CAAC,CAAC,GAAG,CAAA;AAAA,IAC7C,CAAA;AAAA,IAEA,MAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,KAAA,EAAO,OAAM,EAAG;AACnC,MAAA,MAAM,KAAA,GAAQ,MAAM,GAAA,CAAI,CAAC,eAAA,EAAiB,CAAA,CAAE,OAAA,CAAQ,GAAG,CAAA,EAAG,KAAA,EAAO,KAAK,CAAC,CAAA;AACvE,MAAA,OAAO,IAAA,CAAK,KAAK,CAAA,CACd,GAAA,CAAI,CAAC,MAAA,KAAW,KAAA,CAAmB,MAAM,CAAC,EAC1C,MAAA,CAAO,CAAC,MAAA,KAAkC,CAAC,CAAC,MAAM,CAAA;AAAA,IACvD,CAAA;AAAA,IAEA,MAAM,KAAA,CAAM,EAAE,MAAA,EAAQ,UAAS,EAAG;AAChC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAM,GAAA,CAAI,CAAC,OAAA,EAAS,CAAA,CAAE,MAAM,CAAC,CAAC,CAAA,CAAE,GAAA,CAAI,MAAM,CAAA;AAE5D,MAAA,MAAM,QAAA,GAA2B;AAAA,QAC/B,CAAC,kBAAA,EAAoB,CAAA,CAAE,MAAM,MAAA,EAAQ,CAAA,CAAA,EAAI,MAAM,CAAA,CAAE,CAAA;AAAA,QACjD,GAAG,IAAA,CAAK,GAAA,CAAI,CAAC,QAAsB,CAAC,kBAAA,EAAoB,CAAA,CAAE,OAAA,CAAQ,GAAG,CAAA,EAAG,MAAA,EAAQ,CAAA,CAAA,EAAI,MAAM,EAAE,CAAC;AAAA,OAC/F;AACA,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,QAAQ,CAAA;AAC7B,QAAA,MAAM,OAAA,GAAU,KAAK,MAAA,CAAO,CAAC,QAAQ,CAAC,IAAA,CAAK,GAAA,CAAI,GAAG,CAAC,CAAA;AACnD,QAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,UAAA,QAAA,CAAS,KAAK,CAAC,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAQ,GAAG,OAAO,CAAA,EAAG,CAAC,KAAA,EAAO,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,OAAO,CAAC,CAAC,CAAA;AAAA,QAClF;AAAA,MACF;AACA,MAAA,MAAM,KAAK,QAAQ,CAAA;AAAA,IACrB,CAAA;AAAA,IAEA,MAAM,IAAA,CAAK,IAAA,EAAM,KAAA,EAA4B;AAC3C,MAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AACzC,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,CAAC,KAAA,EAAO,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,EAAG,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,GAAG,CAAC,CAAA;AACtE,MAAA,IAAI,QAAA,EAAU,OAAO,EAAE,EAAA,EAAI,IAAA,EAAK;AAChC,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAM,GAAA,CAAI,CAAC,MAAA,EAAQ,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAC,CAAC,CAAA;AAC1D,MAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,cAAc,SAAA,GAAY,CAAA,GAAI,YAAY,GAAA,EAAI;AAAA,IACpE,CAAA;AAAA,IAEA,MAAM,OAAO,IAAA,EAAM;AACjB,MAAA,MAAM,IAAI,CAAC,KAAA,EAAO,EAAE,IAAA,CAAK,IAAI,CAAC,CAAC,CAAA;AAAA,IACjC,CAAA;AAAA,IAEA,KAAA,EAAO;AAAA,MACL,MAAM,IAAI,GAAA,EAAK;AACb,QAAA,MAAM,KAAA,GAAQ,MAAM,GAAA,CAAI,CAAC,OAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAC,CAAC,CAAA;AAC7C,QAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,GAAY,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAAA,MAChH,CAAA;AAAA,MACA,MAAM,GAAA,CAAI,GAAA,EAAK,KAAA,EAAO,KAAA,EAAO;AAC3B,QAAA,MAAM,IAAI,CAAC,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,EAAG,KAAA,EAAO,IAAA,EAAM,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,MAAM,KAAK,CAAC,CAAC,CAAC,CAAA;AAAA,MAC9E;AAAA;AACF,GACF;AACF","file":"chunk-ZI442ZA5.cjs","sourcesContent":["import type { CheckSample, LockResult, RunSummary, StatusStore } from \"../store\";\nimport type { CheckResult } from \"../types\";\n\nexport type RedisCommand = Array<string | number>;\n\n/** Runs a batch of Redis commands and returns their replies in order */\nexport type RedisExec = (commands: RedisCommand[]) => Promise<unknown[]>;\n\nfunction parse<T>(value: unknown): T | undefined {\n if (typeof value !== \"string\") return value && typeof value === \"object\" ? (value as T) : undefined;\n try {\n return JSON.parse(value) as T;\n } catch {\n return undefined;\n }\n}\n\n/** HGETALL replies as a flat [field, value, …] array (RESP2, REST) or as an object (RESP3) */\nfunction toRecord(reply: unknown): Record<string, unknown> {\n if (!reply) return {};\n if (Array.isArray(reply)) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i + 1 < reply.length; i += 2) record[String(reply[i])] = reply[i + 1];\n return record;\n }\n if (reply instanceof Map) return Object.fromEntries(reply);\n return reply as Record<string, unknown>;\n}\n\nconst list = (reply: unknown): unknown[] => (Array.isArray(reply) ? reply : []);\n\n/**\n * A `StatusStore` on top of plain Redis commands. Layout, under `prefix`:\n * - `latest` HASH service key → latest result (JSON)\n * - `runs` ZSET run summaries (JSON), scored by time\n * - `samples:<key>` ZSET samples of one service (JSON), scored by time\n * - `lock:<name>`, `cache:<key>` strings with a time-to-live\n */\nexport function createRedisStore({ exec, prefix = \"sermon\" }: { exec: RedisExec; prefix?: string }): StatusStore {\n const k = {\n latest: `${prefix}:latest`,\n runs: `${prefix}:runs`,\n samples: (key: string) => `${prefix}:samples:${key}`,\n lock: (name: string) => `${prefix}:lock:${name}`,\n cache: (key: string) => `${prefix}:cache:${key}`,\n };\n const one = async (command: RedisCommand) => (await exec([command]))[0];\n\n return {\n async saveRun({ summary, results, samples }) {\n const commands: RedisCommand[] = [];\n if (results.length) {\n commands.push([\"HSET\", k.latest, ...results.flatMap((result) => [result.key, JSON.stringify(result)])]);\n }\n for (const sample of samples) {\n commands.push([\"ZADD\", k.samples(sample.key), sample.at, JSON.stringify(sample)]);\n }\n commands.push([\"ZADD\", k.runs, summary.at, JSON.stringify(summary)]);\n await exec(commands);\n },\n\n async latest() {\n const record = toRecord(await one([\"HGETALL\", k.latest]));\n const latest: Record<string, CheckResult> = {};\n for (const [key, value] of Object.entries(record)) {\n const result = parse<CheckResult>(value);\n if (result) latest[key] = result;\n }\n return latest;\n },\n\n async runs({ since, until }) {\n const reply = await one([\"ZRANGEBYSCORE\", k.runs, since, until]);\n return list(reply)\n .map((member) => parse<RunSummary>(member))\n .filter((run): run is RunSummary => !!run);\n },\n\n async samples(key, { since, until }) {\n const reply = await one([\"ZRANGEBYSCORE\", k.samples(key), since, until]);\n return list(reply)\n .map((member) => parse<CheckSample>(member))\n .filter((sample): sample is CheckSample => !!sample);\n },\n\n async prune({ before, keepKeys }) {\n const keys = list(await one([\"HKEYS\", k.latest])).map(String);\n // \"(\" makes the bound exclusive: entries exactly at `before` are kept\n const commands: RedisCommand[] = [\n [\"ZREMRANGEBYSCORE\", k.runs, \"-inf\", `(${before}`],\n ...keys.map((key): RedisCommand => [\"ZREMRANGEBYSCORE\", k.samples(key), \"-inf\", `(${before}`]),\n ];\n if (keepKeys) {\n const keep = new Set(keepKeys);\n const removed = keys.filter((key) => !keep.has(key));\n if (removed.length) {\n commands.push([\"HDEL\", k.latest, ...removed], [\"DEL\", ...removed.map(k.samples)]);\n }\n }\n await exec(commands);\n },\n\n async lock(name, ttlMs): Promise<LockResult> {\n const ttl = Math.max(1, Math.round(ttlMs));\n const acquired = await one([\"SET\", k.lock(name), \"1\", \"NX\", \"PX\", ttl]);\n if (acquired) return { ok: true };\n const remaining = Number(await one([\"PTTL\", k.lock(name)]));\n return { ok: false, retryAfterMs: remaining > 0 ? remaining : ttl };\n },\n\n async unlock(name) {\n await one([\"DEL\", k.lock(name)]);\n },\n\n cache: {\n async get(key) {\n const value = await one([\"GET\", k.cache(key)]);\n return typeof value === \"string\" ? value : value === null || value === undefined ? null : JSON.stringify(value);\n },\n async set(key, value, ttlMs) {\n await one([\"SET\", k.cache(key), value, \"PX\", Math.max(1, Math.round(ttlMs))]);\n },\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { l as SecretsInput, f as CheckContext, d as CheckReturn, h as CommonOptions } from './types-CctvUAs2.cjs';
|
|
2
|
+
|
|
3
|
+
declare const DEFAULT_SLOW_MS = 2000;
|
|
4
|
+
declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
5
|
+
type AnyCheck = (ctx: CheckContext<SecretsInput>) => Promise<CheckReturn> | CheckReturn;
|
|
6
|
+
/** A service ready to be checked. Built by `custom()` or a preset; not meant to be written by hand. */
|
|
7
|
+
type ServiceDefinition = {
|
|
8
|
+
node: "service";
|
|
9
|
+
/** The preset kind: "custom", "generic.http", "fly.app"… */
|
|
10
|
+
kind: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
critical: boolean;
|
|
14
|
+
slowMs: number;
|
|
15
|
+
timeoutMs: number;
|
|
16
|
+
/** Natural identity (e.g. `fly.app:my-app`): keeps history across renames and moves */
|
|
17
|
+
identity?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Hidden from `console.log`, `JSON.stringify` and object spreads (the property is not
|
|
20
|
+
* enumerable), so a logged or serialized configuration never prints credentials.
|
|
21
|
+
*/
|
|
22
|
+
secrets: SecretsInput;
|
|
23
|
+
/** Other values to redact from public text (e.g. credentials given as headers). Hidden like `secrets`. */
|
|
24
|
+
sensitive?: string[];
|
|
25
|
+
/** Required secrets/fields that are empty: the service is shown as "not configured" and never runs */
|
|
26
|
+
missing: string[];
|
|
27
|
+
check: AnyCheck;
|
|
28
|
+
};
|
|
29
|
+
type GroupDefinition = {
|
|
30
|
+
node: "group";
|
|
31
|
+
title: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
services: ServiceNode[];
|
|
34
|
+
};
|
|
35
|
+
type ServiceNode = ServiceDefinition | GroupDefinition;
|
|
36
|
+
/** A titled set of services. Groups can contain other groups. */
|
|
37
|
+
declare function group(options: {
|
|
38
|
+
title: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
services: ServiceNode[];
|
|
41
|
+
}): GroupDefinition;
|
|
42
|
+
type CustomOptions<Secrets extends SecretsInput> = CommonOptions & {
|
|
43
|
+
/** How to call it (required for custom services) */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Values the check needs. They're redacted from public errors; if one is empty the service is "not configured". */
|
|
46
|
+
secrets?: Secrets;
|
|
47
|
+
/** Set to false to list the service as "not configured" without running it */
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
/** Return `true`/nothing for up, `false` for down, or a full outcome. Throwing means down. */
|
|
50
|
+
check: (ctx: CheckContext<Secrets>) => Promise<CheckReturn> | CheckReturn;
|
|
51
|
+
};
|
|
52
|
+
/** Any check you can write yourself: the escape hatch for services without a preset */
|
|
53
|
+
declare function custom<Secrets extends SecretsInput = Record<never, never>>(options: CustomOptions<Secrets>): ServiceDefinition;
|
|
54
|
+
/**
|
|
55
|
+
* What every preset accepts on top of its own fields. Declare a preset's options as
|
|
56
|
+
* `PresetOptions & { …fields; secrets: { … }; track?: { … } }`.
|
|
57
|
+
*/
|
|
58
|
+
type PresetOptions = CommonOptions;
|
|
59
|
+
/** The shape a preset's options must fit (kept separate so `secrets` and `track` stay exact per preset) */
|
|
60
|
+
type PresetOptionsShape = CommonOptions & {
|
|
61
|
+
secrets?: SecretsInput;
|
|
62
|
+
track?: unknown;
|
|
63
|
+
};
|
|
64
|
+
type SecretsOf<O> = O extends {
|
|
65
|
+
secrets?: infer S;
|
|
66
|
+
} ? (NonNullable<S> extends SecretsInput ? NonNullable<S> : never) : never;
|
|
67
|
+
/** Inside a preset's check: required secrets are strings, optional ones may be missing */
|
|
68
|
+
type PresetContext<O extends PresetOptionsShape, Required extends string> = Omit<CheckContext<SecretsInput>, "secrets"> & {
|
|
69
|
+
secrets: {
|
|
70
|
+
[K in Extract<keyof SecretsOf<O>, string>]: K extends Required ? string : string | undefined;
|
|
71
|
+
};
|
|
72
|
+
/** The options the user passed to the preset */
|
|
73
|
+
options: O;
|
|
74
|
+
};
|
|
75
|
+
type PresetSpec<O extends PresetOptionsShape, Required extends string> = {
|
|
76
|
+
/** Namespaced kind, e.g. "fly.app" */
|
|
77
|
+
kind: string;
|
|
78
|
+
/** Secrets that must be non-empty for the service to run; "all" requires every secret given */
|
|
79
|
+
requiredSecrets?: readonly Required[] | "all";
|
|
80
|
+
/** Options that must be non-empty at runtime (e.g. a URL read from the environment) */
|
|
81
|
+
requiredFields?: readonly (keyof O & string)[];
|
|
82
|
+
defaults?: {
|
|
83
|
+
slowMs?: number;
|
|
84
|
+
timeoutMs?: number;
|
|
85
|
+
};
|
|
86
|
+
/** Name, natural identity and description derived from the options */
|
|
87
|
+
derive: (options: O) => {
|
|
88
|
+
name: string;
|
|
89
|
+
identity?: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
};
|
|
92
|
+
/** Values outside `secrets` that must never show in public text (e.g. credentials in headers) */
|
|
93
|
+
sensitive?: (options: O) => Array<string | undefined>;
|
|
94
|
+
check: (ctx: PresetContext<O, NoInfer<Required>>) => Promise<CheckReturn> | CheckReturn;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Build a preset: a typed function that turns a few options into a service.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* type Options = PresetOptions & { app: string; secrets: { token: string | undefined } };
|
|
101
|
+
* export const app = definePreset<Options>()({
|
|
102
|
+
* kind: "fly.app",
|
|
103
|
+
* requiredSecrets: ["token"],
|
|
104
|
+
* derive: (o) => ({ name: o.name ?? o.app, identity: `fly.app:${o.app}` }),
|
|
105
|
+
* check: ({ options, secrets, http }) => …,
|
|
106
|
+
* });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function definePreset<O extends PresetOptionsShape>(): <const Required extends keyof SecretsOf<O> & string = never>(spec: PresetSpec<O, Required>) => (options: O) => ServiceDefinition;
|
|
110
|
+
/** A service with its derived key */
|
|
111
|
+
type ResolvedService = ServiceDefinition & {
|
|
112
|
+
key: string;
|
|
113
|
+
};
|
|
114
|
+
type ResolvedNode = {
|
|
115
|
+
type: "group";
|
|
116
|
+
key: string;
|
|
117
|
+
title: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
children: ResolvedNode[];
|
|
120
|
+
} | {
|
|
121
|
+
type: "service";
|
|
122
|
+
key: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export { type CustomOptions as C, DEFAULT_SLOW_MS as D, type GroupDefinition as G, type PresetContext as P, type ResolvedNode as R, type ServiceDefinition as S, DEFAULT_TIMEOUT_MS as a, type PresetOptions as b, type PresetSpec as c, type ResolvedService as d, type ServiceNode as e, custom as f, definePreset as g, group as h };
|