@cross-deck/buckets 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/index.d.mts +179 -0
- package/dist/index.d.ts +147 -12
- package/dist/index.js +249 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +236 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +7 -9
- package/dist/adapters/firestore.d.ts +0 -41
- package/dist/adapters/firestore.d.ts.map +0 -1
- package/dist/adapters/firestore.js +0 -145
- package/dist/adapters/firestore.js.map +0 -1
- package/dist/cost-context.d.ts +0 -25
- package/dist/cost-context.d.ts.map +0 -1
- package/dist/cost-context.js +0 -46
- package/dist/cost-context.js.map +0 -1
- package/dist/cost-meter.d.ts +0 -28
- package/dist/cost-meter.d.ts.map +0 -1
- package/dist/cost-meter.js +0 -137
- package/dist/cost-meter.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/sink.d.ts +0 -56
- package/dist/sink.d.ts.map +0 -1
- package/dist/sink.js +0 -42
- package/dist/sink.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cost-context.d.ts","sourceRoot":"","sources":["../src/cost-context.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE9D;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAE/C;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAG3D;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtD"}
|
package/dist/cost-context.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cost-context — the request-scoped tag every counted operation attributes
|
|
3
|
-
* itself to. Set it ONCE at a boundary (or wrap a path with `bucket()`); it
|
|
4
|
-
* rides Node's AsyncLocalStorage down through every async fan-out, so one
|
|
5
|
-
* handler that triggers 15 reads attributes all 15 to the same bucket — with
|
|
6
|
-
* zero per-call-site work.
|
|
7
|
-
*
|
|
8
|
-
* Generic by design: unlike a hardcoded product taxonomy, the only meaningful
|
|
9
|
-
* field a consumer sets is the free-form `label` (the bucket name). `feature`
|
|
10
|
-
* is an optional coarse grouping if you want one; nothing here is
|
|
11
|
-
* Crossdeck-specific.
|
|
12
|
-
*/
|
|
13
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
-
const DEFAULT_TAG = {};
|
|
15
|
-
const store = new AsyncLocalStorage();
|
|
16
|
-
/** Run `fn` with `tag` bound for its entire async subtree. */
|
|
17
|
-
export function runWithCostTag(tag, fn) {
|
|
18
|
-
return store.run({ ...tag }, fn);
|
|
19
|
-
}
|
|
20
|
-
/** Bind a tag for the remainder of the current async context (no closure to wrap). */
|
|
21
|
-
export function enterCostTag(tag) {
|
|
22
|
-
store.enterWith({ ...tag });
|
|
23
|
-
}
|
|
24
|
-
/** Refine the live tag in place (e.g. stamp a feature after the boundary). */
|
|
25
|
-
export function refineCostTag(patch) {
|
|
26
|
-
const cur = store.getStore();
|
|
27
|
-
if (cur)
|
|
28
|
-
Object.assign(cur, patch);
|
|
29
|
-
}
|
|
30
|
-
/** The current tag, or a safe empty default outside any bound context. */
|
|
31
|
-
export function currentCostTag() {
|
|
32
|
-
return store.getStore() ?? DEFAULT_TAG;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* `bucket(name, fn)` — the headline verb, the `track()` of cost. Run `fn` with
|
|
36
|
-
* every operation inside it attributed to the bucket `name`; the attribution
|
|
37
|
-
* rides the async subtree automatically. The one verb most developers ever touch:
|
|
38
|
-
*
|
|
39
|
-
* await bucket("nightly-export", async () => {
|
|
40
|
-
* const rows = await db.collection("events").where(...).get(); // → "nightly-export"
|
|
41
|
-
* });
|
|
42
|
-
*/
|
|
43
|
-
export function bucket(name, fn) {
|
|
44
|
-
return runWithCostTag({ ...currentCostTag(), label: name }, fn);
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=cost-context.js.map
|
package/dist/cost-context.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cost-context.js","sourceRoot":"","sources":["../src/cost-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AASrD,MAAM,WAAW,GAAY,EAAE,CAAC;AAChC,MAAM,KAAK,GAAG,IAAI,iBAAiB,EAAW,CAAC;AAE/C,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAAI,GAAY,EAAE,EAAW;IACzD,OAAO,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,KAAuB;IACnD,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC7B,IAAI,GAAG;QAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc;IAC5B,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,WAAW,CAAC;AACzC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAI,IAAY,EAAE,EAAW;IACjD,OAAO,cAAc,CAAC,EAAE,GAAG,cAAc,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AAClE,CAAC"}
|
package/dist/cost-meter.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Sink } from "./sink.js";
|
|
2
|
-
export type OpType = "read" | "write" | "delete";
|
|
3
|
-
/** Optional read-site hint — the collection touched, derived at the trap from the
|
|
4
|
-
* path. Lets an UNtagged read cascade to `col:<collection>` instead of vanishing. */
|
|
5
|
-
export interface CostHint {
|
|
6
|
-
collection?: string;
|
|
7
|
-
projectId?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface MeterConfig {
|
|
10
|
-
sink: Sink;
|
|
11
|
-
flushIntervalMs?: number;
|
|
12
|
-
onError?: (e: unknown) => void;
|
|
13
|
-
}
|
|
14
|
-
/** Point the meter at a sink. Called by `init()`; pass your own sink to self-host. */
|
|
15
|
-
export declare function configureMeter(config: MeterConfig): void;
|
|
16
|
-
/** Count `n` ops of `op` against the live tag. Never throws. */
|
|
17
|
-
export declare function recordFirestore(op: OpType, n: number, hint?: CostHint): void;
|
|
18
|
-
/** Firestore bills a minimum of one read even for an empty result, so 0 counts as 1. */
|
|
19
|
-
export declare function recordReads(n: number, hint?: CostHint): void;
|
|
20
|
-
export declare function recordWrites(n?: number): void;
|
|
21
|
-
export declare function recordDeletes(n?: number): void;
|
|
22
|
-
/**
|
|
23
|
-
* Coalesce the buffer into one report per UTC day and hand each to the Sink.
|
|
24
|
-
* Snapshots + clears up front so concurrent records land in the next window.
|
|
25
|
-
* Never throws; a sink failure drops that window (surfaced via `onError`).
|
|
26
|
-
*/
|
|
27
|
-
export declare function flush(): Promise<void>;
|
|
28
|
-
//# sourceMappingURL=cost-meter.d.ts.map
|
package/dist/cost-meter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cost-meter.d.ts","sourceRoot":"","sources":["../src/cost-meter.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,IAAI,EAA2B,MAAM,WAAW,CAAC;AAE/D,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEjD;sFACsF;AACtF,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAChC;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAIxD;AAeD,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkB5E;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAE5D;AACD,wBAAgB,YAAY,CAAC,CAAC,SAAI,GAAG,IAAI,CAExC;AACD,wBAAgB,aAAa,CAAC,CAAC,SAAI,GAAG,IAAI,CAEzC;AAOD;;;;GAIG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA2C3C"}
|
package/dist/cost-meter.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cost-meter — counts operations against the ambient tag and flushes them to the
|
|
3
|
-
* configured Sink cheaply.
|
|
4
|
-
*
|
|
5
|
-
* LOW-OVERHEAD CONTRACT (the thing that warns you about reads must not run them
|
|
6
|
-
* up): counts accumulate in an in-memory buffer and flush periodically — NEVER one
|
|
7
|
-
* network call per counted operation. A flush coalesces the whole window into one
|
|
8
|
-
* report per UTC day and hands it to the Sink. At steady state that is ~1 small
|
|
9
|
-
* request a minute, regardless of how many ops you served.
|
|
10
|
-
*
|
|
11
|
-
* BEST-EFFORT CONTRACT: metering must never throw into your code. Every recorder
|
|
12
|
-
* swallows its own errors; a failed flush drops that window's counts (surfaced via
|
|
13
|
-
* `onError` if you pass one) rather than disturbing the app.
|
|
14
|
-
*/
|
|
15
|
-
import { currentCostTag } from "./cost-context.js";
|
|
16
|
-
// NUL separator — a bucket/collection name can contain almost anything except
|
|
17
|
-
// this, so the key never collides with a name that has a "|" or ":" in it.
|
|
18
|
-
const SEP = "\u001f"; // ASCII Unit Separator
|
|
19
|
-
/** key = date <NUL> op <NUL> label → count */
|
|
20
|
-
const labelBuffer = new Map();
|
|
21
|
-
/** key = date <NUL> op <NUL> hour → count */
|
|
22
|
-
const hourBuffer = new Map();
|
|
23
|
-
let sink = null;
|
|
24
|
-
let flushIntervalMs = 60_000;
|
|
25
|
-
let onError = null;
|
|
26
|
-
let timer = null;
|
|
27
|
-
let flushing = false;
|
|
28
|
-
/** Safety valve — flush early if a burst fills the buffer between intervals. */
|
|
29
|
-
const MAX_BUFFER_KEYS = 5_000;
|
|
30
|
-
/** Point the meter at a sink. Called by `init()`; pass your own sink to self-host. */
|
|
31
|
-
export function configureMeter(config) {
|
|
32
|
-
sink = config.sink;
|
|
33
|
-
if (config.flushIntervalMs && config.flushIntervalMs > 0)
|
|
34
|
-
flushIntervalMs = config.flushIntervalMs;
|
|
35
|
-
onError = config.onError ?? null;
|
|
36
|
-
}
|
|
37
|
-
const utcDate = () => new Date().toISOString().slice(0, 10);
|
|
38
|
-
const utcHour = () => new Date().toISOString().slice(11, 13);
|
|
39
|
-
function ensureFlushLoop() {
|
|
40
|
-
if (timer)
|
|
41
|
-
return;
|
|
42
|
-
timer = setInterval(() => void flush(), flushIntervalMs);
|
|
43
|
-
// Don't keep the event loop alive just for metering.
|
|
44
|
-
timer.unref?.();
|
|
45
|
-
// Flush the last window on shutdown.
|
|
46
|
-
process.once?.("SIGTERM", () => void flush());
|
|
47
|
-
process.once?.("beforeExit", () => void flush());
|
|
48
|
-
}
|
|
49
|
-
/** Count `n` ops of `op` against the live tag. Never throws. */
|
|
50
|
-
export function recordFirestore(op, n, hint) {
|
|
51
|
-
try {
|
|
52
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
53
|
-
return;
|
|
54
|
-
const t = currentCostTag();
|
|
55
|
-
const date = utcDate();
|
|
56
|
-
// CASCADE — every op gets a label, by design (no blind spots): the bucket name
|
|
57
|
-
// wins; else the collection it actually touched (`col:posts`); else
|
|
58
|
-
// "uncategorized" as a loud last resort. A read is never invisible.
|
|
59
|
-
const label = t.label || (hint?.collection ? `col:${hint.collection}` : "uncategorized");
|
|
60
|
-
const lk = date + SEP + op + SEP + label;
|
|
61
|
-
labelBuffer.set(lk, (labelBuffer.get(lk) ?? 0) + n);
|
|
62
|
-
const hk = date + SEP + op + SEP + utcHour();
|
|
63
|
-
hourBuffer.set(hk, (hourBuffer.get(hk) ?? 0) + n);
|
|
64
|
-
ensureFlushLoop();
|
|
65
|
-
if (labelBuffer.size + hourBuffer.size > MAX_BUFFER_KEYS)
|
|
66
|
-
void flush();
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
/* metering is best-effort — never disturb the caller */
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/** Firestore bills a minimum of one read even for an empty result, so 0 counts as 1. */
|
|
73
|
-
export function recordReads(n, hint) {
|
|
74
|
-
recordFirestore("read", Math.max(n, 1), hint);
|
|
75
|
-
}
|
|
76
|
-
export function recordWrites(n = 1) {
|
|
77
|
-
recordFirestore("write", n);
|
|
78
|
-
}
|
|
79
|
-
export function recordDeletes(n = 1) {
|
|
80
|
-
recordFirestore("delete", n);
|
|
81
|
-
}
|
|
82
|
-
function add(target, key, op, n) {
|
|
83
|
-
const bag = (target[key] ??= {});
|
|
84
|
-
bag[op] = (bag[op] ?? 0) + n;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Coalesce the buffer into one report per UTC day and hand each to the Sink.
|
|
88
|
-
* Snapshots + clears up front so concurrent records land in the next window.
|
|
89
|
-
* Never throws; a sink failure drops that window (surfaced via `onError`).
|
|
90
|
-
*/
|
|
91
|
-
export async function flush() {
|
|
92
|
-
if (flushing)
|
|
93
|
-
return;
|
|
94
|
-
// Not configured (init not called) — drop, don't grow unbounded.
|
|
95
|
-
if (!sink) {
|
|
96
|
-
labelBuffer.clear();
|
|
97
|
-
hourBuffer.clear();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (labelBuffer.size === 0 && hourBuffer.size === 0)
|
|
101
|
-
return;
|
|
102
|
-
flushing = true;
|
|
103
|
-
const labels = new Map(labelBuffer);
|
|
104
|
-
const hours = new Map(hourBuffer);
|
|
105
|
-
labelBuffer.clear();
|
|
106
|
-
hourBuffer.clear();
|
|
107
|
-
try {
|
|
108
|
-
const byDate = new Map();
|
|
109
|
-
const reportFor = (date) => {
|
|
110
|
-
let r = byDate.get(date);
|
|
111
|
-
if (!r) {
|
|
112
|
-
r = { date, byLabel: {}, byHour: {} };
|
|
113
|
-
byDate.set(date, r);
|
|
114
|
-
}
|
|
115
|
-
return r;
|
|
116
|
-
};
|
|
117
|
-
for (const [k, n] of labels) {
|
|
118
|
-
const [date, op, label] = k.split(SEP);
|
|
119
|
-
add(reportFor(date).byLabel, label, op, n);
|
|
120
|
-
}
|
|
121
|
-
for (const [k, n] of hours) {
|
|
122
|
-
const [date, op, hour] = k.split(SEP);
|
|
123
|
-
add(reportFor(date).byHour, hour, op, n);
|
|
124
|
-
}
|
|
125
|
-
for (const report of byDate.values()) {
|
|
126
|
-
await sink.flush(report);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
catch (e) {
|
|
130
|
-
// Drop this window rather than risk a partial/double report on retry.
|
|
131
|
-
onError?.(e);
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
flushing = false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
//# sourceMappingURL=cost-meter.js.map
|
package/dist/cost-meter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cost-meter.js","sourceRoot":"","sources":["../src/cost-meter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEvF,MAAM,WAAW,WAAW;IAC1B,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,qFAAqF;IACrF,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAI/C;AAED,4DAA4D;AAC5D,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,CAAC;AAG/B,OAAO,EACL,MAAM,EACN,cAAc,EACd,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGvF,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/sink.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sink — where the meter sends a coalesced rollup, and the wire shape it sends.
|
|
3
|
-
*
|
|
4
|
-
* Abstracting the sink is what makes Buckets storage-agnostic: the meter never
|
|
5
|
-
* knows where counts go. The DEFAULT sink (`ReportSink`) reports up to Crossdeck's
|
|
6
|
-
* ingest endpoint so the numbers surface on your Crossdeck dashboard. A team that
|
|
7
|
-
* wants to self-host can implement `Sink` against anything (Postgres, a file, your
|
|
8
|
-
* own API) without touching the meter.
|
|
9
|
-
*/
|
|
10
|
-
export interface OpCounts {
|
|
11
|
-
read?: number;
|
|
12
|
-
write?: number;
|
|
13
|
-
delete?: number;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* One coalesced report — the wire contract (see docs/ROLLUP_SCHEMA.md). The meter
|
|
17
|
-
* produces one of these per UTC day in a flush window (usually exactly one).
|
|
18
|
-
*/
|
|
19
|
-
export interface BucketsReport {
|
|
20
|
-
/** UTC day "YYYY-MM-DD". */
|
|
21
|
-
date: string;
|
|
22
|
-
/** bucket name → counts. The heart of the report. */
|
|
23
|
-
byLabel: Record<string, OpCounts>;
|
|
24
|
-
/** UTC hour "HH" → counts, for the hourly "did my fix land this hour?" view. */
|
|
25
|
-
byHour?: Record<string, OpCounts>;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* A destination for coalesced rollups. `flush` MAY throw on failure — the meter
|
|
29
|
-
* catches it, drops that one window, and never lets it reach your app.
|
|
30
|
-
*/
|
|
31
|
-
export interface Sink {
|
|
32
|
-
flush(report: BucketsReport): Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
export interface ReportSinkConfig {
|
|
35
|
-
/** The project's `cd_sk_` secret key. Server-to-server only. */
|
|
36
|
-
apiKey: string;
|
|
37
|
-
/** Defaults to https://api.cross-deck.com/v1/buckets/report */
|
|
38
|
-
endpoint?: string;
|
|
39
|
-
/** Request timeout (ms); a slow Crossdeck must never stall your flush. */
|
|
40
|
-
timeoutMs?: number;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* The default sink: POST one coalesced rollup to Crossdeck's ingest endpoint.
|
|
44
|
-
* The ingest folds it into the day's maintained doc with `increment`, so many
|
|
45
|
-
* reports a minute coalesce safely. This path does ZERO database reads — it sends
|
|
46
|
-
* a summary, it does not read. Throws on a non-202 so the meter can log/drop the
|
|
47
|
-
* window; the meter guarantees it never reaches your app.
|
|
48
|
-
*/
|
|
49
|
-
export declare class ReportSink implements Sink {
|
|
50
|
-
private readonly endpoint;
|
|
51
|
-
private readonly apiKey;
|
|
52
|
-
private readonly timeoutMs;
|
|
53
|
-
constructor(config: ReportSinkConfig);
|
|
54
|
-
flush(report: BucketsReport): Promise<void>;
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=sink.d.ts.map
|
package/dist/sink.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;GAMG;AACH,qBAAa,UAAW,YAAW,IAAI;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,MAAM,EAAE,gBAAgB;IAM9B,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAclD"}
|
package/dist/sink.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sink — where the meter sends a coalesced rollup, and the wire shape it sends.
|
|
3
|
-
*
|
|
4
|
-
* Abstracting the sink is what makes Buckets storage-agnostic: the meter never
|
|
5
|
-
* knows where counts go. The DEFAULT sink (`ReportSink`) reports up to Crossdeck's
|
|
6
|
-
* ingest endpoint so the numbers surface on your Crossdeck dashboard. A team that
|
|
7
|
-
* wants to self-host can implement `Sink` against anything (Postgres, a file, your
|
|
8
|
-
* own API) without touching the meter.
|
|
9
|
-
*/
|
|
10
|
-
const DEFAULT_ENDPOINT = "https://api.cross-deck.com/v1/buckets/report";
|
|
11
|
-
/**
|
|
12
|
-
* The default sink: POST one coalesced rollup to Crossdeck's ingest endpoint.
|
|
13
|
-
* The ingest folds it into the day's maintained doc with `increment`, so many
|
|
14
|
-
* reports a minute coalesce safely. This path does ZERO database reads — it sends
|
|
15
|
-
* a summary, it does not read. Throws on a non-202 so the meter can log/drop the
|
|
16
|
-
* window; the meter guarantees it never reaches your app.
|
|
17
|
-
*/
|
|
18
|
-
export class ReportSink {
|
|
19
|
-
endpoint;
|
|
20
|
-
apiKey;
|
|
21
|
-
timeoutMs;
|
|
22
|
-
constructor(config) {
|
|
23
|
-
this.endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
24
|
-
this.apiKey = config.apiKey;
|
|
25
|
-
this.timeoutMs = config.timeoutMs ?? 5000;
|
|
26
|
-
}
|
|
27
|
-
async flush(report) {
|
|
28
|
-
const res = await fetch(this.endpoint, {
|
|
29
|
-
method: "POST",
|
|
30
|
-
signal: AbortSignal.timeout(this.timeoutMs),
|
|
31
|
-
headers: {
|
|
32
|
-
"content-type": "application/json",
|
|
33
|
-
authorization: `Bearer ${this.apiKey}`,
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify(report),
|
|
36
|
-
});
|
|
37
|
-
if (res.status !== 202) {
|
|
38
|
-
throw new Error(`Buckets report rejected: HTTP ${res.status}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=sink.js.map
|
package/dist/sink.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sink.js","sourceRoot":"","sources":["../src/sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsCH,MAAM,gBAAgB,GAAG,8CAA8C,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IACJ,QAAQ,CAAS;IACjB,MAAM,CAAS;IACf,SAAS,CAAS;IAEnC,YAAY,MAAwB;QAClC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,gBAAgB,CAAC;QACpD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAqB;QAC/B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;YAC3C,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;aACvC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SAC7B,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;CACF"}
|