@adriangalilea/utils 9.13.0 → 10.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 CHANGED
@@ -79,21 +79,12 @@ Daily totals support counts and averages, not percentile latency or event orderi
79
79
 
80
80
  ## Visits
81
81
 
82
- A site keeps its own visits: ui's `page-sensor` posts each page's lines (the utils contract, "Web pages") to the site's own route, and `@adriangalilea/utils/visits` puts each page in a visit and keeps the rows the views read (ui's `user-flows`). No other server, no cookie, nothing kept in the browser.
82
+ A site's own visits live in the [ui registry](https://ui.adriangalilea.com/visits) as one family: `page-sensor` (the page), `visits` (the route, the store, the readers) and `user-flows` (the views), installed into the site as code it owns (`npx shadcn add @ag/page-sensor` brings `visits`). They build on this package's logger (the event stream the pages write) and its metrics drivers (the store).
83
83
 
84
- ```typescript
85
- // app/api/events/route.ts
86
- import { libsqlDriver } from '@adriangalilea/utils/metrics/libsql'
87
- import { visitsRoute } from '@adriangalilea/utils/visits'
88
-
89
- export const POST = visitsRoute({ db: libsqlDriver(client), site: 'untitled.garden', services: ['garden-web'] })
84
+ #### Migrating from 9.x
90
85
 
91
- // a view
92
- const visits = await readVisits(libsqlDriver(client), { site: 'untitled.garden', days: 14 })
93
- // [{ id, started, places: ['studio', 'lore'], referrer: 'news.ycombinator.com', country: 'ES', … }]
94
- ```
86
+ `@adriangalilea/utils/visits` is gone. Install the registry's `visits` and import from your copy (`@/lib/visits`): the same functions, the same tables and rows.
95
87
 
96
- A visit is one caller's pages, each within 30 minutes of the one before, within one UTC day. The caller is a hash of its IP and user agent with the site and a random salt of the day, deleted when the day is over: a visitor is recognisable within a day, never across two, and once the salt is gone by no one, the site's owner included. One store can hold several sites: a person reading two of them is two callers, never linked. The IP and user agent are never stored; the country (two letters, from the platform's header) is. Pages and visits are kept 180 days, a page's lines (its own start and end, requests, named events, errors) 30, a probe's (an automated browser) and the staff's 7, and both are left out of `readVisits` unless `flagged`. A page flags itself a probe (`navigator.webdriver`); the route's `staff(request)` flags the pages a batch starts as the site's own people reading, answered on the server where the session is (a signed-in owner, a development host). Refused, by line: a service the site did not name, a line outside a page's trace, a field named like a person (`ip`, `user_agent`, `email`, `user`, `name`, `phone`). A crawler's batch is dropped whole. Provision `VISITS_SCHEMA` once (the tables are prefixed `visits_`); the route updates and deletes, so its credential needs both. `readVisits` merges a move within a place (two pages of one project are one step, `pages` counts them), and `events: ['main action']` makes those named lines steps too, right after the page they were written on, as `eventStep(place, name)` (`trash:main action`): where a project's funnel ends. `readVisit(db, id)` returns one visit's pages and its lines as written: every page's trace, read back from the lines alone (ui's `spanTrees`). A page's view count is kept apart, for good: per day and page, how many visits read it, each once and the staff's and probes' never, a number with nothing of who (`visits_views`). A visit reads a page by opening it, or by being shown it whole elsewhere, as a note in a feed is read like a tweet in a timeline: the page writes a `view` line naming it (`{ host, path }`, ui's page-sensor does for `data-view` elements half on screen), counted once per visit with the page itself; `readViews(db, { site, host, paths })` sums it over all time, what a public "N views" reads.
97
88
 
98
89
  ## Installation
99
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "9.13.0",
3
+ "version": "10.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",
@@ -44,10 +44,6 @@
44
44
  "types": "./dist/universal/log.d.ts",
45
45
  "default": "./dist/universal/log.js"
46
46
  },
