@contractspec/lib.metering 1.56.1 → 1.58.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 (63) hide show
  1. package/dist/aggregation/index.d.ts +119 -122
  2. package/dist/aggregation/index.d.ts.map +1 -1
  3. package/dist/aggregation/index.js +257 -265
  4. package/dist/analytics/posthog-metering-reader.d.ts +39 -0
  5. package/dist/analytics/posthog-metering-reader.d.ts.map +1 -0
  6. package/dist/analytics/posthog-metering-reader.js +267 -0
  7. package/dist/analytics/posthog-metering.d.ts +35 -0
  8. package/dist/analytics/posthog-metering.d.ts.map +1 -0
  9. package/dist/analytics/posthog-metering.js +46 -0
  10. package/dist/browser/aggregation/index.js +265 -0
  11. package/dist/browser/analytics/posthog-metering-reader.js +266 -0
  12. package/dist/browser/analytics/posthog-metering.js +45 -0
  13. package/dist/browser/contracts/index.js +617 -0
  14. package/dist/browser/docs/index.js +62 -0
  15. package/dist/browser/docs/metering.docblock.js +62 -0
  16. package/dist/browser/entities/index.js +350 -0
  17. package/dist/browser/events.js +228 -0
  18. package/dist/browser/index.js +1879 -0
  19. package/dist/browser/metering.capability.js +28 -0
  20. package/dist/browser/metering.feature.js +52 -0
  21. package/dist/contracts/index.d.ts +1076 -1082
  22. package/dist/contracts/index.d.ts.map +1 -1
  23. package/dist/contracts/index.js +616 -1030
  24. package/dist/docs/index.d.ts +2 -1
  25. package/dist/docs/index.d.ts.map +1 -0
  26. package/dist/docs/index.js +63 -1
  27. package/dist/docs/metering.docblock.d.ts +2 -1
  28. package/dist/docs/metering.docblock.d.ts.map +1 -0
  29. package/dist/docs/metering.docblock.js +18 -22
  30. package/dist/entities/index.d.ts +186 -191
  31. package/dist/entities/index.d.ts.map +1 -1
  32. package/dist/entities/index.js +340 -407
  33. package/dist/events.d.ts +411 -417
  34. package/dist/events.d.ts.map +1 -1
  35. package/dist/events.js +226 -414
  36. package/dist/index.d.ts +9 -6
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +1880 -8
  39. package/dist/metering.capability.d.ts +2 -7
  40. package/dist/metering.capability.d.ts.map +1 -1
  41. package/dist/metering.capability.js +29 -33
  42. package/dist/metering.feature.d.ts +1 -7
  43. package/dist/metering.feature.d.ts.map +1 -1
  44. package/dist/metering.feature.js +51 -132
  45. package/dist/node/aggregation/index.js +265 -0
  46. package/dist/node/analytics/posthog-metering-reader.js +266 -0
  47. package/dist/node/analytics/posthog-metering.js +45 -0
  48. package/dist/node/contracts/index.js +617 -0
  49. package/dist/node/docs/index.js +62 -0
  50. package/dist/node/docs/metering.docblock.js +62 -0
  51. package/dist/node/entities/index.js +350 -0
  52. package/dist/node/events.js +228 -0
  53. package/dist/node/index.js +1879 -0
  54. package/dist/node/metering.capability.js +28 -0
  55. package/dist/node/metering.feature.js +52 -0
  56. package/package.json +133 -30
  57. package/dist/aggregation/index.js.map +0 -1
  58. package/dist/contracts/index.js.map +0 -1
  59. package/dist/docs/metering.docblock.js.map +0 -1
  60. package/dist/entities/index.js.map +0 -1
  61. package/dist/events.js.map +0 -1
  62. package/dist/metering.capability.js.map +0 -1
  63. package/dist/metering.feature.js.map +0 -1
