@contractspec/lib.metering 1.57.0 → 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 (65) 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 +32 -36
  5. package/dist/analytics/posthog-metering-reader.d.ts.map +1 -1
  6. package/dist/analytics/posthog-metering-reader.js +239 -179
  7. package/dist/analytics/posthog-metering.d.ts +30 -34
  8. package/dist/analytics/posthog-metering.d.ts.map +1 -1
  9. package/dist/analytics/posthog-metering.js +45 -45
  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 +17 -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 -8
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +1880 -10
  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 -6
  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 -34
  57. package/dist/aggregation/index.js.map +0 -1
  58. package/dist/analytics/posthog-metering-reader.js.map +0 -1
  59. package/dist/analytics/posthog-metering.js.map +0 -1
  60. package/dist/contracts/index.js.map +0 -1
  61. package/dist/docs/metering.docblock.js.map +0 -1
  62. package/dist/entities/index.js.map +0 -1
  63. package/dist/events.js.map +0 -1
  64. package/dist/metering.capability.js.map +0 -1
  65. package/dist/metering.feature.js.map +0 -1
@@ -1,8 +1,3 @@
1
- import * as _contractspec_lib_contracts0 from "@contractspec/lib.contracts";
2
-
3
- //#region src/metering.capability.d.ts
4
- declare const MeteringCapability: _contractspec_lib_contracts0.CapabilitySpec;
5
- declare const ThresholdsCapability: _contractspec_lib_contracts0.CapabilitySpec;
6
- //#endregion
7
- export { MeteringCapability, ThresholdsCapability };
1
+ export declare const MeteringCapability: import("@contractspec/lib.contracts").CapabilitySpec;
2
+ export declare const ThresholdsCapability: import("@contractspec/lib.contracts").CapabilitySpec;
8
3
  //# sourceMappingURL=metering.capability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metering.capability.d.ts","names":[],"sources":["../src/metering.capability.ts"],"mappings":";;;cAEa,kBAAA,EAUX,4BAAA,CAV6B,cAAA;AAAA,cAYlB,oBAAA,EAUX,4BAAA,CAV+B,cAAA"}