47
- "./visits": {
48
- "types": "./dist/visits/index.d.ts",
49
- "default": "./dist/visits/index.js"
50
- },
51
47
  "./log/next": {
52
48
  "types": "./dist/next/log.d.ts",
53
49
  "default": "./dist/next/log.js"
@@ -231,10 +227,9 @@
231
227
  "scripts": {
232
228
  "build": "rm -rf dist && tsc",
233
229
  "test": "tsx tests/metrics-test.ts",
234
- "test:all": "pnpm run test && pnpm run test:log && pnpm run test:log-ship && pnpm run test:visits &&pnpm run test:runtime && pnpm run test:text && pnpm run test:tg-html && pnpm run test:tg-md && pnpm run test:flags && pnpm run test:groups && pnpm run test:user && pnpm run test:language-picker && pnpm run test:self-echo && pnpm run test:update-identity && pnpm run test:update-runner &&pnpm run test:bot-text && pnpm run test:url && pnpm run test:bot-urls && pnpm run test:bot-updates && pnpm run test:bot-announce && pnpm run test:reactions && pnpm run test:llm-reset && pnpm run test:raw-writes && pnpm run test:worker-safe",
230
+ "test:all": "pnpm run test && pnpm run test:log && pnpm run test:log-ship && pnpm run test:runtime && pnpm run test:text && pnpm run test:tg-html && pnpm run test:tg-md && pnpm run test:flags && pnpm run test:groups && pnpm run test:user && pnpm run test:language-picker && pnpm run test:self-echo && pnpm run test:update-identity && pnpm run test:update-runner &&pnpm run test:bot-text && pnpm run test:url && pnpm run test:bot-urls && pnpm run test:bot-updates && pnpm run test:bot-announce && pnpm run test:reactions && pnpm run test:llm-reset && pnpm run test:raw-writes && pnpm run test:worker-safe",
235
231
  "test:log": "tsx tests/log-test.ts",
236
232
  "test:log-ship": "tsx tests/log-ship-test.ts",
237
- "test:visits": "tsx tests/visits-test.ts",
238
233
  "test:runtime": "tsx tests/runtime-test.ts",
239
234
  "test:text": "tsx tests/text-test.ts",
240
235
  "example": "tsx tests/example.ts",
@@ -1,131 +0,0 @@
1
- /**
2
- * Visits, kept by the site itself: the server half of a page in the event
3
- * stream (utils README, "Web pages"). ui's page-sensor posts the page's lines
4
- * to the site's own route; this module reads them, puts each page in a visit
5
- * and keeps the rows the views read (ui's user-flows). Framework-free: a
6
- * route is a Request in and a Response out, and the store is the app's own
7
- * SQLite through the metrics drivers (libSQL / Turso, D1).
8
- *
9
- * // app/api/events/route.ts
10
- * export const POST = visitsRoute({ db: libsqlDriver(client), site: "untitled.garden", services: ["garden-web"] })
11
- *
12
- * // a view
13
- * const visits = await readVisits(libsqlDriver(client), { site: "untitled.garden", days: 14 })
14
- *
15
- * A VISIT is one caller's pages, each within VISIT_GAP_MS of the one before,
16
- * within one UTC day. The caller is a hash of its IP and user agent with the
17
- * site and a random salt of the day, the salt deleted once the day is over (one
18
- * store can hold several sites, never linking a person across them): a visitor is
19
- * recognisable within a day, never across two, and once the salt is gone by no
20
- * one. The IP and user agent are never stored; nothing is kept in the browser.
21
- *
22
- * ROWS: `visits_visit` (one per visit: when, how many pages, where it came
23
- * from, coarse country and viewport, whether a probe or the site's staff made
24
- * it), `visits_page` (one per page span: its place, path, how it was reached,
25
- * how long it was on screen, the sections it reached, the vitals),
26
- * `visits_event` (every line of the page's trace as written: the page span's
27
- * own start and end, fetch spans, named events, errors, so a page's trace is
28
- * read back from its lines alone, on the visitor's one clock) and
29
- * `visits_views` (per day, per page, how many visits read it, each once, the
30
- * staff's and probes' never: a count with nothing of who; a visit reads a page
31
- * by opening it or by a `view` line naming it, a post shown whole elsewhere). Pages and visits
32
- * are kept VISIT_DAYS, events EVENT_DAYS, a probe's or staff's FLAGGED_DAYS;
33
- * old rows go on the first write of each day. Views are kept for good: they
34
- * are what a page's all-time count is read from (`readViews`). Provision
35
- * VISITS_SCHEMA once, never on a request.
36
- *
37
- * REFUSED: a line that is no event, a service the site did not name, a line
38
- * outside a page's trace, a field named like a person (ip, user_agent, email,
39
- * user, name, phone). A crawler's batch is dropped whole and answered 204.
40
- */
41
- import type { MetricsDriver, MetricsReader } from "../metrics/sqlite.js";
42
- /** Pages further apart than this are two visits, as every analytics tool counts them. */
43
- export declare const VISIT_GAP_MS: number;
44
- export declare const VISIT_DAYS = 180;
45
- export declare const EVENT_DAYS = 30;
46
- /** A probe's or the staff's visits: the developer's view, kept a week. */
47
- export declare const FLAGGED_DAYS = 7;
48
- /** Run explicitly during provisioning, never on a request. */
49
- export declare const VISITS_SCHEMA: readonly ["CREATE TABLE IF NOT EXISTS visits_salt (day TEXT PRIMARY KEY, salt TEXT NOT NULL)", "CREATE TABLE IF NOT EXISTS visits_caller (day TEXT NOT NULL, caller TEXT NOT NULL, visit TEXT NOT NULL, last INTEGER NOT NULL, PRIMARY KEY (day, caller))", "CREATE TABLE IF NOT EXISTS visits_visit (id TEXT PRIMARY KEY, site TEXT NOT NULL, day TEXT NOT NULL, started INTEGER NOT NULL, last INTEGER NOT NULL, pages INTEGER NOT NULL, referrer TEXT, source TEXT, country TEXT, viewport TEXT, probe INTEGER NOT NULL, staff INTEGER NOT NULL)", "CREATE INDEX IF NOT EXISTS visits_visit_day ON visits_visit (site, day)", "CREATE TABLE IF NOT EXISTS visits_page (span_id TEXT PRIMARY KEY, trace_id TEXT NOT NULL, visit TEXT NOT NULL, seq INTEGER NOT NULL, site TEXT NOT NULL, day TEXT NOT NULL, at INTEGER NOT NULL, host TEXT NOT NULL, path TEXT NOT NULL, place TEXT, route TEXT, nav TEXT NOT NULL, from_path TEXT, build TEXT, dur_ms REAL, reached TEXT, ttfb_ms REAL, lcp_ms REAL, inp_ms REAL, cls REAL)", "CREATE INDEX IF NOT EXISTS visits_page_visit ON visits_page (visit, seq)", "CREATE INDEX IF NOT EXISTS visits_page_trace ON visits_page (trace_id)", "CREATE INDEX IF NOT EXISTS visits_page_day ON visits_page (site, day)", "CREATE TABLE IF NOT EXISTS visits_event (id INTEGER PRIMARY KEY, visit TEXT NOT NULL, trace_id TEXT NOT NULL, day TEXT NOT NULL, at INTEGER NOT NULL, line TEXT NOT NULL)", "CREATE INDEX IF NOT EXISTS visits_event_visit ON visits_event (visit, id)", "CREATE INDEX IF NOT EXISTS visits_event_day ON visits_event (day)", "CREATE TABLE IF NOT EXISTS visits_views (site TEXT NOT NULL, day TEXT NOT NULL, host TEXT NOT NULL, path TEXT NOT NULL, views INTEGER NOT NULL, PRIMARY KEY (site, day, host, path))"];
50
- export interface VisitsOptions {
51
- /** The site's own SQLite, VISITS_SCHEMA provisioned. */
52
- readonly db: MetricsDriver;
53
- /** The registrable domain whose pages these are: its subdomains share its visits. */
54
- readonly site: string;
55
- /** The services the site's pages write as (`garden-web`); any other line is refused. */
56
- readonly services: readonly string[];
57
- /** Who called, from the request. The default reads Cloudflare's, then Vercel's, then any proxy's headers. */
58
- readonly caller?: (request: Request) => Caller;
59
- /**
60
- * The site's own people reading (its signed-in owner, a development host): the
61
- * pages a batch starts are flagged staff, as a page saying so itself is. Asked
62
- * only of a batch that starts a page, on the server, where the session is.
63
- */
64
- readonly staff?: (request: Request) => boolean | Promise<boolean>;
65
- readonly now?: () => number;
66
- }
67
- /** The request's caller: hashed with the day's salt, never stored. `country` is two letters, kept. */
68
- export interface Caller {
69
- readonly ip: string;
70
- readonly agent: string;
71
- readonly country?: string;
72
- }
73
- export declare function callerOf(request: Request): Caller;
74
- export interface Rejection {
75
- readonly line: number;
76
- readonly why: string;
77
- }
78
- /** The route: a page's batch in, `204`, or `400 {accepted, rejected}` with the accepted lines kept. */
79
- export declare function visitsRoute(options: VisitsOptions): (request: Request) => Promise<Response>;
80
- /**
81
- * One visit, as the views read it: the places it moved through in order (a
82
- * page's place, else its path), a move within a place being no new one, and
83
- * how many pages it saw.
84
- */
85
- export interface VisitRow {
86
- readonly id: string;
87
- readonly started: number;
88
- readonly places: readonly string[];
89
- readonly pages: number;
90
- /** The outside host it landed from. */
91
- readonly referrer?: string;
92
- readonly source?: string;
93
- readonly country?: string;
94
- readonly viewport?: string;
95
- readonly probe: boolean;
96
- readonly staff: boolean;
97
- }
98
- /** A named event as a step of a visit: where it happened, then its name
99
- * (`trash:main action`), right after the page it was written on. */
100
- export declare const eventStep: (place: string, event: string) => string;
101
- /**
102
- * The visits of the last `days` UTC days, today included, oldest first. A
103
- * probe's and the staff's are left out unless `flagged`: they are no product
104
- * number. `events` names the lines (by message) that are steps of a visit too,
105
- * each right after the page it was written on, as `eventStep(place, name)`: a
106
- * project's main action is where a funnel ends.
107
- */
108
- export declare function readVisits(db: MetricsReader, q: {
109
- site: string;
110
- days: number;
111
- flagged?: boolean;
112
- events?: readonly string[];
113
- now?: number;
114
- }): Promise<VisitRow[]>;
115
- /**
116
- * How many visits read each of a host's pages, all time: a page's view count, from
117
- * `visits_views` (each visit once, the staff's and probes' never). `paths` limits it
118
- * to those pages; a page no visit read is absent.
119
- */
120
- export declare function readViews(db: MetricsReader, q: {
121
- site: string;
122
- host: string;
123
- paths?: readonly string[];
124
- }): Promise<Record<string, number>>;
125
- /** One visit's page rows and its lines as written, in the order kept: each
126
- * page's trace (its own start and end, its requests, its events) by trace id. */
127
- export declare function readVisit(db: MetricsReader, id: string): Promise<{
128
- pages: Record<string, unknown>[];
129
- lines: string[];
130
- }>;
131
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visits/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EACX,aAAa,EACb,aAAa,EAGb,MAAM,sBAAsB,CAAC;AAG9B,yFAAyF;AACzF,eAAO,MAAM,YAAY,QAAc,CAAC;AACxC,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,0EAA0E;AAC1E,eAAO,MAAM,YAAY,IAAI,CAAC;AAK9B,8DAA8D;AAC9D,eAAO,MAAM,aAAa,wrDAahB,CAAC;AAEX,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,6GAA6G;IAC7G,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,sGAAsG;AACtG,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAejD;AA2CD,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAeD,uGAAuG;AACvG,wBAAgB,WAAW,CAC1B,OAAO,EAAE,aAAa,GACpB,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiGzC;AA2QD;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB;AAED;qEACqE;AACrE,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,MACrC,CAAC;AAErB;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC/B,EAAE,EAAE,aAAa,EACjB,CAAC,EAAE;IACF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GACC,OAAO,CAAC,QAAQ,EAAE,CAAC,CA8DrB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC9B,EAAE,EAAE,aAAa,EACjB,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAYjC;AAED;kFACkF;AAClF,wBAAsB,SAAS,CAC9B,EAAE,EAAE,aAAa,EACjB,EAAE,EAAE,MAAM,GACR,OAAO,CAAC;IACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC,CAcD"}
@@ -1,537 +0,0 @@
1
- /**
2
- * Visits, kept by the site itself: the server half of a page in the event
3
- * stream (utils README, "Web pages"). ui's page-sensor posts the page's lines
4
- * to the site's own route; this module reads them, puts each page in a visit
5
- * and keeps the rows the views read (ui's user-flows). Framework-free: a
6
- * route is a Request in and a Response out, and the store is the app's own
7
- * SQLite through the metrics drivers (libSQL / Turso, D1).
8
- *
9
- * // app/api/events/route.ts
10
- * export const POST = visitsRoute({ db: libsqlDriver(client), site: "untitled.garden", services: ["garden-web"] })
11
- *
12
- * // a view
13
- * const visits = await readVisits(libsqlDriver(client), { site: "untitled.garden", days: 14 })
14
- *
15
- * A VISIT is one caller's pages, each within VISIT_GAP_MS of the one before,
16
- * within one UTC day. The caller is a hash of its IP and user agent with the
17
- * site and a random salt of the day, the salt deleted once the day is over (one
18
- * store can hold several sites, never linking a person across them): a visitor is
19
- * recognisable within a day, never across two, and once the salt is gone by no
20
- * one. The IP and user agent are never stored; nothing is kept in the browser.
21
- *
22
- * ROWS: `visits_visit` (one per visit: when, how many pages, where it came
23
- * from, coarse country and viewport, whether a probe or the site's staff made
24
- * it), `visits_page` (one per page span: its place, path, how it was reached,
25
- * how long it was on screen, the sections it reached, the vitals),
26
- * `visits_event` (every line of the page's trace as written: the page span's
27
- * own start and end, fetch spans, named events, errors, so a page's trace is
28
- * read back from its lines alone, on the visitor's one clock) and
29
- * `visits_views` (per day, per page, how many visits read it, each once, the
30
- * staff's and probes' never: a count with nothing of who; a visit reads a page
31
- * by opening it or by a `view` line naming it, a post shown whole elsewhere). Pages and visits
32
- * are kept VISIT_DAYS, events EVENT_DAYS, a probe's or staff's FLAGGED_DAYS;
33
- * old rows go on the first write of each day. Views are kept for good: they
34
- * are what a page's all-time count is read from (`readViews`). Provision
35
- * VISITS_SCHEMA once, never on a request.
36
- *
37
- * REFUSED: a line that is no event, a service the site did not name, a line
38
- * outside a page's trace, a field named like a person (ip, user_agent, email,
39
- * user, name, phone). A crawler's batch is dropped whole and answered 204.
40
- */
41
- import { parseEvent } from "../universal/log.js";
42
- /** Pages further apart than this are two visits, as every analytics tool counts them. */
43
- export const VISIT_GAP_MS = 30 * 60_000;
44
- export const VISIT_DAYS = 180;
45
- export const EVENT_DAYS = 30;
46
- /** A probe's or the staff's visits: the developer's view, kept a week. */
47
- export const FLAGGED_DAYS = 7;
48
- const MAX_BYTES = 64 * 1024;
49
- const DAY_MS = 86_400_000;
50
- /** Run explicitly during provisioning, never on a request. */
51
- export const VISITS_SCHEMA = [
52
- "CREATE TABLE IF NOT EXISTS visits_salt (day TEXT PRIMARY KEY, salt TEXT NOT NULL)",
53
- "CREATE TABLE IF NOT EXISTS visits_caller (day TEXT NOT NULL, caller TEXT NOT NULL, visit TEXT NOT NULL, last INTEGER NOT NULL, PRIMARY KEY (day, caller))",
54
- "CREATE TABLE IF NOT EXISTS visits_visit (id TEXT PRIMARY KEY, site TEXT NOT NULL, day TEXT NOT NULL, started INTEGER NOT NULL, last INTEGER NOT NULL, pages INTEGER NOT NULL, referrer TEXT, source TEXT, country TEXT, viewport TEXT, probe INTEGER NOT NULL, staff INTEGER NOT NULL)",
55
- "CREATE INDEX IF NOT EXISTS visits_visit_day ON visits_visit (site, day)",
56
- "CREATE TABLE IF NOT EXISTS visits_page (span_id TEXT PRIMARY KEY, trace_id TEXT NOT NULL, visit TEXT NOT NULL, seq INTEGER NOT NULL, site TEXT NOT NULL, day TEXT NOT NULL, at INTEGER NOT NULL, host TEXT NOT NULL, path TEXT NOT NULL, place TEXT, route TEXT, nav TEXT NOT NULL, from_path TEXT, build TEXT, dur_ms REAL, reached TEXT, ttfb_ms REAL, lcp_ms REAL, inp_ms REAL, cls REAL)",
57
- "CREATE INDEX IF NOT EXISTS visits_page_visit ON visits_page (visit, seq)",
58
- "CREATE INDEX IF NOT EXISTS visits_page_trace ON visits_page (trace_id)",
59
- "CREATE INDEX IF NOT EXISTS visits_page_day ON visits_page (site, day)",
60
- "CREATE TABLE IF NOT EXISTS visits_event (id INTEGER PRIMARY KEY, visit TEXT NOT NULL, trace_id TEXT NOT NULL, day TEXT NOT NULL, at INTEGER NOT NULL, line TEXT NOT NULL)",
61
- "CREATE INDEX IF NOT EXISTS visits_event_visit ON visits_event (visit, id)",
62
- "CREATE INDEX IF NOT EXISTS visits_event_day ON visits_event (day)",
63
- "CREATE TABLE IF NOT EXISTS visits_views (site TEXT NOT NULL, day TEXT NOT NULL, host TEXT NOT NULL, path TEXT NOT NULL, views INTEGER NOT NULL, PRIMARY KEY (site, day, host, path))",
64
- ];
65
- export function callerOf(request) {
66
- const h = request.headers;
67
- const ip = h.get("cf-connecting-ip") ??
68
- h.get("x-real-ip") ??
69
- h.get("x-forwarded-for")?.split(",")[0]?.trim() ??
70
- "";
71
- const country = (h.get("cf-ipcountry") ?? h.get("x-vercel-ip-country"))?.toUpperCase();
72
- return {
73
- ip,
74
- agent: h.get("user-agent") ?? "",
75
- country: country && /^[A-Z]{2}$/.test(country) ? country : undefined,
76
- };
77
- }
78
- /** Crawlers are no visitors: dropped, never stored. A probe (an automated browser) says so itself and is kept, flagged. */
79
- const CRAWLER = /bot|crawl|spider|slurp|facebookexternalhit|embedly|preview|monitor|curl|wget|python-requests|go-http-client/i;
80
- /** A field named like a person: refused, so no page or server ever writes one. */
81
- const PERSONAL = new Set([
82
- "ip",
83
- "user_agent",
84
- "email",
85
- "user",
86
- "name",
87
- "phone",
88
- ]);
89
- const utcDay = (ms) => new Date(ms).toISOString().slice(0, 10);
90
- const daysBefore = (ms, days) => utcDay(ms - days * DAY_MS);
91
- function hex(bytes) {
92
- return Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, "0")).join("");
93
- }
94
- async function sha256(text) {
95
- return hex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)));
96
- }
97
- /** Inside the site: the domain, a subdomain, or a local development host. */
98
- function inside(host, site) {
99
- const name = host.replace(/:\d+$/, "");
100
- return (name === site ||
101
- name.endsWith(`.${site}`) ||
102
- name === "localhost" ||
103
- name.endsWith(".localhost"));
104
- }
105
- const json = (status, body) => new Response(`${JSON.stringify(body)}\n`, {
106
- status,
107
- headers: { "content-type": "application/json" },
108
- });
109
- const str = (v) => (typeof v === "string" ? v : null);
110
- const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
111
- /** The day pruning last ran, per process: old rows go on the first write of each day. */
112
- let pruned = "";
113
- /** The route: a page's batch in, `204`, or `400 {accepted, rejected}` with the accepted lines kept. */
114
- export function visitsRoute(options) {
115
- const { db, site, services } = options;
116
- if (!/^[a-z0-9.-]+$/.test(site))
117
- throw new Error(`visits: a site is a domain, got ${site}`);
118
- if (services.length === 0)
119
- throw new Error("visits: name the services the site's pages write as");
120
- const now = options.now ?? Date.now;
121
- const callerFrom = options.caller ?? callerOf;
122
- return async (request) => {
123
- if (request.method !== "POST")
124
- return json(405, { why: "a batch is a POST" });
125
- const type = (request.headers.get("content-type") ?? "")
126
- .toLowerCase()
127
- .replaceAll(" ", "");
128
- if (!type.startsWith("text/plain"))
129
- return json(415, { why: "want Content-Type: text/plain;charset=utf-8" });
130
- const origin = request.headers.get("origin");
131
- if (origin && !inside(new URL(origin).host, site))
132
- return json(403, { why: "a page of another site" });
133
- const body = await request.arrayBuffer();
134
- if (body.byteLength > MAX_BYTES)
135
- return json(413, { why: "a batch is at most 64 KB" });
136
- const caller = callerFrom(request);
137
- if (CRAWLER.test(caller.agent))
138
- return new Response(null, { status: 204 });
139
- const rejected = [];
140
- const events = [];
141
- new TextDecoder()
142
- .decode(body)
143
- .split("\n")
144
- .forEach((line, i) => {
145
- const n = i + 1;
146
- const why = refusal(line, services);
147
- if (typeof why === "string")
148
- rejected.push({ line: n, why });
149
- else
150
- events.push({ n, line, e: why });
151
- });
152
- const at = now();
153
- const day = utcDay(at);
154
- const statements = [];
155
- if (pruned !== day) {
156
- statements.push(...pruning(at));
157
- pruned = day;
158
- }
159
- const paging = events.some(({ e }) => e.msg === "page" && e.phase !== undefined);
160
- const staff = paging && options.staff !== undefined
161
- ? await options.staff(request)
162
- : false;
163
- const { visitOf, viewers } = await assign(db, site, { caller, staff }, day, at, events, statements);
164
- let accepted = 0;
165
- for (const { n, line, e } of events) {
166
- const visit = visitOf(e.trace_id);
167
- if (visit === undefined) {
168
- rejected.push({ line: n, why: "no page of this trace was kept" });
169
- continue;
170
- }
171
- accepted++;
172
- if (e.msg === "page" && e.phase === "end")
173
- statements.push(pageEnd(e));
174
- // A post read where it was shown (a note in a feed): a view of its page.
175
- const host = str(e.fields?.host);
176
- const path = str(e.fields?.path);
177
- if (e.msg === "view" &&
178
- e.phase === undefined &&
179
- host !== null &&
180
- path?.startsWith("/") &&
181
- inside(host, site)) {
182
- let viewer = viewers.get(visit);
183
- if (viewer === undefined) {
184
- viewer = await viewerOf(db, visit);
185
- viewers.set(visit, viewer);
186
- }
187
- statements.push(...view(viewer, { site, day, host, path }));
188
- }
189
- statements.push({
190
- sql: "INSERT INTO visits_event (visit, trace_id, day, at, line) VALUES (?, ?, ?, ?, ?)",
191
- args: [visit, e.trace_id, day, at, line],
192
- });
193
- }
194
- if (statements.length > 0)
195
- await db.transact(statements);
196
- if (rejected.length === 0)
197
- return new Response(null, { status: 204 });
198
- rejected.sort((a, b) => a.line - b.line);
199
- return json(400, { accepted, rejected });
200
- };
201
- }
202
- /** Why a line is refused, or its event. */
203
- function refusal(line, services) {
204
- if (line.length === 0)
205
- return "empty line";
206
- let e;
207
- try {
208
- e = parseEvent(line);
209
- }
210
- catch (err) {
211
- return err.message.slice(0, 300);
212
- }
213
- if (e === null)
214
- return "not an event";
215
- if (!services.includes(e.service))
216
- return `service ${e.service} is not one of the site's pages`;
217
- if (e.trace_id === undefined)
218
- return "outside any page's trace";
219
- const personal = Object.keys(e.fields ?? {}).find((k) => PERSONAL.has(k));
220
- if (personal !== undefined)
221
- return `a field named like a person: ${personal}`;
222
- if (e.msg === "page" && e.phase !== undefined && e.parent_id !== undefined)
223
- return "a page span is the root of its trace";
224
- return e;
225
- }
226
- /**
227
- * Puts each page the batch names and no earlier batch kept in a visit (the
228
- * caller's open one, else a new one) and adds its rows to `statements`;
229
- * returns each trace's visit, the batch's pages first, then pages kept
230
- * before. Batches arrive in any order (a hidden page's beacon can overtake
231
- * the fetch carrying its start), and a page span's end carries every field of
232
- * its start, so whichever of the two arrives first keeps the page.
233
- */
234
- async function assign(db, site, { caller, staff }, day, at, events, statements) {
235
- const viewers = new Map();
236
- const traces = [...new Set(events.map(({ e }) => e.trace_id))];
237
- // One event per page, its start where the batch has it.
238
- const opened = new Map();
239
- for (const { e } of events)
240
- if (e.msg === "page" &&
241
- e.phase !== undefined &&
242
- (e.phase === "start" || !opened.has(e.trace_id)))
243
- opened.set(e.trace_id, e);
244
- const starts = [...opened.values()];
245
- const known = new Map();
246
- if (traces.length > 0) {
247
- const rows = await db.query({
248
- sql: `SELECT trace_id, visit FROM visits_page WHERE site = ? AND trace_id IN (${traces.map(() => "?").join(",")})`,
249
- args: [site, ...traces],
250
- });
251
- for (const r of rows)
252
- known.set(String(r.trace_id), String(r.visit));
253
- }
254
- if (starts.length === 0)
255
- return { visitOf: (t) => known.get(t), viewers };
256
- // The site is in the hash: one store can hold several sites, and a person reading
257
- // two of them is two callers, never one visit across sites nor a link between them.
258
- const hash = await sha256(`${await saltOf(db, day)}\0${site}\0${caller.ip}\0${caller.agent}`);
259
- const id = hash.slice(0, 32);
260
- const [open] = [
261
- ...(await db.query({
262
- sql: "SELECT c.visit, c.last, v.pages FROM visits_caller c JOIN visits_visit v ON v.id = c.visit WHERE c.day = ? AND c.caller = ?",
263
- args: [day, id],
264
- })),
265
- ];
266
- /** The visit the batch's pages join: its pages so far and what it has viewed. */
267
- let visit = open && at - Number(open.last) <= VISIT_GAP_MS
268
- ? {
269
- id: String(open.visit),
270
- pages: Number(open.pages),
271
- ...(await viewerOf(db, String(open.visit))),
272
- }
273
- : null;
274
- for (const e of starts) {
275
- if (known.has(e.trace_id))
276
- continue;
277
- const f = e.fields ?? {};
278
- const flag = (k) => f[k] === true || (k === "staff" && staff) ? 1 : 0;
279
- if (visit === null) {
280
- const referrer = str(f.referrer);
281
- visit = {
282
- id: hex(crypto.getRandomValues(new Uint8Array(8))),
283
- pages: 0,
284
- flagged: false,
285
- seen: new Set(),
286
- };
287
- statements.push({
288
- sql: "INSERT INTO visits_visit (id, site, day, started, last, pages, referrer, source, country, viewport, probe, staff) VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)",
289
- args: [
290
- visit.id,
291
- site,
292
- day,
293
- at,
294
- at,
295
- referrer !== null && !inside(referrer, site) ? referrer : null,
296
- str(f.source),
297
- caller.country ?? null,
298
- str(f.viewport),
299
- flag("probe"),
300
- flag("staff"),
301
- ],
302
- });
303
- }
304
- visit.pages += 1;
305
- known.set(e.trace_id, visit.id);
306
- const host = str(f.host) ?? "";
307
- const path = str(f.path) ?? "/";
308
- visit.flagged ||= flag("probe") === 1 || flag("staff") === 1;
309
- viewers.set(visit.id, visit);
310
- statements.push(...view(visit, { site, day, host, path }));
311
- statements.push({
312
- sql: "INSERT OR IGNORE INTO visits_page (span_id, trace_id, visit, seq, site, day, at, host, path, place, route, nav, from_path, build) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
313
- args: [
314
- e.span_id,
315
- e.trace_id,
316
- visit.id,
317
- visit.pages,
318
- site,
319
- day,
320
- at,
321
- host,
322
- path,
323
- str(f.place),
324
- str(f.route),
325
- str(f.nav) ?? "navigate",
326
- str(f.from),
327
- str(f.build),
328
- ],
329
- }, {
330
- sql: "UPDATE visits_visit SET last = ?, pages = ?, probe = max(probe, ?), staff = max(staff, ?) WHERE id = ?",
331
- args: [at, visit.pages, flag("probe"), flag("staff"), visit.id],
332
- });
333
- }
334
- if (visit !== null)
335
- statements.push({
336
- sql: "INSERT INTO visits_caller (day, caller, visit, last) VALUES (?, ?, ?, ?) ON CONFLICT (day, caller) DO UPDATE SET visit = excluded.visit, last = excluded.last",
337
- args: [day, id, visit.id, at],
338
- });
339
- return { visitOf: (t) => known.get(t), viewers };
340
- }
341
- const viewKey = (host, path) => `${host}\0${path}`;
342
- async function viewerOf(db, visit) {
343
- const [v] = [
344
- ...(await db.query({
345
- sql: "SELECT probe, staff FROM visits_visit WHERE id = ?",
346
- args: [visit],
347
- })),
348
- ];
349
- if (v === undefined)
350
- throw new Error(`visits: no visit ${visit}`);
351
- const read = await db.query({
352
- sql: "SELECT host, path FROM visits_page WHERE visit = ? UNION SELECT json_extract(line, '$.fields.host'), json_extract(line, '$.fields.path') FROM visits_event WHERE visit = ? AND json_extract(line, '$.msg') = 'view' AND json_extract(line, '$.phase') IS NULL",
353
- args: [visit, visit],
354
- });
355
- return {
356
- flagged: Number(v.probe) === 1 || Number(v.staff) === 1,
357
- seen: new Set([...read].map((r) => viewKey(String(r.host), String(r.path)))),
358
- };
359
- }
360
- /** A page read by a visit: a view of it, the first time this visit reads it. */
361
- function view(viewer, at) {
362
- const key = viewKey(at.host, at.path);
363
- const first = !viewer.seen.has(key);
364
- viewer.seen.add(key);
365
- return first && !viewer.flagged
366
- ? [
367
- {
368
- sql: "INSERT INTO visits_views (site, day, host, path, views) VALUES (?, ?, ?, ?, 1) ON CONFLICT (site, day, host, path) DO UPDATE SET views = views + 1",
369
- args: [at.site, at.day, at.host, at.path],
370
- },
371
- ]
372
- : [];
373
- }
374
- /** What a page span's end adds to its row. */
375
- function pageEnd(e) {
376
- const f = e.fields ?? {};
377
- const reached = Array.isArray(f.reached)
378
- ? JSON.stringify(f.reached.filter((s) => typeof s === "string"))
379
- : null;
380
- return {
381
- sql: "UPDATE visits_page SET dur_ms = ?, reached = ?, ttfb_ms = ?, lcp_ms = ?, inp_ms = ?, cls = ? WHERE span_id = ?",
382
- args: [
383
- num(e.dur_ms),
384
- reached,
385
- num(f.ttfb_ms),
386
- num(f.lcp_ms),
387
- num(f.inp_ms),
388
- num(f.cls),
389
- e.span_id,
390
- ],
391
- };
392
- }
393
- /** The day's salt: random, made on the day's first page, deleted once the day is over. */
394
- async function saltOf(db, day) {
395
- await db.transact([
396
- {
397
- sql: "INSERT INTO visits_salt (day, salt) VALUES (?, ?) ON CONFLICT (day) DO NOTHING",
398
- args: [day, hex(crypto.getRandomValues(new Uint8Array(32)))],
399
- },
400
- { sql: "DELETE FROM visits_salt WHERE day < ?", args: [day] },
401
- { sql: "DELETE FROM visits_caller WHERE day < ?", args: [day] },
402
- ]);
403
- const [row] = [
404
- ...(await db.query({
405
- sql: "SELECT salt FROM visits_salt WHERE day = ?",
406
- args: [day],
407
- })),
408
- ];
409
- const salt = row?.salt;
410
- if (typeof salt !== "string")
411
- throw new Error(`visits: no salt for ${day}`);
412
- return salt;
413
- }
414
- function pruning(at) {
415
- const kept = daysBefore(at, VISIT_DAYS);
416
- const flagged = daysBefore(at, FLAGGED_DAYS);
417
- const events = daysBefore(at, EVENT_DAYS);
418
- return [
419
- {
420
- sql: "DELETE FROM visits_page WHERE day < ? OR (day < ? AND visit IN (SELECT id FROM visits_visit WHERE probe = 1 OR staff = 1))",
421
- args: [kept, flagged],
422
- },
423
- {
424
- sql: "DELETE FROM visits_event WHERE day < ? OR (day < ? AND visit IN (SELECT id FROM visits_visit WHERE probe = 1 OR staff = 1))",
425
- args: [events, flagged],
426
- },
427
- {
428
- sql: "DELETE FROM visits_visit WHERE day < ? OR (day < ? AND (probe = 1 OR staff = 1))",
429
- args: [kept, flagged],
430
- },
431
- ];
432
- }
433
- /** A named event as a step of a visit: where it happened, then its name
434
- * (`trash:main action`), right after the page it was written on. */
435
- export const eventStep = (place, event) => `${place}:${event}`;
436
- /**
437
- * The visits of the last `days` UTC days, today included, oldest first. A
438
- * probe's and the staff's are left out unless `flagged`: they are no product
439
- * number. `events` names the lines (by message) that are steps of a visit too,
440
- * each right after the page it was written on, as `eventStep(place, name)`: a
441
- * project's main action is where a funnel ends.
442
- */
443
- export async function readVisits(db, q) {
444
- if (!(Number.isInteger(q.days) && q.days >= 1))
445
- throw new Error(`visits: days is a whole number of days, got ${q.days}`);
446
- const since = daysBefore(q.now ?? Date.now(), q.days - 1);
447
- const args = [q.site, since];
448
- const flags = q.flagged ? "" : " AND v.probe = 0 AND v.staff = 0";
449
- const rows = [
450
- ...(await db.query({
451
- sql: `SELECT v.id, v.started, v.referrer, v.source, v.country, v.viewport, v.probe, v.staff, p.place, p.path, p.trace_id FROM visits_visit v JOIN visits_page p ON p.visit = v.id WHERE v.site = ? AND v.day >= ?${flags} ORDER BY v.started, v.id, p.seq`,
452
- args,
453
- })),
454
- ];
455
- // Each page's named events, in the order written: lines (no phase) of its trace.
456
- const events = new Map();
457
- if (q.events && q.events.length > 0) {
458
- const named = [
459
- ...(await db.query({
460
- sql: `SELECT e.trace_id, json_extract(e.line, '$.msg') AS msg FROM visits_event e JOIN visits_visit v ON v.id = e.visit WHERE v.site = ? AND v.day >= ?${flags} AND json_extract(e.line, '$.phase') IS NULL AND json_extract(e.line, '$.msg') IN (${q.events.map(() => "?").join(",")}) ORDER BY e.id`,
461
- args: [...args, ...q.events],
462
- })),
463
- ];
464
- for (const r of named) {
465
- const list = events.get(String(r.trace_id)) ?? [];
466
- list.push(String(r.msg));
467
- events.set(String(r.trace_id), list);
468
- }
469
- }
470
- const visits = new Map();
471
- for (const r of rows) {
472
- const id = String(r.id);
473
- let v = visits.get(id);
474
- if (v === undefined) {
475
- v = {
476
- id,
477
- started: Number(r.started),
478
- places: [],
479
- pages: 0,
480
- at: undefined,
481
- referrer: str(r.referrer) ?? undefined,
482
- source: str(r.source) ?? undefined,
483
- country: str(r.country) ?? undefined,
484
- viewport: str(r.viewport) ?? undefined,
485
- probe: Number(r.probe) === 1,
486
- staff: Number(r.staff) === 1,
487
- };
488
- visits.set(id, v);
489
- }
490
- // A move within a place is no new step, even past an event written there.
491
- const place = str(r.place) ?? String(r.path);
492
- if (v.at !== place)
493
- v.places.push(place);
494
- v.at = place;
495
- v.pages += 1;
496
- // Pressed twice in a row is one step.
497
- for (const name of events.get(String(r.trace_id)) ?? []) {
498
- const step = eventStep(place, name);
499
- if (v.places.at(-1) !== step)
500
- v.places.push(step);
501
- }
502
- }
503
- return [...visits.values()].map(({ at: _, ...v }) => v);
504
- }
505
- /**
506
- * How many visits read each of a host's pages, all time: a page's view count, from
507
- * `visits_views` (each visit once, the staff's and probes' never). `paths` limits it
508
- * to those pages; a page no visit read is absent.
509
- */
510
- export async function readViews(db, q) {
511
- const only = q.paths === undefined
512
- ? ""
513
- : ` AND path IN (${q.paths.map(() => "?").join(",") || "NULL"})`;
514
- const rows = await db.query({
515
- sql: `SELECT path, SUM(views) AS views FROM visits_views WHERE site = ? AND host = ?${only} GROUP BY path`,
516
- args: [q.site, q.host, ...(q.paths ?? [])],
517
- });
518
- return Object.fromEntries([...rows].map((r) => [String(r.path), Number(r.views)]));
519
- }
520
- /** One visit's page rows and its lines as written, in the order kept: each
521
- * page's trace (its own start and end, its requests, its events) by trace id. */
522
- export async function readVisit(db, id) {
523
- const pages = [
524
- ...(await db.query({
525
- sql: "SELECT * FROM visits_page WHERE visit = ? ORDER BY seq",
526
- args: [id],
527
- })),
528
- ];
529
- const lines = [
530
- ...(await db.query({
531
- sql: "SELECT line FROM visits_event WHERE visit = ? ORDER BY id",
532
- args: [id],
533
- })),
534
- ].map((r) => String(r.line));
535
- return { pages, lines };
536
- }
537
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/visits/index.ts"],"names":[],"mappings":"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"}