@@ -0,0 +1,266 @@
1
+ // src/analytics/posthog-metering-reader.ts
2
+ class PosthogMeteringReader {
3
+ reader;
4
+ eventPrefix;
5
+ constructor(reader, options = {}) {
6
+ this.reader = reader;
7
+ this.eventPrefix = options.eventPrefix ?? "metering";
8
+ }
9
+ async getUsageByMetric(input) {
10
+ const result = await this.queryHogQL({
11
+ query: [
12
+ "select",
13
+ " properties.recordId as recordId,",
14
+ " properties.metricKey as metricKey,",
15
+ " properties.subjectType as subjectType,",
16
+ " distinct_id as subjectId,",
17
+ " properties.quantity as quantity,",
18
+ " properties.source as source,",
19
+ " timestamp as timestamp",
20
+ "from events",
21
+ `where ${buildUsageWhereClause(this.eventPrefix, input)}`,
22
+ "order by timestamp desc",
23
+ `limit ${input.limit ?? 200}`
24
+ ].join(`
25
+ `),
26
+ values: buildUsageValues(input)
27
+ });
28
+ return mapUsageRecords(result);
29
+ }
30
+ async getUsageSummary(input) {
31
+ const result = await this.queryHogQL({
32
+ query: [
33
+ "select",
34
+ " properties.summaryId as summaryId,",
35
+ " properties.metricKey as metricKey,",
36
+ " properties.subjectType as subjectType,",
37
+ " distinct_id as subjectId,",
38
+ " properties.periodType as periodType,",
39
+ " properties.periodStart as periodStart,",
40
+ " properties.periodEnd as periodEnd,",
41
+ " properties.totalQuantity as totalQuantity,",
42
+ " properties.recordCount as recordCount,",
43
+ " timestamp as aggregatedAt",
44
+ "from events",
45
+ `where ${buildSummaryWhereClause(this.eventPrefix, input)}`,
46
+ "order by timestamp desc",
47
+ `limit ${input.limit ?? 200}`
48
+ ].join(`
49
+ `),
50
+ values: buildSummaryValues(input)
51
+ });
52
+ return mapUsageSummaries(result);
53
+ }
54
+ async getUsageTrend(input) {
55
+ const result = await this.queryHogQL({
56
+ query: [
57
+ "select",
58
+ ` ${bucketExpression(input.bucket)} as bucketStart,`,
59
+ " sum(properties.quantity) as totalQuantity,",
60
+ " count() as recordCount",
61
+ "from events",
62
+ `where ${buildUsageWhereClause(this.eventPrefix, input)}`,
63
+ "group by bucketStart",
64
+ "order by bucketStart asc"
65
+ ].join(`
66
+ `),
67
+ values: buildUsageValues(input)
68
+ });
69
+ return mapUsageTrend(result);
70
+ }
71
+ async queryHogQL(input) {
72
+ if (!this.reader.queryHogQL) {
73
+ throw new Error("Analytics reader does not support HogQL queries.");
74
+ }
75
+ return this.reader.queryHogQL(input);
76
+ }
77
+ }
78
+ function buildUsageWhereClause(eventPrefix, input) {
79
+ const clauses = [
80
+ `event = '${eventPrefix}.usage_recorded'`,
81
+ `properties.metricKey = {metricKey}`
82
+ ];
83
+ if (input.subjectId) {
84
+ clauses.push("distinct_id = {subjectId}");
85
+ }
86
+ if (input.dateRange?.from) {
87
+ clauses.push("timestamp >= {dateFrom}");
88
+ }
89
+ if (input.dateRange?.to) {
90
+ clauses.push("timestamp < {dateTo}");
91
+ }
92
+ return clauses.join(" and ");
93
+ }
94
+ function buildSummaryWhereClause(eventPrefix, input) {
95
+ const clauses = [
96
+ `event = '${eventPrefix}.usage_aggregated'`,
97
+ `properties.metricKey = {metricKey}`
98
+ ];
99
+ if (input.subjectId) {
100
+ clauses.push("distinct_id = {subjectId}");
101
+ }
102
+ if (input.periodType) {
103
+ clauses.push("properties.periodType = {periodType}");
104
+ }
105
+ if (input.dateRange?.from) {
106
+ clauses.push("timestamp >= {dateFrom}");
107
+ }
108
+ if (input.dateRange?.to) {
109
+ clauses.push("timestamp < {dateTo}");
110
+ }
111
+ return clauses.join(" and ");
112
+ }
113
+ function buildUsageValues(input) {
114
+ return {
115
+ metricKey: input.metricKey,
116
+ subjectId: input.subjectId,
117
+ dateFrom: toIsoString(input.dateRange?.from),
118
+ dateTo: toIsoString(input.dateRange?.to)
119
+ };
120
+ }
121
+ function buildSummaryValues(input) {
122
+ return {
123
+ metricKey: input.metricKey,
124
+ subjectId: input.subjectId,
125
+ periodType: input.periodType,
126
+ dateFrom: toIsoString(input.dateRange?.from),
127
+ dateTo: toIsoString(input.dateRange?.to)
128
+ };
129
+ }
130
+ function bucketExpression(bucket) {
131
+ switch (bucket) {
132
+ case "hour":
133
+ return "toStartOfHour(timestamp)";
134
+ case "week":
135
+ return "toStartOfWeek(timestamp)";
136
+ case "month":
137
+ return "toStartOfMonth(timestamp)";
138
+ case "day":
139
+ default:
140
+ return "toStartOfDay(timestamp)";
141
+ }
142
+ }
143
+ function mapUsageRecords(result) {
144
+ const rows = mapRows(result);
145
+ return rows.flatMap((row) => {
146
+ const metricKey = asString(row.metricKey);
147
+ const subjectType = asString(row.subjectType);
148
+ const subjectId = asString(row.subjectId);
149
+ const timestamp = asDate(row.timestamp);
150
+ if (!metricKey || !subjectType || !subjectId || !timestamp) {
151
+ return [];
152
+ }
153
+ return [
154
+ {
155
+ recordId: asOptionalString(row.recordId),
156
+ metricKey,
157
+ subjectType,
158
+ subjectId,
159
+ quantity: asNumber(row.quantity),
160
+ source: asOptionalString(row.source),
161
+ timestamp
162
+ }
163
+ ];
164
+ });
165
+ }
166
+ function mapUsageSummaries(result) {
167
+ const rows = mapRows(result);
168
+ return rows.flatMap((row) => {
169
+ const metricKey = asString(row.metricKey);
170
+ const subjectType = asString(row.subjectType);
171
+ const subjectId = asString(row.subjectId);
172
+ const periodType = asString(row.periodType);
173
+ const periodStart = asDate(row.periodStart);
174
+ const periodEnd = asDate(row.periodEnd);
175
+ const aggregatedAt = asDate(row.aggregatedAt);
176
+ if (!metricKey || !subjectType || !subjectId || !periodType || !periodStart || !periodEnd || !aggregatedAt) {
177
+ return [];
178
+ }
179
+ return [
180
+ {
181
+ summaryId: asOptionalString(row.summaryId),
182
+ metricKey,
183
+ subjectType,
184
+ subjectId,
185
+ periodType,
186
+ periodStart,
187
+ periodEnd,
188
+ totalQuantity: asNumber(row.totalQuantity),
189
+ recordCount: asNumber(row.recordCount),
190
+ aggregatedAt
191
+ }
192
+ ];
193
+ });
194
+ }
195
+ function mapUsageTrend(result) {
196
+ const rows = mapRows(result);
197
+ return rows.flatMap((row) => {
198
+ const bucketStart = asString(row.bucketStart);
199
+ if (!bucketStart)
200
+ return [];
201
+ return [
202
+ {
203
+ bucketStart,
204
+ totalQuantity: asNumber(row.totalQuantity),
205
+ recordCount: asNumber(row.recordCount)
206
+ }
207
+ ];
208
+ });
209
+ }
210
+ function mapRows(result) {
211
+ if (!Array.isArray(result.results) || !Array.isArray(result.columns)) {
212
+ return [];
213
+ }
214
+ const columns = result.columns;
215
+ return result.results.flatMap((row) => {
216
+ if (!Array.isArray(row))
217
+ return [];
218
+ const record = {};
219
+ columns.forEach((column, index) => {
220
+ record[column] = row[index];
221
+ });
222
+ return [record];
223
+ });
224
+ }
225
+ function asString(value) {
226
+ if (typeof value === "string" && value.trim())
227
+ return value;
228
+ if (typeof value === "number")
229
+ return String(value);
230
+ return null;
231
+ }
232
+ function asOptionalString(value) {
233
+ if (typeof value === "string")
234
+ return value;
235
+ if (typeof value === "number")
236
+ return String(value);
237
+ return;
238
+ }
239
+ function asNumber(value) {
240
+ if (typeof value === "number" && Number.isFinite(value))
241
+ return value;
242
+ if (typeof value === "string" && value.trim()) {
243
+ const parsed = Number(value);
244
+ if (Number.isFinite(parsed))
245
+ return parsed;
246
+ }
247
+ return 0;
248
+ }
249
+ function asDate(value) {
250
+ if (value instanceof Date)
251
+ return value;
252
+ if (typeof value === "string" || typeof value === "number") {
253
+ const date = new Date(value);
254
+ if (!Number.isNaN(date.getTime()))
255
+ return date;
256
+ }
257
+ return null;
258
+ }
259
+ function toIsoString(value) {
260
+ if (!value)
261
+ return;
262
+ return typeof value === "string" ? value : value.toISOString();
263
+ }
264
+ export {
265
+ PosthogMeteringReader
266
+ };
@@ -0,0 +1,45 @@
1
+ // src/analytics/posthog-metering.ts
2
+ class PosthogMeteringReporter {
3
+ provider;
4
+ eventPrefix;
5
+ includeSource;
6
+ constructor(provider, options = {}) {
7
+ this.provider = provider;
8
+ this.eventPrefix = options.eventPrefix ?? "metering";
9
+ this.includeSource = options.includeSource ?? true;
10
+ }
11
+ async captureUsageRecorded(record) {
12
+ await this.provider.capture({
13
+ distinctId: record.subjectId,
14
+ event: `${this.eventPrefix}.usage_recorded`,
15
+ timestamp: record.timestamp,
16
+ properties: {
17
+ recordId: record.recordId ?? null,
18
+ metricKey: record.metricKey,
19
+ subjectType: record.subjectType,
20
+ quantity: record.quantity,
21
+ ...this.includeSource && record.source ? { source: record.source } : {}
22
+ }
23
+ });
24
+ }
25
+ async captureUsageAggregated(summary) {
26
+ await this.provider.capture({
27
+ distinctId: summary.subjectId,
28
+ event: `${this.eventPrefix}.usage_aggregated`,
29
+ timestamp: summary.aggregatedAt,
30
+ properties: {
31
+ summaryId: summary.summaryId ?? null,
32
+ metricKey: summary.metricKey,
33
+ subjectType: summary.subjectType,
34
+ periodType: summary.periodType,
35
+ periodStart: summary.periodStart.toISOString(),
36
+ periodEnd: summary.periodEnd.toISOString(),
37
+ totalQuantity: summary.totalQuantity,
38
+ recordCount: summary.recordCount
39
+ }
40
+ });
41
+ }
42
+ }
43
+ export {
44
+ PosthogMeteringReporter
45
+ };