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,971 @@
|
|
|
1
|
+
export { generic } from '../chunk-I3346MUL.js';
|
|
2
|
+
import { definePreset, normalizeReturn } from '../chunk-XPXIDJUU.js';
|
|
3
|
+
import { AwsClient } from 'aws4fetch';
|
|
4
|
+
|
|
5
|
+
// src/presets/fly.ts
|
|
6
|
+
var MACHINES_API = "https://api.machines.dev/v1";
|
|
7
|
+
var GONE_STATES = /* @__PURE__ */ new Set(["destroyed", "destroying"]);
|
|
8
|
+
var RESTING_STATES = /* @__PURE__ */ new Set(["stopped", "suspended", "stopping", "suspending", "starting", "created"]);
|
|
9
|
+
function crashReason(machine) {
|
|
10
|
+
const exit = [...machine.events ?? []].filter((event) => event.type === "exit").sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0))[0]?.request?.exit_event;
|
|
11
|
+
if (!exit || exit.requested_stop) return void 0;
|
|
12
|
+
if (exit.oom_killed) return "Out of memory";
|
|
13
|
+
if (exit.exit_code !== void 0 && exit.exit_code !== 0) return `Crashed (exit code ${exit.exit_code})`;
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
function withKeys(machines) {
|
|
17
|
+
const seen = /* @__PURE__ */ new Map();
|
|
18
|
+
return [...machines].sort((a, b) => (a.region ?? "").localeCompare(b.region ?? "") || a.id.localeCompare(b.id)).map((machine) => {
|
|
19
|
+
const region = machine.region ?? "unknown";
|
|
20
|
+
const count = (seen.get(region) ?? 0) + 1;
|
|
21
|
+
seen.set(region, count);
|
|
22
|
+
return { machine, region, key: count === 1 ? region : `${region}#${count}` };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function median(values) {
|
|
26
|
+
if (!values.length) return void 0;
|
|
27
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
28
|
+
return sorted[Math.floor((sorted.length - 1) / 2)];
|
|
29
|
+
}
|
|
30
|
+
var app = definePreset()({
|
|
31
|
+
kind: "fly.app",
|
|
32
|
+
requiredFields: ["app"],
|
|
33
|
+
requiredSecrets: ["token"],
|
|
34
|
+
defaults: { slowMs: 1500 },
|
|
35
|
+
derive: (options) => ({
|
|
36
|
+
name: options.app,
|
|
37
|
+
identity: `fly.app:${options.app}`,
|
|
38
|
+
description: `Fly app "${options.app}": machine states, then health on running machines only`
|
|
39
|
+
}),
|
|
40
|
+
check: async ({ options, secrets, http, slowMs }) => {
|
|
41
|
+
const health = options.track?.health ?? "/";
|
|
42
|
+
const healthPath = health === false ? void 0 : typeof health === "string" ? health : health.path ?? "/";
|
|
43
|
+
const anyStatus = typeof health === "object" && !!health.anyStatus;
|
|
44
|
+
const list = await http(`${MACHINES_API}/apps/${encodeURIComponent(options.app)}/machines`, {
|
|
45
|
+
headers: { Authorization: `Bearer ${secrets.token}` }
|
|
46
|
+
});
|
|
47
|
+
if (list.status === 404) {
|
|
48
|
+
return { status: "down", probed: false, httpStatus: 404, error: `App "${options.app}" not found on Fly` };
|
|
49
|
+
}
|
|
50
|
+
if (!list.ok || !Array.isArray(list.json)) {
|
|
51
|
+
return {
|
|
52
|
+
status: "degraded",
|
|
53
|
+
probed: false,
|
|
54
|
+
httpStatus: list.status,
|
|
55
|
+
error: list.status ? `Fly Machines API HTTP ${list.status}` : `Fly Machines API unreachable: ${list.outcome.error ?? "no response"}`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const machines = list.json.filter((machine) => !GONE_STATES.has(machine.state));
|
|
59
|
+
if (machines.length === 0) {
|
|
60
|
+
return { status: "down", probed: false, error: "No machines: the app has no capacity at all" };
|
|
61
|
+
}
|
|
62
|
+
const instances = await Promise.all(
|
|
63
|
+
withKeys(machines).map(async ({ machine, region, key }) => {
|
|
64
|
+
const base = { key, region, id: machine.id, state: machine.state };
|
|
65
|
+
if (machine.state === "failed") return { ...base, status: "down", error: "Machine failed" };
|
|
66
|
+
if (machine.state !== "started") {
|
|
67
|
+
const crash = machine.state === "stopped" ? crashReason(machine) : void 0;
|
|
68
|
+
if (crash) return { ...base, status: "down", error: crash };
|
|
69
|
+
return { ...base, status: RESTING_STATES.has(machine.state) ? "sleeping" : "degraded" };
|
|
70
|
+
}
|
|
71
|
+
const failingChecks = (machine.checks ?? []).filter((c) => c.status !== "passing").map((c) => c.name);
|
|
72
|
+
const checksError = failingChecks.length ? `Fly health checks failing: ${failingChecks.join(", ")}` : void 0;
|
|
73
|
+
if (!healthPath) {
|
|
74
|
+
return { ...base, status: checksError ? "degraded" : "up", error: checksError };
|
|
75
|
+
}
|
|
76
|
+
const probe = await http(`https://${options.app}.fly.dev${healthPath}`, {
|
|
77
|
+
headers: { "fly-force-instance-id": machine.id },
|
|
78
|
+
okStatuses: anyStatus ? [400, 401, 403, 404, 405, 421] : void 0
|
|
79
|
+
});
|
|
80
|
+
let status2 = probe.outcome.status;
|
|
81
|
+
let error2 = probe.outcome.error;
|
|
82
|
+
if (status2 === "up" && checksError) {
|
|
83
|
+
status2 = "degraded";
|
|
84
|
+
error2 = checksError;
|
|
85
|
+
} else if (status2 === "up" && probe.latencyMs > slowMs) {
|
|
86
|
+
status2 = "degraded";
|
|
87
|
+
error2 = `Slow response: ${probe.latencyMs} ms`;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
...base,
|
|
91
|
+
status: status2,
|
|
92
|
+
latencyMs: probe.status === void 0 ? void 0 : probe.latencyMs,
|
|
93
|
+
httpStatus: probe.status,
|
|
94
|
+
error: error2
|
|
95
|
+
};
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
const down = instances.filter((instance2) => instance2.status === "down");
|
|
99
|
+
const degraded = instances.filter((instance2) => instance2.status === "degraded");
|
|
100
|
+
const awake = instances.filter((instance2) => instance2.status === "up" || instance2.status === "degraded");
|
|
101
|
+
const keysOf = (items) => items.map((instance2) => instance2.key).join(", ");
|
|
102
|
+
let status;
|
|
103
|
+
let error;
|
|
104
|
+
if (down.length === instances.length) {
|
|
105
|
+
status = "down";
|
|
106
|
+
error = `All ${instances.length} machine(s) down`;
|
|
107
|
+
} else if (down.length > 0) {
|
|
108
|
+
status = "degraded";
|
|
109
|
+
error = `${down.length}/${instances.length} machine(s) down: ${keysOf(down)}`;
|
|
110
|
+
} else if (degraded.length > 0) {
|
|
111
|
+
status = "degraded";
|
|
112
|
+
error = `Degraded in ${keysOf(degraded)}`;
|
|
113
|
+
} else {
|
|
114
|
+
status = awake.length ? "up" : "sleeping";
|
|
115
|
+
}
|
|
116
|
+
const probed = instances.some((instance2) => instance2.httpStatus !== void 0 || instance2.latencyMs !== void 0);
|
|
117
|
+
return {
|
|
118
|
+
status,
|
|
119
|
+
probed,
|
|
120
|
+
// Median of the machines we probed; each machine keeps its own latency
|
|
121
|
+
latencyMs: median(instances.map((i) => i.latencyMs).filter((l) => l !== void 0)),
|
|
122
|
+
error,
|
|
123
|
+
metrics: { machines: `${awake.length}/${instances.length} running` },
|
|
124
|
+
instances: options.track?.machines === false ? void 0 : instances
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
var fly = { app };
|
|
129
|
+
|
|
130
|
+
// src/google-auth.ts
|
|
131
|
+
function parseServiceAccount(json) {
|
|
132
|
+
if (!json) return void 0;
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(json);
|
|
135
|
+
return parsed.client_email && parsed.private_key ? parsed : void 0;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var base64url = (bytes) => {
|
|
141
|
+
let binary = "";
|
|
142
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
143
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
144
|
+
};
|
|
145
|
+
var encodeJson = (value) => base64url(new TextEncoder().encode(JSON.stringify(value)));
|
|
146
|
+
function pemToPkcs8(pem) {
|
|
147
|
+
const body = pem.replace(/\\n/g, "\n").replace(/-----BEGIN [^-]+-----|-----END [^-]+-----|\s+/g, "");
|
|
148
|
+
const binary = atob(body);
|
|
149
|
+
const bytes = new Uint8Array(binary.length);
|
|
150
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
151
|
+
return bytes;
|
|
152
|
+
}
|
|
153
|
+
async function signGoogleAssertion({
|
|
154
|
+
account,
|
|
155
|
+
scope,
|
|
156
|
+
now = Date.now()
|
|
157
|
+
}) {
|
|
158
|
+
const issuedAt = Math.floor(now / 1e3);
|
|
159
|
+
const unsigned = `${encodeJson({ alg: "RS256", typ: "JWT" })}.${encodeJson({
|
|
160
|
+
iss: account.client_email,
|
|
161
|
+
scope,
|
|
162
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
163
|
+
iat: issuedAt,
|
|
164
|
+
exp: issuedAt + 3600
|
|
165
|
+
})}`;
|
|
166
|
+
const key = await crypto.subtle.importKey(
|
|
167
|
+
"pkcs8",
|
|
168
|
+
pemToPkcs8(account.private_key),
|
|
169
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
170
|
+
false,
|
|
171
|
+
["sign"]
|
|
172
|
+
);
|
|
173
|
+
const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, new TextEncoder().encode(unsigned));
|
|
174
|
+
return `${unsigned}.${base64url(new Uint8Array(signature))}`;
|
|
175
|
+
}
|
|
176
|
+
var tokens = /* @__PURE__ */ new Map();
|
|
177
|
+
function clearGoogleTokens() {
|
|
178
|
+
tokens.clear();
|
|
179
|
+
}
|
|
180
|
+
async function googleServiceAccountToken({
|
|
181
|
+
account,
|
|
182
|
+
scope,
|
|
183
|
+
http,
|
|
184
|
+
now = Date.now()
|
|
185
|
+
}) {
|
|
186
|
+
const cacheKey = `${account.client_email}:${scope}:${account.private_key.length}:${account.private_key.slice(-48)}`;
|
|
187
|
+
const cached = tokens.get(cacheKey);
|
|
188
|
+
if (cached && cached.expiresAt > now) return cached.token;
|
|
189
|
+
const assertion = await signGoogleAssertion({ account, scope, now });
|
|
190
|
+
const response = await http("https://oauth2.googleapis.com/token", {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
193
|
+
body: new URLSearchParams({
|
|
194
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
195
|
+
assertion
|
|
196
|
+
}).toString()
|
|
197
|
+
});
|
|
198
|
+
const body = response.json;
|
|
199
|
+
if (!response.ok || !body?.access_token) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Google auth failed: ${body?.error_description ?? body?.error ?? response.outcome.error ?? "no access token"}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const ttlMs = Math.max(6e4, ((body.expires_in ?? 3600) - 300) * 1e3);
|
|
205
|
+
tokens.set(cacheKey, { token: body.access_token, expiresAt: now + ttlMs });
|
|
206
|
+
return body.access_token;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/presets/gcp.ts
|
|
210
|
+
var SLEEP_STATES = /* @__PURE__ */ new Set(["TERMINATED", "STOPPED", "SUSPENDED", "STOPPING", "SUSPENDING"]);
|
|
211
|
+
var BOOTING_STATES = /* @__PURE__ */ new Set(["PROVISIONING", "STAGING"]);
|
|
212
|
+
var instance = definePreset()({
|
|
213
|
+
kind: "gcp.instance",
|
|
214
|
+
requiredFields: ["instance", "zone"],
|
|
215
|
+
requiredSecrets: ["serviceAccount"],
|
|
216
|
+
defaults: { slowMs: 1500 },
|
|
217
|
+
derive: (options) => ({
|
|
218
|
+
name: options.instance ?? "",
|
|
219
|
+
identity: `gcp.instance:${options.project ?? ""}/${options.zone ?? ""}/${options.instance ?? ""}`,
|
|
220
|
+
description: `Google Cloud VM "${options.instance}": status, then health only when running`
|
|
221
|
+
}),
|
|
222
|
+
check: async ({ options, secrets, http, now }) => {
|
|
223
|
+
const account = parseServiceAccount(secrets.serviceAccount);
|
|
224
|
+
const project = options.project ?? account?.project_id;
|
|
225
|
+
if (!account || !project) {
|
|
226
|
+
return { status: "down", probed: false, error: "The service account secret is not a service-account JSON key" };
|
|
227
|
+
}
|
|
228
|
+
let token;
|
|
229
|
+
try {
|
|
230
|
+
token = await googleServiceAccountToken({
|
|
231
|
+
account,
|
|
232
|
+
scope: "https://www.googleapis.com/auth/compute.readonly",
|
|
233
|
+
http,
|
|
234
|
+
now
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return { status: "down", probed: false, error: error instanceof Error ? error.message : String(error) };
|
|
238
|
+
}
|
|
239
|
+
const info = await http(
|
|
240
|
+
`https://compute.googleapis.com/compute/v1/projects/${project}/zones/${options.zone}/instances/${options.instance}`,
|
|
241
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
242
|
+
);
|
|
243
|
+
const state = info.json?.status;
|
|
244
|
+
if (!info.ok || !state) {
|
|
245
|
+
return {
|
|
246
|
+
status: "down",
|
|
247
|
+
probed: false,
|
|
248
|
+
httpStatus: info.status,
|
|
249
|
+
error: `Compute API: ${info.outcome.error ?? "no instance status"}`
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const metrics = { vm: state, zone: options.zone };
|
|
253
|
+
if (SLEEP_STATES.has(state)) return { status: "sleeping", probed: false, metrics };
|
|
254
|
+
if (BOOTING_STATES.has(state)) return { status: "degraded", probed: false, metrics, error: `VM is ${state}` };
|
|
255
|
+
if (state !== "RUNNING") return { status: "down", probed: false, metrics, error: `VM is ${state}` };
|
|
256
|
+
const health = options.track?.health;
|
|
257
|
+
if (!health) return { status: "up", probed: false, metrics };
|
|
258
|
+
const probe = await http(health, { okStatuses: [400, 401, 403, 404, 405] });
|
|
259
|
+
return { ...probe.outcome, metrics };
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
var gcp = { instance };
|
|
263
|
+
async function signedCall({
|
|
264
|
+
http,
|
|
265
|
+
secrets,
|
|
266
|
+
service,
|
|
267
|
+
region,
|
|
268
|
+
url,
|
|
269
|
+
init
|
|
270
|
+
}) {
|
|
271
|
+
const client = new AwsClient({ ...secrets, service, region });
|
|
272
|
+
const request = await client.sign(url, init);
|
|
273
|
+
return http(request.url, {
|
|
274
|
+
method: init.method,
|
|
275
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
276
|
+
body: init.body
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
var credentials = definePreset()({
|
|
280
|
+
kind: "aws.credentials",
|
|
281
|
+
requiredSecrets: ["accessKeyId", "secretAccessKey"],
|
|
282
|
+
derive: (options) => ({
|
|
283
|
+
name: options.name,
|
|
284
|
+
description: "Validates the credentials only (STS GetCallerIdentity)"
|
|
285
|
+
}),
|
|
286
|
+
check: async ({ secrets, http }) => {
|
|
287
|
+
const body = "Action=GetCallerIdentity&Version=2011-06-15";
|
|
288
|
+
const response = await signedCall({
|
|
289
|
+
http,
|
|
290
|
+
secrets,
|
|
291
|
+
service: "sts",
|
|
292
|
+
region: "us-east-1",
|
|
293
|
+
url: "https://sts.amazonaws.com/",
|
|
294
|
+
init: { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body }
|
|
295
|
+
});
|
|
296
|
+
return response.outcome;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
var s3Bucket = definePreset()({
|
|
300
|
+
kind: "aws.s3-bucket",
|
|
301
|
+
requiredFields: ["bucket"],
|
|
302
|
+
requiredSecrets: ["accessKeyId", "secretAccessKey"],
|
|
303
|
+
defaults: { slowMs: 1500 },
|
|
304
|
+
derive: (options) => ({
|
|
305
|
+
name: options.bucket ?? "",
|
|
306
|
+
description: "S3 HeadBucket on the storage endpoint"
|
|
307
|
+
}),
|
|
308
|
+
check: async ({ options, secrets, http }) => {
|
|
309
|
+
const region = options.region ?? "us-east-1";
|
|
310
|
+
const endpoint = (options.endpoint ?? `https://s3.${region}.amazonaws.com`).replace(/\/+$/, "");
|
|
311
|
+
const response = await signedCall({
|
|
312
|
+
http,
|
|
313
|
+
secrets,
|
|
314
|
+
service: "s3",
|
|
315
|
+
region,
|
|
316
|
+
url: `${endpoint}/${options.bucket}`,
|
|
317
|
+
init: { method: "HEAD" }
|
|
318
|
+
});
|
|
319
|
+
return response.outcome;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
var aws = { credentials, s3Bucket };
|
|
323
|
+
|
|
324
|
+
// src/presets/databases.ts
|
|
325
|
+
var mongoPing = definePreset()({
|
|
326
|
+
kind: "mongodb.ping",
|
|
327
|
+
requiredFields: ["client"],
|
|
328
|
+
defaults: { slowMs: 500 },
|
|
329
|
+
derive: () => ({ name: "MongoDB", description: "`ping` command on the database" }),
|
|
330
|
+
check: ({ options, measure }) => measure(async () => {
|
|
331
|
+
const client = options.client;
|
|
332
|
+
const database = "command" in client ? client : client.db();
|
|
333
|
+
await database.command({ ping: 1 });
|
|
334
|
+
})
|
|
335
|
+
});
|
|
336
|
+
var mongodb = { ping: mongoPing };
|
|
337
|
+
var redisPing = definePreset()({
|
|
338
|
+
kind: "redis.ping",
|
|
339
|
+
requiredFields: ["client"],
|
|
340
|
+
defaults: { slowMs: 300 },
|
|
341
|
+
derive: () => ({ name: "Redis", description: "PING" }),
|
|
342
|
+
check: ({ options, measure }) => measure(async () => {
|
|
343
|
+
const client = options.client;
|
|
344
|
+
const reply = "ping" in client ? await client.ping() : "call" in client ? await client.call("PING") : await client.sendCommand(["PING"]);
|
|
345
|
+
return String(reply).toUpperCase() === "PONG" ? true : { status: "down", error: `Unexpected reply: ${String(reply)}` };
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
var redis = { ping: redisPing };
|
|
349
|
+
var postgresPing = definePreset()({
|
|
350
|
+
kind: "postgres.ping",
|
|
351
|
+
requiredFields: ["query"],
|
|
352
|
+
defaults: { slowMs: 1e3 },
|
|
353
|
+
derive: () => ({ name: "PostgreSQL", description: "Runs `select 1`" }),
|
|
354
|
+
check: ({ options, measure }) => measure(async () => {
|
|
355
|
+
await options.query("select 1");
|
|
356
|
+
})
|
|
357
|
+
});
|
|
358
|
+
var postgres = { ping: postgresPing };
|
|
359
|
+
|
|
360
|
+
// src/presets/providers/shared.ts
|
|
361
|
+
var bearer = (token) => ({ Authorization: `Bearer ${token}` });
|
|
362
|
+
var round = (value, digits = 1) => Math.round(value * 10 ** digits) / 10 ** digits;
|
|
363
|
+
function base64(text) {
|
|
364
|
+
const bytes = new TextEncoder().encode(text);
|
|
365
|
+
let binary = "";
|
|
366
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
367
|
+
return btoa(binary);
|
|
368
|
+
}
|
|
369
|
+
async function call(http, url, init, refine) {
|
|
370
|
+
const response = await http(url, init);
|
|
371
|
+
if (!response.ok || !refine) return response.outcome;
|
|
372
|
+
return { ...response.outcome, ...normalizeReturn(refine(response)) };
|
|
373
|
+
}
|
|
374
|
+
function quota({
|
|
375
|
+
label,
|
|
376
|
+
used,
|
|
377
|
+
limit,
|
|
378
|
+
unit,
|
|
379
|
+
resetsAt,
|
|
380
|
+
warnAbovePct,
|
|
381
|
+
warning
|
|
382
|
+
}) {
|
|
383
|
+
const pct = limit > 0 ? used / limit * 100 : 0;
|
|
384
|
+
const over = pct >= warnAbovePct;
|
|
385
|
+
return {
|
|
386
|
+
status: over ? "degraded" : "up",
|
|
387
|
+
error: over ? warning : void 0,
|
|
388
|
+
usage: [{ label, used, limit, unit, resetsAt }]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/presets/providers/ai.ts
|
|
393
|
+
var deepgram = definePreset()({
|
|
394
|
+
kind: "deepgram",
|
|
395
|
+
requiredSecrets: ["apiKey"],
|
|
396
|
+
derive: () => ({ name: "Deepgram", identity: "deepgram", description: "Lists projects, then reads the account balance" }),
|
|
397
|
+
check: async ({ options, secrets, http }) => {
|
|
398
|
+
const headers = { Authorization: `Token ${secrets.apiKey}` };
|
|
399
|
+
const projects = await http("https://api.deepgram.com/v1/projects", { headers });
|
|
400
|
+
if (!projects.ok) return projects.outcome;
|
|
401
|
+
const projectId = projects.json?.projects?.[0]?.project_id;
|
|
402
|
+
if (!projectId) return { ...projects.outcome, status: "degraded", error: "No project visible to this key" };
|
|
403
|
+
if (options.track?.balance === false) return projects.outcome;
|
|
404
|
+
const warnBelow = options.track?.balance?.warnBelow ?? 20;
|
|
405
|
+
const balances = await http(`https://api.deepgram.com/v1/projects/${projectId}/balances`, { headers });
|
|
406
|
+
const balance = balances.json?.balances?.[0];
|
|
407
|
+
if (!balances.ok || !balance) return projects.outcome;
|
|
408
|
+
const low = balance.amount < warnBelow;
|
|
409
|
+
return {
|
|
410
|
+
...projects.outcome,
|
|
411
|
+
latencyMs: Math.max(projects.latencyMs, balances.latencyMs),
|
|
412
|
+
status: low ? "degraded" : "up",
|
|
413
|
+
error: low ? "Low balance" : void 0,
|
|
414
|
+
metrics: { balance: `${round(balance.amount, 2)} ${balance.units}` }
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
var gladia = definePreset()({
|
|
419
|
+
kind: "gladia",
|
|
420
|
+
requiredSecrets: ["apiKey"],
|
|
421
|
+
derive: () => ({ name: "Gladia", identity: "gladia", description: "Lists live sessions (limit 1)" }),
|
|
422
|
+
check: ({ secrets, http }) => call(http, "https://api.gladia.io/v2/live?limit=1", { headers: { "x-gladia-key": secrets.apiKey } })
|
|
423
|
+
});
|
|
424
|
+
var speechmatics = definePreset()({
|
|
425
|
+
kind: "speechmatics",
|
|
426
|
+
requiredSecrets: ["apiKey"],
|
|
427
|
+
derive: () => ({
|
|
428
|
+
name: "Speechmatics",
|
|
429
|
+
identity: "speechmatics",
|
|
430
|
+
description: "Lists batch jobs (limit 1), validating the API key"
|
|
431
|
+
}),
|
|
432
|
+
check: ({ options, secrets, http }) => call(http, `${(options.baseUrl ?? "https://asr.api.speechmatics.com").replace(/\/+$/, "")}/v2/jobs?limit=1`, {
|
|
433
|
+
headers: bearer(secrets.apiKey)
|
|
434
|
+
})
|
|
435
|
+
});
|
|
436
|
+
var openai = definePreset()({
|
|
437
|
+
kind: "openai",
|
|
438
|
+
requiredSecrets: ["apiKey"],
|
|
439
|
+
derive: (options) => ({
|
|
440
|
+
name: "OpenAI",
|
|
441
|
+
identity: "openai",
|
|
442
|
+
description: options.track?.model ? `Checks access to ${options.track.model}` : "Lists models"
|
|
443
|
+
}),
|
|
444
|
+
check: ({ options, secrets, http }) => call(
|
|
445
|
+
http,
|
|
446
|
+
options.track?.model ? `https://api.openai.com/v1/models/${encodeURIComponent(options.track.model)}` : "https://api.openai.com/v1/models",
|
|
447
|
+
{
|
|
448
|
+
headers: {
|
|
449
|
+
...bearer(secrets.apiKey),
|
|
450
|
+
...options.organization ? { "OpenAI-Organization": options.organization } : {}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
});
|
|
455
|
+
var anthropic = definePreset()({
|
|
456
|
+
kind: "anthropic",
|
|
457
|
+
requiredSecrets: ["apiKey"],
|
|
458
|
+
derive: () => ({ name: "Anthropic", identity: "anthropic", description: "Lists models" }),
|
|
459
|
+
check: ({ secrets, http }) => call(http, "https://api.anthropic.com/v1/models?limit=1", {
|
|
460
|
+
headers: { "x-api-key": secrets.apiKey, "anthropic-version": "2023-06-01" }
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
var gemini = definePreset()({
|
|
464
|
+
kind: "gemini",
|
|
465
|
+
requiredSecrets: ["apiKey"],
|
|
466
|
+
derive: () => ({ name: "Google Gemini", identity: "gemini", description: "Lists models" }),
|
|
467
|
+
check: ({ secrets, http }) => call(http, "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1", {
|
|
468
|
+
headers: { "x-goog-api-key": secrets.apiKey }
|
|
469
|
+
})
|
|
470
|
+
});
|
|
471
|
+
var cambai = definePreset()({
|
|
472
|
+
kind: "cambai",
|
|
473
|
+
requiredSecrets: ["apiKey"],
|
|
474
|
+
derive: () => ({ name: "CAMB.AI", identity: "cambai", description: "Lists source languages" }),
|
|
475
|
+
check: ({ secrets, http }) => call(http, "https://client.camb.ai/apis/source-languages", { headers: { "x-api-key": secrets.apiKey } })
|
|
476
|
+
});
|
|
477
|
+
var elevenlabs = definePreset()({
|
|
478
|
+
kind: "elevenlabs",
|
|
479
|
+
requiredSecrets: ["apiKey"],
|
|
480
|
+
derive: () => ({ name: "ElevenLabs", identity: "elevenlabs", description: "Reads the subscription" }),
|
|
481
|
+
check: async ({ options, secrets, http }) => {
|
|
482
|
+
const res = await http("https://api.elevenlabs.io/v1/user/subscription", {
|
|
483
|
+
headers: { "xi-api-key": secrets.apiKey }
|
|
484
|
+
});
|
|
485
|
+
const body = res.json ?? {};
|
|
486
|
+
if (res.status === 401 && body.detail?.status === "missing_permissions") {
|
|
487
|
+
return { status: "up", latencyMs: res.latencyMs, httpStatus: 401, metrics: { key: "restricted" } };
|
|
488
|
+
}
|
|
489
|
+
if (!res.ok) return res.outcome;
|
|
490
|
+
const metrics = body.tier ? { tier: body.tier } : void 0;
|
|
491
|
+
const { character_count: used, character_limit: limit } = body;
|
|
492
|
+
if (typeof used !== "number" || !limit) return { ...res.outcome, metrics };
|
|
493
|
+
return {
|
|
494
|
+
...res.outcome,
|
|
495
|
+
...quota({
|
|
496
|
+
label: "Credits",
|
|
497
|
+
used,
|
|
498
|
+
limit,
|
|
499
|
+
unit: "credits",
|
|
500
|
+
// ElevenLabs gives the reset as epoch seconds
|
|
501
|
+
resetsAt: body.next_character_count_reset_unix ? body.next_character_count_reset_unix * 1e3 : void 0,
|
|
502
|
+
warnAbovePct: 100 - (options.track?.quota?.warnBelowPct ?? 10),
|
|
503
|
+
warning: "Credits almost exhausted"
|
|
504
|
+
}),
|
|
505
|
+
metrics
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// src/presets/providers/translation.ts
|
|
511
|
+
var deepl = definePreset()({
|
|
512
|
+
kind: "deepl",
|
|
513
|
+
requiredSecrets: ["apiKey"],
|
|
514
|
+
derive: () => ({ name: "DeepL", identity: "deepl", description: "Reads character usage for the billing period" }),
|
|
515
|
+
check: ({ options, secrets, http }) => {
|
|
516
|
+
const host = secrets.apiKey.endsWith(":fx") ? "api-free.deepl.com" : "api.deepl.com";
|
|
517
|
+
return call(http, `https://${host}/v2/usage`, { headers: { Authorization: `DeepL-Auth-Key ${secrets.apiKey}` } }, (res) => {
|
|
518
|
+
const { character_count: used, character_limit: limit } = res.json ?? {};
|
|
519
|
+
if (typeof used !== "number" || !limit) return void 0;
|
|
520
|
+
return quota({
|
|
521
|
+
label: "Characters",
|
|
522
|
+
used,
|
|
523
|
+
limit,
|
|
524
|
+
unit: "characters",
|
|
525
|
+
warnAbovePct: options.track?.quota?.warnAbovePct ?? 90,
|
|
526
|
+
warning: "Character quota almost exhausted"
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
var azureTranslator = definePreset()({
|
|
532
|
+
kind: "azure.translator",
|
|
533
|
+
requiredSecrets: ["apiKey"],
|
|
534
|
+
derive: () => ({ name: "Azure Translator", identity: "azure.translator", description: "Translates a single character" }),
|
|
535
|
+
check: ({ options, secrets, http }) => call(http, "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=fr", {
|
|
536
|
+
method: "POST",
|
|
537
|
+
headers: {
|
|
538
|
+
"Ocp-Apim-Subscription-Key": secrets.apiKey,
|
|
539
|
+
"Ocp-Apim-Subscription-Region": options.region ?? "westeurope",
|
|
540
|
+
"content-type": "application/json"
|
|
541
|
+
},
|
|
542
|
+
body: JSON.stringify([{ Text: "a" }])
|
|
543
|
+
})
|
|
544
|
+
});
|
|
545
|
+
var googleTranslate = definePreset()({
|
|
546
|
+
kind: "google.translate",
|
|
547
|
+
requiredSecrets: ["serviceAccount"],
|
|
548
|
+
derive: () => ({
|
|
549
|
+
name: "Google Translate",
|
|
550
|
+
identity: "google.translate",
|
|
551
|
+
description: "Lists supported languages with Cloud Translation v3"
|
|
552
|
+
}),
|
|
553
|
+
check: async ({ options, secrets, http, now }) => {
|
|
554
|
+
const account = parseServiceAccount(secrets.serviceAccount);
|
|
555
|
+
if (!account?.project_id) {
|
|
556
|
+
return { status: "down", probed: false, error: "The service account secret is not a service-account JSON key" };
|
|
557
|
+
}
|
|
558
|
+
const token = await googleServiceAccountToken({
|
|
559
|
+
account,
|
|
560
|
+
scope: "https://www.googleapis.com/auth/cloud-translation",
|
|
561
|
+
http,
|
|
562
|
+
now
|
|
563
|
+
});
|
|
564
|
+
return call(
|
|
565
|
+
http,
|
|
566
|
+
`https://translation.googleapis.com/v3/projects/${account.project_id}/locations/${options.location ?? "global"}/supportedLanguages`,
|
|
567
|
+
{ headers: bearer(token) },
|
|
568
|
+
(res) => {
|
|
569
|
+
const languages = res.json?.languages;
|
|
570
|
+
return languages ? { status: "up", metrics: { languages: languages.length } } : void 0;
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// src/presets/providers/media.ts
|
|
577
|
+
var mux = definePreset()({
|
|
578
|
+
kind: "mux",
|
|
579
|
+
requiredSecrets: ["tokenId", "tokenSecret"],
|
|
580
|
+
derive: () => ({ name: "Mux", identity: "mux", description: "Lists live streams (limit 1)" }),
|
|
581
|
+
check: ({ secrets, http }) => call(http, "https://api.mux.com/video/v1/live-streams?limit=1", {
|
|
582
|
+
headers: { Authorization: `Basic ${base64(`${secrets.tokenId}:${secrets.tokenSecret}`)}` }
|
|
583
|
+
})
|
|
584
|
+
});
|
|
585
|
+
var cloudflareStream = definePreset()({
|
|
586
|
+
kind: "cloudflare.stream",
|
|
587
|
+
requiredFields: ["accountId"],
|
|
588
|
+
requiredSecrets: ["token"],
|
|
589
|
+
derive: () => ({
|
|
590
|
+
name: "Cloudflare Stream",
|
|
591
|
+
identity: "cloudflare.stream",
|
|
592
|
+
description: "Lists videos (limit 1) and reads storage usage against the plan limit"
|
|
593
|
+
}),
|
|
594
|
+
check: async ({ options, secrets, http, combine }) => {
|
|
595
|
+
const base = `https://api.cloudflare.com/client/v4/accounts/${options.accountId}/stream`;
|
|
596
|
+
const init = { headers: bearer(secrets.token) };
|
|
597
|
+
const rejected = (json) => json?.success === false ? { status: "down", error: "Cloudflare API reported success=false" } : void 0;
|
|
598
|
+
const videos = call(http, `${base}?limit=1`, init, (res) => rejected(res.json));
|
|
599
|
+
if (options.track?.storage === false) return videos;
|
|
600
|
+
const warnAbovePct = options.track?.storage?.warnAbovePct ?? 90;
|
|
601
|
+
const storage = call(http, `${base}/storage-usage`, init, (res) => {
|
|
602
|
+
const failed = rejected(res.json);
|
|
603
|
+
if (failed) return failed;
|
|
604
|
+
const usage = res.json?.result;
|
|
605
|
+
if (!usage?.totalStorageMinutesLimit) return void 0;
|
|
606
|
+
return {
|
|
607
|
+
...quota({
|
|
608
|
+
label: "Video storage",
|
|
609
|
+
used: Math.round(usage.totalStorageMinutes ?? 0),
|
|
610
|
+
limit: usage.totalStorageMinutesLimit,
|
|
611
|
+
unit: "min",
|
|
612
|
+
warnAbovePct,
|
|
613
|
+
warning: "Stream storage almost full"
|
|
614
|
+
}),
|
|
615
|
+
metrics: { videos: usage.videoCount ?? 0 }
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
return combine(await Promise.all([videos, storage]));
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// src/presets/providers/services.ts
|
|
623
|
+
var mailersend = definePreset()({
|
|
624
|
+
kind: "mailersend",
|
|
625
|
+
requiredSecrets: ["apiKey"],
|
|
626
|
+
derive: () => ({ name: "MailerSend", identity: "mailersend", description: "Reads the API quota" }),
|
|
627
|
+
check: ({ options, secrets, http }) => call(http, "https://api.mailersend.com/v1/api-quota", { headers: bearer(secrets.apiKey) }, (res) => {
|
|
628
|
+
const { quota: limit, remaining, reset } = res.json ?? {};
|
|
629
|
+
if (!limit || typeof remaining !== "number") return void 0;
|
|
630
|
+
const resetsAt = reset ? Date.parse(reset) : NaN;
|
|
631
|
+
return quota({
|
|
632
|
+
label: "API requests",
|
|
633
|
+
used: limit - remaining,
|
|
634
|
+
limit,
|
|
635
|
+
unit: "requests",
|
|
636
|
+
resetsAt: Number.isNaN(resetsAt) ? void 0 : resetsAt,
|
|
637
|
+
warnAbovePct: 100 - (options.track?.quota?.warnBelowPct ?? 10),
|
|
638
|
+
warning: "API quota almost exhausted"
|
|
639
|
+
});
|
|
640
|
+
})
|
|
641
|
+
});
|
|
642
|
+
var github = definePreset()({
|
|
643
|
+
kind: "github",
|
|
644
|
+
requiredSecrets: ["token"],
|
|
645
|
+
derive: (options) => ({
|
|
646
|
+
name: "GitHub",
|
|
647
|
+
identity: options.track?.repo ? `github:${options.track.repo}` : "github",
|
|
648
|
+
description: options.track?.repo ? `Reads the API rate limit and the releases of ${options.track.repo}` : "Reads the API rate limit"
|
|
649
|
+
}),
|
|
650
|
+
check: async ({ options, secrets, http, combine }) => {
|
|
651
|
+
const headers = { ...bearer(secrets.token), "user-agent": "sermon-js", accept: "application/vnd.github+json" };
|
|
652
|
+
const limit = call(http, "https://api.github.com/rate_limit", { headers }, (res) => {
|
|
653
|
+
const core = res.json?.resources?.core;
|
|
654
|
+
if (!core) return void 0;
|
|
655
|
+
return quota({
|
|
656
|
+
label: "API requests this hour",
|
|
657
|
+
used: core.limit - core.remaining,
|
|
658
|
+
limit: core.limit,
|
|
659
|
+
unit: "requests",
|
|
660
|
+
// GitHub gives the reset as epoch seconds
|
|
661
|
+
resetsAt: core.reset * 1e3,
|
|
662
|
+
warnAbovePct: 100 - (options.track?.rateLimit?.warnBelowPct ?? 10),
|
|
663
|
+
warning: "Rate limit almost exhausted"
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
if (!options.track?.repo) return limit;
|
|
667
|
+
const repo = call(http, `https://api.github.com/repos/${options.track.repo}/releases?per_page=1`, { headers });
|
|
668
|
+
return combine(await Promise.all([limit, repo]));
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
var revolut = definePreset()({
|
|
672
|
+
kind: "revolut",
|
|
673
|
+
requiredSecrets: ["apiKey"],
|
|
674
|
+
derive: () => ({ name: "Revolut Merchant", identity: "revolut", description: "Lists orders (limit 1)" }),
|
|
675
|
+
check: ({ options, secrets, http }) => {
|
|
676
|
+
const host = options.environment === "sandbox" ? "sandbox-merchant.revolut.com" : "merchant.revolut.com";
|
|
677
|
+
return call(http, `https://${host}/api/1.0/orders?limit=1`, {
|
|
678
|
+
headers: { ...bearer(secrets.apiKey), "Revolut-Api-Version": "2023-09-01" }
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
var attendee = definePreset()({
|
|
683
|
+
kind: "attendee",
|
|
684
|
+
requiredSecrets: ["apiKey"],
|
|
685
|
+
derive: () => ({ name: "Attendee", identity: "attendee", description: "Lists bots" }),
|
|
686
|
+
check: ({ options, secrets, http }) => call(http, `${(options.baseUrl || "https://app.attendee.dev").replace(/\/+$/, "")}/api/v1/bots?page_size=1`, {
|
|
687
|
+
headers: { Authorization: `Token ${secrets.apiKey}` }
|
|
688
|
+
})
|
|
689
|
+
});
|
|
690
|
+
var axiom = definePreset()({
|
|
691
|
+
kind: "axiom",
|
|
692
|
+
requiredFields: ["dataset"],
|
|
693
|
+
requiredSecrets: ["token"],
|
|
694
|
+
derive: () => ({ name: "Axiom", identity: "axiom", description: "Ingests an empty batch into the logs dataset" }),
|
|
695
|
+
check: ({ options, secrets, http }) => call(
|
|
696
|
+
http,
|
|
697
|
+
options.edge ? `https://${options.edge}/v1/ingest/${options.dataset}` : `https://api.axiom.co/v1/datasets/${options.dataset}/ingest`,
|
|
698
|
+
{ method: "POST", headers: { ...bearer(secrets.token), "content-type": "application/json" }, body: "[]" }
|
|
699
|
+
)
|
|
700
|
+
});
|
|
701
|
+
var upstashRedis = definePreset()({
|
|
702
|
+
kind: "upstash.redis",
|
|
703
|
+
requiredFields: ["url"],
|
|
704
|
+
requiredSecrets: ["token"],
|
|
705
|
+
defaults: { slowMs: 500 },
|
|
706
|
+
derive: () => ({ name: "Redis", description: "PING through the REST API" }),
|
|
707
|
+
check: ({ options, secrets, http }) => call(
|
|
708
|
+
http,
|
|
709
|
+
options.url.replace(/\/+$/, ""),
|
|
710
|
+
{
|
|
711
|
+
method: "POST",
|
|
712
|
+
headers: { ...bearer(secrets.token), "content-type": "application/json" },
|
|
713
|
+
body: '["PING"]'
|
|
714
|
+
},
|
|
715
|
+
(res) => res.json?.result === "PONG" ? void 0 : { status: "down", error: "Unexpected reply to PING" }
|
|
716
|
+
)
|
|
717
|
+
});
|
|
718
|
+
var statuspage = definePreset()({
|
|
719
|
+
kind: "statuspage",
|
|
720
|
+
requiredFields: ["url"],
|
|
721
|
+
derive: (options) => {
|
|
722
|
+
let host = options.url;
|
|
723
|
+
try {
|
|
724
|
+
host = new URL(options.url).host;
|
|
725
|
+
} catch {
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
name: host,
|
|
729
|
+
identity: `statuspage:${host}`,
|
|
730
|
+
description: "Status reported by the provider's public status page"
|
|
731
|
+
};
|
|
732
|
+
},
|
|
733
|
+
check: ({ options, http }) => call(http, `${options.url.replace(/\/+$/, "")}/api/v2/status.json`, void 0, (res) => {
|
|
734
|
+
const { indicator, description } = res.json?.status ?? {};
|
|
735
|
+
if (!indicator) return { status: "degraded", error: "Not a Statuspage answer" };
|
|
736
|
+
const status = indicator === "none" ? "up" : indicator === "minor" ? "degraded" : "down";
|
|
737
|
+
return { status, error: status === "up" ? void 0 : description };
|
|
738
|
+
})
|
|
739
|
+
});
|
|
740
|
+
var resend = definePreset()({
|
|
741
|
+
kind: "resend",
|
|
742
|
+
requiredSecrets: ["apiKey"],
|
|
743
|
+
derive: () => ({ name: "Resend", identity: "resend", description: "Lists domains" }),
|
|
744
|
+
check: async ({ secrets, http }) => {
|
|
745
|
+
const res = await http("https://api.resend.com/domains", { headers: bearer(secrets.apiKey) });
|
|
746
|
+
const body = res.json ?? {};
|
|
747
|
+
if (res.status === 401 && body.name === "restricted_api_key") {
|
|
748
|
+
return { status: "up", latencyMs: res.latencyMs, httpStatus: 401, metrics: { key: "sending only" } };
|
|
749
|
+
}
|
|
750
|
+
if (!res.ok || !Array.isArray(body.data)) return res.outcome;
|
|
751
|
+
const verified = body.data.filter((domain) => domain.status === "verified").length;
|
|
752
|
+
const unverified = body.data.length > 0 && verified === 0;
|
|
753
|
+
return {
|
|
754
|
+
...res.outcome,
|
|
755
|
+
status: unverified ? "degraded" : "up",
|
|
756
|
+
error: unverified ? "No verified domain" : void 0,
|
|
757
|
+
metrics: { domains: body.data.length, verified }
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
var meilisearch = definePreset()({
|
|
762
|
+
kind: "meilisearch",
|
|
763
|
+
requiredFields: ["host"],
|
|
764
|
+
requiredSecrets: ["apiKey"],
|
|
765
|
+
derive: (options) => ({
|
|
766
|
+
name: "Meilisearch",
|
|
767
|
+
identity: options.index ? `meilisearch:${options.index}` : "meilisearch",
|
|
768
|
+
description: options.index ? `Reads the health and the ${options.index} index` : "Reads the health and lists indexes"
|
|
769
|
+
}),
|
|
770
|
+
check: async ({ options, secrets, http, combine }) => {
|
|
771
|
+
const host = options.host.replace(/\/+$/, "");
|
|
772
|
+
const headers = bearer(secrets.apiKey);
|
|
773
|
+
const index = options.index ? `/indexes/${encodeURIComponent(options.index)}` : void 0;
|
|
774
|
+
const health = call(
|
|
775
|
+
http,
|
|
776
|
+
`${host}/health`,
|
|
777
|
+
void 0,
|
|
778
|
+
(res2) => res2.json?.status === "available" ? void 0 : { status: "down", error: "Not available" }
|
|
779
|
+
);
|
|
780
|
+
const access = http(`${host}${index ?? "/indexes?limit=1"}`, { headers }).then((res2) => {
|
|
781
|
+
if (res2.status !== 404 || !options.index) return res2.outcome;
|
|
782
|
+
return {
|
|
783
|
+
status: "degraded",
|
|
784
|
+
latencyMs: res2.latencyMs,
|
|
785
|
+
httpStatus: 404,
|
|
786
|
+
error: `Index ${options.index} not found`
|
|
787
|
+
};
|
|
788
|
+
});
|
|
789
|
+
const stats = http(`${host}${index ?? ""}/stats`, { headers });
|
|
790
|
+
const outcome = combine(await Promise.all([health, access]));
|
|
791
|
+
const res = await stats;
|
|
792
|
+
if (!res.ok) return outcome;
|
|
793
|
+
const body = res.json ?? {};
|
|
794
|
+
const documents = body.numberOfDocuments ?? Object.values(body.indexes ?? {}).reduce((sum, entry) => sum + (entry.numberOfDocuments ?? 0), 0);
|
|
795
|
+
return { ...outcome, metrics: { ...outcome.metrics, documents, ...body.isIndexing ? { indexing: "yes" } : {} } };
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// src/presets/providers/pusher.ts
|
|
800
|
+
var MAX_CHANNELS = 500;
|
|
801
|
+
var CONCURRENCY = 10;
|
|
802
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
803
|
+
async function hmacSha256Hex(secret, message) {
|
|
804
|
+
const encoder = new TextEncoder();
|
|
805
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
|
|
806
|
+
"sign"
|
|
807
|
+
]);
|
|
808
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
809
|
+
return [...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
810
|
+
}
|
|
811
|
+
async function signPusherRequest({
|
|
812
|
+
appId,
|
|
813
|
+
key,
|
|
814
|
+
secret,
|
|
815
|
+
path,
|
|
816
|
+
params = {},
|
|
817
|
+
timestamp = Math.floor(Date.now() / 1e3)
|
|
818
|
+
}) {
|
|
819
|
+
const fullPath = `/apps/${appId}${path}`;
|
|
820
|
+
const search = new URLSearchParams({
|
|
821
|
+
...params,
|
|
822
|
+
auth_key: key,
|
|
823
|
+
auth_timestamp: String(timestamp),
|
|
824
|
+
auth_version: "1.0"
|
|
825
|
+
});
|
|
826
|
+
search.sort();
|
|
827
|
+
const query = decodeURIComponent(search.toString());
|
|
828
|
+
const signature = await hmacSha256Hex(secret, ["GET", fullPath, query].join("\n"));
|
|
829
|
+
return `${fullPath}?${query}&auth_signature=${signature}`;
|
|
830
|
+
}
|
|
831
|
+
function baseUrl(options) {
|
|
832
|
+
const host = options.host || `api-${options.cluster || "mt1"}.pusher.com`;
|
|
833
|
+
const port = options.port === void 0 || options.port === "" ? void 0 : String(options.port);
|
|
834
|
+
const tls = options.tls ?? (!port || port === "443");
|
|
835
|
+
const defaultPort = tls ? "443" : "80";
|
|
836
|
+
return `${tls ? "https" : "http"}://${host}${port && port !== defaultPort ? `:${port}` : ""}`;
|
|
837
|
+
}
|
|
838
|
+
async function mapLimit(items, limit, fn) {
|
|
839
|
+
const results = new Array(items.length);
|
|
840
|
+
let next = 0;
|
|
841
|
+
await Promise.all(
|
|
842
|
+
Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
843
|
+
while (next < items.length) {
|
|
844
|
+
const index = next++;
|
|
845
|
+
results[index] = await fn(items[index]);
|
|
846
|
+
}
|
|
847
|
+
})
|
|
848
|
+
);
|
|
849
|
+
return results;
|
|
850
|
+
}
|
|
851
|
+
var utcDay = (now) => new Date(now).toISOString().slice(0, 10);
|
|
852
|
+
var pusher = definePreset()({
|
|
853
|
+
kind: "pusher",
|
|
854
|
+
requiredFields: ["appId"],
|
|
855
|
+
requiredSecrets: ["key", "secret"],
|
|
856
|
+
defaults: { slowMs: 1e3 },
|
|
857
|
+
derive: (options) => ({
|
|
858
|
+
name: "Pusher",
|
|
859
|
+
identity: `pusher:${options.appId ?? ""}`,
|
|
860
|
+
description: "Lists occupied channels and estimates connections from their subscription counts"
|
|
861
|
+
}),
|
|
862
|
+
check: async ({ options, secrets, http, cache, now }) => {
|
|
863
|
+
const get = async (path, params) => http(
|
|
864
|
+
`${baseUrl(options)}${await signPusherRequest({
|
|
865
|
+
appId: options.appId,
|
|
866
|
+
key: secrets.key,
|
|
867
|
+
secret: secrets.secret,
|
|
868
|
+
path,
|
|
869
|
+
params,
|
|
870
|
+
timestamp: Math.floor(now / 1e3)
|
|
871
|
+
})}`
|
|
872
|
+
);
|
|
873
|
+
const list = await get("/channels");
|
|
874
|
+
if (!list.ok) return list.outcome;
|
|
875
|
+
const names = Object.keys(list.json?.channels ?? {});
|
|
876
|
+
const queried = names.slice(0, MAX_CHANNELS);
|
|
877
|
+
let failed = 0;
|
|
878
|
+
let countingDisabled = false;
|
|
879
|
+
const counts = await mapLimit(queried, CONCURRENCY, async (name) => {
|
|
880
|
+
const res = await get(`/channels/${encodeURIComponent(name)}`, { info: "subscription_count" });
|
|
881
|
+
const count = res.json?.subscription_count;
|
|
882
|
+
if (!res.ok) failed += 1;
|
|
883
|
+
else if (typeof count !== "number") countingDisabled = true;
|
|
884
|
+
return typeof count === "number" ? count : 0;
|
|
885
|
+
});
|
|
886
|
+
const connections = counts.reduce((sum, count) => sum + count, 0);
|
|
887
|
+
const signedInUsers = queried.reduce(
|
|
888
|
+
(sum, name, index) => name.startsWith("#server-to-user-") ? sum + (counts[index] ?? 0) : sum,
|
|
889
|
+
0
|
|
890
|
+
);
|
|
891
|
+
const peakKey = `peak:${utcDay(now)}`;
|
|
892
|
+
const peak = Math.max(connections, Number(await cache.get(peakKey)) || 0);
|
|
893
|
+
await cache.set(peakKey, String(peak), 8 * DAY_MS);
|
|
894
|
+
const metrics = { channels: names.length, signedInUsers };
|
|
895
|
+
if (names.length > MAX_CHANNELS) metrics.channelsCounted = MAX_CHANNELS;
|
|
896
|
+
const max = options.track?.connections?.max;
|
|
897
|
+
const warnAbovePct = options.track?.connections?.warnAbovePct ?? 90;
|
|
898
|
+
const usage = [];
|
|
899
|
+
if (max) {
|
|
900
|
+
usage.push(
|
|
901
|
+
{ label: "Connections now (estimate)", used: connections, limit: max, unit: "connections" },
|
|
902
|
+
{
|
|
903
|
+
label: "Peak connections today (UTC)",
|
|
904
|
+
used: peak,
|
|
905
|
+
limit: max,
|
|
906
|
+
unit: "connections",
|
|
907
|
+
resetsAt: Date.parse(`${utcDay(now)}T00:00:00Z`) + DAY_MS
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
} else {
|
|
911
|
+
metrics.connections = connections;
|
|
912
|
+
metrics.peakToday = peak;
|
|
913
|
+
}
|
|
914
|
+
const warnings = [];
|
|
915
|
+
if (max && peak / max * 100 >= warnAbovePct) warnings.push("Connections close to the plan limit");
|
|
916
|
+
if (countingDisabled) warnings.push("Subscription counting is disabled in the Pusher app settings");
|
|
917
|
+
const notes = failed > 0 ? [`${failed} channel(s) could not be counted`] : [];
|
|
918
|
+
return {
|
|
919
|
+
status: warnings.length ? "degraded" : "up",
|
|
920
|
+
latencyMs: list.latencyMs,
|
|
921
|
+
httpStatus: list.status,
|
|
922
|
+
error: [...warnings, ...notes].join("; ") || void 0,
|
|
923
|
+
metrics,
|
|
924
|
+
usage: usage.length ? usage : void 0
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// src/presets/providers/vercel.ts
|
|
930
|
+
var aiGateway = definePreset()({
|
|
931
|
+
kind: "vercel.aiGateway",
|
|
932
|
+
requiredSecrets: ["apiKey"],
|
|
933
|
+
derive: (options) => ({
|
|
934
|
+
name: "Vercel AI Gateway",
|
|
935
|
+
identity: "vercel.aiGateway",
|
|
936
|
+
description: options.track?.credits === false ? "Lists models" : "Reads the credit balance"
|
|
937
|
+
}),
|
|
938
|
+
check: ({ options, secrets, http }) => {
|
|
939
|
+
const headers = bearer(secrets.apiKey);
|
|
940
|
+
if (options.track?.credits === false) return call(http, "https://ai-gateway.vercel.sh/v1/models", { headers });
|
|
941
|
+
const warnBelow = options.track?.credits?.warnBelow ?? 5;
|
|
942
|
+
return call(http, "https://ai-gateway.vercel.sh/v1/credits", { headers }, (res) => {
|
|
943
|
+
const body = res.json ?? {};
|
|
944
|
+
const balance = Number(body.balance);
|
|
945
|
+
if (body.balance === void 0 || !Number.isFinite(balance)) return void 0;
|
|
946
|
+
const used = Number(body.total_used);
|
|
947
|
+
const low = balance < warnBelow;
|
|
948
|
+
return {
|
|
949
|
+
status: low ? "degraded" : "up",
|
|
950
|
+
error: low ? "Low credit balance" : void 0,
|
|
951
|
+
metrics: {
|
|
952
|
+
balance: `${round(balance, 2)} USD`,
|
|
953
|
+
...body.total_used !== void 0 && Number.isFinite(used) ? { used: `${round(used, 2)} USD` } : {}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
var blob = definePreset()({
|
|
960
|
+
kind: "vercel.blob",
|
|
961
|
+
requiredSecrets: ["token"],
|
|
962
|
+
derive: () => ({ name: "Vercel Blob", identity: "vercel.blob", description: "Lists one blob of the store" }),
|
|
963
|
+
check: ({ secrets, http }) => call(http, "https://vercel.com/api/blob?limit=1", {
|
|
964
|
+
headers: { ...bearer(secrets.token), "x-api-version": "12" }
|
|
965
|
+
})
|
|
966
|
+
});
|
|
967
|
+
var vercel = { aiGateway, blob };
|
|
968
|
+
|
|
969
|
+
export { anthropic, attendee, aws, axiom, azureTranslator, cambai, clearGoogleTokens, cloudflareStream, crashReason, deepgram, deepl, elevenlabs, fly, gcp, gemini, github, gladia, googleServiceAccountToken, googleTranslate, mailersend, meilisearch, mongodb, mux, openai, parseServiceAccount, postgres, pusher, redis, resend, revolut, signGoogleAssertion, signPusherRequest, speechmatics, statuspage, upstashRedis, vercel };
|
|
970
|
+
//# sourceMappingURL=index.js.map
|
|
971
|
+
//# sourceMappingURL=index.js.map
|