@the-situation/indexer 0.17.1 → 0.18.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.
Files changed (122) hide show
  1. package/dist/aggregator/cohort.d.ts +30 -0
  2. package/dist/aggregator/cohort.d.ts.map +1 -0
  3. package/dist/aggregator/cohort.js +153 -0
  4. package/dist/aggregator/cohort.js.map +1 -0
  5. package/dist/aggregator/daily.d.ts +71 -0
  6. package/dist/aggregator/daily.d.ts.map +1 -0
  7. package/dist/aggregator/daily.js +249 -0
  8. package/dist/aggregator/daily.js.map +1 -0
  9. package/dist/aggregator/domains.d.ts +20 -0
  10. package/dist/aggregator/domains.d.ts.map +1 -0
  11. package/dist/aggregator/domains.js +38 -0
  12. package/dist/aggregator/domains.js.map +1 -0
  13. package/dist/aggregator/index.d.ts +31 -0
  14. package/dist/aggregator/index.d.ts.map +1 -0
  15. package/dist/aggregator/index.js +100 -0
  16. package/dist/aggregator/index.js.map +1 -0
  17. package/dist/aggregator/lifetime.d.ts +52 -0
  18. package/dist/aggregator/lifetime.d.ts.map +1 -0
  19. package/dist/aggregator/lifetime.js +222 -0
  20. package/dist/aggregator/lifetime.js.map +1 -0
  21. package/dist/aggregator/roi.d.ts +42 -0
  22. package/dist/aggregator/roi.d.ts.map +1 -0
  23. package/dist/aggregator/roi.js +153 -0
  24. package/dist/aggregator/roi.js.map +1 -0
  25. package/dist/aggregator/sources.d.ts +59 -0
  26. package/dist/aggregator/sources.d.ts.map +1 -0
  27. package/dist/aggregator/sources.js +53 -0
  28. package/dist/aggregator/sources.js.map +1 -0
  29. package/dist/aggregator/writers.d.ts +22 -0
  30. package/dist/aggregator/writers.d.ts.map +1 -0
  31. package/dist/aggregator/writers.js +147 -0
  32. package/dist/aggregator/writers.js.map +1 -0
  33. package/dist/api/app.d.ts +177 -13
  34. package/dist/api/app.d.ts.map +1 -1
  35. package/dist/api/app.js +4 -3
  36. package/dist/api/app.js.map +1 -1
  37. package/dist/api/routes/admin-subscriptions.d.ts +10 -10
  38. package/dist/api/routes/analytics.d.ts +205 -0
  39. package/dist/api/routes/analytics.d.ts.map +1 -0
  40. package/dist/api/routes/analytics.js +122 -0
  41. package/dist/api/routes/analytics.js.map +1 -0
  42. package/dist/api/routes/index.d.ts +1 -0
  43. package/dist/api/routes/index.d.ts.map +1 -1
  44. package/dist/api/routes/index.js +1 -0
  45. package/dist/api/routes/index.js.map +1 -1
  46. package/dist/api/routes/lp-history.d.ts +1 -1
  47. package/dist/api/routes/rankings.d.ts +17 -3
  48. package/dist/api/routes/rankings.d.ts.map +1 -1
  49. package/dist/api/routes/rankings.js +44 -5
  50. package/dist/api/routes/rankings.js.map +1 -1
  51. package/dist/api/routes/trader-stats.d.ts +11 -2
  52. package/dist/api/routes/trader-stats.d.ts.map +1 -1
  53. package/dist/api/routes/trader-stats.js +72 -2
  54. package/dist/api/routes/trader-stats.js.map +1 -1
  55. package/dist/client/IndexerClient.d.ts +30 -1
  56. package/dist/client/IndexerClient.d.ts.map +1 -1
  57. package/dist/client/IndexerClient.js.map +1 -1
  58. package/dist/client/IndexerClientLive.d.ts.map +1 -1
  59. package/dist/client/IndexerClientLive.js +50 -1
  60. package/dist/client/IndexerClientLive.js.map +1 -1
  61. package/dist/client/convenience.d.ts +9 -2
  62. package/dist/client/convenience.d.ts.map +1 -1
  63. package/dist/client/convenience.js +9 -1
  64. package/dist/client/convenience.js.map +1 -1
  65. package/dist/client/index.d.ts +3 -2
  66. package/dist/client/index.d.ts.map +1 -1
  67. package/dist/client/index.js +1 -1
  68. package/dist/client/index.js.map +1 -1
  69. package/dist/config.d.ts +9 -0
  70. package/dist/config.d.ts.map +1 -1
  71. package/dist/config.js +2 -0
  72. package/dist/config.js.map +1 -1
  73. package/dist/etl/event-indexer.d.ts +1 -1
  74. package/dist/etl/lp-position-refresher.d.ts +2 -1
  75. package/dist/etl/lp-position-refresher.d.ts.map +1 -1
  76. package/dist/etl/lp-position-refresher.js +24 -11
  77. package/dist/etl/lp-position-refresher.js.map +1 -1
  78. package/dist/etl/position-refresher.d.ts +1 -1
  79. package/dist/etl/state-refresher.d.ts +1 -1
  80. package/dist/index.js +54 -5
  81. package/dist/index.js.map +1 -1
  82. package/dist/layers/ChainReaderLive.d.ts.map +1 -1
  83. package/dist/layers/ChainReaderLive.js +15 -0
  84. package/dist/layers/ChainReaderLive.js.map +1 -1
  85. package/dist/services/ChainReader.d.ts +10 -0
  86. package/dist/services/ChainReader.d.ts.map +1 -1
  87. package/dist/services/ChainReader.js.map +1 -1
  88. package/dist/types/analytics.d.ts +194 -0
  89. package/dist/types/analytics.d.ts.map +1 -0
  90. package/dist/types/analytics.js +10 -0
  91. package/dist/types/analytics.js.map +1 -0
  92. package/dist/types/index.d.ts +1 -0
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/warehouse/analytics-helpers.d.ts +36 -0
  95. package/dist/warehouse/analytics-helpers.d.ts.map +1 -0
  96. package/dist/warehouse/analytics-helpers.js +142 -0
  97. package/dist/warehouse/analytics-helpers.js.map +1 -0
  98. package/dist/warehouse/backfill.d.ts +26 -0
  99. package/dist/warehouse/backfill.d.ts.map +1 -0
  100. package/dist/warehouse/backfill.js +77 -0
  101. package/dist/warehouse/backfill.js.map +1 -0
  102. package/dist/warehouse/connection.d.ts +18 -0
  103. package/dist/warehouse/connection.d.ts.map +1 -0
  104. package/dist/warehouse/connection.js +24 -0
  105. package/dist/warehouse/connection.js.map +1 -0
  106. package/dist/warehouse/index.d.ts +14 -0
  107. package/dist/warehouse/index.d.ts.map +1 -0
  108. package/dist/warehouse/index.js +14 -0
  109. package/dist/warehouse/index.js.map +1 -0
  110. package/dist/warehouse/repositories/analytics.d.ts +61 -0
  111. package/dist/warehouse/repositories/analytics.d.ts.map +1 -0
  112. package/dist/warehouse/repositories/analytics.js +418 -0
  113. package/dist/warehouse/repositories/analytics.js.map +1 -0
  114. package/dist/warehouse/schema.d.ts +9 -0
  115. package/dist/warehouse/schema.d.ts.map +1 -0
  116. package/dist/warehouse/schema.js +219 -0
  117. package/dist/warehouse/schema.js.map +1 -0
  118. package/dist/warehouse/seed-domains.d.ts +29 -0
  119. package/dist/warehouse/seed-domains.d.ts.map +1 -0
  120. package/dist/warehouse/seed-domains.js +223 -0
  121. package/dist/warehouse/seed-domains.js.map +1 -0
  122. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2
