modelrig 0.5.0 → 0.7.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/CHANGELOG.md +125 -0
- package/README.md +54 -4
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +36 -7
- package/dist/adapters/attempt-meta-map.d.ts +63 -0
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
- package/dist/adapters/attempt-meta-map.js +154 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +25 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +29 -2
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +25 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +25 -2
- package/dist/adapters/openai-stream.d.ts +2 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +23 -3
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +36 -6
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +24 -5
- package/dist/artifacts/classification.d.ts +27 -0
- package/dist/artifacts/classification.d.ts.map +1 -0
- package/dist/artifacts/classification.js +38 -0
- package/dist/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +10 -1
- package/dist/artifacts/run-context.d.ts +25 -3
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +61 -0
- package/dist/artifacts/scrub.d.ts +26 -0
- package/dist/artifacts/scrub.d.ts.map +1 -1
- package/dist/artifacts/scrub.js +53 -0
- package/dist/artifacts/step.d.ts +9 -0
- package/dist/artifacts/step.d.ts.map +1 -1
- package/dist/artifacts/step.js +9 -0
- package/dist/bakeoff/engine.d.ts +46 -4
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +269 -19
- package/dist/bakeoff/eval-cases.d.ts +5 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +3 -0
- package/dist/bakeoff/judge.d.ts +39 -0
- package/dist/bakeoff/judge.d.ts.map +1 -0
- package/dist/bakeoff/judge.js +102 -0
- package/dist/bakeoff/quality.d.ts +44 -0
- package/dist/bakeoff/quality.d.ts.map +1 -0
- package/dist/bakeoff/quality.js +126 -0
- package/dist/bakeoff/reference.d.ts +34 -0
- package/dist/bakeoff/reference.d.ts.map +1 -0
- package/dist/bakeoff/reference.js +68 -0
- package/dist/bakeoff/replay.d.ts +25 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +48 -1
- package/dist/bakeoff/report.d.ts +8 -1
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +83 -12
- package/dist/bakeoff/samples.d.ts +35 -2
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +67 -6
- package/dist/bakeoff/stats.d.ts +17 -1
- package/dist/bakeoff/stats.d.ts.map +1 -1
- package/dist/bakeoff/stats.js +67 -0
- package/dist/bakeoff/types.d.ts +76 -5
- package/dist/bakeoff/types.d.ts.map +1 -1
- package/dist/bakeoff/types.js +4 -3
- package/dist/cli-query.d.ts +14 -0
- package/dist/cli-query.d.ts.map +1 -0
- package/dist/cli-query.js +59 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -23
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/detect/detector.d.ts +82 -0
- package/dist/detect/detector.d.ts.map +1 -0
- package/dist/detect/detector.js +23 -0
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -11
- package/dist/query/analytics.d.ts +84 -0
- package/dist/query/analytics.d.ts.map +1 -0
- package/dist/query/analytics.js +280 -0
- package/dist/query/columns.d.ts +59 -0
- package/dist/query/columns.d.ts.map +1 -0
- package/dist/query/columns.js +89 -0
- package/dist/query/grammar.d.ts +74 -7
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +179 -71
- package/dist/query/saved.d.ts +26 -0
- package/dist/query/saved.d.ts.map +1 -0
- package/dist/query/saved.js +51 -0
- package/dist/routes/loader.js +15 -10
- package/dist/routes/objective-loader.d.ts +35 -0
- package/dist/routes/objective-loader.d.ts.map +1 -0
- package/dist/routes/objective-loader.js +286 -0
- package/dist/run/accept.d.ts +43 -0
- package/dist/run/accept.d.ts.map +1 -0
- package/dist/run/accept.js +65 -0
- package/dist/run/cost.d.ts +13 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +27 -0
- package/dist/run/dispatch.d.ts +24 -1
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +19 -0
- package/dist/run/ladder-key.d.ts +4 -0
- package/dist/run/ladder-key.d.ts.map +1 -1
- package/dist/run/ladder-key.js +3 -0
- package/dist/run/quality-gate.d.ts +39 -0
- package/dist/run/quality-gate.d.ts.map +1 -0
- package/dist/run/quality-gate.js +70 -0
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +22 -3
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +22 -1
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +11 -4
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +46 -39
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +10 -0
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +17 -0
- package/dist/telemetry/attempt-meta.d.ts +178 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -0
- package/dist/telemetry/attempt-meta.js +319 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +6 -0
- package/dist/telemetry/ingest-protocol.d.ts +11 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +3 -0
- package/dist/telemetry/otel-map.d.ts +10 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -1
- package/dist/telemetry/otel-map.js +64 -0
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +15 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +25 -7
- package/dist/types/failures-teaching.d.ts.map +1 -1
- package/dist/types/failures-teaching.js +20 -0
- package/dist/types/failures.d.ts +1 -1
- package/dist/types/failures.d.ts.map +1 -1
- package/dist/types/failures.js +1 -0
- package/dist/types/provider.d.ts +9 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/quality.d.ts +41 -0
- package/dist/types/quality.d.ts.map +1 -0
- package/dist/types/quality.js +12 -0
- package/dist/types/route.d.ts +89 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +13 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The analytics evaluator — WI-1b (spec §5, plan §3). Turns a parsed
|
|
4
|
+
* {@link QueryAst} into (a) a PostgREST read plan over `inferences`, and (b) a
|
|
5
|
+
* pure JS fold that groups + aggregates the RLS-scoped rows the server reads.
|
|
6
|
+
*
|
|
7
|
+
* ── WHY JS AGGREGATION, NOT SQL ──────────────────────────────────────────
|
|
8
|
+
* The control plane is read through the identity-forwarding PostgREST reader
|
|
9
|
+
* (T12, cache-stats precedent) — there is no raw-SQL executor and never will be
|
|
10
|
+
* (a hand-filter is the XOS smell the reader deletes). So the server reads raw
|
|
11
|
+
* RLS-scoped rows (a bounded scan) and folds them here. The fold's semantics
|
|
12
|
+
* mirror the SQLite compiler's (grammar.ts) column-for-column, so the local and
|
|
13
|
+
* hosted `/query` surfaces answer the same question the same way.
|
|
14
|
+
*
|
|
15
|
+
* ── NEVER CONTENT ────────────────────────────────────────────────────────
|
|
16
|
+
* Every selectable column is an enum/number/boolean/hash/identifier/timestamp
|
|
17
|
+
* (columns.ts). {@link selectableColumnsAreMetaSafe} proves it; the property
|
|
18
|
+
* test pins it. A caller cannot name a column that would carry a prompt or a
|
|
19
|
+
* completion, so the query surface cannot return content by construction.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.ANALYTICS_SCAN_CAP = void 0;
|
|
23
|
+
exports.describeAnalyticsSchema = describeAnalyticsSchema;
|
|
24
|
+
exports.selectableColumnsAreMetaSafe = selectableColumnsAreMetaSafe;
|
|
25
|
+
exports.planAnalyticsRead = planAnalyticsRead;
|
|
26
|
+
exports.evaluateAnalyticsQuery = evaluateAnalyticsQuery;
|
|
27
|
+
const columns_1 = require("./columns");
|
|
28
|
+
const attempt_meta_1 = require("../telemetry/attempt-meta");
|
|
29
|
+
/** The bounded scan the analytics reader issues — the newest rows for the org,
|
|
30
|
+
* folded in JS. A summary surface, not an export; matches the cache-stats
|
|
31
|
+
* ROLLUP ceiling. When a scan fills this cap, the result is flagged `truncated`. */
|
|
32
|
+
exports.ANALYTICS_SCAN_CAP = 10000;
|
|
33
|
+
/** The fixed row projection the analytics reader selects: the plain columns the
|
|
34
|
+
* grammar exposes plus the two jsonb envelopes (`tags`, `meta`) it folds. The
|
|
35
|
+
* whole jsonb objects travel so any reserved-dim / meta.* column resolves in
|
|
36
|
+
* JS without a per-column PostgREST select. */
|
|
37
|
+
const READ_PROJECTION = "route,provider,model,ts,failure_class,served_variant," +
|
|
38
|
+
"cost_estimate_usd,tokens_in,tokens_out,tokens_cached,latency_ms,ttfb_ms,tags,meta";
|
|
39
|
+
function fieldOf(def) {
|
|
40
|
+
return { name: def.name, type: def.classification, classification: def.classification, since: def.since };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The self-describing schema for `/v1/analytics/meta` (spec §5) — every
|
|
44
|
+
* dimension and metric the org can query, DERIVED from the grammar registry
|
|
45
|
+
* (columns.ts), so there is no hand-maintained list to drift. Count(*) is always
|
|
46
|
+
* available and needs no metric column.
|
|
47
|
+
*/
|
|
48
|
+
function describeAnalyticsSchema() {
|
|
49
|
+
return {
|
|
50
|
+
dimensions: columns_1.DIMENSION_COLUMNS.map(fieldOf),
|
|
51
|
+
metrics: columns_1.METRIC_COLUMNS.map(fieldOf),
|
|
52
|
+
reserved_tags: [...columns_1.RESERVED_DIMENSION_NAMES],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** The classifications that are, by definition, not free text. If a selectable
|
|
56
|
+
* column ever carried any other classification, the never-content guarantee
|
|
57
|
+
* would be broken — so the property test asserts this set covers every column. */
|
|
58
|
+
const META_SAFE = new Set([
|
|
59
|
+
"enum",
|
|
60
|
+
"number",
|
|
61
|
+
"boolean",
|
|
62
|
+
"hash",
|
|
63
|
+
"identifier",
|
|
64
|
+
"timestamp",
|
|
65
|
+
]);
|
|
66
|
+
/**
|
|
67
|
+
* Prove that EVERY selectable column (dimension + metric) is metadata-safe — an
|
|
68
|
+
* enum/number/boolean/hash/identifier/timestamp, never content (spec §6.1). The
|
|
69
|
+
* `/v1/analytics/query` never-returns-content promise rests on this being total.
|
|
70
|
+
*/
|
|
71
|
+
function selectableColumnsAreMetaSafe() {
|
|
72
|
+
return [...columns_1.DIMENSION_COLUMNS, ...columns_1.METRIC_COLUMNS].every((c) => META_SAFE.has(c.classification));
|
|
73
|
+
}
|
|
74
|
+
// ── The PostgREST read plan ──────────────────────────────────────────────────
|
|
75
|
+
/** PostgREST operator for a grammar operator. Only these six exist. */
|
|
76
|
+
const PGREST_OP = {
|
|
77
|
+
"=": "eq",
|
|
78
|
+
"!=": "neq",
|
|
79
|
+
">": "gt",
|
|
80
|
+
">=": "gte",
|
|
81
|
+
"<": "lt",
|
|
82
|
+
"<=": "lte",
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The bounded read the analytics reader issues: the fixed projection, newest
|
|
86
|
+
* first, capped at {@link ANALYTICS_SCAN_CAP}. A `ts` bound in the query is
|
|
87
|
+
* pushed down to PostgREST (it narrows the scan cheaply and correctly — `ts` is
|
|
88
|
+
* a real timestamptz column); every other predicate is applied in the JS fold,
|
|
89
|
+
* where the grammar's semantics are authoritative regardless of jsonb typing.
|
|
90
|
+
*/
|
|
91
|
+
function planAnalyticsRead(ast) {
|
|
92
|
+
const parts = [`select=${READ_PROJECTION}`, "order=ts.desc", `limit=${exports.ANALYTICS_SCAN_CAP}`];
|
|
93
|
+
for (const cond of ast.where) {
|
|
94
|
+
if (cond.column.source === "plain" && cond.column.key === "ts" && typeof cond.value === "string") {
|
|
95
|
+
const op = PGREST_OP[cond.op];
|
|
96
|
+
if (op !== undefined)
|
|
97
|
+
parts.push(`ts=${op}.${encodeURIComponent(cond.value)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { query: parts.join("&") };
|
|
101
|
+
}
|
|
102
|
+
// ── The JS fold (grouping + aggregation), mirroring the SQLite semantics ─────
|
|
103
|
+
/** Extract a column's raw value from a PostgREST row. */
|
|
104
|
+
function extract(row, ref) {
|
|
105
|
+
if (ref.source === "plain")
|
|
106
|
+
return row[ref.key];
|
|
107
|
+
const bag = ref.source === "tag" ? row.tags : row.meta;
|
|
108
|
+
if (ref.source === "tag")
|
|
109
|
+
return tagValue(bag, ref.key);
|
|
110
|
+
if (bag === null || bag === undefined || typeof bag !== "object")
|
|
111
|
+
return undefined;
|
|
112
|
+
// A meta key may be a dotted path (`cost.in`); a tag key is a single segment.
|
|
113
|
+
let cur = bag;
|
|
114
|
+
for (const seg of ref.key.split(".")) {
|
|
115
|
+
if (cur === null || cur === undefined || typeof cur !== "object")
|
|
116
|
+
return undefined;
|
|
117
|
+
cur = cur[seg];
|
|
118
|
+
}
|
|
119
|
+
return cur;
|
|
120
|
+
}
|
|
121
|
+
/** A tag value, or null when it is a string that fails the metadata-safe rule
|
|
122
|
+
* (tags are unconstrained at ingest; a free-text tag must not surface through
|
|
123
|
+
* a surface that promises "never content"). */
|
|
124
|
+
function tagValue(bag, key) {
|
|
125
|
+
if (bag === null || bag === undefined || typeof bag !== "object")
|
|
126
|
+
return undefined;
|
|
127
|
+
const v = bag[key];
|
|
128
|
+
if (typeof v === "string" && !(0, attempt_meta_1.isMetaSafeString)(v))
|
|
129
|
+
return null;
|
|
130
|
+
return v;
|
|
131
|
+
}
|
|
132
|
+
/** A finite number, else null (drops null/NaN/non-numeric for aggregation). */
|
|
133
|
+
function toNum(v) {
|
|
134
|
+
if (typeof v === "number")
|
|
135
|
+
return Number.isFinite(v) ? v : null;
|
|
136
|
+
if (typeof v === "string" && v !== "" && Number.isFinite(Number(v)))
|
|
137
|
+
return Number(v);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
/** Compare under one operator over already-resolved numeric operands. */
|
|
141
|
+
function cmpNum(l, op, r) {
|
|
142
|
+
switch (op) {
|
|
143
|
+
case "=": return l === r;
|
|
144
|
+
case "!=": return l !== r;
|
|
145
|
+
case ">": return l > r;
|
|
146
|
+
case ">=": return l >= r;
|
|
147
|
+
case "<": return l < r;
|
|
148
|
+
case "<=": return l <= r;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Compare a row value against a grammar literal under one operator, matching the
|
|
154
|
+
* SQLite compiler. `timestamp` columns are compared by parsed epoch ms so a
|
|
155
|
+
* PostgREST `+00:00` serialization and a `.000Z` bound agree at the boundary
|
|
156
|
+
* (the JS re-filter must never drop a row the ts pushdown correctly included);
|
|
157
|
+
* otherwise numeric when both sides are numbers, else a string compare (correct
|
|
158
|
+
* for enums/identifiers/hashes).
|
|
159
|
+
*/
|
|
160
|
+
function matches(left, op, right, classification) {
|
|
161
|
+
if (classification === "timestamp") {
|
|
162
|
+
const l = Date.parse(String(left));
|
|
163
|
+
const r = typeof right === "number" ? right : Date.parse(String(right));
|
|
164
|
+
if (Number.isFinite(l) && Number.isFinite(r))
|
|
165
|
+
return cmpNum(l, op, r);
|
|
166
|
+
// fall through to string compare when either side is unparseable
|
|
167
|
+
}
|
|
168
|
+
const ln = toNum(left);
|
|
169
|
+
const rn = typeof right === "number" ? right : toNum(right);
|
|
170
|
+
if (ln !== null && rn !== null)
|
|
171
|
+
return cmpNum(ln, op, rn);
|
|
172
|
+
if (op === "=" || op === "!=") {
|
|
173
|
+
const eq = stringOf(left) === String(right);
|
|
174
|
+
return op === "=" ? eq : !eq;
|
|
175
|
+
}
|
|
176
|
+
const ls = stringOf(left);
|
|
177
|
+
const rs = String(right);
|
|
178
|
+
switch (op) {
|
|
179
|
+
case ">": return ls > rs;
|
|
180
|
+
case ">=": return ls >= rs;
|
|
181
|
+
case "<": return ls < rs;
|
|
182
|
+
case "<=": return ls <= rs;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
/** A bake-off replay/shadow row — `tags.replay` is set by the replay path. */
|
|
187
|
+
function isReplayRow(row) {
|
|
188
|
+
const tags = row.tags;
|
|
189
|
+
return tags !== null && tags !== undefined && typeof tags === "object" && tags.replay !== undefined;
|
|
190
|
+
}
|
|
191
|
+
/** A row value as a comparable string; undefined/null never equal a literal. */
|
|
192
|
+
function stringOf(v) {
|
|
193
|
+
if (v === null || v === undefined)
|
|
194
|
+
return "<null>";
|
|
195
|
+
if (typeof v === "boolean")
|
|
196
|
+
return v ? "true" : "false";
|
|
197
|
+
return String(v);
|
|
198
|
+
}
|
|
199
|
+
function passesWhere(row, ast) {
|
|
200
|
+
return ast.where.every((cond) => matches(extract(row, cond.column), cond.op, cond.value, cond.column.classification));
|
|
201
|
+
}
|
|
202
|
+
function projectValue(row, ref) {
|
|
203
|
+
const v = extract(row, ref);
|
|
204
|
+
return v === undefined ? null : v;
|
|
205
|
+
}
|
|
206
|
+
function fold(fn, rows, ref) {
|
|
207
|
+
const nums = rows.map((r) => toNum(extract(r, ref))).filter((n) => n !== null);
|
|
208
|
+
if (fn === "sum" || fn === "avg") {
|
|
209
|
+
if (nums.length === 0)
|
|
210
|
+
return null;
|
|
211
|
+
const sum = nums.reduce((a, b) => a + b, 0);
|
|
212
|
+
return fn === "sum" ? sum : sum / nums.length;
|
|
213
|
+
}
|
|
214
|
+
// min/max: numeric when the column is a number, else ordered by string
|
|
215
|
+
// (correct for ISO timestamps and snapshot ids — mirrors SQLite MIN/MAX).
|
|
216
|
+
if (ref.numeric) {
|
|
217
|
+
if (nums.length === 0)
|
|
218
|
+
return null;
|
|
219
|
+
return fn === "min" ? Math.min(...nums) : Math.max(...nums);
|
|
220
|
+
}
|
|
221
|
+
const strs = rows
|
|
222
|
+
.map((r) => extract(r, ref))
|
|
223
|
+
.filter((v) => v !== null && v !== undefined)
|
|
224
|
+
.map((v) => String(v));
|
|
225
|
+
if (strs.length === 0)
|
|
226
|
+
return null;
|
|
227
|
+
return strs.reduce((a, b) => (fn === "min" ? (b < a ? b : a) : b > a ? b : a));
|
|
228
|
+
}
|
|
229
|
+
function projectGroup(items, rows) {
|
|
230
|
+
const out = {};
|
|
231
|
+
const rep = rows[0];
|
|
232
|
+
for (const item of items) {
|
|
233
|
+
if (item.kind === "count")
|
|
234
|
+
out.count = rows.length;
|
|
235
|
+
else if (item.kind === "aggregate")
|
|
236
|
+
out[item.alias] = fold(item.fn, rows, item.metric);
|
|
237
|
+
else
|
|
238
|
+
out[item.column.alias] = rep !== undefined ? projectValue(rep, item.column) : null;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
const hasAggregate = (ast) => ast.select.some((s) => s.kind === "count" || s.kind === "aggregate");
|
|
243
|
+
/**
|
|
244
|
+
* Evaluate a parsed query over the rows the analytics reader returned. Filters,
|
|
245
|
+
* groups, aggregates, and projects exactly as the SQLite compiler would, then
|
|
246
|
+
* applies the grammar LIMIT. `truncated` is true when the scan filled the cap
|
|
247
|
+
* (the answer may be partial) OR the grouped result exceeded the limit.
|
|
248
|
+
*/
|
|
249
|
+
function evaluateAnalyticsQuery(ast, rows, opts = {}) {
|
|
250
|
+
// Live rows only (odometer rule, same as every console surface): bake-off
|
|
251
|
+
// replay rows run through the production route with `tags.replay` set and
|
|
252
|
+
// must never move a route's numbers — the grammar cannot express IS NULL, so
|
|
253
|
+
// the exclusion is unconditional here and in the SQLite compiler alike.
|
|
254
|
+
const kept = rows.filter((r) => !isReplayRow(r) && passesWhere(r, ast));
|
|
255
|
+
let grouped;
|
|
256
|
+
if (ast.groupBy.length > 0) {
|
|
257
|
+
const groups = new Map();
|
|
258
|
+
for (const row of kept) {
|
|
259
|
+
// Join on U+0001 so ("ab","") and ("a","b") never collide into one group —
|
|
260
|
+
// a control byte cannot appear in a metadata-safe (enum/id/hash) value.
|
|
261
|
+
const key = ast.groupBy.map((g) => stringOf(extract(row, g))).join("\u0001");
|
|
262
|
+
const bucket = groups.get(key);
|
|
263
|
+
if (bucket === undefined)
|
|
264
|
+
groups.set(key, [row]);
|
|
265
|
+
else
|
|
266
|
+
bucket.push(row);
|
|
267
|
+
}
|
|
268
|
+
grouped = [...groups.values()].map((g) => projectGroup(ast.select, g));
|
|
269
|
+
}
|
|
270
|
+
else if (hasAggregate(ast)) {
|
|
271
|
+
// Aggregates with no GROUP BY collapse to a single row (SQL semantics).
|
|
272
|
+
grouped = [projectGroup(ast.select, kept)];
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// A plain projection — one output row per input row.
|
|
276
|
+
grouped = kept.map((r) => projectGroup(ast.select, [r]));
|
|
277
|
+
}
|
|
278
|
+
const truncated = (opts.scannedAtCap ?? false) || grouped.length > ast.limit;
|
|
279
|
+
return { rows: grouped.slice(0, ast.limit), truncated, limit: ast.limit };
|
|
280
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The analytics column/metric registry — WI-1b (spec §5, plan §3).
|
|
3
|
+
*
|
|
4
|
+
* ── ONE SOURCE OF TRUTH ──────────────────────────────────────────────────
|
|
5
|
+
* Every dimension and metric the telemetry grammar exposes is declared HERE,
|
|
6
|
+
* with its storage source, classification, and "since" version. The SQLite
|
|
7
|
+
* grammar renderer (grammar.ts), the PostgREST analytics planner
|
|
8
|
+
* (analytics.ts), and the self-describing `/v1/analytics/meta` endpoint all
|
|
9
|
+
* read this table — so the grammar, the query surface, and its documentation
|
|
10
|
+
* can never drift (spec §5: "self-description derived from the grammar").
|
|
11
|
+
*
|
|
12
|
+
* ── THE METADATA-SAFE INVARIANT ──────────────────────────────────────────
|
|
13
|
+
* NO column here is free text. Every `classification` is one of enum | number
|
|
14
|
+
* | boolean | hash | identifier | timestamp — the classification rule from
|
|
15
|
+
* spec §2. A reserved dimension (`subject`, …) is an OPAQUE identifier: a
|
|
16
|
+
* customer-side id, hashed by the caller if it looks like PII. This is what
|
|
17
|
+
* lets `/v1/analytics/query` promise "never returns content" — there is no
|
|
18
|
+
* selectable column that could carry a prompt or a completion.
|
|
19
|
+
*/
|
|
20
|
+
/** The metadata-safe classifications (spec §2). A column may only be one of
|
|
21
|
+
* these — there is deliberately no "text"/"string-freeform" bucket, which is
|
|
22
|
+
* the invariant the `/query` never-content guarantee rests on. */
|
|
23
|
+
export type Classification = "enum" | "number" | "boolean" | "hash" | "identifier" | "timestamp";
|
|
24
|
+
/** Where a column's value lives on the row. `plain` = a first-class column;
|
|
25
|
+
* `tag` = a key in the `tags` jsonb (SQLite: `tags_json`); `meta` = a path in
|
|
26
|
+
* the `meta` jsonb (SQLite: `meta_json`) — the WI-1a attempt envelope. */
|
|
27
|
+
export type ColumnSource = "plain" | "tag" | "meta";
|
|
28
|
+
export interface ColumnDef {
|
|
29
|
+
/** The grammar identifier a caller types (e.g. `route`, `subject`,
|
|
30
|
+
* `meta.finish_reason`, `meta.cost.in`). */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly source: ColumnSource;
|
|
33
|
+
/** The leaf key or path within the source. For `meta.cost.in` this is
|
|
34
|
+
* `cost.in`; for `subject` (tag) it is `subject`; for `route` it is `route`. */
|
|
35
|
+
readonly key: string;
|
|
36
|
+
readonly classification: Classification;
|
|
37
|
+
/** Whether the column is a NUMBER usable as an aggregate metric
|
|
38
|
+
* (`sum(...)`/`avg(...)`/`min(...)`/`max(...)`). */
|
|
39
|
+
readonly numeric: boolean;
|
|
40
|
+
/** The product version the column became queryable in. */
|
|
41
|
+
readonly since: string;
|
|
42
|
+
}
|
|
43
|
+
/** Plain numeric metrics (Phase 4 WS5) — the original aggregate targets. These
|
|
44
|
+
* are first-class columns on `inferences`; the grammar names them without a
|
|
45
|
+
* `meta.` prefix. `key` is the storage column (SQLite + Postgres share it). */
|
|
46
|
+
export declare const PLAIN_METRICS: readonly ColumnDef[];
|
|
47
|
+
/** Every column the grammar can GROUP BY / FILTER by (dimensions). Order is
|
|
48
|
+
* stable so `/meta` renders deterministically. */
|
|
49
|
+
export declare const DIMENSION_COLUMNS: readonly ColumnDef[];
|
|
50
|
+
/** Every column usable as an aggregate metric (`numeric`), keyed by grammar
|
|
51
|
+
* name — plain metrics plus the numeric envelope fields. */
|
|
52
|
+
export declare const METRIC_COLUMNS: readonly ColumnDef[];
|
|
53
|
+
/** Grammar name → dimension def (group/filter). */
|
|
54
|
+
export declare const DIMENSIONS_BY_NAME: ReadonlyMap<string, ColumnDef>;
|
|
55
|
+
/** Grammar name → metric def (aggregate target). */
|
|
56
|
+
export declare const METRICS_BY_NAME: ReadonlyMap<string, ColumnDef>;
|
|
57
|
+
/** The reserved tag keys, in declaration order (spec §4). */
|
|
58
|
+
export declare const RESERVED_DIMENSION_NAMES: readonly string[];
|
|
59
|
+
//# sourceMappingURL=columns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns.d.ts","sourceRoot":"","sources":["../../src/query/columns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;kEAEkE;AAClE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,CAAC;AAEjG;;0EAE0E;AAC1E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,SAAS;IACxB;gDAC4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;oFACgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;wDACoD;IACpD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AA8CD;;+EAE+E;AAC/E,eAAO,MAAM,aAAa,EAAE,SAAS,SAAS,EAO7C,CAAC;AAEF;kDACkD;AAClD,eAAO,MAAM,iBAAiB,EAAE,SAAS,SAAS,EAAkD,CAAC;AAErG;4DAC4D;AAC5D,eAAO,MAAM,cAAc,EAAE,SAAS,SAAS,EAG9C,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAE7D,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAE1D,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAqC,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The analytics column/metric registry — WI-1b (spec §5, plan §3).
|
|
4
|
+
*
|
|
5
|
+
* ── ONE SOURCE OF TRUTH ──────────────────────────────────────────────────
|
|
6
|
+
* Every dimension and metric the telemetry grammar exposes is declared HERE,
|
|
7
|
+
* with its storage source, classification, and "since" version. The SQLite
|
|
8
|
+
* grammar renderer (grammar.ts), the PostgREST analytics planner
|
|
9
|
+
* (analytics.ts), and the self-describing `/v1/analytics/meta` endpoint all
|
|
10
|
+
* read this table — so the grammar, the query surface, and its documentation
|
|
11
|
+
* can never drift (spec §5: "self-description derived from the grammar").
|
|
12
|
+
*
|
|
13
|
+
* ── THE METADATA-SAFE INVARIANT ──────────────────────────────────────────
|
|
14
|
+
* NO column here is free text. Every `classification` is one of enum | number
|
|
15
|
+
* | boolean | hash | identifier | timestamp — the classification rule from
|
|
16
|
+
* spec §2. A reserved dimension (`subject`, …) is an OPAQUE identifier: a
|
|
17
|
+
* customer-side id, hashed by the caller if it looks like PII. This is what
|
|
18
|
+
* lets `/v1/analytics/query` promise "never returns content" — there is no
|
|
19
|
+
* selectable column that could carry a prompt or a completion.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.RESERVED_DIMENSION_NAMES = exports.METRICS_BY_NAME = exports.DIMENSIONS_BY_NAME = exports.METRIC_COLUMNS = exports.DIMENSION_COLUMNS = exports.PLAIN_METRICS = void 0;
|
|
23
|
+
// ── Plain columns (Phase 4 WS5 — the original grammar) ───────────────────────
|
|
24
|
+
const PLAIN = [
|
|
25
|
+
{ name: "route", source: "plain", key: "route", classification: "identifier", numeric: false, since: "0.1.0" },
|
|
26
|
+
{ name: "provider", source: "plain", key: "provider", classification: "enum", numeric: false, since: "0.1.0" },
|
|
27
|
+
{ name: "model", source: "plain", key: "model", classification: "identifier", numeric: false, since: "0.1.0" },
|
|
28
|
+
{ name: "ts", source: "plain", key: "ts", classification: "timestamp", numeric: false, since: "0.1.0" },
|
|
29
|
+
{ name: "failure_class", source: "plain", key: "failure_class", classification: "enum", numeric: false, since: "0.1.0" },
|
|
30
|
+
{ name: "served_variant", source: "plain", key: "served_variant", classification: "identifier", numeric: false, since: "0.1.0" },
|
|
31
|
+
];
|
|
32
|
+
// ── Reserved dimensions (spec §4) — first-class group/filter columns that
|
|
33
|
+
// remain `tags` keys in storage (GIN-indexed; no DDL). An opaque customer-side
|
|
34
|
+
// id ⇒ identifier classification. ──────────────────────────────────────────
|
|
35
|
+
const RESERVED_DIMS = [
|
|
36
|
+
{ name: "subject", source: "tag", key: "subject", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
37
|
+
{ name: "feature", source: "tag", key: "feature", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
38
|
+
{ name: "user", source: "tag", key: "user", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
39
|
+
{ name: "session", source: "tag", key: "session", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
40
|
+
{ name: "cost_center", source: "tag", key: "cost_center", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
41
|
+
];
|
|
42
|
+
// ── Attempt-envelope scalars (spec §2) — `meta.<field>` columns. Every one is
|
|
43
|
+
// an enum / number / boolean / hash / identifier — never content. ────────────
|
|
44
|
+
const META_SCALARS = [
|
|
45
|
+
{ name: "meta.finish_reason", source: "meta", key: "finish_reason", classification: "enum", numeric: false, since: "0.6.0" },
|
|
46
|
+
{ name: "meta.refusal", source: "meta", key: "refusal", classification: "boolean", numeric: false, since: "0.6.0" },
|
|
47
|
+
{ name: "meta.streaming", source: "meta", key: "streaming", classification: "boolean", numeric: false, since: "0.6.0" },
|
|
48
|
+
{ name: "meta.model_snapshot", source: "meta", key: "model_snapshot", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
49
|
+
{ name: "meta.context_bucket", source: "meta", key: "context_bucket", classification: "enum", numeric: false, since: "0.6.0" },
|
|
50
|
+
{ name: "meta.error_code", source: "meta", key: "error_code", classification: "enum", numeric: false, since: "0.6.0" },
|
|
51
|
+
{ name: "meta.region", source: "meta", key: "region", classification: "identifier", numeric: false, since: "0.6.0" },
|
|
52
|
+
{ name: "meta.input_hash", source: "meta", key: "input_hash", classification: "hash", numeric: false, since: "0.6.0" },
|
|
53
|
+
{ name: "meta.output_hash", source: "meta", key: "output_hash", classification: "hash", numeric: false, since: "0.6.0" },
|
|
54
|
+
// Numeric envelope fields — group/filter dimensions AND aggregate metrics.
|
|
55
|
+
{ name: "meta.http_status", source: "meta", key: "http_status", classification: "number", numeric: true, since: "0.6.0" },
|
|
56
|
+
{ name: "meta.reasoning_tokens", source: "meta", key: "reasoning_tokens", classification: "number", numeric: true, since: "0.6.0" },
|
|
57
|
+
{ name: "meta.response_chars", source: "meta", key: "response_chars", classification: "number", numeric: true, since: "0.6.0" },
|
|
58
|
+
{ name: "meta.cost.in", source: "meta", key: "cost.in", classification: "number", numeric: true, since: "0.6.0" },
|
|
59
|
+
{ name: "meta.cost.out", source: "meta", key: "cost.out", classification: "number", numeric: true, since: "0.6.0" },
|
|
60
|
+
{ name: "meta.cost.cache_read", source: "meta", key: "cost.cache_read", classification: "number", numeric: true, since: "0.6.0" },
|
|
61
|
+
{ name: "meta.cost.cache_write", source: "meta", key: "cost.cache_write", classification: "number", numeric: true, since: "0.6.0" },
|
|
62
|
+
{ name: "meta.cost.other", source: "meta", key: "cost.other", classification: "number", numeric: true, since: "0.6.0" },
|
|
63
|
+
];
|
|
64
|
+
/** Plain numeric metrics (Phase 4 WS5) — the original aggregate targets. These
|
|
65
|
+
* are first-class columns on `inferences`; the grammar names them without a
|
|
66
|
+
* `meta.` prefix. `key` is the storage column (SQLite + Postgres share it). */
|
|
67
|
+
exports.PLAIN_METRICS = [
|
|
68
|
+
{ name: "cost", source: "plain", key: "cost_estimate_usd", classification: "number", numeric: true, since: "0.1.0" },
|
|
69
|
+
{ name: "tokens_in", source: "plain", key: "tokens_in", classification: "number", numeric: true, since: "0.1.0" },
|
|
70
|
+
{ name: "tokens_out", source: "plain", key: "tokens_out", classification: "number", numeric: true, since: "0.1.0" },
|
|
71
|
+
{ name: "tokens_cached", source: "plain", key: "tokens_cached", classification: "number", numeric: true, since: "0.1.0" },
|
|
72
|
+
{ name: "latency_ms", source: "plain", key: "latency_ms", classification: "number", numeric: true, since: "0.1.0" },
|
|
73
|
+
{ name: "ttfb_ms", source: "plain", key: "ttfb_ms", classification: "number", numeric: true, since: "0.1.0" },
|
|
74
|
+
];
|
|
75
|
+
/** Every column the grammar can GROUP BY / FILTER by (dimensions). Order is
|
|
76
|
+
* stable so `/meta` renders deterministically. */
|
|
77
|
+
exports.DIMENSION_COLUMNS = [...PLAIN, ...RESERVED_DIMS, ...META_SCALARS];
|
|
78
|
+
/** Every column usable as an aggregate metric (`numeric`), keyed by grammar
|
|
79
|
+
* name — plain metrics plus the numeric envelope fields. */
|
|
80
|
+
exports.METRIC_COLUMNS = [
|
|
81
|
+
...exports.PLAIN_METRICS,
|
|
82
|
+
...META_SCALARS.filter((c) => c.numeric),
|
|
83
|
+
];
|
|
84
|
+
/** Grammar name → dimension def (group/filter). */
|
|
85
|
+
exports.DIMENSIONS_BY_NAME = new Map(exports.DIMENSION_COLUMNS.map((c) => [c.name, c]));
|
|
86
|
+
/** Grammar name → metric def (aggregate target). */
|
|
87
|
+
exports.METRICS_BY_NAME = new Map(exports.METRIC_COLUMNS.map((c) => [c.name, c]));
|
|
88
|
+
/** The reserved tag keys, in declaration order (spec §4). */
|
|
89
|
+
exports.RESERVED_DIMENSION_NAMES = RESERVED_DIMS.map((c) => c.name);
|
package/dist/query/grammar.d.ts
CHANGED
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The telemetry query grammar — Phase 4 WS5 ("your exhaust is your data")
|
|
2
|
+
* The telemetry query grammar — Phase 4 WS5 ("your exhaust is your data"),
|
|
3
|
+
* extended for WI-1b (the attempt-envelope analytics surface).
|
|
4
|
+
*
|
|
3
5
|
* A hand-rolled recursive-descent parser over a DELIBERATELY tiny SQL-ish
|
|
4
6
|
* language:
|
|
5
7
|
*
|
|
6
8
|
* SELECT item (, item)* FROM inferences
|
|
7
9
|
* [WHERE cond (AND cond)*] [GROUP BY col (, col)*] [LIMIT n]
|
|
8
10
|
* item := col | count(*) | sum(m)|avg(m)|min(m)|max(m)
|
|
9
|
-
* col := route|provider|model|ts|failure_class|served_variant
|
|
11
|
+
* col := route|provider|model|ts|failure_class|served_variant
|
|
12
|
+
* | subject|feature|user|session|cost_center (reserved dims)
|
|
13
|
+
* | meta.<field> (attempt envelope)
|
|
14
|
+
* | tag.<name>
|
|
10
15
|
* m := cost|tokens_in|tokens_out|tokens_cached|latency_ms|ttfb_ms
|
|
16
|
+
* | meta.http_status|meta.reasoning_tokens|meta.response_chars
|
|
17
|
+
* | meta.cost.in|meta.cost.out|meta.cost.cache_read|meta.cost.cache_write|meta.cost.other
|
|
11
18
|
* cond := col op literal op := = != > < >= <=
|
|
12
19
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* ── TWO EXECUTION TARGETS, ONE PARSER (S1: extend, don't twin) ────────────
|
|
21
|
+
* `parseQuery` produces a {@link QueryAst}. `compileQuery` renders that AST to a
|
|
22
|
+
* single read-only SQLite SELECT (the local `/query` surface, `runTelemetryQuery`).
|
|
23
|
+
* The server analytics surface (`/v1/analytics/query`) evaluates the SAME AST
|
|
24
|
+
* over PostgREST-read rows in JS (query/analytics.ts) — so the grammar, its
|
|
25
|
+
* SQLite semantics, and its multi-tenant semantics can never disagree.
|
|
26
|
+
*
|
|
27
|
+
* Security model = the allowlist (columns.ts): unknown tokens throw, string
|
|
28
|
+
* literals only ever travel as bind parameters, the SQLite output is a single
|
|
29
|
+
* read-only SELECT. There is no raw-SQL path — by construction, not by filtering.
|
|
30
|
+
* The metadata-safe invariant is structural: every selectable column is an
|
|
31
|
+
* enum/number/boolean/hash/identifier/timestamp (columns.ts), never content.
|
|
18
32
|
*/
|
|
33
|
+
import type { Classification, ColumnSource } from "./columns";
|
|
34
|
+
export { describeAnalyticsSchema, evaluateAnalyticsQuery, planAnalyticsRead, ANALYTICS_SCAN_CAP, selectableColumnsAreMetaSafe, } from "./analytics";
|
|
35
|
+
export type { AnalyticsSchema, AnalyticsSchemaField, AnalyticsResult, AnalyticsReadPlan, AnalyticsRow } from "./analytics";
|
|
36
|
+
export { SAVED_QUERIES, savedQueryById, sinceIsoFor } from "./saved";
|
|
37
|
+
export type { SavedQuery } from "./saved";
|
|
38
|
+
export { DIMENSION_COLUMNS, METRIC_COLUMNS, RESERVED_DIMENSION_NAMES } from "./columns";
|
|
39
|
+
export type { ColumnDef, Classification, ColumnSource } from "./columns";
|
|
19
40
|
export declare class QueryGrammarError extends Error {
|
|
20
41
|
constructor(message: string);
|
|
21
42
|
}
|
|
@@ -31,5 +52,51 @@ export interface CompiledQuery {
|
|
|
31
52
|
readonly sql: string;
|
|
32
53
|
readonly params: ReadonlyArray<string | number>;
|
|
33
54
|
}
|
|
55
|
+
export type AggFn = "sum" | "avg" | "min" | "max";
|
|
56
|
+
/** A resolved reference to one column, carrying its storage + classification so
|
|
57
|
+
* the JS evaluator (analytics.ts) needs no second lookup. */
|
|
58
|
+
export interface AstColumnRef {
|
|
59
|
+
readonly name: string;
|
|
60
|
+
readonly source: ColumnSource;
|
|
61
|
+
readonly key: string;
|
|
62
|
+
readonly classification: Classification;
|
|
63
|
+
readonly numeric: boolean;
|
|
64
|
+
readonly alias: string;
|
|
65
|
+
}
|
|
66
|
+
export type AstSelectItem = {
|
|
67
|
+
readonly kind: "column";
|
|
68
|
+
readonly column: AstColumnRef;
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: "count";
|
|
71
|
+
readonly alias: "count";
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: "aggregate";
|
|
74
|
+
readonly fn: AggFn;
|
|
75
|
+
readonly metric: AstColumnRef;
|
|
76
|
+
readonly alias: string;
|
|
77
|
+
};
|
|
78
|
+
export interface AstCondition {
|
|
79
|
+
readonly column: AstColumnRef;
|
|
80
|
+
readonly op: string;
|
|
81
|
+
readonly value: string | number;
|
|
82
|
+
}
|
|
83
|
+
export interface QueryAst {
|
|
84
|
+
readonly select: readonly AstSelectItem[];
|
|
85
|
+
readonly where: readonly AstCondition[];
|
|
86
|
+
readonly groupBy: readonly AstColumnRef[];
|
|
87
|
+
readonly limit: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse a query string into a {@link QueryAst}. Pure and total: every path
|
|
91
|
+
* either returns a validated AST or throws {@link QueryGrammarError}. This is
|
|
92
|
+
* the single parser — both the SQLite compiler (below) and the PostgREST
|
|
93
|
+
* evaluator (analytics.ts) consume its output.
|
|
94
|
+
*/
|
|
95
|
+
export declare function parseQuery(input: string): QueryAst;
|
|
96
|
+
/**
|
|
97
|
+
* Compile a query string to a single read-only SQLite SELECT (the local
|
|
98
|
+
* telemetry store). Row scoping is ANDed into WHERE unconditionally — the caller
|
|
99
|
+
* cannot opt out of it.
|
|
100
|
+
*/
|
|
34
101
|
export declare function compileQuery(input: string, scope: QueryScope): CompiledQuery;
|
|
35
102
|
//# sourceMappingURL=grammar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/query/grammar.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/query/grammar.ts"],"names":[],"mappings":"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"}
|