@commonpub/server 2.81.0 → 2.82.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.
@@ -14,6 +14,8 @@ export { getApiKeyUsageStats } from './usage.js';
14
14
  export type { ApiKeyUsageStats } from './usage.js';
15
15
  export { METRICS_MIN_BUCKET, getMetricsOverview, getTopContent, getTrendingTags, getTopContributors, getEngagementMetrics, getFederationReach, } from './metrics.js';
16
16
  export type { MetricsOverview, ContentMetric, MetricsTopContributor, MetricsEngagement, MetricsFederationReach, } from './metrics.js';
17
+ export { TIMESERIES_METRICS, runDailyRollup, backfillMetricsDaily, getMetricsTimeseries, } from './metricsRollup.js';
18
+ export type { MetricKind, TimeseriesInterval, TimeseriesPoint, MetricsTimeseries, } from './metricsRollup.js';
17
19
  export { toPublicUser, isPublicUser, toPublicContentSummary, toPublicContentDetail, isPublicContent, toPublicHub, isPublicHub, toAdminApiKeyView, toPublicLearningPath, isPublicLearningPath, toPublicEvent, isPublicEvent, toPublicContest, isPublicContest, toPublicVideo, isPublicVideo, toPublicDocSite, isPublicDocSite, toPublicTag, } from './serializers.js';
18
20
  export type { PublicUser, PublicUserRow, PublicContentSummary, PublicContentDetail, PublicContentRow, PublicHub, PublicHubRow, PublicInstance, AdminApiKeyView, PublicLearningPath, PublicLearningPathRow, PublicEvent, PublicEventRow, PublicContest, PublicContestRow, PublicVideo, PublicVideoRow, PublicDocSite, PublicDocSiteRow, PublicTag, PublicTagRow, } from './serializers.js';
19
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAC"}
@@ -6,5 +6,6 @@ export { authenticateApiKey } from './auth.js';
6
6
  export { createApiKey, listApiKeys, revokeApiKey, getApiKeyById, logApiKeyUsage, touchLastUsed, } from './adminOps.js';
7
7
  export { getApiKeyUsageStats } from './usage.js';
8
8
  export { METRICS_MIN_BUCKET, getMetricsOverview, getTopContent, getTrendingTags, getTopContributors, getEngagementMetrics, getFederationReach, } from './metrics.js';
9
+ export { TIMESERIES_METRICS, runDailyRollup, backfillMetricsDaily, getMetricsTimeseries, } from './metricsRollup.js';
9
10
  export { toPublicUser, isPublicUser, toPublicContentSummary, toPublicContentDetail, isPublicContent, toPublicHub, isPublicHub, toAdminApiKeyView, toPublicLearningPath, isPublicLearningPath, toPublicEvent, isPublicEvent, toPublicContest, isPublicContest, toPublicVideo, isPublicVideo, toPublicDocSite, isPublicDocSite, toPublicTag, } from './serializers.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGtF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAQtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGtF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAQtB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { DB } from '../types.js';