1
+ {"version":3,"file":"metering.capability.d.ts","sourceRoot":"","sources":["../src/metering.capability.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,sDAU7B,CAAC;AAEH,eAAO,MAAM,oBAAoB,sDAU/B,CAAC"}
@@ -1,33 +1,29 @@
1
- import { StabilityEnum, defineCapability } from "@contractspec/lib.contracts";
2
-
3
- //#region src/metering.capability.ts
4
- const MeteringCapability = defineCapability({ meta: {
5
- key: "metering",
6
- version: "1.0.0",
7
- kind: "api",
8
- stability: StabilityEnum.Experimental,
9
- description: "Usage metering and tracking",
10
- owners: ["@platform.finance"],
11
- tags: [
12
- "metering",
13
- "usage",
14
- "billing"
15
- ]
16
- } });
17
- const ThresholdsCapability = defineCapability({ meta: {
18
- key: "thresholds",
19
- version: "1.0.0",
20
- kind: "api",
21
- stability: StabilityEnum.Experimental,
22
- description: "Usage threshold alerts and limits",
23
- owners: ["@platform.finance"],
24
- tags: [
25
- "thresholds",
26
- "limits",
27
- "metering"
28
- ]
29
- } });
30
-
31
- //#endregion
32
- export { MeteringCapability, ThresholdsCapability };
33
- //# sourceMappingURL=metering.capability.js.map
1
+ // @bun
2
+ // src/metering.capability.ts
3
+ import { defineCapability, StabilityEnum } from "@contractspec/lib.contracts";
4
+ var MeteringCapability = defineCapability({
5
+ meta: {
6
+ key: "metering",
7
+ version: "1.0.0",
8
+ kind: "api",
9
+ stability: StabilityEnum.Experimental,
10
+ description: "Usage metering and tracking",
11
+ owners: ["@platform.finance"],
12
+ tags: ["metering", "usage", "billing"]
13
+ }
14
+ });
15
+ var ThresholdsCapability = defineCapability({
16
+ meta: {
17
+ key: "thresholds",
18
+ version: "1.0.0",
19
+ kind: "api",
20
+ stability: StabilityEnum.Experimental,
21
+ description: "Usage threshold alerts and limits",
22
+ owners: ["@platform.finance"],
23
+ tags: ["thresholds", "limits", "metering"]
24
+ }
25
+ });
26
+ export {
27
+ ThresholdsCapability,
28
+ MeteringCapability
29
+ };
@@ -1,11 +1,6 @@
1
- import * as _contractspec_lib_contracts0 from "@contractspec/lib.contracts";
2
-
3
- //#region src/metering.feature.d.ts
4
1
  /**
5
2
  * Metering feature module that bundles usage tracking,
6
3
  * metrics definitions, and billing capabilities.
7
4
  */
8
- declare const MeteringFeature: _contractspec_lib_contracts0.FeatureModuleSpec;
9
- //#endregion
10
- export { MeteringFeature };
5
+ export declare const MeteringFeature: import("@contractspec/lib.contracts").FeatureModuleSpec;
11
6
  //# sourceMappingURL=metering.feature.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metering.feature.d.ts","names":[],"sources":["../src/metering.feature.ts"],"mappings":";;;;;;AAWA;cAAa,eAAA,EAgEX,4BAAA,CAhE0B,iBAAA"}
1
+ {"version":3,"file":"metering.feature.d.ts","sourceRoot":"","sources":["../src/metering.feature.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,eAAO,MAAM,eAAe,yDAgE1B,CAAC"}
@@ -1,134 +1,53 @@
1
+ // @bun
2
+ // src/metering.feature.ts
1
3
  import { defineFeature } from "@contractspec/lib.contracts";
2
-
3
- //#region src/metering.feature.ts
4
- /**
5
- * Metering Feature Module Specification
6
- *
7
- * Defines the feature module for usage metering and threshold management.
8
- */
9
- /**
10
- * Metering feature module that bundles usage tracking,
11
- * metrics definitions, and billing capabilities.
12
- */
13
- const MeteringFeature = defineFeature({
14
- meta: {
15
- key: "metrics",
16
- version: "1.0.0",
17
- title: "Usage Metering",
18
- description: "Usage metering, metric definitions, and threshold alerting",
19
- domain: "platform",
20
- owners: ["@platform.metering"],
21
- tags: [
22
- "metering",
23
- "usage",
24
- "billing",
25
- "thresholds"
26
- ],
27
- stability: "stable"
28
- },
29
- operations: [
30
- {
31
- key: "metric.define",
32
- version: "1.0.0"
33
- },
34
- {
35
- key: "metric.update",
36
- version: "1.0.0"
37
- },
38
- {
39
- key: "metric.delete",
40
- version: "1.0.0"
41
- },
42
- {
43
- key: "metric.get",
44
- version: "1.0.0"
45
- },
46
- {
47
- key: "metric.list",
48
- version: "1.0.0"
49
- },
50
- {
51
- key: "usage.record",
52
- version: "1.0.0"
53
- },
54
- {
55
- key: "usage.recordBatch",
56
- version: "1.0.0"
57
- },
58
- {
59
- key: "usage.get",
60
- version: "1.0.0"
61
- },
62
- {
63
- key: "usage.getSummary",
64
- version: "1.0.0"
65
- },
66
- {
67
- key: "threshold.create",
68
- version: "1.0.0"
69
- },
70
- {
71
- key: "threshold.update",
72
- version: "1.0.0"
73
- },
74
- {
75
- key: "threshold.delete",
76
- version: "1.0.0"
77
- },
78
- {
79
- key: "threshold.list",
80
- version: "1.0.0"
81
- }
82
- ],
83
- events: [
84
- {
85
- key: "metric.defined",
86
- version: "1.0.0"
87
- },
88
- {
89
- key: "metric.updated",
90
- version: "1.0.0"
91
- },
92
- {
93
- key: "usage.recorded",
94
- version: "1.0.0"
95
- },
96
- {
97
- key: "usage.batch_recorded",
98
- version: "1.0.0"
99
- },
100
- {
101
- key: "usage.aggregated",
102
- version: "1.0.0"
103
- },
104
- {
105
- key: "threshold.created",
106
- version: "1.0.0"
107
- },
108
- {
109
- key: "threshold.exceeded",
110
- version: "1.0.0"
111
- },
112
- {
113
- key: "threshold.approaching",
114
- version: "1.0.0"
115
- }
116
- ],
117
- presentations: [],
118
- opToPresentation: [],
119
- presentationsTargets: [],
120
- capabilities: {
121
- provides: [{
122
- key: "metering",
123
- version: "1.0.0"
124
- }, {
125
- key: "thresholds",
126
- version: "1.0.0"
127
- }],
128
- requires: []
129
- }
4
+ var MeteringFeature = defineFeature({
5
+ meta: {
6
+ key: "metrics",
7
+ version: "1.0.0",
8
+ title: "Usage Metering",
9
+ description: "Usage metering, metric definitions, and threshold alerting",
10
+ domain: "platform",
11
+ owners: ["@platform.metering"],
12
+ tags: ["metering", "usage", "billing", "thresholds"],
13
+ stability: "stable"
14
+ },
15
+ operations: [
16
+ { key: "metric.define", version: "1.0.0" },
17
+ { key: "metric.update", version: "1.0.0" },
18
+ { key: "metric.delete", version: "1.0.0" },
19
+ { key: "metric.get", version: "1.0.0" },
20
+ { key: "metric.list", version: "1.0.0" },
21
+ { key: "usage.record", version: "1.0.0" },
22
+ { key: "usage.recordBatch", version: "1.0.0" },
23
+ { key: "usage.get", version: "1.0.0" },
24
+ { key: "usage.getSummary", version: "1.0.0" },
25
+ { key: "threshold.create", version: "1.0.0" },
26
+ { key: "threshold.update", version: "1.0.0" },
27
+ { key: "threshold.delete", version: "1.0.0" },
28
+ { key: "threshold.list", version: "1.0.0" }
29
+ ],
30
+ events: [
31
+ { key: "metric.defined", version: "1.0.0" },
32
+ { key: "metric.updated", version: "1.0.0" },
33
+ { key: "usage.recorded", version: "1.0.0" },
34
+ { key: "usage.batch_recorded", version: "1.0.0" },
35
+ { key: "usage.aggregated", version: "1.0.0" },
36
+ { key: "threshold.created", version: "1.0.0" },
37
+ { key: "threshold.exceeded", version: "1.0.0" },
38
+ { key: "threshold.approaching", version: "1.0.0" }
39
+ ],
40
+ presentations: [],
41
+ opToPresentation: [],
42
+ presentationsTargets: [],
43
+ capabilities: {
44
+ provides: [
45
+ { key: "metering", version: "1.0.0" },
46
+ { key: "thresholds", version: "1.0.0" }
47
+ ],
48
+ requires: []
49
+ }
130
50
  });
131
-
132
- //#endregion
133
- export { MeteringFeature };
134
- //# sourceMappingURL=metering.feature.js.map
51
+ export {
52
+ MeteringFeature
53
+ };
@@ -0,0 +1,265 @@
1
+ // src/aggregation/index.ts
2
+ function getPeriodStart(date, periodType) {
3
+ const d = new Date(date);
4
+ switch (periodType) {
5
+ case "HOURLY":
6
+ d.setMinutes(0, 0, 0);
7
+ return d;
8
+ case "DAILY":
9
+ d.setHours(0, 0, 0, 0);
10
+ return d;
11
+ case "WEEKLY": {
12
+ d.setHours(0, 0, 0, 0);
13
+ const day = d.getDay();
14
+ d.setDate(d.getDate() - day);
15
+ return d;
16
+ }
17
+ case "MONTHLY":
18
+ d.setHours(0, 0, 0, 0);
19
+ d.setDate(1);
20
+ return d;
21
+ case "YEARLY":
22
+ d.setHours(0, 0, 0, 0);
23
+ d.setMonth(0, 1);
24
+ return d;
25
+ }
26
+ }
27
+ function getPeriodEnd(date, periodType) {
28
+ const start = getPeriodStart(date, periodType);
29
+ switch (periodType) {
30
+ case "HOURLY":
31
+ return new Date(start.getTime() + 60 * 60 * 1000);
32
+ case "DAILY":
33
+ return new Date(start.getTime() + 24 * 60 * 60 * 1000);
34
+ case "WEEKLY":
35
+ return new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000);
36
+ case "MONTHLY": {
37
+ const end = new Date(start);
38
+ end.setMonth(end.getMonth() + 1);
39
+ return end;
40
+ }
41
+ case "YEARLY": {
42
+ const end = new Date(start);
43
+ end.setFullYear(end.getFullYear() + 1);
44
+ return end;
45
+ }
46
+ }
47
+ }
48
+ function formatPeriodKey(date, periodType) {
49
+ const start = getPeriodStart(date, periodType);
50
+ const year = start.getFullYear();
51
+ const month = String(start.getMonth() + 1).padStart(2, "0");
52
+ const day = String(start.getDate()).padStart(2, "0");
53
+ const hour = String(start.getHours()).padStart(2, "0");
54
+ switch (periodType) {
55
+ case "HOURLY":
56
+ return `${year}-${month}-${day}T${hour}`;
57
+ case "DAILY":
58
+ return `${year}-${month}-${day}`;
59
+ case "WEEKLY":
60
+ return `${year}-W${getWeekNumber(start)}`;
61
+ case "MONTHLY":
62
+ return `${year}-${month}`;
63
+ case "YEARLY":
64
+ return `${year}`;
65
+ }
66
+ }
67
+ function getWeekNumber(date) {
68
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
69
+ const dayNum = d.getUTCDay() || 7;
70
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
71
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
72
+ const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
73
+ return String(weekNum).padStart(2, "0");
74
+ }
75
+
76
+ class UsageAggregator {
77
+ storage;
78
+ batchSize;
79
+ constructor(options) {
80
+ this.storage = options.storage;
81
+ this.batchSize = options.batchSize || 1000;
82
+ }
83
+ async aggregate(params) {
84
+ const { periodType, periodStart, metricKey } = params;
85
+ const periodEnd = params.periodEnd || getPeriodEnd(periodStart, periodType);
86
+ const result = {
87
+ periodType,
88
+ periodStart,
89
+ periodEnd,
90
+ recordsProcessed: 0,
91
+ summariesCreated: 0,
92
+ summariesUpdated: 0,
93
+ errors: []
94
+ };
95
+ const records = await this.storage.getUnaggregatedRecords({
96
+ metricKey,
97
+ periodStart,
98
+ periodEnd,
99
+ limit: this.batchSize
100
+ });
101
+ if (records.length === 0) {
102
+ return result;
103
+ }
104
+ const groups = this.groupRecords(records, periodType);
105
+ for (const [groupKey, groupRecords] of groups.entries()) {
106
+ try {
107
+ await this.aggregateGroup(groupKey, groupRecords, periodType, result);
108
+ } catch (error) {
109
+ const [metricKey2, subjectType, subjectId] = groupKey.split("::");
110
+ result.errors.push({
111
+ metricKey: metricKey2 ?? "unknown",
112
+ subjectType: subjectType ?? "unknown",
113
+ subjectId: subjectId ?? "unknown",
114
+ error: error instanceof Error ? error.message : String(error)
115
+ });
116
+ }
117
+ }
118
+ const recordIds = records.map((r) => r.id);
119
+ await this.storage.markRecordsAggregated(recordIds, new Date);
120
+ result.recordsProcessed = records.length;
121
+ return result;
122
+ }
123
+ groupRecords(records, periodType) {
124
+ const groups = new Map;
125
+ for (const record of records) {
126
+ const periodKey = formatPeriodKey(record.timestamp, periodType);
127
+ const groupKey = `${record.metricKey}::${record.subjectType}::${record.subjectId}::${periodKey}`;
128
+ const existing = groups.get(groupKey) || [];
129
+ existing.push(record);
130
+ groups.set(groupKey, existing);
131
+ }
132
+ return groups;
133
+ }
134
+ async aggregateGroup(groupKey, records, periodType, result) {
135
+ const [metricKey, subjectType, subjectId] = groupKey.split("::");
136
+ if (!metricKey || !subjectType || !subjectId || records.length === 0) {
137
+ return;
138
+ }
139
+ const firstRecord = records[0];
140
+ if (!firstRecord)
141
+ return;
142
+ const periodStart = getPeriodStart(firstRecord.timestamp, periodType);
143
+ const periodEnd = getPeriodEnd(firstRecord.timestamp, periodType);
144
+ const metric = await this.storage.getMetric(metricKey);
145
+ const aggregationType = metric?.aggregationType || "SUM";
146
+ const quantities = records.map((r) => r.quantity);
147
+ const aggregated = this.calculateAggregation(quantities, aggregationType);
148
+ await this.storage.upsertSummary({
149
+ metricKey,
150
+ subjectType,
151
+ subjectId,
152
+ periodType,
153
+ periodStart,
154
+ periodEnd,
155
+ totalQuantity: aggregated.total,
156
+ recordCount: records.length,
157
+ minQuantity: aggregated.min,
158
+ maxQuantity: aggregated.max,
159
+ avgQuantity: aggregated.avg
160
+ });
161
+ result.summariesCreated++;
162
+ }
163
+ calculateAggregation(quantities, aggregationType) {
164
+ if (quantities.length === 0) {
165
+ return { total: 0, min: 0, max: 0, avg: 0 };
166
+ }
167
+ const min = Math.min(...quantities);
168
+ const max = Math.max(...quantities);
169
+ const sum = quantities.reduce((a, b) => a + b, 0);
170
+ const avg = sum / quantities.length;
171
+ const count = quantities.length;
172
+ let total;
173
+ switch (aggregationType) {
174
+ case "COUNT":
175
+ total = count;
176
+ break;
177
+ case "SUM":
178
+ total = sum;
179
+ break;
180
+ case "AVG":
181
+ total = avg;
182
+ break;
183
+ case "MAX":
184
+ total = max;
185
+ break;
186
+ case "MIN":
187
+ total = min;
188
+ break;
189
+ case "LAST":
190
+ total = quantities[quantities.length - 1] ?? 0;
191
+ break;
192
+ default:
193
+ total = sum;
194
+ }
195
+ return { total, min, max, avg };
196
+ }
197
+ }
198
+
199
+ class InMemoryUsageStorage {
200
+ records = [];
201
+ summaries = new Map;
202
+ metrics = new Map;
203
+ addRecord(record) {
204
+ this.records.push(record);
205
+ }
206
+ addMetric(metric) {
207
+ this.metrics.set(metric.key, metric);
208
+ }
209
+ async getUnaggregatedRecords(options) {
210
+ let records = this.records.filter((r) => {
211
+ const inPeriod = r.timestamp >= options.periodStart && r.timestamp < options.periodEnd;
212
+ const matchesMetric = !options.metricKey || r.metricKey === options.metricKey;
213
+ return inPeriod && matchesMetric;
214
+ });
215
+ if (options.limit) {
216
+ records = records.slice(0, options.limit);
217
+ }
218
+ return records;
219
+ }
220
+ async markRecordsAggregated(recordIds) {
221
+ this.records = this.records.filter((r) => !recordIds.includes(r.id));
222
+ }
223
+ async upsertSummary(summary) {
224
+ const key = `${summary.metricKey}::${summary.subjectType}::${summary.subjectId}::${summary.periodType}::${summary.periodStart.toISOString()}`;
225
+ const existing = this.summaries.get(key);
226
+ if (existing) {
227
+ existing.totalQuantity += summary.totalQuantity;
228
+ existing.recordCount += summary.recordCount;
229
+ if (summary.minQuantity !== undefined) {
230
+ existing.minQuantity = Math.min(existing.minQuantity ?? Infinity, summary.minQuantity);
231
+ }
232
+ if (summary.maxQuantity !== undefined) {
233
+ existing.maxQuantity = Math.max(existing.maxQuantity ?? -Infinity, summary.maxQuantity);
234
+ }
235
+ return existing;
236
+ }
237
+ const newSummary = {
238
+ id: `summary-${Date.now()}-${Math.random().toString(36).slice(2)}`,
239
+ ...summary
240
+ };
241
+ this.summaries.set(key, newSummary);
242
+ return newSummary;
243
+ }
244
+ async getMetric(key) {
245
+ return this.metrics.get(key) || null;
246
+ }
247
+ async listMetrics() {
248
+ return Array.from(this.metrics.values());
249
+ }
250
+ getSummaries() {
251
+ return Array.from(this.summaries.values());
252
+ }
253
+ clear() {
254
+ this.records = [];
255
+ this.summaries.clear();
256
+ this.metrics.clear();
257
+ }
258
+ }
259
+ export {
260
+ getPeriodStart,
261
+ getPeriodEnd,
262
+ formatPeriodKey,
263
+ UsageAggregator,
264
+ InMemoryUsageStorage
265
+ };