@adriangalilea/utils 4.1.0 → 5.0.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/README.md +32 -18
- package/dist/metrics/d1.d.ts +16 -0
- package/dist/metrics/d1.d.ts.map +1 -0
- package/dist/metrics/d1.js +11 -0
- package/dist/metrics/d1.js.map +1 -0
- package/dist/metrics/libsql.d.ts +11 -0
- package/dist/metrics/libsql.d.ts.map +1 -0
- package/dist/metrics/libsql.js +8 -0
- package/dist/metrics/libsql.js.map +1 -0
- package/dist/metrics/report.d.ts +23 -0
- package/dist/metrics/report.d.ts.map +1 -1
- package/dist/metrics/report.js +2 -0
- package/dist/metrics/report.js.map +1 -1
- package/dist/metrics/sqlite.d.ts +31 -15
- package/dist/metrics/sqlite.d.ts.map +1 -1
- package/dist/metrics/sqlite.js +71 -14
- package/dist/metrics/sqlite.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -7,21 +7,36 @@ TypeScript utilities - logger, currency, offensive programming, file operations,
|
|
|
7
7
|
```ts
|
|
8
8
|
import { createClient } from "@libsql/client"
|
|
9
9
|
import { defineMetrics } from "@adriangalilea/utils/metrics"
|
|
10
|
-
import {
|
|
10
|
+
import { libsqlDriver } from "@adriangalilea/utils/metrics/libsql"
|
|
11
|
+
import { metricsWriter } from "@adriangalilea/utils/metrics/sqlite"
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const driver = libsqlDriver(createClient({ url: process.env.METRICS_DATABASE_URL!, authToken: process.env.METRICS_AUTH_TOKEN }))
|
|
13
14
|
const metrics = defineMetrics({
|
|
14
15
|
installCopy: { kind: "counter", label: "install command copies", dimensions: { component: ["chat", "glass"] } },
|
|
15
|
-
}, { write:
|
|
16
|
+
}, { write: metricsWriter(driver, "my-project") })
|
|
16
17
|
|
|
17
18
|
await metrics.installCopy.bump({ dimensions: { component: "chat" } })
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
One SQLite dialect, many stores. `metrics/sqlite` holds the schema, the writer and
|
|
22
|
+
the readers over a `MetricsDriver`: two verbs, `query(statement)` for rows and
|
|
23
|
+
`transact(statements)` for an all-or-nothing write batch. `metrics/libsql` wraps a
|
|
24
|
+
Turso / libSQL client, `metrics/d1` wraps a Cloudflare D1 binding (`d1Driver(env.DB)`),
|
|
25
|
+
each with the driver's native calls and structural types, so no database client is
|
|
26
|
+
bundled. Any other SQLite driver implements the two verbs in a few lines (the test
|
|
27
|
+
does it over `node:sqlite`). A store that does not speak SQLite implements the
|
|
28
|
+
injected `write(measurement)` contract and its own readers returning the
|
|
29
|
+
`metrics/report` types.
|
|
30
|
+
|
|
20
31
|
Provision the exported `METRICS_SCHEMA` once before collecting. `describe()` returns
|
|
21
|
-
the declarations; the
|
|
22
|
-
discover metrics without another hardcoded list. `readMetrics(
|
|
32
|
+
the declarations; the writer stores labels beside daily counters so reports
|
|
33
|
+
discover metrics without another hardcoded list. `readMetrics(driver, { from, to,
|
|
23
34
|
project? })` returns daily rows with labels and dimensions, using inclusive UTC dates.
|
|
24
|
-
|
|
35
|
+
`readAudience(driver, { from, to, project }, describe().overlaps)` answers who did it
|
|
36
|
+
from the opt-in per-user rows: per key, distinct actors, actors with more than one
|
|
37
|
+
sample on a single day, actors seen on more than one day, then each declared
|
|
38
|
+
overlap as `fromUsers` and `bothUsers`. Readers propagate database errors rather
|
|
39
|
+
than presenting missing data as zero.
|
|
25
40
|
`summarizeMetrics(rows)` from `metrics/report` produces one generic report shape;
|
|
26
41
|
`renderMetrics(rows, { daily? })` from `metrics/cli` renders it with the existing
|
|
27
42
|
ANSI-aware `cli.table` helpers. Both discover every recorded metric from its
|
|
@@ -37,8 +52,8 @@ disappearing series, count changes and weighted timing-average changes. Percent
|
|
|
37
52
|
change is null without a nonzero baseline; missing timing samples stay null.
|
|
38
53
|
Changing a metric's kind or unit across the inputs is rejected. Missing observations
|
|
39
54
|
are not proof that collection was running: deltas compare recorded data only.
|
|
40
|
-
The
|
|
41
|
-
|
|
55
|
+
The writer rejects changes to an existing key's kind or unit atomically, before
|
|
56
|
+
adding observations. Use a new key for a new meaning; labels and help can be edited.
|
|
42
57
|
|
|
43
58
|
Measurements validate finite nonnegative samples, positive integer counts, and
|
|
44
59
|
declared dimension values. Calls resolve even when validation or storage fails;
|
|
@@ -53,16 +68,15 @@ Enable it only with a deletion and retention policy; no identity is inferred.
|
|
|
53
68
|
`overlaps: [["a", "b"]]` declares unordered audience overlap, not an ordered funnel.
|
|
54
69
|
Daily totals support counts and averages, not percentile latency or event ordering.
|
|
55
70
|
|
|
56
|
-
**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
identifiers: introduce a new key when changing meaning rather than relabelling history.
|
|
71
|
+
**5.0 migration:** `sqliteMetricsWriter(client, project)` and `MetricsDatabase` are
|
|
72
|
+
gone. Wrap the client in a driver, `libsqlDriver(client)` or `d1Driver(binding)`, and
|
|
73
|
+
pass it to `metricsWriter(driver, project)`, `readMetrics(driver, …)` and the new
|
|
74
|
+
`readAudience(driver, …)`. A private store with its own tables moves its rows into
|
|
75
|
+
`METRICS_SCHEMA` (`project`, `key`, `dimensions` as `'{}'` when it had none, `day`,
|
|
76
|
+
`user` as text) and seeds `metric_definition` for the keys it already holds, or the
|
|
77
|
+
history stays invisible to `readMetrics` until each key's first write. Metric keys and
|
|
78
|
+
dimensions are storage identifiers: introduce a new key when changing meaning rather
|
|
79
|
+
than relabelling history.
|
|
66
80
|
|
|
67
81
|
## Installation
|
|
68
82
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MetricsDriver } from "./sqlite.js";
|
|
2
|
+
/** The slice of Cloudflare D1 the metrics driver touches, typed structurally: no workers-types dependency. */
|
|
3
|
+
export interface D1Statement {
|
|
4
|
+
bind(...values: unknown[]): D1Statement;
|
|
5
|
+
all<T = Record<string, unknown>>(): Promise<{
|
|
6
|
+
results: T[];
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export interface D1Binding {
|
|
10
|
+
prepare(sql: string): D1Statement;
|
|
11
|
+
/** D1 runs a batch as one transaction: a failing statement rolls back the whole sequence. */
|
|
12
|
+
batch(statements: D1Statement[]): Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** A D1 binding as a metrics driver. */
|
|
15
|
+
export declare function d1Driver(db: D1Binding): MetricsDriver;
|
|
16
|
+
//# sourceMappingURL=d1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/metrics/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,8GAA8G;AAC9G,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC;IACxC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;CAC9D;AACD,MAAM,WAAW,SAAS;IACzB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;IAClC,6FAA6F;IAC7F,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnD;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,aAAa,CAYrD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A D1 binding as a metrics driver. */
|
|
2
|
+
export function d1Driver(db) {
|
|
3
|
+
return {
|
|
4
|
+
query: async ({ sql, args }) => (await db
|
|
5
|
+
.prepare(sql)
|
|
6
|
+
.bind(...args)
|
|
7
|
+
.all()).results,
|
|
8
|
+
transact: (statements) => db.batch(statements.map((s) => db.prepare(s.sql).bind(...s.args))),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=d1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/metrics/d1.ts"],"names":[],"mappings":"AAaA,wCAAwC;AACxC,MAAM,UAAU,QAAQ,CAAC,EAAa;IACrC,OAAO;QACN,KAAK,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAC9B,CACC,MAAM,EAAE;aACN,OAAO,CAAC,GAAG,CAAC;aACZ,IAAI,CAAC,GAAG,IAAI,CAAC;aACb,GAAG,EAAE,CACP,CAAC,OAAO;QACV,QAAQ,EAAE,CAAC,UAAU,EAAE,EAAE,CACxB,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;KACnE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MetricsDriver, SqlStatement } from "./sqlite.js";
|
|
2
|
+
/** The slice of `@libsql/client` the metrics driver touches, typed structurally: no client is bundled. */
|
|
3
|
+
export interface LibsqlClient {
|
|
4
|
+
execute(statement: SqlStatement): Promise<{
|
|
5
|
+
rows: Iterable<Record<string, unknown>>;
|
|
6
|
+
}>;
|
|
7
|
+
batch(statements: SqlStatement[], mode: "write"): Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
/** A Turso / libSQL / sqld connection as a metrics driver; `batch` in write mode is one transaction. */
|
|
10
|
+
export declare function libsqlDriver(client: LibsqlClient): MetricsDriver;
|
|
11
|
+
//# sourceMappingURL=libsql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libsql.d.ts","sourceRoot":"","sources":["../../src/metrics/libsql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,0GAA0G;AAC1G,MAAM,WAAW,YAAY;IAC5B,OAAO,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC;QACzC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC,CAAC;IACH,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,wGAAwG;AACxG,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAKhE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A Turso / libSQL / sqld connection as a metrics driver; `batch` in write mode is one transaction. */
|
|
2
|
+
export function libsqlDriver(client) {
|
|
3
|
+
return {
|
|
4
|
+
query: async (statement) => (await client.execute(statement)).rows,
|
|
5
|
+
transact: (statements) => client.batch(statements, "write"),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=libsql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libsql.js","sourceRoot":"","sources":["../../src/metrics/libsql.ts"],"names":[],"mappings":"AAUA,wGAAwG;AACxG,MAAM,UAAU,YAAY,CAAC,MAAoB;IAChD,OAAO;QACN,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QAClE,QAAQ,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC;KAC3D,CAAC;AACH,CAAC"}
|
package/dist/metrics/report.d.ts
CHANGED
|
@@ -11,6 +11,29 @@ export interface DailyMetric {
|
|
|
11
11
|
count: number;
|
|
12
12
|
sum: number;
|
|
13
13
|
}
|
|
14
|
+
/** Per key, from opt-in per-user rows: keys nobody touched in the window are absent. */
|
|
15
|
+
export interface AudienceMetric {
|
|
16
|
+
project: string;
|
|
17
|
+
key: string;
|
|
18
|
+
/** Distinct actors. */
|
|
19
|
+
uniques: number;
|
|
20
|
+
/** Actors with more than one sample on a single day. */
|
|
21
|
+
repeatUsers: number;
|
|
22
|
+
/** Actors seen on more than one day. */
|
|
23
|
+
returningUsers: number;
|
|
24
|
+
}
|
|
25
|
+
/** Unordered: how many of `from`'s actors also touched `to`, in the same window. */
|
|
26
|
+
export interface AudienceOverlap {
|
|
27
|
+
project: string;
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
fromUsers: number;
|
|
31
|
+
bothUsers: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Audience {
|
|
34
|
+
metrics: AudienceMetric[];
|
|
35
|
+
overlaps: AudienceOverlap[];
|
|
36
|
+
}
|
|
14
37
|
export interface MetricTotal {
|
|
15
38
|
project: string;
|
|
16
39
|
key: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/metrics/report.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,YAAY,CAAC;IAC1B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO;;;;;;;;;;EAkBpD;AASD,yFAAyF;AACzF,wBAAgB,cAAc,CAC7B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC9B,gBAAgB,EAAE,CA4CpB;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/metrics/report.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,YAAY,CAAC;IAC1B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO;;;;;;;;;;EAkBpD;AASD,yFAAyF;AACzF,wBAAgB,cAAc,CAC7B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC9B,gBAAgB,EAAE,CA4CpB;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAqC5E"}
|
package/dist/metrics/report.js
CHANGED
|
@@ -74,6 +74,8 @@ export function summarizeMetrics(rows) {
|
|
|
74
74
|
sum: 0,
|
|
75
75
|
average: null,
|
|
76
76
|
};
|
|
77
|
+
if (total.kind !== row.kind || total.unit !== row.unit)
|
|
78
|
+
throw new Error(`metrics: changed meaning for ${row.project}.${row.key}; use a new key`);
|
|
77
79
|
total.count += row.count;
|
|
78
80
|
total.sum += row.sum;
|
|
79
81
|
total.average =
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/metrics/report.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/metrics/report.ts"],"names":[],"mappings":"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"}
|
package/dist/metrics/sqlite.d.ts
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import type { Measurement } from "./index.js";
|
|
2
|
-
import type { DailyMetric } from "./report.js";
|
|
3
|
-
|
|
4
|
-
export interface
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
import type { Audience, DailyMetric } from "./report.js";
|
|
3
|
+
export type SqlValue = string | number;
|
|
4
|
+
export interface SqlStatement {
|
|
5
|
+
sql: string;
|
|
6
|
+
args: SqlValue[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The SQLite dialect below needs exactly two verbs from a store. `metrics/libsql` and
|
|
10
|
+
* `metrics/d1` implement them with each driver's native calls; any driver that speaks
|
|
11
|
+
* SQLite (node:sqlite, bun:sqlite, better-sqlite3) does the same in a few lines.
|
|
12
|
+
*/
|
|
13
|
+
export interface MetricsDriver {
|
|
14
|
+
/** The rows of one read statement. */
|
|
15
|
+
query(statement: SqlStatement): Promise<Iterable<Record<string, unknown>>>;
|
|
16
|
+
/** Every statement or none: a constraint failure in any of them rolls the batch back. */
|
|
17
|
+
transact(statements: SqlStatement[]): Promise<unknown>;
|
|
15
18
|
}
|
|
16
19
|
/** Run explicitly during provisioning, never on a user's request. */
|
|
17
20
|
export declare const METRICS_SCHEMA: readonly ["CREATE TABLE IF NOT EXISTS metric_definition (project TEXT NOT NULL, key TEXT NOT NULL, kind TEXT NOT NULL, label TEXT NOT NULL, help TEXT NOT NULL, unit TEXT NOT NULL, PRIMARY KEY(project, key))", "CREATE TABLE IF NOT EXISTS metric_daily (project TEXT NOT NULL, key TEXT NOT NULL, dimensions TEXT NOT NULL, day TEXT NOT NULL, count INTEGER NOT NULL CHECK(count >= 0), sum REAL NOT NULL CHECK(sum >= 0), PRIMARY KEY(project, key, dimensions, day))", "CREATE TABLE IF NOT EXISTS metric_user_daily (project TEXT NOT NULL, key TEXT NOT NULL, dimensions TEXT NOT NULL, day TEXT NOT NULL, user TEXT NOT NULL, count INTEGER NOT NULL CHECK(count >= 0), sum REAL NOT NULL CHECK(sum >= 0), PRIMARY KEY(project, key, dimensions, day, user))", "CREATE INDEX IF NOT EXISTS metric_user_identity ON metric_user_daily(project, user)"];
|
|
18
|
-
export declare function
|
|
21
|
+
export declare function metricsWriter(driver: MetricsDriver, project: string): (m: Measurement) => Promise<void>;
|
|
19
22
|
/** Inclusive UTC dates. Errors propagate: unavailable data must never read as zero. */
|
|
20
|
-
export declare function readMetrics(
|
|
23
|
+
export declare function readMetrics(driver: MetricsDriver, range: {
|
|
21
24
|
from: string;
|
|
22
25
|
to: string;
|
|
23
26
|
project?: string;
|
|
24
27
|
}): Promise<DailyMetric[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Who did it, over the opt-in per-user rows of one project, all dimensions folded: per key,
|
|
30
|
+
* distinct actors, actors with more than one sample on a single day, actors seen on more
|
|
31
|
+
* than one day; then the declared unordered overlaps. Keys nobody touched are absent.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readAudience(driver: MetricsDriver, range: {
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
project: string;
|
|
37
|
+
}, overlaps: ReadonlyArray<{
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
}>): Promise<Audience>;
|
|
25
41
|
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/metrics/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/metrics/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AACvC,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,sCAAsC;IACtC,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3E,yFAAyF;IACzF,QAAQ,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,g0BAKjB,CAAC;AAiBX,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAErD,GAAG,WAAW,mBAoC5B;AAED,uFAAuF;AACvF,wBAAsB,WAAW,CAChC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAAC,WAAW,EAAE,CAAC,CAmBxB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACpD,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,OAAO,CAAC,QAAQ,CAAC,CA0CnB"}
|
package/dist/metrics/sqlite.js
CHANGED
|
@@ -5,14 +5,29 @@ export const METRICS_SCHEMA = [
|
|
|
5
5
|
`CREATE TABLE IF NOT EXISTS metric_user_daily (project TEXT NOT NULL, key TEXT NOT NULL, dimensions TEXT NOT NULL, day TEXT NOT NULL, user TEXT NOT NULL, count INTEGER NOT NULL CHECK(count >= 0), sum REAL NOT NULL CHECK(sum >= 0), PRIMARY KEY(project, key, dimensions, day, user))`,
|
|
6
6
|
`CREATE INDEX IF NOT EXISTS metric_user_identity ON metric_user_daily(project, user)`,
|
|
7
7
|
];
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const PROJECT = /^[a-z][a-z0-9-]*$/;
|
|
9
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
|
+
function assertProject(project) {
|
|
11
|
+
if (!PROJECT.test(project))
|
|
10
12
|
throw new Error("metrics: invalid project");
|
|
13
|
+
}
|
|
14
|
+
function assertRange(range) {
|
|
15
|
+
for (const day of [range.from, range.to]) {
|
|
16
|
+
if (!DAY.test(day) || new Date(day).toISOString().slice(0, 10) !== day)
|
|
17
|
+
throw new Error("metrics: invalid UTC date");
|
|
18
|
+
}
|
|
19
|
+
if (range.from > range.to)
|
|
20
|
+
throw new Error("metrics: reversed date range");
|
|
21
|
+
}
|
|
22
|
+
export function metricsWriter(driver, project) {
|
|
23
|
+
assertProject(project);
|
|
11
24
|
return async (m) => {
|
|
12
25
|
const dimensions = JSON.stringify(m.dimensions);
|
|
13
26
|
const statements = [
|
|
14
27
|
{
|
|
15
|
-
|
|
28
|
+
// NOT NULL makes a changed kind/unit fail the whole atomic batch, including counts.
|
|
29
|
+
// Labels/help may evolve, but historical measurements must retain their meaning.
|
|
30
|
+
sql: `INSERT INTO metric_definition VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(project, key) DO UPDATE SET kind=CASE WHEN kind=excluded.kind THEN kind ELSE NULL END, label=excluded.label, help=excluded.help, unit=CASE WHEN unit=excluded.unit THEN unit ELSE NULL END`,
|
|
16
31
|
args: [
|
|
17
32
|
project,
|
|
18
33
|
m.key,
|
|
@@ -40,23 +55,19 @@ export function sqliteMetricsWriter(db, project) {
|
|
|
40
55
|
m.sum,
|
|
41
56
|
],
|
|
42
57
|
});
|
|
43
|
-
await
|
|
58
|
+
await driver.transact(statements);
|
|
44
59
|
};
|
|
45
60
|
}
|
|
46
61
|
/** Inclusive UTC dates. Errors propagate: unavailable data must never read as zero. */
|
|
47
|
-
export async function readMetrics(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
if (range.from > range.to)
|
|
54
|
-
throw new Error("metrics: reversed date range");
|
|
55
|
-
const result = await db.execute({
|
|
62
|
+
export async function readMetrics(driver, range) {
|
|
63
|
+
assertRange(range);
|
|
64
|
+
if (range.project !== undefined)
|
|
65
|
+
assertProject(range.project);
|
|
66
|
+
const rows = await driver.query({
|
|
56
67
|
sql: `SELECT d.*, s.label, s.kind, s.help, s.unit FROM metric_daily d JOIN metric_definition s USING(project, key) WHERE day >= ? AND day <= ? ${range.project ? "AND project = ?" : ""} ORDER BY project, key, dimensions, day`,
|
|
57
68
|
args: [range.from, range.to, ...(range.project ? [range.project] : [])],
|
|
58
69
|
});
|
|
59
|
-
return Array.from(
|
|
70
|
+
return Array.from(rows, (r) => ({
|
|
60
71
|
project: String(r.project),
|
|
61
72
|
key: String(r.key),
|
|
62
73
|
label: String(r.label),
|
|
@@ -69,4 +80,50 @@ export async function readMetrics(db, range) {
|
|
|
69
80
|
sum: Number(r.sum),
|
|
70
81
|
}));
|
|
71
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Who did it, over the opt-in per-user rows of one project, all dimensions folded: per key,
|
|
85
|
+
* distinct actors, actors with more than one sample on a single day, actors seen on more
|
|
86
|
+
* than one day; then the declared unordered overlaps. Keys nobody touched are absent.
|
|
87
|
+
*/
|
|
88
|
+
export async function readAudience(driver, range, overlaps) {
|
|
89
|
+
assertRange(range);
|
|
90
|
+
assertProject(range.project);
|
|
91
|
+
const window = [range.project, range.from, range.to];
|
|
92
|
+
const perKey = await driver.query({
|
|
93
|
+
sql: `WITH per_day AS (SELECT key, user, day, SUM(count) AS count FROM metric_user_daily WHERE project = ? AND day >= ? AND day <= ? GROUP BY key, user, day), per_user AS (SELECT key, user, COUNT(*) AS days, MAX(count) AS peak FROM per_day GROUP BY key, user) SELECT key, COUNT(*) AS uniques, SUM(CASE WHEN peak > 1 THEN 1 ELSE 0 END) AS repeat_users, SUM(CASE WHEN days > 1 THEN 1 ELSE 0 END) AS returning_users FROM per_user GROUP BY key ORDER BY key`,
|
|
94
|
+
args: [...window],
|
|
95
|
+
});
|
|
96
|
+
const metrics = Array.from(perKey, (r) => ({
|
|
97
|
+
project: range.project,
|
|
98
|
+
key: String(r.key),
|
|
99
|
+
uniques: Number(r.uniques),
|
|
100
|
+
repeatUsers: Number(r.repeat_users),
|
|
101
|
+
returningUsers: Number(r.returning_users),
|
|
102
|
+
}));
|
|
103
|
+
const shared = [];
|
|
104
|
+
const actors = `SELECT DISTINCT user FROM metric_user_daily WHERE project = ? AND key = ? AND day >= ? AND day <= ?`;
|
|
105
|
+
for (const pair of overlaps) {
|
|
106
|
+
const [row] = Array.from(await driver.query({
|
|
107
|
+
sql: `WITH a AS (${actors}), b AS (${actors}) SELECT (SELECT COUNT(*) FROM a) AS from_users, (SELECT COUNT(*) FROM a WHERE user IN (SELECT user FROM b)) AS both_users`,
|
|
108
|
+
args: [
|
|
109
|
+
range.project,
|
|
110
|
+
pair.from,
|
|
111
|
+
range.from,
|
|
112
|
+
range.to,
|
|
113
|
+
range.project,
|
|
114
|
+
pair.to,
|
|
115
|
+
range.from,
|
|
116
|
+
range.to,
|
|
117
|
+
],
|
|
118
|
+
}));
|
|
119
|
+
shared.push({
|
|
120
|
+
project: range.project,
|
|
121
|
+
from: pair.from,
|
|
122
|
+
to: pair.to,
|
|
123
|
+
fromUsers: Number(row?.from_users ?? 0),
|
|
124
|
+
bothUsers: Number(row?.both_users ?? 0),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return { metrics, overlaps: shared };
|
|
128
|
+
}
|
|
72
129
|
//# sourceMappingURL=sqlite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/metrics/sqlite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/metrics/sqlite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adriangalilea/utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -192,6 +192,14 @@
|
|
|
192
192
|
"types": "./dist/metrics/sqlite.d.ts",
|
|
193
193
|
"default": "./dist/metrics/sqlite.js"
|
|
194
194
|
},
|
|
195
|
+
"./metrics/libsql": {
|
|
196
|
+
"types": "./dist/metrics/libsql.d.ts",
|
|
197
|
+
"default": "./dist/metrics/libsql.js"
|
|
198
|
+
},
|
|
199
|
+
"./metrics/d1": {
|
|
200
|
+
"types": "./dist/metrics/d1.d.ts",
|
|
201
|
+
"default": "./dist/metrics/d1.js"
|
|
202
|
+
},
|
|
195
203
|
"./metrics/report": {
|
|
196
204
|
"types": "./dist/metrics/report.d.ts",
|
|
197
205
|
"default": "./dist/metrics/report.js"
|