+ const SLUG_RE = /^[a-z][a-z0-9_-]*$/;
3
+ export function parseNumeric(raw) {
4
+ if (raw == null)
5
+ return null;
6
+ if (typeof raw === 'number') {
7
+ return Number.isFinite(raw) ? raw : null;
8
+ }
9
+ if (typeof raw === 'string') {
10
+ const trimmed = raw.trim();
11
+ if (trimmed.length === 0)
12
+ return null;
13
+ const v = Number(trimmed);
14
+ return Number.isFinite(v) ? v : null;
15
+ }
16
+ if (typeof raw === 'bigint') {
17
+ const v = Number(raw);
18
+ return Number.isFinite(v) ? v : null;
19
+ }
20
+ return null;
21
+ }
22
+ export function parseAnalyticsFilters(q) {
23
+ let domain;
24
+ if (q.domain == null || q.domain === '') {
25
+ domain = 'all';
26
+ }
27
+ else if (typeof q.domain !== 'string') {
28
+ throw new Error('domain must be a string');
29
+ }
30
+ else {
31
+ domain = q.domain.toLowerCase();
32
+ if (domain !== 'all' && !SLUG_RE.test(domain)) {
33
+ throw new Error(`Invalid domain slug: ${q.domain}`);
34
+ }
35
+ }
36
+ const from = parseDateOrNull(q.from, 'from');
37
+ const to = parseDateOrNull(q.to, 'to');
38
+ if (from && to && from > to) {
39
+ throw new Error(`Invalid window: from (${from}) is after to (${to})`);
40
+ }
41
+ return { domain, from, to };
42
+ }
43
+ function parseDateOrNull(raw, name) {
44
+ if (raw == null || raw === '')
45
+ return null;
46
+ if (typeof raw !== 'string') {
47
+ throw new Error(`${name} must be a string YYYY-MM-DD`);
48
+ }
49
+ if (!ISO_DATE_RE.test(raw)) {
50
+ throw new Error(`${name} must match YYYY-MM-DD (got "${raw}")`);
51
+ }
52
+ // Validate calendar correctness — Date.parse accepts overflow in some impls
53
+ const ms = Date.parse(`${raw}T00:00:00Z`);
54
+ if (!Number.isFinite(ms)) {
55
+ throw new Error(`Invalid date: ${raw}`);
56
+ }
57
+ return raw;
58
+ }
59
+ /**
60
+ * First week_offset in the cohort where retentionPct strictly drops below 50%.
61
+ * Returns null if the cohort never reaches half-life within its observed window.
62
+ *
63
+ * Performance: O(N log N) once for the sort. Cohorts are small (typically < 100
64
+ * weeks of observations), so this is negligible.
65
+ */
66
+ export function computeCohortHalfLife(cohort) {
67
+ if (cohort.retention.length === 0)
68
+ return null;
69
+ const sorted = [...cohort.retention].sort((a, b) => a.weekOffset - b.weekOffset);
70
+ for (const r of sorted) {
71
+ if (r.retentionPct < 50)
72
+ return r.weekOffset;
73
+ }
74
+ return null;
75
+ }
76
+ export function shapeCohortRows(rows) {
77
+ // Group by cohort_week.
78
+ const grouped = new Map();
79
+ for (const row of rows) {
80
+ const week = toDateString(row.cohort_week);
81
+ let bucket = grouped.get(week);
82
+ if (!bucket) {
83
+ bucket = { cohortSize: row.cohort_size, retention: [] };
84
+ grouped.set(week, bucket);
85
+ }
86
+ // Cohort size should be constant across rows for the same cohort; defend
87
+ // against drift by taking the max (paranoid).
88
+ if (row.cohort_size > bucket.cohortSize) {
89
+ bucket.cohortSize = row.cohort_size;
90
+ }
91
+ const pct = bucket.cohortSize > 0 ? (row.retained / bucket.cohortSize) * 100 : 0;
92
+ bucket.retention.push({
93
+ weekOffset: row.week_offset,
94
+ retained: row.retained,
95
+ retentionPct: pct,
96
+ });
97
+ }
98
+ const out = [];
99
+ for (const [week, bucket] of grouped) {
100
+ bucket.retention.sort((a, b) => a.weekOffset - b.weekOffset);
101
+ out.push({
102
+ cohortWeek: week,
103
+ cohortSize: bucket.cohortSize,
104
+ retention: bucket.retention,
105
+ });
106
+ }
107
+ // Newest cohorts first
108
+ out.sort((a, b) => (a.cohortWeek > b.cohortWeek ? -1 : 1));
109
+ return out;
110
+ }
111
+ export function toDateString(value) {
112
+ if (value == null)
113
+ return '';
114
+ if (typeof value === 'string') {
115
+ // Postgres DATE comes back as 'YYYY-MM-DD' string — passthrough.
116
+ if (ISO_DATE_RE.test(value))
117
+ return value;
118
+ // Some drivers return a Date as ISO string.
119
+ return value.slice(0, 10);
120
+ }
121
+ const yyyy = value.getUTCFullYear();
122
+ const mm = String(value.getUTCMonth() + 1).padStart(2, '0');
123
+ const dd = String(value.getUTCDate()).padStart(2, '0');
124
+ return `${yyyy}-${mm}-${dd}`;
125
+ }
126
+ export function dateToUnixSeconds(value) {
127
+ if (value == null)
128
+ return null;
129
+ if (value instanceof Date) {
130
+ return Math.floor(value.getTime() / 1000);
131
+ }
132
+ const ms = Date.parse(value);
133
+ if (!Number.isFinite(ms))
134
+ return null;
135
+ return Math.floor(ms / 1000);
136
+ }
137
+ export function parseEntityType(raw) {
138
+ if (raw === 'lp')
139
+ return 'lp';
140
+ return 'trader';
141
+ }
142
+ //# sourceMappingURL=analytics-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-helpers.js","sourceRoot":"","sources":["../../src/warehouse/analytics-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Market → domain backfill.
3
+ *
4
+ * Reads `markets.category` and `markets.topics` from the SQLite OLTP store and
5
+ * writes a `market_domains` row for each (market, domain) pair into Postgres.
6
+ *
7
+ * Behaviour:
8
+ * - For each market, classify category + each topic via `classifyTopic`.
9
+ * - Deduplicate domain slugs.
10
+ * - Always insert at least one row per market — falls back to `other` when
11
+ * no rule matches.
12
+ * - The first slug found becomes the primary; subsequent slugs are secondary.
13
+ * - `source = 'auto'` for backfilled rows. Manual admin assignments use
14
+ * `source = 'manual'` and are NEVER overwritten by this job.
15
+ *
16
+ * Safe to re-run: only auto-assigned rows are touched. Manual overrides survive.
17
+ */
18
+ import type { SQL } from 'bun';
19
+ import type { Database as SqliteDatabase } from 'bun:sqlite';
20
+ export interface BackfillResult {
21
+ readonly markets: number;
22
+ readonly assignments: number;
23
+ readonly fallbacks: number;
24
+ }
25
+ export declare function backfillMarketDomains(sqlite: SqliteDatabase, pg: SQL): Promise<BackfillResult>;
26
+ //# sourceMappingURL=backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.d.ts","sourceRoot":"","sources":["../../src/warehouse/backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC;AAS7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,GAAG,GACN,OAAO,CAAC,cAAc,CAAC,CAkDzB"}
@@ -0,0 +1,77 @@
1
+ import { classifyTopic } from './seed-domains';
2
+ export async function backfillMarketDomains(sqlite, pg) {
3
+ const rows = sqlite
4
+ .query('SELECT address, category, topics FROM markets')
5
+ .all();
6
+ let assignmentCount = 0;
7
+ let fallbackCount = 0;
8
+ for (const market of rows) {
9
+ const slugs = collectSlugs(market);
10
+ if (slugs.length === 0) {
11
+ slugs.push('other');
12
+ fallbackCount += 1;
13
+ }
14
+ // Drop existing auto rows for this market — manual assignments stay.
15
+ await pg `
16
+ DELETE FROM market_domains
17
+ WHERE market_address = ${market.address} AND source = 'auto'
18
+ `;
19
+ const existingManual = await pg `
20
+ SELECT domain_slug, is_primary FROM market_domains
21
+ WHERE market_address = ${market.address} AND source = 'manual'
22
+ `;
23
+ const manualSlugs = new Set(existingManual.map((r) => r.domain_slug));
24
+ const hasManualPrimary = existingManual.some((r) => r.is_primary);
25
+ let primaryAssigned = hasManualPrimary;
26
+ for (const slug of slugs) {
27
+ if (manualSlugs.has(slug)) {
28
+ // Already manually set; skip.
29
+ continue;
30
+ }
31
+ const isPrimary = !primaryAssigned;
32
+ primaryAssigned = primaryAssigned || isPrimary;
33
+ await pg `
34
+ INSERT INTO market_domains (market_address, domain_slug, is_primary, source, assigned_at)
35
+ VALUES (${market.address}, ${slug}, ${isPrimary}, 'auto', now())
36
+ ON CONFLICT (market_address, domain_slug) DO NOTHING
37
+ `;
38
+ assignmentCount += 1;
39
+ }
40
+ }
41
+ return {
42
+ markets: rows.length,
43
+ assignments: assignmentCount,
44
+ fallbacks: fallbackCount,
45
+ };
46
+ }
47
+ function collectSlugs(market) {
48
+ const slugs = [];
49
+ const seen = new Set();
50
+ const candidates = [];
51
+ if (market.category && market.category.trim().length > 0) {
52
+ candidates.push(market.category);
53
+ }
54
+ try {
55
+ const topics = JSON.parse(market.topics);
56
+ for (const t of topics) {
57
+ if (typeof t === 'string' && t.trim().length > 0) {
58
+ candidates.push(t);
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // ignore malformed topics JSON
64
+ }
65
+ for (const candidate of candidates) {
66
+ const slug = classifyTopic(candidate);
67
+ if (slug === 'other') {
68
+ continue;
69
+ }
70
+ if (!seen.has(slug)) {
71
+ seen.add(slug);
72
+ slugs.push(slug);
73
+ }
74
+ }
75
+ return slugs;
76
+ }
77
+ //# sourceMappingURL=backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../src/warehouse/backfill.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAc/C,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAsB,EACtB,EAAO;IAEP,MAAM,IAAI,GAAG,MAAM;SAChB,KAAK,CAAC,+CAA+C,CAAC;SACtD,GAAG,EAA0B,CAAC;IAEjC,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,aAAa,IAAI,CAAC,CAAC;QACrB,CAAC;QAED,qEAAqE;QACrE,MAAM,EAAE,CAAA;;+BAEmB,MAAM,CAAC,OAAO;KACxC,CAAC;QAEF,MAAM,cAAc,GAAG,MAAM,EAAE,CAAA;;+BAEJ,MAAM,CAAC,OAAO;KACe,CAAC;QACzD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;QACtE,MAAM,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAElE,IAAI,eAAe,GAAG,gBAAgB,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,8BAA8B;gBAC9B,SAAS;YACX,CAAC;YACD,MAAM,SAAS,GAAG,CAAC,eAAe,CAAC;YACnC,eAAe,GAAG,eAAe,IAAI,SAAS,CAAC;YAC/C,MAAM,EAAE,CAAA;;kBAEI,MAAM,CAAC,OAAO,KAAK,IAAI,KAAK,SAAS;;OAEhD,CAAC;YACF,eAAe,IAAI,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,WAAW,EAAE,eAAe;QAC5B,SAAS,EAAE,aAAa;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,MAAiB;IACrC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAuB,CAAC;QAC/D,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Postgres warehouse connection (Bun.sql).
3
+ *
4
+ * The warehouse holds derived analytics tables (domains, daily fact tables,
5
+ * cohort retention, ROI distributions) that are populated by the aggregator
6
+ * job from the SQLite OLTP store. SQLite remains the source of truth for raw
7
+ * events; Postgres is the OLAP layer.
8
+ *
9
+ * Bun.sql is the project standard (see CLAUDE.md). Do not introduce `pg` or
10
+ * `postgres.js`.
11
+ */
12
+ import { SQL } from 'bun';
13
+ export interface Warehouse {
14
+ readonly sql: SQL;
15
+ readonly close: () => Promise<void>;
16
+ }
17
+ export declare function createWarehouse(url: string): Promise<Warehouse>;
18
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/warehouse/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAUrE"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Postgres warehouse connection (Bun.sql).
3
+ *
4
+ * The warehouse holds derived analytics tables (domains, daily fact tables,
5
+ * cohort retention, ROI distributions) that are populated by the aggregator
6
+ * job from the SQLite OLTP store. SQLite remains the source of truth for raw
7
+ * events; Postgres is the OLAP layer.
8
+ *
9
+ * Bun.sql is the project standard (see CLAUDE.md). Do not introduce `pg` or
10
+ * `postgres.js`.
11
+ */
12
+ import { SQL } from 'bun';
13
+ export async function createWarehouse(url) {
14
+ const sql = new SQL(url);
15
+ // Ping to fail fast on bad credentials / unreachable host.
16
+ await sql `SELECT 1`;
17
+ return {
18
+ sql,
19
+ close: async () => {
20
+ await sql.end();
21
+ },
22
+ };
23
+ }
24
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/warehouse/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAO1B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,2DAA2D;IAC3D,MAAM,GAAG,CAAA,UAAU,CAAC;IACpB,OAAO;QACL,GAAG;QACH,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Warehouse barrel export.
3
+ *
4
+ * The warehouse is the Postgres OLAP layer that sits next to the SQLite OLTP
5
+ * store. It holds derived analytics tables (domains, daily fact tables,
6
+ * lifetime/CLTV, cohort retention, ROI distributions). All writes flow through
7
+ * the aggregator job; reads are exposed via `/api/analytics/*` and via the
8
+ * domain/time-window-filtered variants of `/api/rankings` and `/api/positions/:trader/stats`.
9
+ */
10
+ export { createWarehouse, type Warehouse } from './connection';
11
+ export { initializeWarehouseSchema } from './schema';
12
+ export { CANONICAL_DOMAINS, classifyTopic, seedCanonicalDomains, type CanonicalDomain, } from './seed-domains';
13
+ export { backfillMarketDomains, type BackfillResult } from './backfill';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/warehouse/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Warehouse barrel export.
3
+ *
4
+ * The warehouse is the Postgres OLAP layer that sits next to the SQLite OLTP
5
+ * store. It holds derived analytics tables (domains, daily fact tables,
6
+ * lifetime/CLTV, cohort retention, ROI distributions). All writes flow through
7
+ * the aggregator job; reads are exposed via `/api/analytics/*` and via the
8
+ * domain/time-window-filtered variants of `/api/rankings` and `/api/positions/:trader/stats`.
9
+ */
10
+ export { createWarehouse } from './connection';
11
+ export { initializeWarehouseSchema } from './schema';
12
+ export { CANONICAL_DOMAINS, classifyTopic, seedCanonicalDomains, } from './seed-domains';
13
+ export { backfillMarketDomains } from './backfill';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/warehouse/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAkB,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,oBAAoB,GAErB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAuB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Analytics warehouse repository.
3
+ *
4
+ * All `/api/analytics/*` routes go through this single class. Each method
5
+ * takes a parsed/validated AnalyticsFilters and returns a public response
6
+ * shape. Methods are async (Bun.sql is async).
7
+ *
8
+ * Performance:
9
+ * - Indexed lookups everywhere: see warehouse/schema.ts for column-coverage.
10
+ * - The "top traders by domain" and "leaderboard with time-window" queries
11
+ * can be expensive on the daily fact tables. We rely on
12
+ * idx_daily_trader_stats_domain_date (covering filter + sort prefix) and
13
+ * idx_trader_lifetime_pnl. With ~10k–100k traders this is sub-100ms.
14
+ * - All NUMERIC columns return as strings; we coerce to JS number at the
15
+ * boundary via `parseNumeric` to keep the public API plain numbers.
16
+ *
17
+ * Memory:
18
+ * - We never SELECT the entire fact table — every public method has a LIMIT
19
+ * clause or aggregates by date.
20
+ * - Per-domain rollups for the overview endpoint sum directly in SQL; we
21
+ * don't ship per-trader rows back to JS.
22
+ */
23
+ import type { SQL } from 'bun';
24
+ import type { AnalyticsFilters, AnalyticsLeaderboardResponse, AnalyticsOverviewResponse, CohortsResponse, DomainDetailResponse, DomainsResponse, EntityType, RoiResponse, TraderAnalyticsResponse } from '../../types/analytics';
25
+ export interface AnalyticsRepository {
26
+ getOverview(): Promise<AnalyticsOverviewResponse>;
27
+ getDomains(): Promise<DomainsResponse>;
28
+ getDomainDetail(slug: string): Promise<DomainDetailResponse | null>;
29
+ getCohorts(filters: {
30
+ entityType: EntityType;
31
+ domain: string;
32
+ }): Promise<CohortsResponse>;
33
+ getRoi(filters: {
34
+ entityType: EntityType;
35
+ domain: string;
36
+ }): Promise<RoiResponse>;
37
+ getLeaderboard(filters: AnalyticsFilters & {
38
+ limit: number;
39
+ }): Promise<AnalyticsLeaderboardResponse>;
40
+ getTraderAnalytics(trader: string): Promise<TraderAnalyticsResponse>;
41
+ }
42
+ export declare class PgAnalyticsRepository implements AnalyticsRepository {
43
+ private readonly sql;
44
+ constructor(sql: SQL);
45
+ getOverview(): Promise<AnalyticsOverviewResponse>;
46
+ getDomains(): Promise<DomainsResponse>;
47
+ getDomainDetail(slug: string): Promise<DomainDetailResponse | null>;
48
+ getCohorts(filters: {
49
+ entityType: EntityType;
50
+ domain: string;
51
+ }): Promise<CohortsResponse>;
52
+ getRoi(filters: {
53
+ entityType: EntityType;
54
+ domain: string;
55
+ }): Promise<RoiResponse>;
56
+ getLeaderboard(filters: AnalyticsFilters & {
57
+ limit: number;
58
+ }): Promise<AnalyticsLeaderboardResponse>;
59
+ getTraderAnalytics(trader: string): Promise<TraderAnalyticsResponse>;
60
+ }
61
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/warehouse/repositories/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EACV,gBAAgB,EAEhB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,oBAAoB,EAEpB,eAAe,EACf,UAAU,EACV,WAAW,EACX,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAS/B,MAAM,WAAW,mBAAmB;IAClC,WAAW,IAAI,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAClD,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACpE,UAAU,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1F,MAAM,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClF,cAAc,CACZ,OAAO,EAAE,gBAAgB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACzC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtE;AAED,qBAAa,qBAAsB,YAAW,mBAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,GAAG;IAE/B,WAAW,IAAI,OAAO,CAAC,yBAAyB,CAAC;IA2EjD,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;IA2DtC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAmFnE,UAAU,CAAC,OAAO,EAAE;QACxB,UAAU,EAAE,UAAU,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiCtB,MAAM,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAmDjF,cAAc,CAClB,OAAO,EAAE,gBAAgB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,4BAA4B,CAAC;IAyHlC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;CA+G3E"}