blogwright-analytics 0.3.3
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 +162 -0
- package/dist/adapters/duckdb-ingest.d.ts +76 -0
- package/dist/adapters/duckdb-ingest.js +173 -0
- package/dist/adapters/duckdb-query.d.ts +56 -0
- package/dist/adapters/duckdb-query.js +80 -0
- package/dist/adapters/duckdb-session.d.ts +168 -0
- package/dist/adapters/duckdb-session.js +330 -0
- package/dist/app/_app/immutable/assets/0.BTQrrh5B.css +1 -0
- package/dist/app/_app/immutable/assets/2.CZSK3rT8.css +1 -0
- package/dist/app/_app/immutable/assets/BrushContext.D7c8UPey.css +1 -0
- package/dist/app/_app/immutable/assets/ChartAnnotations.CPxIG7Mw.css +1 -0
- package/dist/app/_app/immutable/assets/Circle.C5MKzgk2.css +1 -0
- package/dist/app/_app/immutable/assets/DefaultTooltip.C5-uctZ7.css +1 -0
- package/dist/app/_app/immutable/assets/Group.DV48xipa.css +1 -0
- package/dist/app/_app/immutable/assets/Labels.BxZ4NUVz.css +1 -0
- package/dist/app/_app/immutable/assets/Legend.CxnrE4Ye.css +1 -0
- package/dist/app/_app/immutable/assets/Line.fkmsECm9.css +1 -0
- package/dist/app/_app/immutable/assets/Path.CvpwNZ6g.css +1 -0
- package/dist/app/_app/immutable/assets/Rect.CtRaGMmQ.css +1 -0
- package/dist/app/_app/immutable/assets/Text.j9l35qB0.css +1 -0
- package/dist/app/_app/immutable/assets/TransformContext.Bs_HkpAk.css +1 -0
- package/dist/app/_app/immutable/assets/Voronoi.ce7atosu.css +1 -0
- package/dist/app/_app/immutable/chunks/-aNGNaBT.js +1 -0
- package/dist/app/_app/immutable/chunks/6djn-yLs.js +1 -0
- package/dist/app/_app/immutable/chunks/B1amyutE.js +1 -0
- package/dist/app/_app/immutable/chunks/B3vZDoek.js +1 -0
- package/dist/app/_app/immutable/chunks/B5KRA4hC.js +1 -0
- package/dist/app/_app/immutable/chunks/BClnVG6H.js +1 -0
- package/dist/app/_app/immutable/chunks/BID1NNRh.js +1 -0
- package/dist/app/_app/immutable/chunks/BR2LaRms.js +1 -0
- package/dist/app/_app/immutable/chunks/Bd1gDe3Y.js +1 -0
- package/dist/app/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/dist/app/_app/immutable/chunks/Bl052uUt.js +1 -0
- package/dist/app/_app/immutable/chunks/Bye3lL0c.js +1 -0
- package/dist/app/_app/immutable/chunks/C58PZtCD.js +4 -0
- package/dist/app/_app/immutable/chunks/CAzydqEO.js +1 -0
- package/dist/app/_app/immutable/chunks/CCch3uox.js +1 -0
- package/dist/app/_app/immutable/chunks/CIlSMUH9.js +1 -0
- package/dist/app/_app/immutable/chunks/CO1vUXfR.js +1 -0
- package/dist/app/_app/immutable/chunks/CPbD8C65.js +5 -0
- package/dist/app/_app/immutable/chunks/CRTcXoMo.js +1 -0
- package/dist/app/_app/immutable/chunks/CjjyIQAO.js +1 -0
- package/dist/app/_app/immutable/chunks/CuXAxjvF.js +1 -0
- package/dist/app/_app/immutable/chunks/CvyVA_jC.js +1 -0
- package/dist/app/_app/immutable/chunks/CxGCFVdy.js +1 -0
- package/dist/app/_app/immutable/chunks/D0Ty6LN0.js +1 -0
- package/dist/app/_app/immutable/chunks/D2AaQUUW.js +1 -0
- package/dist/app/_app/immutable/chunks/D2BnX0Uk.js +3 -0
- package/dist/app/_app/immutable/chunks/DJc8C0NK.js +1 -0
- package/dist/app/_app/immutable/chunks/DKMlMI4a.js +1 -0
- package/dist/app/_app/immutable/chunks/DVXZkpbf.js +1 -0
- package/dist/app/_app/immutable/chunks/DVt8ukQ_.js +1 -0
- package/dist/app/_app/immutable/chunks/DZPlYdq_.js +1 -0
- package/dist/app/_app/immutable/chunks/Db0q5_zr.js +1 -0
- package/dist/app/_app/immutable/chunks/Dfvzj6n2.js +1 -0
- package/dist/app/_app/immutable/chunks/Dh958be7.js +1 -0
- package/dist/app/_app/immutable/chunks/DjKLLdnY.js +15 -0
- package/dist/app/_app/immutable/chunks/Doz7YX1W.js +1 -0
- package/dist/app/_app/immutable/chunks/DthYhn6Y.js +2 -0
- package/dist/app/_app/immutable/chunks/DtuTIrAM.js +1 -0
- package/dist/app/_app/immutable/chunks/HclGiUj8.js +1 -0
- package/dist/app/_app/immutable/chunks/Hx0TNsV3.js +1 -0
- package/dist/app/_app/immutable/chunks/RobXhXPM.js +1 -0
- package/dist/app/_app/immutable/chunks/V9ZjaxiY.js +1 -0
- package/dist/app/_app/immutable/chunks/Y5urAfNy.js +1 -0
- package/dist/app/_app/immutable/chunks/caXkbKD3.js +1 -0
- package/dist/app/_app/immutable/chunks/devYm2ud.js +1 -0
- package/dist/app/_app/immutable/chunks/mtZWP0zR.js +1 -0
- package/dist/app/_app/immutable/chunks/vDgBJUjM.js +1 -0
- package/dist/app/_app/immutable/chunks/xIq_fFFM.js +1 -0
- package/dist/app/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/app/_app/immutable/chunks/z05MoCFz.js +1 -0
- package/dist/app/_app/immutable/entry/app.CLAerUAN.js +2 -0
- package/dist/app/_app/immutable/entry/start.D3MqnNci.js +1 -0
- package/dist/app/_app/immutable/nodes/0.UTMEigHJ.js +1 -0
- package/dist/app/_app/immutable/nodes/1.Cn4f11bT.js +1 -0
- package/dist/app/_app/immutable/nodes/2.B39cIcr2.js +6 -0
- package/dist/app/_app/version.json +1 -0
- package/dist/app/index.html +82 -0
- package/dist/aws/clients.d.ts +70 -0
- package/dist/aws/clients.js +52 -0
- package/dist/aws/errors.d.ts +41 -0
- package/dist/aws/errors.js +70 -0
- package/dist/aws/firehose.d.ts +228 -0
- package/dist/aws/firehose.js +347 -0
- package/dist/aws/glue.d.ts +103 -0
- package/dist/aws/glue.js +225 -0
- package/dist/aws/lambda.d.ts +132 -0
- package/dist/aws/lambda.js +339 -0
- package/dist/aws/s3tables.d.ts +120 -0
- package/dist/aws/s3tables.js +281 -0
- package/dist/backfill.d.ts +100 -0
- package/dist/backfill.js +294 -0
- package/dist/commands.d.ts +124 -0
- package/dist/commands.js +336 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +317 -0
- package/dist/fixture-ingest.d.ts +49 -0
- package/dist/fixture-ingest.js +43 -0
- package/dist/fixture-query.d.ts +39 -0
- package/dist/fixture-query.js +70 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/nodes.d.ts +404 -0
- package/dist/nodes.js +2708 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.js +47 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +248 -0
- package/dist/ports.d.ts +113 -0
- package/dist/ports.js +35 -0
- package/dist/queries.d.ts +301 -0
- package/dist/queries.js +414 -0
- package/dist/schema.d.ts +240 -0
- package/dist/schema.js +154 -0
- package/dist/server.d.ts +150 -0
- package/dist/server.js +499 -0
- package/dist/transform/bots.d.ts +47 -0
- package/dist/transform/bots.js +73 -0
- package/dist/transform/handler.d.ts +135 -0
- package/dist/transform/handler.js +177 -0
- package/dist/transform/map-record.d.ts +110 -0
- package/dist/transform/map-record.js +275 -0
- package/dist/transform/visitor-key.d.ts +83 -0
- package/dist/transform/visitor-key.js +120 -0
- package/dist/transform-bundle/index.mjs +21456 -0
- package/dist/transform-bundle/transform-manifest.json +4 -0
- package/dist/transform-hash.d.ts +135 -0
- package/dist/transform-hash.js +186 -0
- package/dist/write-transform-manifest.mjs +365 -0
- package/package.json +59 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixed set of named, parameterised queries this package answers from -
|
|
3
|
+
* never SQL supplied by a client. Seven of them are the dashboard's panels;
|
|
4
|
+
* see [the change spec's §Analytics dashboard → Local
|
|
5
|
+
* server](../../../.specs/changes/merged/2026-07-26-analytics_plugin.md). The eighth,
|
|
6
|
+
* {@link ROW_COUNT_QUERY}, serves `analytics status`, and is here for the same
|
|
7
|
+
* reason the other seven are: a command reaches the table through the
|
|
8
|
+
* `AnalyticsQuery` port, and the port takes a name from this set, never a
|
|
9
|
+
* statement.
|
|
10
|
+
*
|
|
11
|
+
* **Parameterised is a shape here, not a convention.** A definition's SQL is
|
|
12
|
+
* built by the {@link sql} tag, whose only substitution slot accepts
|
|
13
|
+
* {@link SqlRelation} - a branded type whose single inhabitant is
|
|
14
|
+
* {@link PAGE_VIEWS}, declared in this module. Interpolating anything else is
|
|
15
|
+
* a compile error (`TS2345: Argument of type 'string' is not assignable to
|
|
16
|
+
* parameter of type 'SqlRelation'`), and a plain string cannot be assigned to
|
|
17
|
+
* {@link SqlText} either (`TS2322`), so a definition has nowhere to put a
|
|
18
|
+
* caller's value: caller values reach the statement only as `$name`
|
|
19
|
+
* placeholders, bound by {@link prepareQuery}. The tag is module-private, so
|
|
20
|
+
* no other module can mint SQL for this port at all.
|
|
21
|
+
*
|
|
22
|
+
* **The type-level block is the one holding the property; the runtime tests
|
|
23
|
+
* are a partial net under it, not a second copy of it.** `tsc` rejects *every*
|
|
24
|
+
* splice of a caller value that is not deliberately cast, and `pnpm typecheck`
|
|
25
|
+
* runs it in CI (`.github/workflows/ci.yml:22`). The tests in
|
|
26
|
+
* `queries.test.ts` catch only what a splice leaves visible in the finished
|
|
27
|
+
* string - a quoted literal the definition did not declare, or anything
|
|
28
|
+
* day-shaped. They do not catch the rest: splicing `'0 OR 1=1'` into
|
|
29
|
+
* `status-codes` as `` AND status >= ${'0 OR 1=1'} `` leaves the whole suite
|
|
30
|
+
* green while `tsc` reports `TS2345` (observed, 2026-08-30). What the net does
|
|
31
|
+
* still add is independence over the forms it does cover: vitest transpiles
|
|
32
|
+
* without typechecking, so `` AND day >= '${'2026-08-01' as SqlRelation}' ``
|
|
33
|
+
* passes `tsc` and reddens the parameterisation test anyway. Read the two as a
|
|
34
|
+
* check total up to a deliberate cast and a partial backstop under it, not as
|
|
35
|
+
* equals - weakening or dropping the branded types because "the tests cover
|
|
36
|
+
* it" would leave only the partial net.
|
|
37
|
+
*
|
|
38
|
+
* **Why the relation is a fixed name rather than the configured triple.** SQL
|
|
39
|
+
* binds *values*, not identifiers: a configured `<tableBucket>/<namespace>/
|
|
40
|
+
* <table>` spliced into the statement would be exactly the interpolation this
|
|
41
|
+
* module exists to make impossible. So every definition reads one relation,
|
|
42
|
+
* {@link PAGE_VIEWS_RELATION}, and binding that name to the configured triple
|
|
43
|
+
* is the adapter's job - it holds the plugin context, so it takes
|
|
44
|
+
* `resolveAnalyticsConfig(ctx)` and attaches or aliases accordingly. The
|
|
45
|
+
* configurability task 44 declares is therefore preserved, in the one place
|
|
46
|
+
* that has the environment to resolve it.
|
|
47
|
+
*
|
|
48
|
+
* Pure data and pure functions only: no `node:` builtin, no vendor SDK, no
|
|
49
|
+
* `fetch`. Nothing here runs a statement - that is `AnalyticsQuery`'s adapter.
|
|
50
|
+
*/
|
|
51
|
+
import type { AnalyticsConfig } from './config.js';
|
|
52
|
+
import type { PageViewColumnName } from './schema.js';
|
|
53
|
+
/**
|
|
54
|
+
* A statement one of the named definitions carries. Branded, and the brand's
|
|
55
|
+
* symbol is not exported, so the only way to a value of this type is the
|
|
56
|
+
* {@link sql} tag below - and the tag is module-private too. A `string` is not
|
|
57
|
+
* assignable to it.
|
|
58
|
+
*/
|
|
59
|
+
declare const SQL_TEXT: unique symbol;
|
|
60
|
+
type SqlText = string & {
|
|
61
|
+
readonly [SQL_TEXT]: true;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The relation every named query reads. Not "the configured table name" - the
|
|
65
|
+
* adapter binds this name to the configured `<namespace>.<table>` inside the
|
|
66
|
+
* attached catalog before it runs anything, and this module never sees the
|
|
67
|
+
* configuration. See the module doc comment for why an identifier cannot be a
|
|
68
|
+
* bind parameter.
|
|
69
|
+
*/
|
|
70
|
+
export declare const PAGE_VIEWS_RELATION = "page_views";
|
|
71
|
+
/**
|
|
72
|
+
* The parameters a definition binds, as `$name` placeholders in its SQL. Every
|
|
73
|
+
* query takes the date range and the bot-inclusion flag the spec requires of
|
|
74
|
+
* all of them; the list is per-definition so a statement that forgot one is a
|
|
75
|
+
* test failure naming that query rather than a filter that quietly never ran.
|
|
76
|
+
*/
|
|
77
|
+
declare const QUERY_PARAM_NAMES: readonly ["from", "to", "include_bots"];
|
|
78
|
+
/** One of {@link QUERY_PARAM_NAMES}. */
|
|
79
|
+
type QueryParamName = (typeof QUERY_PARAM_NAMES)[number];
|
|
80
|
+
/** A value bound to a placeholder. Days are bound as text and cast in the SQL. */
|
|
81
|
+
type BindValue = string | boolean;
|
|
82
|
+
/** An inclusive range of UTC days, each `YYYY-MM-DD` - the `day` partition's own form. */
|
|
83
|
+
interface DateRange {
|
|
84
|
+
/** First day of the range, inclusive. */
|
|
85
|
+
readonly from: string;
|
|
86
|
+
/** Last day of the range, inclusive. */
|
|
87
|
+
readonly to: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* What a caller hands {@link AnalyticsQuery.run}. `includeBots` is optional
|
|
91
|
+
* because its default is not this module's to state: it comes from
|
|
92
|
+
* `config.analytics.bots` (task 44) through {@link BOTS_INCLUDED_BY_DEFAULT}.
|
|
93
|
+
*/
|
|
94
|
+
export interface QueryParams {
|
|
95
|
+
/** The days to report over. */
|
|
96
|
+
readonly range: DateRange;
|
|
97
|
+
/**
|
|
98
|
+
* Whether bot-flagged rows are counted. Absent means "whatever
|
|
99
|
+
* `config.analytics.bots` says": `flag` keeps them, `filter` excludes them.
|
|
100
|
+
* Records are stored either way - filtering is a query concern, per the
|
|
101
|
+
* spec's Decision *Flag bots, do not drop them*.
|
|
102
|
+
*/
|
|
103
|
+
readonly includeBots?: boolean | undefined;
|
|
104
|
+
}
|
|
105
|
+
/** One named query: its statement, what it reads, what it binds, what it returns. */
|
|
106
|
+
interface QueryDefinition {
|
|
107
|
+
/**
|
|
108
|
+
* What one result row means, in one line. The dashboard renders it beside
|
|
109
|
+
* the chart, which is why `unique-visitors` says "summed daily uniques"
|
|
110
|
+
* here as well as in its column names - see {@link ANALYTICS_QUERIES}.
|
|
111
|
+
*/
|
|
112
|
+
readonly rowMeaning: string;
|
|
113
|
+
/** The `page_views` columns the statement reads, typed against `schema.ts`. */
|
|
114
|
+
readonly columns: readonly PageViewColumnName[];
|
|
115
|
+
/** The placeholders the statement carries, one bound value each. */
|
|
116
|
+
readonly binds: readonly QueryParamName[];
|
|
117
|
+
/** The columns a result row carries, in the order the statement selects them. */
|
|
118
|
+
readonly resultColumns: readonly string[];
|
|
119
|
+
/**
|
|
120
|
+
* Constants the statement spells for itself, quoted inside the SQL. Declared
|
|
121
|
+
* so the parameterisation test can tell a domain constant from an
|
|
122
|
+
* interpolated caller value: any literal in the SQL that is not on this list
|
|
123
|
+
* fails, naming the query.
|
|
124
|
+
*/
|
|
125
|
+
readonly literals: readonly string[];
|
|
126
|
+
/** The statement. Only {@link sql} can produce one. */
|
|
127
|
+
readonly sql: SqlText;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The name of the row-count query. Not one of the seven the spec's §Local
|
|
131
|
+
* server lists - those answer the dashboard's panels; this one answers
|
|
132
|
+
* `analytics status`, which reports the table's current row count beside the
|
|
133
|
+
* plugin's twelve nodes. It lives in this set rather than in the command
|
|
134
|
+
* because the command may not write SQL: every statement this package runs is
|
|
135
|
+
* one of these definitions, reached through the `AnalyticsQuery` port.
|
|
136
|
+
*/
|
|
137
|
+
export declare const ROW_COUNT_QUERY = "row-count";
|
|
138
|
+
/** The one column {@link ROW_COUNT_QUERY} selects. Named so no caller spells it twice. */
|
|
139
|
+
export declare const ROW_COUNT_COLUMN = "row_count";
|
|
140
|
+
/**
|
|
141
|
+
* The range {@link ROW_COUNT_QUERY} is asked over when the caller wants the
|
|
142
|
+
* whole table, as `analytics status` does.
|
|
143
|
+
*
|
|
144
|
+
* Every definition in this set is bounded on the `day` partition - the spec
|
|
145
|
+
* requires the range and the bot flag of all of them - so "the whole table" is
|
|
146
|
+
* expressed as the widest range the column can hold rather than as an
|
|
147
|
+
* unbounded statement. Both ends are calendar days {@link isCalendarDay}
|
|
148
|
+
* accepts, so this constant goes through exactly the validation a caller's
|
|
149
|
+
* range does. `from` is the Unix epoch, which no `day` can precede: the column
|
|
150
|
+
* is derived from the request's own timestamp. `to` is the last day of the
|
|
151
|
+
* four-digit years, which is the largest day this module's `YYYY-MM-DD` shape
|
|
152
|
+
* can express at all.
|
|
153
|
+
*/
|
|
154
|
+
export declare const WHOLE_TABLE_RANGE: {
|
|
155
|
+
readonly from: "1970-01-01";
|
|
156
|
+
readonly to: "9999-12-31";
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Every named query, keyed by the name a caller asks for: the seven the spec's
|
|
160
|
+
* §Local server lists, in its order, and then {@link ROW_COUNT_QUERY}, which
|
|
161
|
+
* no panel draws and `analytics status` reports.
|
|
162
|
+
*
|
|
163
|
+
* Every statement reads {@link PAGE_VIEWS}, bounds itself on the `day`
|
|
164
|
+
* partition with `$from`/`$to`, and honours `$include_bots` - a row whose
|
|
165
|
+
* `is_bot` is null counts as not-a-bot, since the transform leaves the column
|
|
166
|
+
* absent when it has nothing to say.
|
|
167
|
+
*
|
|
168
|
+
* **`unique-visitors` is the one whose semantic cannot be read off its name.**
|
|
169
|
+
* `visitor_key` is a daily-salted digest and the salt turns over at every UTC
|
|
170
|
+
* day boundary (the spec's Decision *Daily salt rotation stands*, settled
|
|
171
|
+
* 2026-07-27), so the same person is a different `visitor_key` tomorrow.
|
|
172
|
+
* A `count(DISTINCT visitor_key)` spanning a range therefore does not error -
|
|
173
|
+
* it returns a plausible number that means nothing. The definition counts
|
|
174
|
+
* distinct keys *within* a day and reports the range total as the sum of those
|
|
175
|
+
* daily counts, and says so in its column names (`daily_unique_visitors`,
|
|
176
|
+
* `summed_daily_unique_visitors`) and its `rowMeaning`, so a dashboard cannot
|
|
177
|
+
* relabel the total "unique visitors" without deleting the words that say
|
|
178
|
+
* otherwise. The sum over-counts a visitor who returns on another day; that is
|
|
179
|
+
* the accepted cost of bounding what one day of brute-forced salt could ever
|
|
180
|
+
* correlate.
|
|
181
|
+
*/
|
|
182
|
+
export declare const ANALYTICS_QUERIES: {
|
|
183
|
+
readonly 'views-over-time': {
|
|
184
|
+
readonly rowMeaning: "one UTC day and the number of requests served that day";
|
|
185
|
+
readonly columns: readonly ["day", "is_bot"];
|
|
186
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
187
|
+
readonly resultColumns: readonly ["day", "views"];
|
|
188
|
+
readonly literals: readonly [];
|
|
189
|
+
readonly sql: SqlText;
|
|
190
|
+
};
|
|
191
|
+
readonly 'top-paths': {
|
|
192
|
+
readonly rowMeaning: "one request path and the number of requests for it over the range";
|
|
193
|
+
readonly columns: readonly ["day", "uri", "is_bot"];
|
|
194
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
195
|
+
readonly resultColumns: readonly ["uri", "views"];
|
|
196
|
+
readonly literals: readonly [];
|
|
197
|
+
readonly sql: SqlText;
|
|
198
|
+
};
|
|
199
|
+
readonly referrers: {
|
|
200
|
+
readonly rowMeaning: "one referring URL and the number of requests it sent over the range";
|
|
201
|
+
readonly columns: readonly ["day", "referrer", "is_bot"];
|
|
202
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
203
|
+
readonly resultColumns: readonly ["referrer", "views"];
|
|
204
|
+
readonly literals: readonly [];
|
|
205
|
+
readonly sql: SqlText;
|
|
206
|
+
};
|
|
207
|
+
readonly countries: {
|
|
208
|
+
readonly rowMeaning: "one viewer country and the number of requests from it over the range";
|
|
209
|
+
readonly columns: readonly ["day", "country", "is_bot"];
|
|
210
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
211
|
+
readonly resultColumns: readonly ["country", "views"];
|
|
212
|
+
readonly literals: readonly [];
|
|
213
|
+
readonly sql: SqlText;
|
|
214
|
+
};
|
|
215
|
+
readonly 'status-codes': {
|
|
216
|
+
readonly rowMeaning: "one HTTP status code and the number of responses carrying it over the range";
|
|
217
|
+
readonly columns: readonly ["day", "status", "is_bot"];
|
|
218
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
219
|
+
readonly resultColumns: readonly ["status", "views"];
|
|
220
|
+
readonly literals: readonly [];
|
|
221
|
+
readonly sql: SqlText;
|
|
222
|
+
};
|
|
223
|
+
readonly 'cache-hit-ratio': {
|
|
224
|
+
readonly rowMeaning: "one UTC day, its requests, the edge cache hits among them, and hits divided by requests";
|
|
225
|
+
readonly columns: readonly ["day", "result_type", "is_bot"];
|
|
226
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
227
|
+
readonly resultColumns: readonly ["day", "requests", "cache_hits", "cache_hit_ratio"];
|
|
228
|
+
readonly literals: readonly ["Hit", "RefreshHit"];
|
|
229
|
+
readonly sql: SqlText;
|
|
230
|
+
};
|
|
231
|
+
readonly 'unique-visitors': {
|
|
232
|
+
readonly rowMeaning: "one UTC day and its distinct visitor_key count, beside the range total - the sum of those daily counts, not a distinct count across days, because the salt rotates daily";
|
|
233
|
+
readonly columns: readonly ["day", "visitor_key", "is_bot"];
|
|
234
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
235
|
+
readonly resultColumns: readonly ["day", "daily_unique_visitors", "summed_daily_unique_visitors"];
|
|
236
|
+
readonly literals: readonly [];
|
|
237
|
+
readonly sql: SqlText;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Not a dashboard panel: the figure `analytics status` reports beside the
|
|
241
|
+
* node listing, so an operator can tell "the pipeline is provisioned" from
|
|
242
|
+
* "the pipeline has delivered something". Bots are counted when the caller
|
|
243
|
+
* asks for them - a row is a row - which is why the status command binds
|
|
244
|
+
* `include_bots` explicitly rather than leaving it to `config.analytics.bots`.
|
|
245
|
+
*/
|
|
246
|
+
readonly "row-count": {
|
|
247
|
+
readonly rowMeaning: "the number of rows the table holds over the range, one row carrying the count";
|
|
248
|
+
readonly columns: readonly ["day", "is_bot"];
|
|
249
|
+
readonly binds: readonly ["from", "to", "include_bots"];
|
|
250
|
+
readonly resultColumns: readonly ["row_count"];
|
|
251
|
+
readonly literals: readonly [];
|
|
252
|
+
readonly sql: SqlText;
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
/** The name of one of the {@link ANALYTICS_QUERIES}. */
|
|
256
|
+
export type QueryName = keyof typeof ANALYTICS_QUERIES;
|
|
257
|
+
/**
|
|
258
|
+
* Every query name, in declaration order - what the unknown-name error lists
|
|
259
|
+
* and what the test suite iterates. Derived from the table rather than
|
|
260
|
+
* restated beside it, so the two cannot drift; the cast is `Object.keys`'
|
|
261
|
+
* `string[]` narrowed back to the keys it just enumerated.
|
|
262
|
+
*/
|
|
263
|
+
export declare const ANALYTICS_QUERY_NAMES: readonly QueryName[];
|
|
264
|
+
/**
|
|
265
|
+
* Resolve a name to its definition, raising with the available names when it
|
|
266
|
+
* is not one of them. Takes a `string` rather than a {@link QueryName} because
|
|
267
|
+
* this is the boundary the local server's HTTP path arrives at, where the
|
|
268
|
+
* compiler's guarantee has already been erased.
|
|
269
|
+
*
|
|
270
|
+
* The lookup is guarded by `Object.hasOwn` and not by a test for `undefined`,
|
|
271
|
+
* for the reason `build.ts`'s `contentType` is: an unguarded index answers
|
|
272
|
+
* every `Object.prototype` key with an inherited function, so
|
|
273
|
+
* `GET /api/queries/constructor` would resolve to a truthy "definition" and
|
|
274
|
+
* fail later as `definition.binds is not iterable` instead of the rejection
|
|
275
|
+
* below. The names in the message are the only ones that resolve.
|
|
276
|
+
*/
|
|
277
|
+
export declare function queryDefinition(name: string): QueryDefinition;
|
|
278
|
+
/** A named query resolved against its caller's parameters, ready for an adapter to execute. */
|
|
279
|
+
export interface PreparedQuery {
|
|
280
|
+
/** The name that resolved. */
|
|
281
|
+
readonly name: QueryName;
|
|
282
|
+
/** The statement to execute, exactly as its definition carries it. */
|
|
283
|
+
readonly sql: string;
|
|
284
|
+
/** The columns a row of the result carries. */
|
|
285
|
+
readonly resultColumns: readonly string[];
|
|
286
|
+
/** One entry per placeholder the statement carries, keyed by placeholder name. */
|
|
287
|
+
readonly bindings: Readonly<Record<string, BindValue>>;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Resolve a name and its parameters into the statement and the bindings an
|
|
291
|
+
* adapter runs - the one boundary where an unknown name and a bad range are
|
|
292
|
+
* both rejected, so every implementation of `AnalyticsQuery` (the DuckDB
|
|
293
|
+
* adapter and the fixture-backed fake alike) refuses the same inputs with the
|
|
294
|
+
* same messages.
|
|
295
|
+
*
|
|
296
|
+
* `config` is the validated `analytics` block off `ctx.pluginConfig`, taken for
|
|
297
|
+
* its `bots` mode alone: a resolved config satisfies it too, so a caller passes
|
|
298
|
+
* whichever it is holding.
|
|
299
|
+
*/
|
|
300
|
+
export declare function prepareQuery(name: string, params: QueryParams, config: Pick<AnalyticsConfig, 'bots'>): PreparedQuery;
|
|
301
|
+
export {};
|