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,146 @@
|
|
|
1
|
+
import { d as ResolvedService, R as ResolvedNode, e as ServiceNode } from './config-w1tm1bdu.js';
|
|
2
|
+
import { o as Status$1, k as ScoreLabel, T as Trigger, g as CheckResult, p as StatusSnapshot, H as HistoryRange, i as HistoryPayload } from './types-CctvUAs2.js';
|
|
3
|
+
import { C as CheckSample, L as LockResult, S as StatusStore } from './store-_GuzSP0z.js';
|
|
4
|
+
|
|
5
|
+
/** A number of milliseconds, or a string like "30s", "5m", "1h", "7d" */
|
|
6
|
+
type Duration = number | `${number}${"ms" | "s" | "m" | "h" | "d"}`;
|
|
7
|
+
declare function toMs(duration: Duration): number;
|
|
8
|
+
|
|
9
|
+
type Scoring = {
|
|
10
|
+
/** How much more a critical service weighs than a normal one (default 3) */
|
|
11
|
+
criticalWeight: number;
|
|
12
|
+
/** Value of a degraded service between 0 and 1 (default 0.6) */
|
|
13
|
+
degradedValue: number;
|
|
14
|
+
thresholds: {
|
|
15
|
+
/** Score from which the system is "operational" (default 98) */
|
|
16
|
+
operational: number;
|
|
17
|
+
/** Score from which the system is "degraded" rather than "outage" (default 85) */
|
|
18
|
+
degraded: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
declare const DEFAULT_SCORING: Scoring;
|
|
22
|
+
type ScoringInput = {
|
|
23
|
+
criticalWeight?: number;
|
|
24
|
+
degradedValue?: number;
|
|
25
|
+
thresholds?: Partial<Scoring["thresholds"]>;
|
|
26
|
+
};
|
|
27
|
+
declare function labelForScore(score: number | null, scoring?: Scoring): ScoreLabel;
|
|
28
|
+
/**
|
|
29
|
+
* Weighted share of healthy services, 0–100. Sleeping services are expected (scale to zero)
|
|
30
|
+
* and count as healthy; unconfigured ones are left out. Any critical service being down
|
|
31
|
+
* forces the "outage" label, whatever the number.
|
|
32
|
+
*/
|
|
33
|
+
declare function computeScore({ results, scoring, }: {
|
|
34
|
+
results: Array<{
|
|
35
|
+
status: Status$1;
|
|
36
|
+
critical: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
scoring?: Scoring;
|
|
39
|
+
}): {
|
|
40
|
+
score: number | null;
|
|
41
|
+
label: ScoreLabel;
|
|
42
|
+
counts: Record<Status$1, number>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type StatusConfig = {
|
|
46
|
+
/** What to supervise: services built with presets or `custom()`, optionally nested in `group()`s */
|
|
47
|
+
services: ServiceNode[];
|
|
48
|
+
/** Where history is kept. Defaults to memory, which lasts as long as the process. */
|
|
49
|
+
store?: StatusStore;
|
|
50
|
+
/** How often a full run is due (default "5m"). Used by `tick()`, the scheduler and staleness. */
|
|
51
|
+
interval?: Duration;
|
|
52
|
+
/** How long history is kept (default "7d") */
|
|
53
|
+
retention?: Duration;
|
|
54
|
+
scoring?: ScoringInput;
|
|
55
|
+
/**
|
|
56
|
+
* Never quote what a service replied in public errors: a failed HTTP answer is reported by
|
|
57
|
+
* its status code only ("HTTP 500"). Turn it on when a checked service could answer with
|
|
58
|
+
* something sensitive, such as a debug page. Default false. Errors returned by `custom()`
|
|
59
|
+
* checks are yours and are kept (redacted) either way.
|
|
60
|
+
*/
|
|
61
|
+
hideResponseBodies?: boolean;
|
|
62
|
+
/** Receives configuration warnings (default: `console.warn`) */
|
|
63
|
+
onWarning?: (message: string) => void;
|
|
64
|
+
/** Override `fetch` (tests, custom agents) */
|
|
65
|
+
fetch?: typeof fetch;
|
|
66
|
+
/** Override the clock (tests) */
|
|
67
|
+
now?: () => number;
|
|
68
|
+
};
|
|
69
|
+
type RunReport = {
|
|
70
|
+
at: number;
|
|
71
|
+
trigger: Trigger;
|
|
72
|
+
/** True when every service was checked */
|
|
73
|
+
full: boolean;
|
|
74
|
+
score: number | null;
|
|
75
|
+
label: ScoreLabel;
|
|
76
|
+
results: CheckResult[];
|
|
77
|
+
};
|
|
78
|
+
type ServiceHistory = {
|
|
79
|
+
schemaVersion: 1;
|
|
80
|
+
key: string;
|
|
81
|
+
range: HistoryRange;
|
|
82
|
+
samples: CheckSample[];
|
|
83
|
+
};
|
|
84
|
+
type TickResult = {
|
|
85
|
+
ran: true;
|
|
86
|
+
report: RunReport;
|
|
87
|
+
} | {
|
|
88
|
+
ran: false;
|
|
89
|
+
/** "not-due": the last full run is recent enough. "locked": another tick is running. */
|
|
90
|
+
reason: "not-due" | "locked";
|
|
91
|
+
/** When the next full run is due (ms epoch) */
|
|
92
|
+
nextDueAt: number;
|
|
93
|
+
};
|
|
94
|
+
type Status = {
|
|
95
|
+
/** Check every service (or only `keys`) now, and record the results */
|
|
96
|
+
run(options?: {
|
|
97
|
+
keys?: string[];
|
|
98
|
+
trigger?: Trigger;
|
|
99
|
+
}): Promise<RunReport>;
|
|
100
|
+
/**
|
|
101
|
+
* Run every service only if a full run is due (the last one is older than `interval`), and
|
|
102
|
+
* never twice at once. Safe to call as often as you like, from any number of triggers:
|
|
103
|
+
* a cron, a platform scheduler, a timer, a page view.
|
|
104
|
+
*/
|
|
105
|
+
tick(options?: {
|
|
106
|
+
force?: boolean;
|
|
107
|
+
}): Promise<TickResult>;
|
|
108
|
+
/** Everything a status page needs: the tree with latest results, statistics and the score */
|
|
109
|
+
snapshot(): Promise<StatusSnapshot>;
|
|
110
|
+
/** Score history in fixed buckets */
|
|
111
|
+
history(options?: {
|
|
112
|
+
range?: HistoryRange;
|
|
113
|
+
}): Promise<HistoryPayload>;
|
|
114
|
+
/** Raw samples of one service */
|
|
115
|
+
serviceHistory(key: string, options?: {
|
|
116
|
+
range?: HistoryRange;
|
|
117
|
+
}): Promise<ServiceHistory>;
|
|
118
|
+
/**
|
|
119
|
+
* Remove every configured secret (and anything shaped like a credential) from a text.
|
|
120
|
+
* Use it before logging errors that come from checks, stores or drivers.
|
|
121
|
+
*/
|
|
122
|
+
redact(text: string): string;
|
|
123
|
+
/** Whether a service with this key exists */
|
|
124
|
+
has(key: string): boolean;
|
|
125
|
+
/** Take a named lock for `ttlMs` (the store's when it has one, otherwise in-process) */
|
|
126
|
+
lock(name: string, ttlMs: number): Promise<LockResult>;
|
|
127
|
+
/** When the last full run happened, if any within the retention window */
|
|
128
|
+
lastFullRun(): Promise<{
|
|
129
|
+
at: number;
|
|
130
|
+
trigger: Trigger;
|
|
131
|
+
} | null>;
|
|
132
|
+
readonly services: ReadonlyMap<string, ResolvedService>;
|
|
133
|
+
readonly tree: readonly ResolvedNode[];
|
|
134
|
+
readonly store: StatusStore;
|
|
135
|
+
readonly intervalMs: number;
|
|
136
|
+
readonly retentionMs: number;
|
|
137
|
+
readonly scoring: Scoring;
|
|
138
|
+
readonly warnings: readonly string[];
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Describe what to supervise and get a status engine: run checks, read snapshots and history.
|
|
142
|
+
* Nothing here depends on a host or a framework; see "sermon-js/api" to expose it over HTTP.
|
|
143
|
+
*/
|
|
144
|
+
declare function defineStatus(config: StatusConfig): Status;
|
|
145
|
+
|
|
146
|
+
export { type Duration as D, type RunReport as R, type Status as S, type TickResult as T, type Scoring as a, DEFAULT_SCORING as b, type ScoringInput as c, type ServiceHistory as d, type StatusConfig as e, computeScore as f, defineStatus as g, labelForScore as l, toMs as t };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { o as Status, T as Trigger, k as ScoreLabel, g as CheckResult } from './types-CctvUAs2.cjs';
|
|
2
|
+
|
|
3
|
+
/** One entry per run: drives the score history */
|
|
4
|
+
type RunSummary = {
|
|
5
|
+
at: number;
|
|
6
|
+
trigger: Trigger;
|
|
7
|
+
/** True when every service was checked (single-service runs are partial) */
|
|
8
|
+
full: boolean;
|
|
9
|
+
score: number | null;
|
|
10
|
+
label: ScoreLabel;
|
|
11
|
+
/** Only services that were degraded or down: key → status */
|
|
12
|
+
failing: Record<string, Status>;
|
|
13
|
+
};
|
|
14
|
+
/** One entry per service per run: drives uptime, latency percentiles and sparklines */
|
|
15
|
+
type CheckSample = {
|
|
16
|
+
key: string;
|
|
17
|
+
at: number;
|
|
18
|
+
status: Status;
|
|
19
|
+
latencyMs?: number;
|
|
20
|
+
httpStatus?: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
/** Per instance (e.g. region): key → [status, latency] */
|
|
23
|
+
instances?: Record<string, [Status, number?]>;
|
|
24
|
+
};
|
|
25
|
+
type TimeRange = {
|
|
26
|
+
since: number;
|
|
27
|
+
until: number;
|
|
28
|
+
};
|
|
29
|
+
type LockResult = {
|
|
30
|
+
ok: true;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
retryAfterMs: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Persistence for sermon-js. Implement these few methods to store history anywhere
|
|
37
|
+
* (Redis, Postgres, MySQL, SQLite, MongoDB…). Statistics and bucketing are computed by the
|
|
38
|
+
* library from what the store returns, so adapters only save and read rows.
|
|
39
|
+
*
|
|
40
|
+
* Verify an implementation with `runStoreConformance` from "sermon-js/testing".
|
|
41
|
+
*/
|
|
42
|
+
interface StatusStore {
|
|
43
|
+
/** Persist one run: upsert the latest result of each service, append its sample and the summary */
|
|
44
|
+
saveRun(run: {
|
|
45
|
+
summary: RunSummary;
|
|
46
|
+
results: CheckResult[];
|
|
47
|
+
samples: CheckSample[];
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/** The latest result of every service, by key */
|
|
50
|
+
latest(): Promise<Record<string, CheckResult>>;
|
|
51
|
+
/** Run summaries within the range (inclusive), oldest first */
|
|
52
|
+
runs(range: TimeRange): Promise<RunSummary[]>;
|
|
53
|
+
/** Samples of one service within the range (inclusive), oldest first */
|
|
54
|
+
samples(key: string, range: TimeRange): Promise<CheckSample[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Drop runs and samples older than `before`. When `keepKeys` is given, also drop everything
|
|
57
|
+
* (latest results and samples) belonging to services not in that list.
|
|
58
|
+
*/
|
|
59
|
+
prune(options: {
|
|
60
|
+
before: number;
|
|
61
|
+
keepKeys?: string[];
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Optional cross-instance lock (rate limits, single scheduled run). Without it the library
|
|
65
|
+
* falls back to an in-process lock, which is enough for a single server.
|
|
66
|
+
*/
|
|
67
|
+
lock?(name: string, ttlMs: number): Promise<LockResult>;
|
|
68
|
+
/** Release a lock taken with `lock` before its time-to-live (optional; without it locks just expire) */
|
|
69
|
+
unlock?(name: string): Promise<void>;
|
|
70
|
+
/** Optional small cache (computed statistics, check state such as daily peaks) */
|
|
71
|
+
cache?: {
|
|
72
|
+
get(key: string): Promise<string | null>;
|
|
73
|
+
set(key: string, value: string, ttlMs: number): Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type { CheckSample as C, LockResult as L, RunSummary as R, StatusStore as S, TimeRange as T };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { o as Status, T as Trigger, k as ScoreLabel, g as CheckResult } from './types-CctvUAs2.js';
|
|
2
|
+
|
|
3
|
+
/** One entry per run: drives the score history */
|
|
4
|
+
type RunSummary = {
|
|
5
|
+
at: number;
|
|
6
|
+
trigger: Trigger;
|
|
7
|
+
/** True when every service was checked (single-service runs are partial) */
|
|
8
|
+
full: boolean;
|
|
9
|
+
score: number | null;
|
|
10
|
+
label: ScoreLabel;
|
|
11
|
+
/** Only services that were degraded or down: key → status */
|
|
12
|
+
failing: Record<string, Status>;
|
|
13
|
+
};
|
|
14
|
+
/** One entry per service per run: drives uptime, latency percentiles and sparklines */
|
|
15
|
+
type CheckSample = {
|
|
16
|
+
key: string;
|
|
17
|
+
at: number;
|
|
18
|
+
status: Status;
|
|
19
|
+
latencyMs?: number;
|
|
20
|
+
httpStatus?: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
/** Per instance (e.g. region): key → [status, latency] */
|
|
23
|
+
instances?: Record<string, [Status, number?]>;
|
|
24
|
+
};
|
|
25
|
+
type TimeRange = {
|
|
26
|
+
since: number;
|
|
27
|
+
until: number;
|
|
28
|
+
};
|
|
29
|
+
type LockResult = {
|
|
30
|
+
ok: true;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
retryAfterMs: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Persistence for sermon-js. Implement these few methods to store history anywhere
|
|
37
|
+
* (Redis, Postgres, MySQL, SQLite, MongoDB…). Statistics and bucketing are computed by the
|
|
38
|
+
* library from what the store returns, so adapters only save and read rows.
|
|
39
|
+
*
|
|
40
|
+
* Verify an implementation with `runStoreConformance` from "sermon-js/testing".
|
|
41
|
+
*/
|
|
42
|
+
interface StatusStore {
|
|
43
|
+
/** Persist one run: upsert the latest result of each service, append its sample and the summary */
|
|
44
|
+
saveRun(run: {
|
|
45
|
+
summary: RunSummary;
|
|
46
|
+
results: CheckResult[];
|
|
47
|
+
samples: CheckSample[];
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/** The latest result of every service, by key */
|
|
50
|
+
latest(): Promise<Record<string, CheckResult>>;
|
|
51
|
+
/** Run summaries within the range (inclusive), oldest first */
|
|
52
|
+
runs(range: TimeRange): Promise<RunSummary[]>;
|
|
53
|
+
/** Samples of one service within the range (inclusive), oldest first */
|
|
54
|
+
samples(key: string, range: TimeRange): Promise<CheckSample[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Drop runs and samples older than `before`. When `keepKeys` is given, also drop everything
|
|
57
|
+
* (latest results and samples) belonging to services not in that list.
|
|
58
|
+
*/
|
|
59
|
+
prune(options: {
|
|
60
|
+
before: number;
|
|
61
|
+
keepKeys?: string[];
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Optional cross-instance lock (rate limits, single scheduled run). Without it the library
|
|
65
|
+
* falls back to an in-process lock, which is enough for a single server.
|
|
66
|
+
*/
|
|
67
|
+
lock?(name: string, ttlMs: number): Promise<LockResult>;
|
|
68
|
+
/** Release a lock taken with `lock` before its time-to-live (optional; without it locks just expire) */
|
|
69
|
+
unlock?(name: string): Promise<void>;
|
|
70
|
+
/** Optional small cache (computed statistics, check state such as daily peaks) */
|
|
71
|
+
cache?: {
|
|
72
|
+
get(key: string): Promise<string | null>;
|
|
73
|
+
set(key: string, value: string, ttlMs: number): Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type { CheckSample as C, LockResult as L, RunSummary as R, StatusStore as S, TimeRange as T };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/stores/mongodb.ts
|
|
4
|
+
var DUPLICATE_KEY = 11e3;
|
|
5
|
+
function mongoStore({ db, collectionPrefix = "sermon_" }) {
|
|
6
|
+
const latest = db.collection(`${collectionPrefix}latest`);
|
|
7
|
+
const runs = db.collection(`${collectionPrefix}runs`);
|
|
8
|
+
const samples = db.collection(`${collectionPrefix}samples`);
|
|
9
|
+
const kv = db.collection(`${collectionPrefix}kv`);
|
|
10
|
+
let indexed;
|
|
11
|
+
const ready = () => {
|
|
12
|
+
indexed ??= Promise.all([
|
|
13
|
+
runs.createIndex({ at: 1 }),
|
|
14
|
+
samples.createIndex({ key: 1, at: 1 }),
|
|
15
|
+
samples.createIndex({ at: 1 })
|
|
16
|
+
]).catch((error) => {
|
|
17
|
+
indexed = void 0;
|
|
18
|
+
throw error;
|
|
19
|
+
});
|
|
20
|
+
return indexed;
|
|
21
|
+
};
|
|
22
|
+
const strip = (doc) => {
|
|
23
|
+
const { _id, ...rest } = doc;
|
|
24
|
+
return rest;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
async saveRun({ summary, results, samples: newSamples }) {
|
|
28
|
+
await ready();
|
|
29
|
+
if (results.length) {
|
|
30
|
+
await latest.bulkWrite(
|
|
31
|
+
results.map((result) => ({
|
|
32
|
+
replaceOne: { filter: { _id: result.key }, replacement: { ...result }, upsert: true }
|
|
33
|
+
}))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (newSamples.length) await samples.insertMany(newSamples.map((sample) => ({ ...sample })));
|
|
37
|
+
await runs.insertOne({ ...summary });
|
|
38
|
+
},
|
|
39
|
+
async latest() {
|
|
40
|
+
await ready();
|
|
41
|
+
const docs = await latest.find({}).toArray();
|
|
42
|
+
return Object.fromEntries(docs.map((doc) => [String(doc._id), strip(doc)]));
|
|
43
|
+
},
|
|
44
|
+
async runs({ since, until }) {
|
|
45
|
+
await ready();
|
|
46
|
+
const docs = await runs.find({ at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();
|
|
47
|
+
return docs.map((doc) => strip(doc));
|
|
48
|
+
},
|
|
49
|
+
async samples(key, { since, until }) {
|
|
50
|
+
await ready();
|
|
51
|
+
const docs = await samples.find({ key, at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();
|
|
52
|
+
return docs.map((doc) => strip(doc));
|
|
53
|
+
},
|
|
54
|
+
async prune({ before, keepKeys }) {
|
|
55
|
+
await ready();
|
|
56
|
+
await Promise.all([runs.deleteMany({ at: { $lt: before } }), samples.deleteMany({ at: { $lt: before } })]);
|
|
57
|
+
if (keepKeys) {
|
|
58
|
+
await Promise.all([
|
|
59
|
+
latest.deleteMany({ _id: { $nin: keepKeys } }),
|
|
60
|
+
samples.deleteMany({ key: { $nin: keepKeys } })
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async lock(name, ttlMs) {
|
|
65
|
+
const id = `lock:${name}`;
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const expiresAt = now + Math.max(1, Math.round(ttlMs));
|
|
68
|
+
try {
|
|
69
|
+
await kv.updateOne({ _id: id, expiresAt: { $lte: now } }, { $set: { expiresAt } }, { upsert: true });
|
|
70
|
+
return { ok: true };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.code !== DUPLICATE_KEY) throw error;
|
|
73
|
+
const held = await kv.findOne({ _id: id });
|
|
74
|
+
const until = Number(held?.expiresAt);
|
|
75
|
+
return { ok: false, retryAfterMs: Number.isFinite(until) ? Math.max(1, until - Date.now()) : ttlMs };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async unlock(name) {
|
|
79
|
+
await kv.deleteOne({ _id: `lock:${name}` });
|
|
80
|
+
},
|
|
81
|
+
cache: {
|
|
82
|
+
async get(key) {
|
|
83
|
+
const doc = await kv.findOne({ _id: `cache:${key}`, expiresAt: { $gt: Date.now() } });
|
|
84
|
+
return typeof doc?.value === "string" ? doc.value : null;
|
|
85
|
+
},
|
|
86
|
+
async set(key, value, ttlMs) {
|
|
87
|
+
await kv.updateOne(
|
|
88
|
+
{ _id: `cache:${key}` },
|
|
89
|
+
{ $set: { value, expiresAt: Date.now() + Math.max(1, Math.round(ttlMs)) } },
|
|
90
|
+
{ upsert: true }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
exports.mongoStore = mongoStore;
|
|
98
|
+
//# sourceMappingURL=mongodb.cjs.map
|
|
99
|
+
//# sourceMappingURL=mongodb.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stores/mongodb.ts"],"names":[],"mappings":"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","file":"mongodb.cjs","sourcesContent":["import type { CheckSample, LockResult, RunSummary, StatusStore } from \"../store\";\nimport type { CheckResult } from \"../types\";\n\n// Structural types for the few driver methods used, so the `mongodb` package isn't required\n// to build against (any 5.x / 6.x `Db` fits).\ntype Doc = Record<string, unknown>;\ntype MongoCursor = { sort(spec: Record<string, 1 | -1>): MongoCursor; toArray(): Promise<Doc[]> };\n/* eslint-disable @typescript-eslint/no-explicit-any -- the driver's own generics are stricter than we need */\ntype MongoCollection = {\n find(filter: any): MongoCursor;\n findOne(filter: any): Promise<Doc | null>;\n insertOne(doc: any): Promise<unknown>;\n insertMany(docs: any[]): Promise<unknown>;\n updateOne(filter: any, update: any, options?: any): Promise<unknown>;\n bulkWrite(operations: any[]): Promise<unknown>;\n deleteOne(filter: any): Promise<unknown>;\n deleteMany(filter: any): Promise<unknown>;\n createIndex(spec: any, options?: any): Promise<unknown>;\n};\n/* eslint-enable @typescript-eslint/no-explicit-any */\nexport type MongoDbLike = { collection(name: string): MongoCollection };\n\nexport type MongoStoreOptions = {\n /** A `Db` from a connected MongoClient: `client.db(\"status\")` */\n db: MongoDbLike;\n /** Prefix of the four collections (default \"sermon_\") */\n collectionPrefix?: string;\n};\n\nconst DUPLICATE_KEY = 11000;\n\n/**\n * Store on MongoDB, with the client you already have:\n *\n * ```ts\n * mongoStore({ db: client.db(\"status\") })\n * ```\n *\n * It uses four small collections and creates their indexes on first use.\n */\nexport function mongoStore({ db, collectionPrefix = \"sermon_\" }: MongoStoreOptions): StatusStore {\n const latest = db.collection(`${collectionPrefix}latest`);\n const runs = db.collection(`${collectionPrefix}runs`);\n const samples = db.collection(`${collectionPrefix}samples`);\n const kv = db.collection(`${collectionPrefix}kv`);\n\n let indexed: Promise<unknown> | undefined;\n const ready = () => {\n indexed ??= Promise.all([\n runs.createIndex({ at: 1 }),\n samples.createIndex({ key: 1, at: 1 }),\n samples.createIndex({ at: 1 }),\n ]).catch((error) => {\n indexed = undefined;\n throw error;\n });\n return indexed;\n };\n\n /** Stored documents carry Mongo's own `_id`: give back exactly what was saved */\n const strip = <T>(doc: Doc): T => {\n const { _id, ...rest } = doc;\n void _id;\n return rest as T;\n };\n\n return {\n async saveRun({ summary, results, samples: newSamples }) {\n await ready();\n if (results.length) {\n await latest.bulkWrite(\n results.map((result) => ({\n replaceOne: { filter: { _id: result.key }, replacement: { ...result }, upsert: true },\n }))\n );\n }\n if (newSamples.length) await samples.insertMany(newSamples.map((sample) => ({ ...sample })));\n await runs.insertOne({ ...summary });\n },\n\n async latest() {\n await ready();\n const docs = await latest.find({}).toArray();\n return Object.fromEntries(docs.map((doc) => [String(doc._id), strip<CheckResult>(doc)]));\n },\n\n async runs({ since, until }) {\n await ready();\n const docs = await runs.find({ at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();\n return docs.map((doc) => strip<RunSummary>(doc));\n },\n\n async samples(key, { since, until }) {\n await ready();\n const docs = await samples.find({ key, at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();\n return docs.map((doc) => strip<CheckSample>(doc));\n },\n\n async prune({ before, keepKeys }) {\n await ready();\n await Promise.all([runs.deleteMany({ at: { $lt: before } }), samples.deleteMany({ at: { $lt: before } })]);\n if (keepKeys) {\n await Promise.all([\n latest.deleteMany({ _id: { $nin: keepKeys } }),\n samples.deleteMany({ key: { $nin: keepKeys } }),\n ]);\n }\n },\n\n async lock(name, ttlMs): Promise<LockResult> {\n const id = `lock:${name}`;\n const now = Date.now();\n const expiresAt = now + Math.max(1, Math.round(ttlMs));\n try {\n // Takes the lock if it is free or expired. If a live lock exists the filter doesn't\n // match, the upsert tries to insert the same _id, and the duplicate key tells us so.\n await kv.updateOne({ _id: id, expiresAt: { $lte: now } }, { $set: { expiresAt } }, { upsert: true });\n return { ok: true };\n } catch (error) {\n if ((error as { code?: number }).code !== DUPLICATE_KEY) throw error;\n const held = await kv.findOne({ _id: id });\n const until = Number(held?.expiresAt);\n return { ok: false, retryAfterMs: Number.isFinite(until) ? Math.max(1, until - Date.now()) : ttlMs };\n }\n },\n\n async unlock(name) {\n await kv.deleteOne({ _id: `lock:${name}` });\n },\n\n cache: {\n async get(key) {\n const doc = await kv.findOne({ _id: `cache:${key}`, expiresAt: { $gt: Date.now() } });\n return typeof doc?.value === \"string\" ? doc.value : null;\n },\n async set(key, value, ttlMs) {\n await kv.updateOne(\n { _id: `cache:${key}` },\n { $set: { value, expiresAt: Date.now() + Math.max(1, Math.round(ttlMs)) } },\n { upsert: true }\n );\n },\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { S as StatusStore } from '../store-DybhDphS.cjs';
|
|
2
|
+
import '../types-CctvUAs2.cjs';
|
|
3
|
+
|
|
4
|
+
type Doc = Record<string, unknown>;
|
|
5
|
+
type MongoCursor = {
|
|
6
|
+
sort(spec: Record<string, 1 | -1>): MongoCursor;
|
|
7
|
+
toArray(): Promise<Doc[]>;
|
|
8
|
+
};
|
|
9
|
+
type MongoCollection = {
|
|
10
|
+
find(filter: any): MongoCursor;
|
|
11
|
+
findOne(filter: any): Promise<Doc | null>;
|
|
12
|
+
insertOne(doc: any): Promise<unknown>;
|
|
13
|
+
insertMany(docs: any[]): Promise<unknown>;
|
|
14
|
+
updateOne(filter: any, update: any, options?: any): Promise<unknown>;
|
|
15
|
+
bulkWrite(operations: any[]): Promise<unknown>;
|
|
16
|
+
deleteOne(filter: any): Promise<unknown>;
|
|
17
|
+
deleteMany(filter: any): Promise<unknown>;
|
|
18
|
+
createIndex(spec: any, options?: any): Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
type MongoDbLike = {
|
|
21
|
+
collection(name: string): MongoCollection;
|
|
22
|
+
};
|
|
23
|
+
type MongoStoreOptions = {
|
|
24
|
+
/** A `Db` from a connected MongoClient: `client.db("status")` */
|
|
25
|
+
db: MongoDbLike;
|
|
26
|
+
/** Prefix of the four collections (default "sermon_") */
|
|
27
|
+
collectionPrefix?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Store on MongoDB, with the client you already have:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* mongoStore({ db: client.db("status") })
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* It uses four small collections and creates their indexes on first use.
|
|
37
|
+
*/
|
|
38
|
+
declare function mongoStore({ db, collectionPrefix }: MongoStoreOptions): StatusStore;
|
|
39
|
+
|
|
40
|
+
export { type MongoDbLike, type MongoStoreOptions, mongoStore };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { S as StatusStore } from '../store-_GuzSP0z.js';
|
|
2
|
+
import '../types-CctvUAs2.js';
|
|
3
|
+
|
|
4
|
+
type Doc = Record<string, unknown>;
|
|
5
|
+
type MongoCursor = {
|
|
6
|
+
sort(spec: Record<string, 1 | -1>): MongoCursor;
|
|
7
|
+
toArray(): Promise<Doc[]>;
|
|
8
|
+
};
|
|
9
|
+
type MongoCollection = {
|
|
10
|
+
find(filter: any): MongoCursor;
|
|
11
|
+
findOne(filter: any): Promise<Doc | null>;
|
|
12
|
+
insertOne(doc: any): Promise<unknown>;
|
|
13
|
+
insertMany(docs: any[]): Promise<unknown>;
|
|
14
|
+
updateOne(filter: any, update: any, options?: any): Promise<unknown>;
|
|
15
|
+
bulkWrite(operations: any[]): Promise<unknown>;
|
|
16
|
+
deleteOne(filter: any): Promise<unknown>;
|
|
17
|
+
deleteMany(filter: any): Promise<unknown>;
|
|
18
|
+
createIndex(spec: any, options?: any): Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
type MongoDbLike = {
|
|
21
|
+
collection(name: string): MongoCollection;
|
|
22
|
+
};
|
|
23
|
+
type MongoStoreOptions = {
|
|
24
|
+
/** A `Db` from a connected MongoClient: `client.db("status")` */
|
|
25
|
+
db: MongoDbLike;
|
|
26
|
+
/** Prefix of the four collections (default "sermon_") */
|
|
27
|
+
collectionPrefix?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Store on MongoDB, with the client you already have:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* mongoStore({ db: client.db("status") })
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* It uses four small collections and creates their indexes on first use.
|
|
37
|
+
*/
|
|
38
|
+
declare function mongoStore({ db, collectionPrefix }: MongoStoreOptions): StatusStore;
|
|
39
|
+
|
|
40
|
+
export { type MongoDbLike, type MongoStoreOptions, mongoStore };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/stores/mongodb.ts
|
|
2
|
+
var DUPLICATE_KEY = 11e3;
|
|
3
|
+
function mongoStore({ db, collectionPrefix = "sermon_" }) {
|
|
4
|
+
const latest = db.collection(`${collectionPrefix}latest`);
|
|
5
|
+
const runs = db.collection(`${collectionPrefix}runs`);
|
|
6
|
+
const samples = db.collection(`${collectionPrefix}samples`);
|
|
7
|
+
const kv = db.collection(`${collectionPrefix}kv`);
|
|
8
|
+
let indexed;
|
|
9
|
+
const ready = () => {
|
|
10
|
+
indexed ??= Promise.all([
|
|
11
|
+
runs.createIndex({ at: 1 }),
|
|
12
|
+
samples.createIndex({ key: 1, at: 1 }),
|
|
13
|
+
samples.createIndex({ at: 1 })
|
|
14
|
+
]).catch((error) => {
|
|
15
|
+
indexed = void 0;
|
|
16
|
+
throw error;
|
|
17
|
+
});
|
|
18
|
+
return indexed;
|
|
19
|
+
};
|
|
20
|
+
const strip = (doc) => {
|
|
21
|
+
const { _id, ...rest } = doc;
|
|
22
|
+
return rest;
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
async saveRun({ summary, results, samples: newSamples }) {
|
|
26
|
+
await ready();
|
|
27
|
+
if (results.length) {
|
|
28
|
+
await latest.bulkWrite(
|
|
29
|
+
results.map((result) => ({
|
|
30
|
+
replaceOne: { filter: { _id: result.key }, replacement: { ...result }, upsert: true }
|
|
31
|
+
}))
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (newSamples.length) await samples.insertMany(newSamples.map((sample) => ({ ...sample })));
|
|
35
|
+
await runs.insertOne({ ...summary });
|
|
36
|
+
},
|
|
37
|
+
async latest() {
|
|
38
|
+
await ready();
|
|
39
|
+
const docs = await latest.find({}).toArray();
|
|
40
|
+
return Object.fromEntries(docs.map((doc) => [String(doc._id), strip(doc)]));
|
|
41
|
+
},
|
|
42
|
+
async runs({ since, until }) {
|
|
43
|
+
await ready();
|
|
44
|
+
const docs = await runs.find({ at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();
|
|
45
|
+
return docs.map((doc) => strip(doc));
|
|
46
|
+
},
|
|
47
|
+
async samples(key, { since, until }) {
|
|
48
|
+
await ready();
|
|
49
|
+
const docs = await samples.find({ key, at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();
|
|
50
|
+
return docs.map((doc) => strip(doc));
|
|
51
|
+
},
|
|
52
|
+
async prune({ before, keepKeys }) {
|
|
53
|
+
await ready();
|
|
54
|
+
await Promise.all([runs.deleteMany({ at: { $lt: before } }), samples.deleteMany({ at: { $lt: before } })]);
|
|
55
|
+
if (keepKeys) {
|
|
56
|
+
await Promise.all([
|
|
57
|
+
latest.deleteMany({ _id: { $nin: keepKeys } }),
|
|
58
|
+
samples.deleteMany({ key: { $nin: keepKeys } })
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async lock(name, ttlMs) {
|
|
63
|
+
const id = `lock:${name}`;
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const expiresAt = now + Math.max(1, Math.round(ttlMs));
|
|
66
|
+
try {
|
|
67
|
+
await kv.updateOne({ _id: id, expiresAt: { $lte: now } }, { $set: { expiresAt } }, { upsert: true });
|
|
68
|
+
return { ok: true };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error.code !== DUPLICATE_KEY) throw error;
|
|
71
|
+
const held = await kv.findOne({ _id: id });
|
|
72
|
+
const until = Number(held?.expiresAt);
|
|
73
|
+
return { ok: false, retryAfterMs: Number.isFinite(until) ? Math.max(1, until - Date.now()) : ttlMs };
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
async unlock(name) {
|
|
77
|
+
await kv.deleteOne({ _id: `lock:${name}` });
|
|
78
|
+
},
|
|
79
|
+
cache: {
|
|
80
|
+
async get(key) {
|
|
81
|
+
const doc = await kv.findOne({ _id: `cache:${key}`, expiresAt: { $gt: Date.now() } });
|
|
82
|
+
return typeof doc?.value === "string" ? doc.value : null;
|
|
83
|
+
},
|
|
84
|
+
async set(key, value, ttlMs) {
|
|
85
|
+
await kv.updateOne(
|
|
86
|
+
{ _id: `cache:${key}` },
|
|
87
|
+
{ $set: { value, expiresAt: Date.now() + Math.max(1, Math.round(ttlMs)) } },
|
|
88
|
+
{ upsert: true }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { mongoStore };
|
|
96
|
+
//# sourceMappingURL=mongodb.js.map
|
|
97
|
+
//# sourceMappingURL=mongodb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stores/mongodb.ts"],"names":[],"mappings":"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","file":"mongodb.js","sourcesContent":["import type { CheckSample, LockResult, RunSummary, StatusStore } from \"../store\";\nimport type { CheckResult } from \"../types\";\n\n// Structural types for the few driver methods used, so the `mongodb` package isn't required\n// to build against (any 5.x / 6.x `Db` fits).\ntype Doc = Record<string, unknown>;\ntype MongoCursor = { sort(spec: Record<string, 1 | -1>): MongoCursor; toArray(): Promise<Doc[]> };\n/* eslint-disable @typescript-eslint/no-explicit-any -- the driver's own generics are stricter than we need */\ntype MongoCollection = {\n find(filter: any): MongoCursor;\n findOne(filter: any): Promise<Doc | null>;\n insertOne(doc: any): Promise<unknown>;\n insertMany(docs: any[]): Promise<unknown>;\n updateOne(filter: any, update: any, options?: any): Promise<unknown>;\n bulkWrite(operations: any[]): Promise<unknown>;\n deleteOne(filter: any): Promise<unknown>;\n deleteMany(filter: any): Promise<unknown>;\n createIndex(spec: any, options?: any): Promise<unknown>;\n};\n/* eslint-enable @typescript-eslint/no-explicit-any */\nexport type MongoDbLike = { collection(name: string): MongoCollection };\n\nexport type MongoStoreOptions = {\n /** A `Db` from a connected MongoClient: `client.db(\"status\")` */\n db: MongoDbLike;\n /** Prefix of the four collections (default \"sermon_\") */\n collectionPrefix?: string;\n};\n\nconst DUPLICATE_KEY = 11000;\n\n/**\n * Store on MongoDB, with the client you already have:\n *\n * ```ts\n * mongoStore({ db: client.db(\"status\") })\n * ```\n *\n * It uses four small collections and creates their indexes on first use.\n */\nexport function mongoStore({ db, collectionPrefix = \"sermon_\" }: MongoStoreOptions): StatusStore {\n const latest = db.collection(`${collectionPrefix}latest`);\n const runs = db.collection(`${collectionPrefix}runs`);\n const samples = db.collection(`${collectionPrefix}samples`);\n const kv = db.collection(`${collectionPrefix}kv`);\n\n let indexed: Promise<unknown> | undefined;\n const ready = () => {\n indexed ??= Promise.all([\n runs.createIndex({ at: 1 }),\n samples.createIndex({ key: 1, at: 1 }),\n samples.createIndex({ at: 1 }),\n ]).catch((error) => {\n indexed = undefined;\n throw error;\n });\n return indexed;\n };\n\n /** Stored documents carry Mongo's own `_id`: give back exactly what was saved */\n const strip = <T>(doc: Doc): T => {\n const { _id, ...rest } = doc;\n void _id;\n return rest as T;\n };\n\n return {\n async saveRun({ summary, results, samples: newSamples }) {\n await ready();\n if (results.length) {\n await latest.bulkWrite(\n results.map((result) => ({\n replaceOne: { filter: { _id: result.key }, replacement: { ...result }, upsert: true },\n }))\n );\n }\n if (newSamples.length) await samples.insertMany(newSamples.map((sample) => ({ ...sample })));\n await runs.insertOne({ ...summary });\n },\n\n async latest() {\n await ready();\n const docs = await latest.find({}).toArray();\n return Object.fromEntries(docs.map((doc) => [String(doc._id), strip<CheckResult>(doc)]));\n },\n\n async runs({ since, until }) {\n await ready();\n const docs = await runs.find({ at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();\n return docs.map((doc) => strip<RunSummary>(doc));\n },\n\n async samples(key, { since, until }) {\n await ready();\n const docs = await samples.find({ key, at: { $gte: since, $lte: until } }).sort({ at: 1 }).toArray();\n return docs.map((doc) => strip<CheckSample>(doc));\n },\n\n async prune({ before, keepKeys }) {\n await ready();\n await Promise.all([runs.deleteMany({ at: { $lt: before } }), samples.deleteMany({ at: { $lt: before } })]);\n if (keepKeys) {\n await Promise.all([\n latest.deleteMany({ _id: { $nin: keepKeys } }),\n samples.deleteMany({ key: { $nin: keepKeys } }),\n ]);\n }\n },\n\n async lock(name, ttlMs): Promise<LockResult> {\n const id = `lock:${name}`;\n const now = Date.now();\n const expiresAt = now + Math.max(1, Math.round(ttlMs));\n try {\n // Takes the lock if it is free or expired. If a live lock exists the filter doesn't\n // match, the upsert tries to insert the same _id, and the duplicate key tells us so.\n await kv.updateOne({ _id: id, expiresAt: { $lte: now } }, { $set: { expiresAt } }, { upsert: true });\n return { ok: true };\n } catch (error) {\n if ((error as { code?: number }).code !== DUPLICATE_KEY) throw error;\n const held = await kv.findOne({ _id: id });\n const until = Number(held?.expiresAt);\n return { ok: false, retryAfterMs: Number.isFinite(until) ? Math.max(1, until - Date.now()) : ttlMs };\n }\n },\n\n async unlock(name) {\n await kv.deleteOne({ _id: `lock:${name}` });\n },\n\n cache: {\n async get(key) {\n const doc = await kv.findOne({ _id: `cache:${key}`, expiresAt: { $gt: Date.now() } });\n return typeof doc?.value === \"string\" ? doc.value : null;\n },\n async set(key, value, ttlMs) {\n await kv.updateOne(\n { _id: `cache:${key}` },\n { $set: { value, expiresAt: Date.now() + Math.max(1, Math.round(ttlMs)) } },\n { upsert: true }\n );\n },\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkZI442ZA5_cjs = require('../chunk-ZI442ZA5.cjs');
|
|
4
|
+
|
|
5
|
+
// src/stores/redis.ts
|
|
6
|
+
function redisStore({ client, command, prefix }) {
|
|
7
|
+
let send = command;
|
|
8
|
+
if (!send && client) {
|
|
9
|
+
if ("call" in client && typeof client.call === "function") {
|
|
10
|
+
send = ([name, ...args]) => client.call(name, ...args);
|
|
11
|
+
} else if ("sendCommand" in client && typeof client.sendCommand === "function") {
|
|
12
|
+
send = (args) => client.sendCommand(args);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (!send) throw new Error("sermon-js: redisStore needs a `client` (ioredis or node-redis) or a `command` function");
|
|
16
|
+
const run = send;
|
|
17
|
+
const exec = (commands) => Promise.all(commands.map((args) => run(args.map(String))));
|
|
18
|
+
return chunkZI442ZA5_cjs.createRedisStore({ exec, prefix });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.redisStore = redisStore;
|
|
22
|
+
//# sourceMappingURL=redis.cjs.map
|
|
23
|
+
//# sourceMappingURL=redis.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stores/redis.ts"],"names":["createRedisStore"],"mappings":";;;;;AA0BO,SAAS,UAAA,CAAW,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAO,EAAmC;AACtF,EAAA,IAAI,IAAA,GAAO,OAAA;AACX,EAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,IAAA,IAAI,MAAA,IAAU,MAAA,IAAU,OAAO,MAAA,CAAO,SAAS,UAAA,EAAY;AACzD,MAAA,IAAA,GAAO,CAAC,CAAC,IAAA,EAAS,GAAA,IAAI,MAAM,MAAA,CAAO,IAAA,CAAK,IAAA,EAAgB,GAAI,IAAiB,CAAA;AAAA,IAC/E,WAAW,aAAA,IAAiB,MAAA,IAAU,OAAO,MAAA,CAAO,gBAAgB,UAAA,EAAY;AAC9E,MAAA,IAAA,GAAO,CAAC,IAAA,KAAS,MAAA,CAAO,WAAA,CAAY,IAAI,CAAA;AAAA,IAC1C;AAAA,EACF;AACA,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,wFAAwF,CAAA;AAEnH,EAAA,MAAM,GAAA,GAAM,IAAA;AACZ,EAAA,MAAM,IAAA,GAAkB,CAAC,QAAA,KAAa,OAAA,CAAQ,IAAI,QAAA,CAAS,GAAA,CAAI,CAAC,IAAA,KAAS,IAAI,IAAA,CAAK,GAAA,CAAI,MAAM,CAAC,CAAC,CAAC,CAAA;AAC/F,EAAA,OAAOA,kCAAA,CAAiB,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AAC1C","file":"redis.cjs","sourcesContent":["import type { StatusStore } from \"../store\";\nimport { createRedisStore, type RedisExec } from \"./redis-core\";\n\n/** ioredis: `client.call(\"GET\", \"key\")` */\ntype CallClient = { call: (command: string, ...args: Array<string | number>) => Promise<unknown> };\n/** node-redis: `client.sendCommand([\"GET\", \"key\"])` */\ntype SendCommandClient = { sendCommand: (args: string[]) => Promise<unknown> };\n\nexport type RedisStoreOptions = {\n /** An ioredis or node-redis client you created and connected */\n client?: CallClient | SendCommandClient;\n /** Or any function that sends one raw command, for other clients */\n command?: (args: string[]) => Promise<unknown>;\n /** Prefix of every key (default \"sermon\"): lets several status pages share one database */\n prefix?: string;\n};\n\n/**\n * Store on any Redis-compatible server (Redis, Valkey, KeyDB, Dragonfly…), using the client\n * you already have:\n *\n * ```ts\n * redisStore({ client: new Redis(process.env.REDIS_URL) }) // ioredis\n * redisStore({ client: await createClient({ url }).connect() }) // node-redis\n * ```\n */\nexport function redisStore({ client, command, prefix }: RedisStoreOptions): StatusStore {\n let send = command;\n if (!send && client) {\n if (\"call\" in client && typeof client.call === \"function\") {\n send = ([name, ...args]) => client.call(name as string, ...(args as string[]));\n } else if (\"sendCommand\" in client && typeof client.sendCommand === \"function\") {\n send = (args) => client.sendCommand(args);\n }\n }\n if (!send) throw new Error(\"sermon-js: redisStore needs a `client` (ioredis or node-redis) or a `command` function\");\n\n const run = send;\n const exec: RedisExec = (commands) => Promise.all(commands.map((args) => run(args.map(String))));\n return createRedisStore({ exec, prefix });\n}\n"]}
|