@gscdump/cloudflare 0.36.1 → 0.36.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.
@@ -3,9 +3,13 @@ import { ARCHETYPE_EXECUTION_CLASS } from "@gscdump/contracts/archetypes";
3
3
  import { err, ok, unwrapResult } from "gscdump/result";
4
4
  import { SEARCH_TYPE_INT } from "@gscdump/engine/iceberg";
5
5
  const TABLE_PLACEHOLDER = "{{TABLE}}";
6
+ const FACT_ALIAS = "fact";
7
+ function factTableRef() {
8
+ return `${TABLE_PLACEHOLDER} AS ${FACT_ALIAS}`;
9
+ }
6
10
  function dimColumn(dim) {
7
11
  if (dim === "page") return "url";
8
- if (dim === "queryCanonical") return "COALESCE((SELECT qd.query_canonical FROM query_dim qd WHERE qd.query = query LIMIT 1), query)";
12
+ if (dim === "queryCanonical") return `COALESCE((SELECT qd.query_canonical FROM query_dim qd WHERE qd.query = ${FACT_ALIAS}.query LIMIT 1), ${FACT_ALIAS}.query)`;
9
13
  return dim;
10
14
  }
11
15
  function dimSelect(dim) {
@@ -20,6 +24,15 @@ function tableForDimensions(dims) {
20
24
  if (set.has("device")) return "dates";
21
25
  return "pages";
22
26
  }
27
+ function tableForTopNBreakdown(q) {
28
+ const dims = [q.dimension];
29
+ for (const facet of q.facets ?? []) {
30
+ const hasPage = q.dimension === "page" || facet.column === "page";
31
+ const hasQuery = q.dimension === "query" || q.dimension === "queryCanonical" || facet.column === "query" || facet.column === "queryCanonical";
32
+ if (hasPage && hasQuery) dims.push(facet.column);
33
+ }
34
+ return tableForDimensions(dims);
35
+ }
23
36
  function metricExpr(metric) {
24
37
  switch (metric) {
25
38
  case "clicks": return "SUM(clicks) AS clicks";
@@ -149,7 +162,7 @@ function buildSiteDailyTimeseries(q, pruned, mode) {
149
162
  return {
150
163
  table: "dates",
151
164
  params: w.params,
152
- sql: `SELECT date, ${metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause} GROUP BY date ORDER BY date ASC`
165
+ sql: `SELECT date, ${metrics} FROM ${factTableRef()} WHERE ${w.clause} GROUP BY date ORDER BY date ASC`
153
166
  };
154
167
  }
155
168
  function buildEntityDailyTimeseries(q, pruned, mode) {
@@ -160,7 +173,7 @@ function buildEntityDailyTimeseries(q, pruned, mode) {
160
173
  return {
161
174
  table,
162
175
  params: [...w.params, q.entity.value],
163
- sql: `SELECT date, ${metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause} AND ${col} = ? GROUP BY date ORDER BY date ASC`
176
+ sql: `SELECT date, ${metrics} FROM ${factTableRef()} WHERE ${w.clause} AND ${col} = ? GROUP BY date ORDER BY date ASC`
164
177
  };
165
178
  }
166
179
  function buildEntityDailySparkline(q, pruned, mode) {
@@ -172,11 +185,11 @@ function buildEntityDailySparkline(q, pruned, mode) {
172
185
  return {
173
186
  table,
174
187
  params: w.params,
175
- sql: `SELECT date, ${dimSelect(q.dimension)}, ${metricExpr(q.metric)} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause} AND ${col} IN (${inList}) GROUP BY date, ${col} ORDER BY date ASC`
188
+ sql: `SELECT date, ${dimSelect(q.dimension)}, ${metricExpr(q.metric)} FROM ${factTableRef()} WHERE ${w.clause} AND ${col} IN (${inList}) GROUP BY date, ${col} ORDER BY date ASC`
176
189
  };
177
190
  }
178
191
  function buildTopNBreakdown(q, pruned, mode) {
179
- const table = tableForDimensions([q.dimension]);
192
+ const table = tableForTopNBreakdown(q);
180
193
  const w = partitionWhere(q, pruned, mode);
181
194
  if (!q.orderBy || !q.orderBy.metric || !q.orderBy.dir) throw new Error(`[archetype-sql] top-n-breakdown requires orderBy.{metric,dir}, got: ${JSON.stringify(q.orderBy)}`);
182
195
  const order = `${metricAlias(q.orderBy.metric)} ${q.orderBy.dir.toUpperCase()}`;
@@ -198,7 +211,7 @@ function buildTopNBreakdown(q, pruned, mode) {
198
211
  const deviceSelects = (clause, ml) => DEVICE_SUFFIXES.map((suffix) => {
199
212
  const source = deviceSource(suffix);
200
213
  const metrics = ml.map((m) => metricExprForSource(m, source)).join(", ");
201
- return `SELECT '${suffix.toUpperCase()}' AS device, ${metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${clause}`;
214
+ return `SELECT '${suffix.toUpperCase()}' AS device, ${metrics} FROM ${factTableRef()} WHERE ${clause}`;
202
215
  }).join(" UNION ALL ");
203
216
  const inner = `SELECT COALESCE(c.device, p.device) AS device, ${metricList.map((m) => coalesceMetric(m, "c", m)).join(", ")}, ${STD_METRICS.map((m) => coalesceMetric(m, "p", prevAlias(m))).join(", ")} FROM cur c FULL OUTER JOIN prev p ON c.device = p.device`;
204
217
  const sql = `WITH cur AS (${deviceSelects(w.clause, metricList)}), prev AS (${deviceSelects(wPrev.clause, STD_METRICS)}) SELECT * FROM (${inner}) t ORDER BY ${order} ${limit}${offset}`;
@@ -211,7 +224,7 @@ function buildTopNBreakdown(q, pruned, mode) {
211
224
  const sql = `${DEVICE_SUFFIXES.map((suffix) => {
212
225
  const source = deviceSource(suffix);
213
226
  const metrics = metricList.map((m) => metricExprForSource(m, source)).join(", ");
214
- return `SELECT '${suffix.toUpperCase()}' AS device, ${metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause}`;
227
+ return `SELECT '${suffix.toUpperCase()}' AS device, ${metrics} FROM ${factTableRef()} WHERE ${w.clause}`;
215
228
  }).join(" UNION ALL ")} ORDER BY ${order} ${limit}${offset}`;
216
229
  return {
217
230
  table,
@@ -235,7 +248,7 @@ function buildTopNBreakdown(q, pruned, mode) {
235
248
  const moverWhere = q.movers ? `WHERE ${moverClause(q.movers).where} ` : "";
236
249
  const outerOrder = q.movers ? moverOrderByAlias(q.movers) : order;
237
250
  const inner = `SELECT COALESCE(c.k, p.k) AS ${q.dimension}, ${curCols}, ${prevCols}${variantOut}${totalCol} FROM cur c FULL OUTER JOIN prev p ON c.k = p.k ${moverWhere}`;
238
- const sql = `WITH cur AS (SELECT ${col} AS k, ${curMetrics}${variantSel} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause}${facet.sql} GROUP BY ${col}), prev AS (SELECT ${col} AS k, ${prevMetrics} FROM ${TABLE_PLACEHOLDER} WHERE ${wPrev.clause}${facet.sql} GROUP BY ${col}) SELECT * FROM (${inner}) t ORDER BY ${outerOrder} ${limit}${offset}`;
251
+ const sql = `WITH cur AS (SELECT ${col} AS k, ${curMetrics}${variantSel} FROM ${factTableRef()} WHERE ${w.clause}${facet.sql} GROUP BY ${col}), prev AS (SELECT ${col} AS k, ${prevMetrics} FROM ${factTableRef()} WHERE ${wPrev.clause}${facet.sql} GROUP BY ${col}) SELECT * FROM (${inner}) t ORDER BY ${outerOrder} ${limit}${offset}`;
239
252
  return {
240
253
  table,
241
254
  params: [
@@ -248,7 +261,7 @@ function buildTopNBreakdown(q, pruned, mode) {
248
261
  };
249
262
  }
250
263
  const metrics = metricList.map(metricExpr).join(", ");
251
- const sql = `SELECT ${dimSelect(q.dimension)}, ${metrics}${variantSel}${totalCol} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause}${facet.sql} GROUP BY ${col} ORDER BY ${order} ${limit}${offset}`;
264
+ const sql = `SELECT ${dimSelect(q.dimension)}, ${metrics}${variantSel}${totalCol} FROM ${factTableRef()} WHERE ${w.clause}${facet.sql} GROUP BY ${col} ORDER BY ${order} ${limit}${offset}`;
252
265
  return {
253
266
  table,
254
267
  params: [...w.params, ...facet.params],
@@ -270,7 +283,7 @@ function buildSingleRowLookup(q, pruned, mode) {
270
283
  return {
271
284
  table,
272
285
  params,
273
- sql: `SELECT ${dims.length > 0 ? `${dims.map(dimSelect).join(", ")}, ${metrics}` : metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${clause}${groupBy}`
286
+ sql: `SELECT ${dims.length > 0 ? `${dims.map(dimSelect).join(", ")}, ${metrics}` : metrics} FROM ${factTableRef()} WHERE ${clause}${groupBy}`
274
287
  };
275
288
  }
276
289
  function buildMultiSeriesStackedDaily(q, pruned, mode) {
@@ -279,7 +292,7 @@ function buildMultiSeriesStackedDaily(q, pruned, mode) {
279
292
  if (q.seriesDimension === "device") {
280
293
  const selects = DEVICE_SUFFIXES.map((suffix) => {
281
294
  const source = deviceSource(suffix);
282
- return `SELECT date, '${suffix.toUpperCase()}' AS device, ${metricExprForSource(q.metric, source)} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause} GROUP BY date`;
295
+ return `SELECT date, '${suffix.toUpperCase()}' AS device, ${metricExprForSource(q.metric, source)} FROM ${factTableRef()} WHERE ${w.clause} GROUP BY date`;
283
296
  });
284
297
  return {
285
298
  table,
@@ -291,7 +304,7 @@ function buildMultiSeriesStackedDaily(q, pruned, mode) {
291
304
  return {
292
305
  table,
293
306
  params: w.params,
294
- sql: `SELECT date, ${dimSelect(q.seriesDimension)}, ${metricExpr(q.metric)} FROM ${TABLE_PLACEHOLDER} WHERE ${w.clause} GROUP BY date, ${col} ORDER BY date ASC`
307
+ sql: `SELECT date, ${dimSelect(q.seriesDimension)}, ${metricExpr(q.metric)} FROM ${factTableRef()} WHERE ${w.clause} GROUP BY date, ${col} ORDER BY date ASC`
295
308
  };
296
309
  }
297
310
  function buildTwoDimensionDetail(q, pruned, mode) {
@@ -309,7 +322,7 @@ function buildTwoDimensionDetail(q, pruned, mode) {
309
322
  const facet = facetPredicate(q);
310
323
  clause += facet.sql;
311
324
  params.push(...facet.params);
312
- let sql = `SELECT url, query, ${(q.orderBy && !q.metrics.includes(q.orderBy.metric) ? [...q.metrics, q.orderBy.metric] : q.metrics).map(metricExpr).join(", ")} FROM ${TABLE_PLACEHOLDER} WHERE ${clause} GROUP BY url, query`;
325
+ let sql = `SELECT url, query, ${(q.orderBy && !q.metrics.includes(q.orderBy.metric) ? [...q.metrics, q.orderBy.metric] : q.metrics).map(metricExpr).join(", ")} FROM ${factTableRef()} WHERE ${clause} GROUP BY url, query`;
313
326
  if (q.orderBy) sql += ` ORDER BY ${metricAlias(q.orderBy.metric)} ${q.orderBy.dir.toUpperCase()}`;
314
327
  if (q.limit && q.limit > 0) sql += ` LIMIT ${Math.floor(q.limit)}`;
315
328
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/cloudflare",
3
3
  "type": "module",
4
- "version": "0.36.1",
4
+ "version": "0.36.3",
5
5
  "description": "Cloudflare-Workers-flavored helpers for the gscdump analytics stack: AnalyticsEnv binding contract, R2 SigV4 presigner, size-hint HMAC, DuckDB Workers shims, engine factory.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -46,10 +46,10 @@
46
46
  "dependencies": {
47
47
  "@uwdata/flechette": "^2.5.0",
48
48
  "aws4fetch": "^1.0.20",
49
- "@gscdump/contracts": "0.36.1",
50
- "@gscdump/engine": "0.36.1",
51
- "@gscdump/engine-sqlite": "0.36.1",
52
- "gscdump": "0.36.1"
49
+ "@gscdump/contracts": "0.36.3",
50
+ "@gscdump/engine": "0.36.3",
51
+ "@gscdump/engine-sqlite": "0.36.3",
52
+ "gscdump": "0.36.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@cloudflare/vitest-pool-workers": "^0.18.0",