@gscdump/cloudflare 0.35.11 → 0.36.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.
@@ -78,22 +78,19 @@ function moverClause(movers) {
78
78
  const curImpr = "COALESCE(c.impressions, 0)";
79
79
  const prevImpr = "COALESCE(p.impressions, 0)";
80
80
  switch (movers) {
81
- case "improving": return {
82
- where: `${curClicks} > ${prevClicks}`,
83
- order: `(${curClicks} - ${prevClicks}) DESC`
84
- };
85
- case "declining": return {
86
- where: `${curClicks} < ${prevClicks}`,
87
- order: `(${curClicks} - ${prevClicks}) ASC`
88
- };
89
- case "new": return {
90
- where: `${prevImpr} = 0 AND ${curImpr} > 0`,
91
- order: `${curClicks} DESC, ${curImpr} DESC`
92
- };
93
- case "lost": return {
94
- where: `${curImpr} = 0 AND ${prevImpr} > 0`,
95
- order: `${prevImpr} DESC`
96
- };
81
+ case "improving": return { where: `${curClicks} > ${prevClicks}` };
82
+ case "declining": return { where: `${curClicks} < ${prevClicks}` };
83
+ case "new": return { where: `${prevImpr} = 0 AND ${curImpr} > 0` };
84
+ case "lost": return { where: `${curImpr} = 0 AND ${prevImpr} > 0` };
85
+ default: throw new Error(`[archetype-sql] unknown movers mode: ${movers}`);
86
+ }
87
+ }
88
+ function moverOrderByAlias(movers) {
89
+ switch (movers) {
90
+ case "improving": return "(clicks - prevClicks) DESC";
91
+ case "declining": return "(clicks - prevClicks) ASC";
92
+ case "new": return "clicks DESC, impressions DESC";
93
+ case "lost": return "prevImpressions DESC";
97
94
  default: throw new Error(`[archetype-sql] unknown movers mode: ${movers}`);
98
95
  }
99
96
  }
@@ -183,10 +180,14 @@ function buildTopNBreakdown(q, pruned, mode) {
183
180
  const w = partitionWhere(q, pruned, mode);
184
181
  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)}`);
185
182
  const order = `${metricAlias(q.orderBy.metric)} ${q.orderBy.dir.toUpperCase()}`;
186
- const compareOrder = `COALESCE(c.${metricAlias(q.orderBy.metric)}, 0) ${q.orderBy.dir.toUpperCase()}`;
187
183
  const limit = `LIMIT ${Math.max(0, Math.floor(q.limit))}`;
188
184
  const offset = q.offset && q.offset > 0 ? ` OFFSET ${Math.floor(q.offset)}` : "";
189
- const metricList = q.metrics.includes(q.orderBy.metric) ? q.metrics : [...q.metrics, q.orderBy.metric];
185
+ const metricList0 = q.metrics.includes(q.orderBy.metric) ? q.metrics : [...q.metrics, q.orderBy.metric];
186
+ const metricList = q.compareRange && q.movers ? [.../* @__PURE__ */ new Set([
187
+ ...metricList0,
188
+ "clicks",
189
+ "impressions"
190
+ ])] : metricList0;
190
191
  const variantSel = q.dimension === "queryCanonical" ? ", COUNT(DISTINCT query) AS variantCount" : "";
191
192
  if (q.dimension === "device") {
192
193
  if (q.compareRange) {
@@ -199,9 +200,8 @@ function buildTopNBreakdown(q, pruned, mode) {
199
200
  const metrics = ml.map((m) => metricExprForSource(m, source)).join(", ");
200
201
  return `SELECT '${suffix.toUpperCase()}' AS device, ${metrics} FROM ${TABLE_PLACEHOLDER} WHERE ${clause}`;
201
202
  }).join(" UNION ALL ");
202
- const curCols = metricList.map((m) => coalesceMetric(m, "c", m)).join(", ");
203
- const prevCols = STD_METRICS.map((m) => coalesceMetric(m, "p", prevAlias(m))).join(", ");
204
- const sql = `WITH cur AS (${deviceSelects(w.clause, metricList)}), prev AS (${deviceSelects(wPrev.clause, STD_METRICS)}) SELECT COALESCE(c.device, p.device) AS device, ${curCols}, ${prevCols} FROM cur c FULL OUTER JOIN prev p ON c.device = p.device ORDER BY ${compareOrder} ${limit}${offset}`;
203
+ 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
+ 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}`;
205
205
  return {
206
206
  table,
207
207
  params: [...DEVICE_SUFFIXES.flatMap(() => w.params), ...DEVICE_SUFFIXES.flatMap(() => wPrev.params)],
@@ -232,10 +232,10 @@ function buildTopNBreakdown(q, pruned, mode) {
232
232
  const curCols = metricList.map((m) => coalesceMetric(m, "c", m)).join(", ");
233
233
  const prevCols = STD_METRICS.map((m) => coalesceMetric(m, "p", prevAlias(m))).join(", ");
234
234
  const variantOut = q.dimension === "queryCanonical" ? ", c.variantCount AS variantCount" : "";
235
- const mover = q.movers ? moverClause(q.movers) : null;
236
- const moverWhere = mover ? `WHERE ${mover.where} ` : "";
237
- const orderSql = mover ? `ORDER BY ${mover.order}` : `ORDER BY ${compareOrder}`;
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 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}${orderSql} ${limit}${offset}`;
235
+ const moverWhere = q.movers ? `WHERE ${moverClause(q.movers).where} ` : "";
236
+ const outerOrder = q.movers ? moverOrderByAlias(q.movers) : order;
237
+ 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}`;
239
239
  return {
240
240
  table,
241
241
  params: [
@@ -380,13 +380,15 @@ function createServerTailDispatcher(config) {
380
380
  async function runOn(engine, query) {
381
381
  if (engine === "r2-sql") {
382
382
  const res = await config.r2Sql.runArchetype(query);
383
+ const { rows, totalRows } = extractTotal(res.rows);
383
384
  return {
384
385
  archetype: query.archetype,
385
- rows: res.rows,
386
+ rows,
386
387
  source: sourceFor("r2-sql"),
387
388
  meta: {
388
- rowCount: res.rows.length,
389
- queryMs: res.queryMs
389
+ rowCount: rows.length,
390
+ queryMs: res.queryMs,
391
+ ...totalRows !== void 0 ? { totalRows } : {}
390
392
  }
391
393
  };
392
394
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/cloudflare",
3
3
  "type": "module",
4
- "version": "0.35.11",
4
+ "version": "0.36.0",
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/engine": "0.35.11",
50
- "@gscdump/engine-sqlite": "0.35.11",
51
- "@gscdump/contracts": "0.35.11",
52
- "gscdump": "0.35.11"
49
+ "@gscdump/contracts": "0.36.0",
50
+ "@gscdump/engine-sqlite": "0.36.0",
51
+ "gscdump": "0.36.0",
52
+ "@gscdump/engine": "0.36.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@cloudflare/vitest-pool-workers": "^0.18.0",