@the-situation/indexer 0.17.1 → 0.18.1

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 +24 -0
  103. package/dist/warehouse/connection.d.ts.map +1 -0
  104. package/dist/warehouse/connection.js +30 -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
@@ -28,10 +28,10 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
28
28
  name: string;
29
29
  webhook_url: string;
30
30
  filters: {
31
- traders?: string[];
32
- eventTypes?: ("settlement" | "trade_move" | "market_created")[];
31
+ eventTypes?: ("trade_move" | "settlement" | "market_created")[];
33
32
  marketTypes?: string[];
34
33
  marketAddresses?: string[];
34
+ traders?: string[];
35
35
  minCollateral?: number;
36
36
  };
37
37
  };
@@ -39,9 +39,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
39
39
  query: unknown;
40
40
  headers: unknown;
41
41
  response: {
42
- 200: {
42
+ 200: SubscriptionResponse | {
43
43
  error: string;
44
- } | SubscriptionResponse;
44
+ };
45
45
  422: {
46
46
  type: "validation";
47
47
  on: string;
@@ -83,9 +83,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
83
83
  query: unknown;
84
84
  headers: unknown;
85
85
  response: {
86
- 200: {
86
+ 200: SubscriptionResponse | {
87
87
  error: string;
88
- } | SubscriptionResponse | {
88
+ } | {
89
89
  error: string;
90
90
  };
91
91
  422: {
@@ -112,10 +112,10 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
112
112
  webhook_url?: string;
113
113
  secret?: string;
114
114
  filters?: {
115
- traders?: string[];
116
- eventTypes?: ("settlement" | "trade_move" | "market_created")[];
115
+ eventTypes?: ("trade_move" | "settlement" | "market_created")[];
117
116
  marketTypes?: string[];
118
117
  marketAddresses?: string[];
118
+ traders?: string[];
119
119
  minCollateral?: number;
120
120
  };
121
121
  is_active?: boolean;
@@ -126,9 +126,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
126
126
  query: unknown;
127
127
  headers: unknown;
128
128
  response: {
129
- 200: {
129
+ 200: SubscriptionResponse | {
130
130
  error: string;
131
- } | SubscriptionResponse | {
131
+ } | {
132
132
  error: string;
133
133
  };
134
134
  422: {
@@ -0,0 +1,205 @@
1
+ /**
2
+ * GET /api/analytics/* — analytics endpoints backed by the Postgres warehouse.
3
+ *
4
+ * Routes:
5
+ * GET /api/analytics/overview — platform-wide headline stats
6
+ * GET /api/analytics/domains — per-domain summary list
7
+ * GET /api/analytics/domains/:slug — domain detail (time-series + top-N)
8
+ * GET /api/analytics/cohorts — cohort retention curves
9
+ * GET /api/analytics/roi — ROI distribution + summary stats
10
+ * GET /api/analytics/leaderboard — leaderboard with ?domain= and time window
11
+ * GET /api/analytics/traders/:address — per-trader analytics (per-domain + cohort)
12
+ *
13
+ * These routes are only registered when WAREHOUSE_DATABASE_URL is set.
14
+ *
15
+ * Validation errors throw via parseAnalyticsFilters / parseEntityType — the
16
+ * shared error-handler maps Error → 400 (we tag it explicitly via `set.status`).
17
+ */
18
+ import { Elysia } from 'elysia';
19
+ import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
20
+ /**
21
+ * Registers analytics routes. When `repo` is null (warehouse not configured),
22
+ * every analytics route returns 503 with a clear message so consumers can
23
+ * fall back gracefully.
24
+ */
25
+ export declare const analyticsRoutes: (repo: AnalyticsRepository | null) => Elysia<"", {
26
+ decorator: {};
27
+ store: {};
28
+ derive: {};
29
+ resolve: {};
30
+ }, {
31
+ typebox: {};
32
+ error: {};
33
+ }, {
34
+ schema: {};
35
+ standaloneSchema: {};
36
+ macro: {};
37
+ macroFn: {};
38
+ parser: {};
39
+ response: {};
40
+ }, {
41
+ api: {
42
+ analytics: {
43
+ overview: {
44
+ get: {
45
+ body: unknown;
46
+ params: {};
47
+ query: unknown;
48
+ headers: unknown;
49
+ response: {
50
+ 200: import("../../types").AnalyticsOverviewResponse | {
51
+ error: string;
52
+ };
53
+ };
54
+ };
55
+ };
56
+ };
57
+ };
58
+ } & {
59
+ api: {
60
+ analytics: {
61
+ domains: {
62
+ get: {
63
+ body: unknown;
64
+ params: {};
65
+ query: unknown;
66
+ headers: unknown;
67
+ response: {
68
+ 200: import("../../types").DomainsResponse | {
69
+ error: string;
70
+ };
71
+ };
72
+ };
73
+ };
74
+ };
75
+ };
76
+ } & {
77
+ api: {
78
+ analytics: {
79
+ domains: {
80
+ ":slug": {
81
+ get: {
82
+ body: unknown;
83
+ params: {
84
+ slug: string;
85
+ } & {};
86
+ query: unknown;
87
+ headers: unknown;
88
+ response: {
89
+ 200: import("../../types").DomainDetailResponse | {
90
+ error: string;
91
+ };
92
+ 422: {
93
+ type: "validation";
94
+ on: string;
95
+ summary?: string;
96
+ message?: string;
97
+ found?: unknown;
98
+ property?: string;
99
+ expected?: string;
100
+ };
101
+ };
102
+ };
103
+ };
104
+ };
105
+ };
106
+ };
107
+ } & {
108
+ api: {
109
+ analytics: {
110
+ cohorts: {
111
+ get: {
112
+ body: unknown;
113
+ params: {};
114
+ query: unknown;
115
+ headers: unknown;
116
+ response: {
117
+ 200: import("../../types").CohortsResponse | {
118
+ error: string;
119
+ };
120
+ };
121
+ };
122
+ };
123
+ };
124
+ };
125
+ } & {
126
+ api: {
127
+ analytics: {
128
+ roi: {
129
+ get: {
130
+ body: unknown;
131
+ params: {};
132
+ query: unknown;
133
+ headers: unknown;
134
+ response: {
135
+ 200: import("../../types").RoiResponse | {
136
+ error: string;
137
+ };
138
+ };
139
+ };
140
+ };
141
+ };
142
+ };
143
+ } & {
144
+ api: {
145
+ analytics: {
146
+ leaderboard: {
147
+ get: {
148
+ body: unknown;
149
+ params: {};
150
+ query: unknown;
151
+ headers: unknown;
152
+ response: {
153
+ 200: import("../../types").AnalyticsLeaderboardResponse | {
154
+ error: string;
155
+ };
156
+ };
157
+ };
158
+ };
159
+ };
160
+ };
161
+ } & {
162
+ api: {
163
+ analytics: {
164
+ traders: {
165
+ ":address": {
166
+ get: {
167
+ body: unknown;
168
+ params: {
169
+ address: string;
170
+ } & {};
171
+ query: unknown;
172
+ headers: unknown;
173
+ response: {
174
+ 200: import("../../types").TraderAnalyticsResponse | {
175
+ error: string;
176
+ };
177
+ 422: {
178
+ type: "validation";
179
+ on: string;
180
+ summary?: string;
181
+ message?: string;
182
+ found?: unknown;
183
+ property?: string;
184
+ expected?: string;
185
+ };
186
+ };
187
+ };
188
+ };
189
+ };
190
+ };
191
+ };
192
+ }, {
193
+ derive: {};
194
+ resolve: {};
195
+ schema: {};
196
+ standaloneSchema: {};
197
+ response: {};
198
+ }, {
199
+ derive: {};
200
+ resolve: {};
201
+ schema: {};
202
+ standaloneSchema: {};
203
+ response: {};
204
+ }>;
205
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/api/routes/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EACV,mBAAmB,EACpB,MAAM,wCAAwC,CAAC;AAahD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuG1D,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * GET /api/analytics/* — analytics endpoints backed by the Postgres warehouse.
3
+ *
4
+ * Routes:
5
+ * GET /api/analytics/overview — platform-wide headline stats
6
+ * GET /api/analytics/domains — per-domain summary list
7
+ * GET /api/analytics/domains/:slug — domain detail (time-series + top-N)
8
+ * GET /api/analytics/cohorts — cohort retention curves
9
+ * GET /api/analytics/roi — ROI distribution + summary stats
10
+ * GET /api/analytics/leaderboard — leaderboard with ?domain= and time window
11
+ * GET /api/analytics/traders/:address — per-trader analytics (per-domain + cohort)
12
+ *
13
+ * These routes are only registered when WAREHOUSE_DATABASE_URL is set.
14
+ *
15
+ * Validation errors throw via parseAnalyticsFilters / parseEntityType — the
16
+ * shared error-handler maps Error → 400 (we tag it explicitly via `set.status`).
17
+ */
18
+ import { Elysia } from 'elysia';
19
+ import { parseAnalyticsFilters, parseEntityType, } from '../../warehouse/analytics-helpers';
20
+ const ADDRESS_RE = /^0x[0-9a-fA-F]+$/;
21
+ const SLUG_RE = /^[a-z][a-z0-9_-]*$/;
22
+ const NOT_CONFIGURED_BODY = {
23
+ error: 'Analytics warehouse is not configured (set WAREHOUSE_DATABASE_URL).',
24
+ };
25
+ /**
26
+ * Registers analytics routes. When `repo` is null (warehouse not configured),
27
+ * every analytics route returns 503 with a clear message so consumers can
28
+ * fall back gracefully.
29
+ */
30
+ export const analyticsRoutes = (repo) => new Elysia()
31
+ .get('/api/analytics/overview', async ({ set }) => {
32
+ if (!repo) {
33
+ set.status = 503;
34
+ return NOT_CONFIGURED_BODY;
35
+ }
36
+ return repo.getOverview();
37
+ })
38
+ .get('/api/analytics/domains', async ({ set }) => {
39
+ if (!repo) {
40
+ set.status = 503;
41
+ return NOT_CONFIGURED_BODY;
42
+ }
43
+ return repo.getDomains();
44
+ })
45
+ .get('/api/analytics/domains/:slug', async ({ params, set }) => {
46
+ if (!repo) {
47
+ set.status = 503;
48
+ return NOT_CONFIGURED_BODY;
49
+ }
50
+ const slug = String(params.slug ?? '').toLowerCase();
51
+ if (!SLUG_RE.test(slug)) {
52
+ set.status = 400;
53
+ return { error: `Invalid domain slug: ${slug}` };
54
+ }
55
+ const detail = await repo.getDomainDetail(slug);
56
+ if (!detail) {
57
+ set.status = 404;
58
+ return { error: `Domain not found: ${slug}` };
59
+ }
60
+ return detail;
61
+ })
62
+ .get('/api/analytics/cohorts', async ({ query, set }) => {
63
+ if (!repo) {
64
+ set.status = 503;
65
+ return NOT_CONFIGURED_BODY;
66
+ }
67
+ const q = query;
68
+ try {
69
+ const filters = parseAnalyticsFilters({ domain: q.domain });
70
+ const entityType = parseEntityType(q.entityType ?? q.entity_type);
71
+ return await repo.getCohorts({ entityType, domain: filters.domain });
72
+ }
73
+ catch (err) {
74
+ set.status = 400;
75
+ return { error: err instanceof Error ? err.message : String(err) };
76
+ }
77
+ })
78
+ .get('/api/analytics/roi', async ({ query, set }) => {
79
+ if (!repo) {
80
+ set.status = 503;
81
+ return NOT_CONFIGURED_BODY;
82
+ }
83
+ const q = query;
84
+ try {
85
+ const filters = parseAnalyticsFilters({ domain: q.domain });
86
+ const entityType = parseEntityType(q.entityType ?? q.entity_type);
87
+ return await repo.getRoi({ entityType, domain: filters.domain });
88
+ }
89
+ catch (err) {
90
+ set.status = 400;
91
+ return { error: err instanceof Error ? err.message : String(err) };
92
+ }
93
+ })
94
+ .get('/api/analytics/leaderboard', async ({ query, set }) => {
95
+ if (!repo) {
96
+ set.status = 503;
97
+ return NOT_CONFIGURED_BODY;
98
+ }
99
+ const q = query;
100
+ try {
101
+ const filters = parseAnalyticsFilters(q);
102
+ const limit = Math.min(Math.max(1, Number.parseInt(q.limit ?? '50', 10) || 50), 500);
103
+ return await repo.getLeaderboard({ ...filters, limit });
104
+ }
105
+ catch (err) {
106
+ set.status = 400;
107
+ return { error: err instanceof Error ? err.message : String(err) };
108
+ }
109
+ })
110
+ .get('/api/analytics/traders/:address', async ({ params, set }) => {
111
+ if (!repo) {
112
+ set.status = 503;
113
+ return NOT_CONFIGURED_BODY;
114
+ }
115
+ const address = String(params.address ?? '');
116
+ if (!ADDRESS_RE.test(address)) {
117
+ set.status = 400;
118
+ return { error: `Invalid address: ${address}` };
119
+ }
120
+ return await repo.getTraderAnalytics(address);
121
+ });
122
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/api/routes/analytics.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export { activityFeedRoutes } from './activity-feed';
2
+ export { analyticsRoutes } from './analytics';
2
3
  export { adminRoutes } from './admin';
3
4
  export { adminSubscriptionRoutes } from './admin-subscriptions';
4
5
  export { healthRoutes } from './health';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { activityFeedRoutes } from './activity-feed';
2
+ export { analyticsRoutes } from './analytics';
2
3
  export { adminRoutes } from './admin';
3
4
  export { adminSubscriptionRoutes } from './admin-subscriptions';
4
5
  export { healthRoutes } from './health';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
@@ -29,8 +29,8 @@ export declare const lpHistoryRoutes: (db: DatabaseService) => Elysia<"", {
29
29
  get: {
30
30
  body: unknown;
31
31
  params: {
32
- provider: string;
33
32
  address: string;
33
+ provider: string;
34
34
  } & {};
35
35
  query: unknown;
36
36
  headers: unknown;
@@ -1,9 +1,21 @@
1
1
  /**
2
- * GET /api/rankings — global leaderboard.
2
+ * GET /api/rankings — leaderboard with optional ?domain=&from=&to= filters.
3
+ *
4
+ * Filter behaviour:
5
+ * - No filters → SQLite-backed lifetime leaderboard (current behaviour).
6
+ * - domain=<slug>, from=, to= → routed to the warehouse-backed analytics
7
+ * leaderboard, which knows how to slice by domain & time-window. Response
8
+ * shape stays compatible (RankingEntry[]) so existing webapp consumers
9
+ * don't break.
10
+ *
11
+ * If filters are passed but the warehouse is not configured, we 503 — the
12
+ * filtered view requires the warehouse to be running.
3
13
  */
4
14
  import { Elysia } from 'elysia';
5
15
  import type { DatabaseService } from '../../services/Database';
6
- export declare const rankingRoutes: (db: DatabaseService) => Elysia<"", {
16
+ import type { RankingEntry } from '../../types/api';
17
+ import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
18
+ export declare const rankingRoutes: (db: DatabaseService, analytics: AnalyticsRepository | null) => Elysia<"", {
7
19
  decorator: {};
8
20
  store: {};
9
21
  derive: {};
@@ -27,7 +39,9 @@ export declare const rankingRoutes: (db: DatabaseService) => Elysia<"", {
27
39
  query: unknown;
28
40
  headers: unknown;
29
41
  response: {
30
- 200: import("../../types").RankingEntry[];
42
+ 200: RankingEntry[] | {
43
+ error: string;
44
+ };
31
45
  };
32
46
  };
33
47
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rankings.d.ts","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,eAAO,MAAM,aAAa,GAAI,IAAI,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK7C,CAAC"}
1
+ {"version":3,"file":"rankings.d.ts","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAGlF,eAAO,MAAM,aAAa,GACxB,IAAI,eAAe,EACnB,WAAW,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CnC,CAAC"}
@@ -1,10 +1,49 @@
1
1
  /**
2
- * GET /api/rankings — global leaderboard.
2
+ * GET /api/rankings — leaderboard with optional ?domain=&from=&to= filters.
3
+ *
4
+ * Filter behaviour:
5
+ * - No filters → SQLite-backed lifetime leaderboard (current behaviour).
6
+ * - domain=<slug>, from=, to= → routed to the warehouse-backed analytics
7
+ * leaderboard, which knows how to slice by domain & time-window. Response
8
+ * shape stays compatible (RankingEntry[]) so existing webapp consumers
9
+ * don't break.
10
+ *
11
+ * If filters are passed but the warehouse is not configured, we 503 — the
12
+ * filtered view requires the warehouse to be running.
3
13
  */
4
14
  import { Elysia } from 'elysia';
5
- export const rankingRoutes = (db) => new Elysia().get('/api/rankings', ({ query }) => {
6
- const typedQuery = query;
7
- const limit = Math.min(Math.max(1, parseInt(typedQuery.limit ?? '50', 10) || 50), 200);
8
- return db.rankings.getLeaderboard(limit);
15
+ import { parseAnalyticsFilters } from '../../warehouse/analytics-helpers';
16
+ export const rankingRoutes = (db, analytics) => new Elysia().get('/api/rankings', async ({ query, set }) => {
17
+ const q = query;
18
+ const limit = Math.min(Math.max(1, parseInt(q.limit ?? '50', 10) || 50), 200);
19
+ const wantsFiltered = (q.domain != null && q.domain !== '' && q.domain !== 'all') ||
20
+ q.from != null ||
21
+ q.to != null;
22
+ if (!wantsFiltered) {
23
+ return db.rankings.getLeaderboard(limit);
24
+ }
25
+ if (!analytics) {
26
+ set.status = 503;
27
+ return {
28
+ error: 'Filtered rankings require the analytics warehouse, which is not configured.',
29
+ };
30
+ }
31
+ let filters;
32
+ try {
33
+ filters = parseAnalyticsFilters(q);
34
+ }
35
+ catch (err) {
36
+ set.status = 400;
37
+ return { error: err instanceof Error ? err.message : String(err) };
38
+ }
39
+ const result = await analytics.getLeaderboard({ ...filters, limit });
40
+ // Backwards-compatible shape — webapp's existing useLeaderboard consumes RankingEntry[].
41
+ const compat = result.entries.map((e) => ({
42
+ trader: e.trader,
43
+ totalPnl: e.totalPnl,
44
+ marketsTraded: e.marketsTraded,
45
+ totalTrades: e.totalTrades,
46
+ }));
47
+ return compat;
9
48
  });
10
49
  //# sourceMappingURL=rankings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAmB,EAAE,EAAE,CACnD,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;IAC9C,MAAM,UAAU,GAAG,KAAoC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,IAAI,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACvF,OAAO,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,EAAmB,EACnB,SAAqC,EACrC,EAAE,CACF,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IACzD,MAAM,CAAC,GAAG,KAKT,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAE9E,MAAM,aAAa,GACjB,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC;QAC3D,CAAC,CAAC,IAAI,IAAI,IAAI;QACd,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC;IAEf,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO;YACL,KAAK,EAAE,6EAA6E;SACrF,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,yFAAyF;IACzF,MAAM,MAAM,GAAmB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,WAAW,EAAE,CAAC,CAAC,WAAW;KAC3B,CAAC,CAAC,CAAC;IACJ,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC,CAAC"}
@@ -1,9 +1,16 @@
1
1
  /**
2
2
  * GET /api/positions/:trader/stats — aggregated trader statistics.
3
+ *
4
+ * Optional ?domain=<slug> routes the request to the warehouse-backed analytics
5
+ * trader endpoint and returns a domain-filtered subset of the same fields.
6
+ * Time-window filters (?from=, ?to=) are accepted but currently apply only to
7
+ * the warehouse path — they sum daily fact rows in the window.
3
8
  */
4
9
  import { Elysia } from 'elysia';
5
10
  import type { DatabaseService } from '../../services/Database';
6
- export declare const traderStatsRoutes: (db: DatabaseService) => Elysia<"", {
11
+ import type { TraderStatsResponse } from '../../types/api';
12
+ import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
13
+ export declare const traderStatsRoutes: (db: DatabaseService, analytics: AnalyticsRepository | null) => Elysia<"", {
7
14
  decorator: {};
8
15
  store: {};
9
16
  derive: {};
@@ -31,7 +38,9 @@ export declare const traderStatsRoutes: (db: DatabaseService) => Elysia<"", {
31
38
  query: unknown;
32
39
  headers: unknown;
33
40
  response: {
34
- 200: import("../../types").TraderStatsResponse;
41
+ 200: TraderStatsResponse | {
42
+ error: string;
43
+ };
35
44
  422: {
36
45
  type: "validation";
37
46
  on: string;
@@ -1 +1 @@
1
- {"version":3,"file":"trader-stats.d.ts","sourceRoot":"","sources":["../../../src/api/routes/trader-stats.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,eAAO,MAAM,iBAAiB,GAAI,IAAI,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjD,CAAC"}
1
+ {"version":3,"file":"trader-stats.d.ts","sourceRoot":"","sources":["../../../src/api/routes/trader-stats.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAGlF,eAAO,MAAM,iBAAiB,GAC5B,IAAI,eAAe,EACnB,WAAW,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0EnC,CAAC"}
@@ -1,8 +1,78 @@
1
1
  /**
2
2
  * GET /api/positions/:trader/stats — aggregated trader statistics.
3
+ *
4
+ * Optional ?domain=<slug> routes the request to the warehouse-backed analytics
5
+ * trader endpoint and returns a domain-filtered subset of the same fields.
6
+ * Time-window filters (?from=, ?to=) are accepted but currently apply only to
7
+ * the warehouse path — they sum daily fact rows in the window.
3
8
  */
4
9
  import { Elysia } from 'elysia';
5
- export const traderStatsRoutes = (db) => new Elysia().get('/api/positions/:trader/stats', ({ params }) => {
6
- return db.rankings.getTraderStats(params.trader);
10
+ import { parseAnalyticsFilters } from '../../warehouse/analytics-helpers';
11
+ export const traderStatsRoutes = (db, analytics) => new Elysia().get('/api/positions/:trader/stats', async ({ params, query, set }) => {
12
+ const q = query;
13
+ const wantsFiltered = (q.domain != null && q.domain !== '' && q.domain !== 'all') ||
14
+ q.from != null ||
15
+ q.to != null;
16
+ if (!wantsFiltered) {
17
+ return db.rankings.getTraderStats(params.trader);
18
+ }
19
+ if (!analytics) {
20
+ set.status = 503;
21
+ return {
22
+ error: 'Filtered trader stats require the analytics warehouse, which is not configured.',
23
+ };
24
+ }
25
+ let filters;
26
+ try {
27
+ filters = parseAnalyticsFilters(q);
28
+ }
29
+ catch (err) {
30
+ set.status = 400;
31
+ return { error: err instanceof Error ? err.message : String(err) };
32
+ }
33
+ const traderAnalytics = await analytics.getTraderAnalytics(params.trader);
34
+ if (!traderAnalytics.lifetime) {
35
+ // Trader has never traded — return zero stats consistent with TraderStatsResponse shape.
36
+ const empty = {
37
+ trader: params.trader,
38
+ totalPnl: 0,
39
+ unrealizedPnl: 0,
40
+ biggestWin: 0,
41
+ totalTrades: 0,
42
+ marketsTraded: 0,
43
+ firstTradeAt: null,
44
+ lastTradeAt: null,
45
+ };
46
+ return empty;
47
+ }
48
+ if (filters.domain !== 'all') {
49
+ // Domain-filtered slice — read from perDomain.
50
+ const domainSlice = traderAnalytics.perDomain.find((d) => d.slug === filters.domain);
51
+ const stats = {
52
+ trader: params.trader,
53
+ // P&L is not currently tracked per-domain; expose lifetime totals so the
54
+ // client can show "domain stats + portfolio P&L". This is honest about
55
+ // current capability — no fake per-domain P&L.
56
+ totalPnl: traderAnalytics.lifetime.totalPnl,
57
+ unrealizedPnl: traderAnalytics.lifetime.unrealizedPnl,
58
+ biggestWin: 0,
59
+ totalTrades: domainSlice?.tradeCount ?? 0,
60
+ marketsTraded: domainSlice?.marketsTraded ?? 0,
61
+ firstTradeAt: traderAnalytics.lifetime.firstTradeAt,
62
+ lastTradeAt: traderAnalytics.lifetime.lastTradeAt,
63
+ };
64
+ return stats;
65
+ }
66
+ const stats = {
67
+ trader: params.trader,
68
+ totalPnl: traderAnalytics.lifetime.totalPnl,
69
+ unrealizedPnl: traderAnalytics.lifetime.unrealizedPnl,
70
+ biggestWin: 0,
71
+ totalTrades: traderAnalytics.lifetime.totalTrades,
72
+ marketsTraded: traderAnalytics.lifetime.marketsTraded,
73
+ firstTradeAt: traderAnalytics.lifetime.firstTradeAt,
74
+ lastTradeAt: traderAnalytics.lifetime.lastTradeAt,
75
+ };
76
+ return stats;
7
77
  });
8
78
  //# sourceMappingURL=trader-stats.js.map