weifuwu 0.17.6 → 0.17.7

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.
@@ -1,6 +1,9 @@
1
1
  import type { Handler, Middleware } from './types.ts';
2
2
  export interface AnalyticsOptions {
3
3
  excluded?: string[];
4
+ pg?: {
5
+ sql: (strings: TemplateStringsArray, ...values: any[]) => Promise<any[]>;
6
+ };
4
7
  }
5
8
  export declare function analytics(options?: AnalyticsOptions): {
6
9
  middleware: Middleware;
package/dist/index.js CHANGED
@@ -6870,32 +6870,53 @@ var MemStore = class {
6870
6870
  devices: { mobile: Math.round(totalMobile / total * 1e3) / 10, desktop: Math.round(totalDesktop / total * 1e3) / 10 }
6871
6871
  };
6872
6872
  }
6873
- handler() {
6874
- return async (req) => {
6875
- const url = new URL(req.url);
6876
- const days = Math.min(Math.max(Number(url.searchParams.get("days")) || 7, 1), 365);
6877
- const data = this.query(days);
6878
- if (url.pathname === "/__analytics/data") {
6879
- return Response.json(data);
6880
- }
6881
- return new Response(this.renderDashboard(days, data), {
6882
- headers: { "content-type": "text/html; charset=utf-8" }
6883
- });
6884
- };
6885
- }
6886
- renderDashboard(days, data) {
6887
- const { total_pv, total_uv, daily, top_pages, referrers, devices } = data;
6888
- const maxPv = Math.max(...daily.map((d) => d.pv), 1);
6889
- const bars = daily.map(
6890
- (d) => `<div class="bar-wrap"><div class="bar" style="height:${d.pv / maxPv * 100}%"></div><span class="bar-label">${d.date.slice(5)}</span></div>`
6891
- ).join("");
6892
- const rows = top_pages.map(
6893
- (p, i) => `<tr><td class="num">${i + 1}</td><td class="path">${p.path}</td><td class="num">${p.pv}</td></tr>`
6894
- ).join("");
6895
- const refRows = referrers.map(
6896
- (r) => `<tr><td>${r.domain}</td><td class="num">${r.count}</td></tr>`
6897
- ).join("");
6898
- return `<!DOCTYPE html>
6873
+ };
6874
+ async function queryPg(sql2, days) {
6875
+ const since = /* @__PURE__ */ new Date();
6876
+ since.setDate(since.getDate() - days);
6877
+ const daily = await sql2`
6878
+ SELECT date, SUM(count) as pv, COUNT(DISTINCT path) as uv
6879
+ FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
6880
+ GROUP BY date ORDER BY date
6881
+ `;
6882
+ const pageRows = await sql2`
6883
+ SELECT path, SUM(count) as pv
6884
+ FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
6885
+ GROUP BY path ORDER BY pv DESC LIMIT 20
6886
+ `;
6887
+ const totalRes = await sql2`
6888
+ SELECT COALESCE(SUM(count), 0) as total_pv,
6889
+ COALESCE(SUM(mobile), 0) as total_mobile,
6890
+ COALESCE(SUM(desktop), 0) as total_desktop
6891
+ FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
6892
+ `;
6893
+ const total = totalRes[0];
6894
+ const totalMobileDesktop = total.total_mobile + total.total_desktop || 1;
6895
+ return {
6896
+ total_pv: total.total_pv,
6897
+ total_uv: pageRows.length,
6898
+ daily: daily.map((d) => ({ date: d.date, pv: Number(d.pv), uv: Number(d.uv) })),
6899
+ top_pages: pageRows.map((p) => ({ path: p.path, pv: Number(p.pv) })),
6900
+ referrers: [],
6901
+ devices: {
6902
+ mobile: Math.round(total.total_mobile / totalMobileDesktop * 1e3) / 10,
6903
+ desktop: Math.round(total.total_desktop / totalMobileDesktop * 1e3) / 10
6904
+ }
6905
+ };
6906
+ }
6907
+ function renderDashboard(days, data) {
6908
+ const { total_pv, total_uv, daily, top_pages, referrers } = data;
6909
+ const maxPv = Math.max(...daily.map((d) => d.pv), 1);
6910
+ const bars = daily.map(
6911
+ (d) => `<div class="bar-wrap"><div class="bar" style="height:${d.pv / maxPv * 100}%"></div><span class="bar-label">${d.date.slice(5)}</span></div>`
6912
+ ).join("");
6913
+ const rows = top_pages.map(
6914
+ (p, i) => `<tr><td class="num">${i + 1}</td><td class="path">${p.path}</td><td class="num">${p.pv}</td></tr>`
6915
+ ).join("");
6916
+ const refRows = referrers.map(
6917
+ (r) => `<tr><td>${r.domain}</td><td class="num">${r.count}</td></tr>`
6918
+ ).join("");
6919
+ return `<!DOCTYPE html>
6899
6920
  <html lang="en">
6900
6921
  <head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Analytics - weifuwu</title>
6901
6922
  <style>
@@ -6924,36 +6945,52 @@ tr:hover td{background:#f8faff}
6924
6945
  <div class="cards">
6925
6946
  <div class="card"><div class="val">${total_pv}</div><div class="lbl">Page Views (${days}d)</div></div>
6926
6947
  <div class="card"><div class="val">${total_uv}</div><div class="lbl">Unique Pages</div></div>
6927
- <div class="card"><div class="val">${devices.mobile}%</div><div class="lbl">Mobile</div></div>
6928
- <div class="card"><div class="val">${devices.desktop}%</div><div class="lbl">Desktop</div></div>
6948
+ <div class="card"><div class="val">${data.devices.mobile}%</div><div class="lbl">Mobile</div></div>
6949
+ <div class="card"><div class="val">${data.devices.desktop}%</div><div class="lbl">Desktop</div></div>
6929
6950
  </div>
6930
6951
  <div class="section"><h2>Daily Page Views</h2><div class="chart">${bars}</div></div>
6931
6952
  <div class="section"><h2>Top Pages</h2>
6932
6953
  <table><thead><tr><th style="width:32px">#</th><th>Path</th><th style="width:64px">Views</th></tr></thead><tbody>${rows}</tbody></table></div>
6933
6954
  ${referrers.length ? `<div class="section"><h2>Referrers</h2><table><thead><tr><th>Domain</th><th style="width:64px">Views</th></tr></thead><tbody>${refRows}</tbody></table></div>` : ""}
6934
6955
  </body></html>`;
6935
- }
6936
- };
6956
+ }
6937
6957
  function analytics(options) {
6938
6958
  const excluded = options?.excluded ?? DEFAULT_EXCLUDED;
6939
- const store = new MemStore();
6959
+ const pg = options?.pg;
6960
+ const store = pg ? null : new MemStore();
6940
6961
  const middleware = async (req, ctx, next) => {
6941
6962
  const url = new URL(req.url);
6942
6963
  const path2 = url.pathname;
6943
- if (!excluded.some((e) => path2.startsWith(e))) {
6944
- const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6945
- const ref = req.headers.get("referer") || "";
6964
+ if (excluded.some((e) => path2.startsWith(e))) return next(req, ctx);
6965
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6966
+ const ref = req.headers.get("referer") || "";
6967
+ const ua = req.headers.get("user-agent") || "";
6968
+ const mobile = /mobile|android|iphone|ipad/i.test(ua);
6969
+ if (pg) {
6970
+ await pg.sql`
6971
+ INSERT INTO __analytics (date, path, count, mobile, desktop)
6972
+ VALUES (${date}, ${path2}, 1, ${mobile ? 1 : 0}, ${mobile ? 0 : 1})
6973
+ ON CONFLICT (date, path) DO UPDATE SET
6974
+ count = __analytics.count + 1,
6975
+ mobile = __analytics.mobile + ${mobile ? 1 : 0},
6976
+ desktop = __analytics.desktop + ${mobile ? 0 : 1}
6977
+ `;
6978
+ } else {
6946
6979
  const refDomain = ref ? new URL(ref).hostname.replace(/^www\./, "") : "";
6947
- const ua = req.headers.get("user-agent") || "";
6948
- const mobile = /mobile|android|iphone|ipad/i.test(ua);
6949
6980
  store.record(path2, date, refDomain, mobile);
6950
6981
  }
6951
6982
  return next(req, ctx);
6952
6983
  };
6953
- return {
6954
- middleware,
6955
- handler: store.handler()
6984
+ const handler = async (req) => {
6985
+ const url = new URL(req.url);
6986
+ const days = Math.min(Math.max(Number(url.searchParams.get("days")) || 7, 1), 365);
6987
+ const data = pg ? await queryPg(pg.sql, days) : store.query(days);
6988
+ if (url.pathname === "/__analytics/data") return Response.json(data);
6989
+ return new Response(renderDashboard(days, data), {
6990
+ headers: { "content-type": "text/html; charset=utf-8" }
6991
+ });
6956
6992
  };
6993
+ return { middleware, handler };
6957
6994
  }
6958
6995
 
6959
6996
  // preferences.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.17.6",
3
+ "version": "0.17.7",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",