2
+ /**
3
+ * Daily analytics rollups (Phase 3). Aggregate snapshots only — no per-user
4
+ * data, no new PII. Written by the `metrics-rollup` Nitro plugin into
5
+ * `metrics_daily`; read by `getMetricsTimeseries`.
6
+ *
7
+ * Two metric kinds:
8
+ * - `flow` (e.g. `users.new`): that day's count. Deterministic from timestamps,
9
+ * so fully backfillable and idempotent on re-run.
10
+ * - `cumulative` (e.g. `users.total`, `content.views`): running total as of a
11
+ * day. Count-based cumulatives are backfilled as the survivorship curve
12
+ * (currently-live rows by their creation/publish date). Sum-based engagement
13
+ * cumulatives (views/likes/comments) have no per-day history, so they are
14
+ * snapshot forward from the first rollup only (never backfilled).
15
+ */
16
+ export type MetricKind = 'flow' | 'cumulative';
17
+ /** Every metric the timeseries endpoint will serve, with its aggregation kind. */
18
+ export declare const TIMESERIES_METRICS: Record<string, MetricKind>;
19
+ /**
20
+ * Snapshot today's metrics. Count-based + flow metrics are deterministic
21
+ * (`users.total` = currently-live users; `users.new` = created today). Sum-based
22
+ * engagement cumulatives capture the current running total under `today`.
23
+ * Idempotent: re-running for the same day overwrites that day's rows.
24
+ */
25
+ export declare function runDailyRollup(db: DB, today: string): Promise<void>;
26
+ /**
27
+ * Backfill the deterministic count-based series for ALL of history from
28
+ * timestamps: `users.new`/`content.new` (daily flow) and `users.total`/
29
+ * `content.total` (cumulative window-sum = survivorship curve). Sum-based
30
+ * engagement metrics are NOT backfilled (no per-day history exists). Idempotent.
31
+ * Returns the number of rows written.
32
+ */
33
+ export declare function backfillMetricsDaily(db: DB): Promise<number>;
34
+ export type TimeseriesInterval = 'day' | 'week' | 'month';
35
+ export interface TimeseriesPoint {
36
+ date: string;
37
+ value: number;
38
+ delta: number;
39
+ }
40
+ export interface MetricsTimeseries {
41
+ metric: string;
42
+ kind: MetricKind;
43
+ interval: TimeseriesInterval;
44
+ from: string;
45
+ to: string;
46
+ since: string | null;
47
+ points: TimeseriesPoint[];
48
+ }
49
+ export declare function getMetricsTimeseries(db: DB, opts: {
50
+ metric: string;
51
+ interval: TimeseriesInterval;
52
+ from: string;
53
+ to: string;
54
+ }): Promise<MetricsTimeseries>;
55
+ //# sourceMappingURL=metricsRollup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsRollup.d.ts","sourceRoot":"","sources":["../../src/publicApi/metricsRollup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAE/C,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAQzD,CAAC;AA6BF;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BzE;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClE;AAID,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAaD,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,EAAE,EACN,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,iBAAiB,CAAC,CA4C5B"}
@@ -0,0 +1,158 @@
1
+ import { metricsDaily, contentItems, users } from '@commonpub/schema';
2
+ import { and, asc, eq, gte, isNull, lte, sql } from 'drizzle-orm';
3
+ /** Every metric the timeseries endpoint will serve, with its aggregation kind. */
4
+ export const TIMESERIES_METRICS = {
5
+ 'users.total': 'cumulative',
6
+ 'users.new': 'flow',
7
+ 'content.total': 'cumulative',
8
+ 'content.new': 'flow',
9
+ 'content.views': 'cumulative',
10
+ 'content.likes': 'cumulative',
11
+ 'content.comments': 'cumulative',
12
+ };
13
+ const DIM = ''; // global series sentinel (see metrics_daily schema note)
14
+ function activeUsersWhere() {
15
+ return and(isNull(users.deletedAt), eq(users.status, 'active'));
16
+ }
17
+ function publicContentWhere() {
18
+ return and(eq(contentItems.status, 'published'), eq(contentItems.visibility, 'public'), isNull(contentItems.deletedAt));
19
+ }
20
+ async function upsertRows(db, rows) {
21
+ if (rows.length === 0)
22
+ return;
23
+ await db
24
+ .insert(metricsDaily)
25
+ .values(rows.map((r) => ({ day: r.day, metric: r.metric, dimension: DIM, value: r.value })))
26
+ .onConflictDoUpdate({
27
+ target: [metricsDaily.day, metricsDaily.metric, metricsDaily.dimension],
28
+ set: { value: sql `excluded.value` },
29
+ });
30
+ }
31
+ /**
32
+ * Snapshot today's metrics. Count-based + flow metrics are deterministic
33
+ * (`users.total` = currently-live users; `users.new` = created today). Sum-based
34
+ * engagement cumulatives capture the current running total under `today`.
35
+ * Idempotent: re-running for the same day overwrites that day's rows.
36
+ */
37
+ export async function runDailyRollup(db, today) {
38
+ const [[u], [c]] = await Promise.all([
39
+ db
40
+ .select({
41
+ total: sql `count(*)::int`,
42
+ newToday: sql `count(*) FILTER (WHERE ${users.createdAt}::date = ${today})::int`,
43
+ })
44
+ .from(users)
45
+ .where(activeUsersWhere()),
46
+ db
47
+ .select({
48
+ total: sql `count(*)::int`,
49
+ newToday: sql `count(*) FILTER (WHERE ${contentItems.publishedAt}::date = ${today})::int`,
50
+ views: sql `coalesce(sum(${contentItems.viewCount}), 0)::float8`,
51
+ likes: sql `coalesce(sum(${contentItems.likeCount}), 0)::float8`,
52
+ comments: sql `coalesce(sum(${contentItems.commentCount}), 0)::float8`,
53
+ })
54
+ .from(contentItems)
55
+ .where(publicContentWhere()),
56
+ ]);
57
+ await upsertRows(db, [
58
+ { day: today, metric: 'users.total', value: u?.total ?? 0 },
59
+ { day: today, metric: 'users.new', value: u?.newToday ?? 0 },
60
+ { day: today, metric: 'content.total', value: c?.total ?? 0 },
61
+ { day: today, metric: 'content.new', value: c?.newToday ?? 0 },
62
+ { day: today, metric: 'content.views', value: c?.views ?? 0 },
63
+ { day: today, metric: 'content.likes', value: c?.likes ?? 0 },
64
+ { day: today, metric: 'content.comments', value: c?.comments ?? 0 },
65
+ ]);
66
+ }
67
+ /**
68
+ * Backfill the deterministic count-based series for ALL of history from
69
+ * timestamps: `users.new`/`content.new` (daily flow) and `users.total`/
70
+ * `content.total` (cumulative window-sum = survivorship curve). Sum-based
71
+ * engagement metrics are NOT backfilled (no per-day history exists). Idempotent.
72
+ * Returns the number of rows written.
73
+ */
74
+ export async function backfillMetricsDaily(db) {
75
+ const [userDays, contentDays] = await Promise.all([
76
+ db
77
+ .select({
78
+ day: sql `to_char(${users.createdAt}::date, 'YYYY-MM-DD')`,
79
+ flow: sql `count(*)::int`,
80
+ cumulative: sql `sum(count(*)) OVER (ORDER BY ${users.createdAt}::date)::int`,
81
+ })
82
+ .from(users)
83
+ .where(activeUsersWhere())
84
+ .groupBy(sql `${users.createdAt}::date`)
85
+ .orderBy(sql `${users.createdAt}::date`),
86
+ db
87
+ .select({
88
+ day: sql `to_char(${contentItems.publishedAt}::date, 'YYYY-MM-DD')`,
89
+ flow: sql `count(*)::int`,
90
+ cumulative: sql `sum(count(*)) OVER (ORDER BY ${contentItems.publishedAt}::date)::int`,
91
+ })
92
+ .from(contentItems)
93
+ .where(and(publicContentWhere(), sql `${contentItems.publishedAt} IS NOT NULL`))
94
+ .groupBy(sql `${contentItems.publishedAt}::date`)
95
+ .orderBy(sql `${contentItems.publishedAt}::date`),
96
+ ]);
97
+ const rows = [];
98
+ for (const r of userDays) {
99
+ rows.push({ day: r.day, metric: 'users.new', value: r.flow });
100
+ rows.push({ day: r.day, metric: 'users.total', value: r.cumulative });
101
+ }
102
+ for (const r of contentDays) {
103
+ rows.push({ day: r.day, metric: 'content.new', value: r.flow });
104
+ rows.push({ day: r.day, metric: 'content.total', value: r.cumulative });
105
+ }
106
+ await upsertRows(db, rows);
107
+ return rows.length;
108
+ }
109
+ /** UTC bucket-start key for a YYYY-MM-DD day under the given interval. */
110
+ function bucketKey(day, interval) {
111
+ if (interval === 'day')
112
+ return day;
113
+ if (interval === 'month')
114
+ return `${day.slice(0, 7)}-01`;
115
+ // week: Monday of the ISO week (UTC).
116
+ const d = new Date(`${day}T00:00:00Z`);
117
+ const dow = (d.getUTCDay() + 6) % 7; // 0 = Monday
118
+ d.setUTCDate(d.getUTCDate() - dow);
119
+ return d.toISOString().slice(0, 10);
120
+ }
121
+ export async function getMetricsTimeseries(db, opts) {
122
+ const kind = TIMESERIES_METRICS[opts.metric];
123
+ if (!kind)
124
+ throw new Error(`Unknown metric: ${opts.metric}`);
125
+ const rows = await db
126
+ .select({ day: metricsDaily.day, value: metricsDaily.value })
127
+ .from(metricsDaily)
128
+ .where(and(eq(metricsDaily.metric, opts.metric), eq(metricsDaily.dimension, DIM), gte(metricsDaily.day, opts.from), lte(metricsDaily.day, opts.to)))
129
+ .orderBy(asc(metricsDaily.day));
130
+ // Bucket in JS (range is bounded by the caller). flow -> sum within bucket;
131
+ // cumulative -> last value within bucket (the running total at bucket end).
132
+ const buckets = new Map();
133
+ for (const r of rows) {
134
+ const key = bucketKey(r.day, opts.interval);
135
+ const prev = buckets.get(key);
136
+ if (kind === 'flow')
137
+ buckets.set(key, (prev ?? 0) + r.value);
138
+ else
139
+ buckets.set(key, r.value); // ordered asc, so last write wins = bucket-end value
140
+ }
141
+ const ordered = [...buckets.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
142
+ const points = [];
143
+ let prevValue = null;
144
+ for (const [date, value] of ordered) {
145
+ points.push({ date, value, delta: prevValue === null ? 0 : value - prevValue });
146
+ prevValue = value;
147
+ }
148
+ return {
149
+ metric: opts.metric,
150
+ kind,
151
+ interval: opts.interval,
152
+ from: opts.from,
153
+ to: opts.to,
154
+ since: rows[0]?.day ?? null,
155
+ points,
156
+ };
157
+ }
158
+ //# sourceMappingURL=metricsRollup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsRollup.js","sourceRoot":"","sources":["../../src/publicApi/metricsRollup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/server",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "type": "module",
5
5
  "description": "Framework-agnostic business logic for CommonPub instances",
6
6
  "license": "AGPL-3.0-or-later",
@@ -115,13 +115,13 @@
115
115
  "megalodon": "^10.3.0",
116
116
  "turndown": "^7.2.4",
117
117
  "@commonpub/auth": "0.8.0",
118
+ "@commonpub/config": "0.19.0",
119
+ "@commonpub/learning": "0.5.2",
118
120
  "@commonpub/protocol": "0.13.0",
119
- "@commonpub/editor": "0.7.11",
120
- "@commonpub/docs": "0.6.3",
121
- "@commonpub/schema": "0.34.0",
121
+ "@commonpub/schema": "0.35.0",
122
122
  "@commonpub/infra": "0.8.0",
123
- "@commonpub/learning": "0.5.2",
124
- "@commonpub/config": "0.19.0"
123
+ "@commonpub/docs": "0.6.3",
124
+ "@commonpub/editor": "0.7.11"
125
125
  },
126
126
  "peerDependencies": {
127
127
  "drizzle-orm": "^0.45.1"