@contractspec/lib.metering 3.7.16 → 3.7.18

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 (34) hide show
  1. package/dist/aggregation/index.js +1 -265
  2. package/dist/analytics/posthog-metering-reader.js +4 -266
  3. package/dist/analytics/posthog-metering.js +1 -45
  4. package/dist/browser/aggregation/index.js +1 -265
  5. package/dist/browser/analytics/posthog-metering-reader.js +4 -266
  6. package/dist/browser/analytics/posthog-metering.js +1 -45
  7. package/dist/browser/contracts/index.js +1 -617
  8. package/dist/browser/docs/index.js +4 -18
  9. package/dist/browser/docs/metering.docblock.js +4 -18
  10. package/dist/browser/entities/index.js +1 -350
  11. package/dist/browser/events.js +1 -269
  12. package/dist/browser/index.js +7 -1877
  13. package/dist/browser/metering.capability.js +1 -31
  14. package/dist/browser/metering.feature.js +1 -53
  15. package/dist/contracts/index.js +1 -617
  16. package/dist/docs/index.js +4 -18
  17. package/dist/docs/metering.docblock.js +4 -18
  18. package/dist/entities/index.js +1 -350
  19. package/dist/events.js +1 -269
  20. package/dist/index.js +7 -1877
  21. package/dist/metering.capability.js +1 -31
  22. package/dist/metering.feature.js +1 -53
  23. package/dist/node/aggregation/index.js +1 -265
  24. package/dist/node/analytics/posthog-metering-reader.js +4 -266
  25. package/dist/node/analytics/posthog-metering.js +1 -45
  26. package/dist/node/contracts/index.js +1 -617
  27. package/dist/node/docs/index.js +4 -18
  28. package/dist/node/docs/metering.docblock.js +4 -18
  29. package/dist/node/entities/index.js +1 -350
  30. package/dist/node/events.js +1 -269
  31. package/dist/node/index.js +7 -1877
  32. package/dist/node/metering.capability.js +1 -31
  33. package/dist/node/metering.feature.js +1 -53
  34. package/package.json +8 -8
@@ -1,1180 +1,7 @@
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
-
260
- // src/analytics/posthog-metering-reader.ts
261
- class PosthogMeteringReader {
262
- reader;
263
- eventPrefix;
264
- constructor(reader, options = {}) {
265
- this.reader = reader;
266
- this.eventPrefix = options.eventPrefix ?? "metering";
267
- }
268
- async getUsageByMetric(input) {
269
- const result = await this.queryHogQL({
270
- query: [
271
- "select",
272
- " properties.recordId as recordId,",
273
- " properties.metricKey as metricKey,",
274
- " properties.subjectType as subjectType,",
275
- " distinct_id as subjectId,",
276
- " properties.quantity as quantity,",
277
- " properties.source as source,",
278
- " timestamp as timestamp",
279
- "from events",
280
- `where ${buildUsageWhereClause(this.eventPrefix, input)}`,
281
- "order by timestamp desc",
282
- `limit ${input.limit ?? 200}`
283
- ].join(`
284
- `),
285
- values: buildUsageValues(input)
286
- });
287
- return mapUsageRecords(result);
288
- }
289
- async getUsageSummary(input) {
290
- const result = await this.queryHogQL({
291
- query: [
292
- "select",
293
- " properties.summaryId as summaryId,",
294
- " properties.metricKey as metricKey,",
295
- " properties.subjectType as subjectType,",
296
- " distinct_id as subjectId,",
297
- " properties.periodType as periodType,",
298
- " properties.periodStart as periodStart,",
299
- " properties.periodEnd as periodEnd,",
300
- " properties.totalQuantity as totalQuantity,",
301
- " properties.recordCount as recordCount,",
302
- " timestamp as aggregatedAt",
303
- "from events",
304
- `where ${buildSummaryWhereClause(this.eventPrefix, input)}`,
305
- "order by timestamp desc",
306
- `limit ${input.limit ?? 200}`
307
- ].join(`
308
- `),
309
- values: buildSummaryValues(input)
310
- });
311
- return mapUsageSummaries(result);
312
- }
313
- async getUsageTrend(input) {
314
- const result = await this.queryHogQL({
315
- query: [
316
- "select",
317
- ` ${bucketExpression(input.bucket)} as bucketStart,`,
318
- " sum(properties.quantity) as totalQuantity,",
319
- " count() as recordCount",
320
- "from events",
321
- `where ${buildUsageWhereClause(this.eventPrefix, input)}`,
322
- "group by bucketStart",
323
- "order by bucketStart asc"
324
- ].join(`
325
- `),
326
- values: buildUsageValues(input)
327
- });
328
- return mapUsageTrend(result);
329
- }
330
- async queryHogQL(input) {
331
- if (!this.reader.queryHogQL) {
332
- throw new Error("Analytics reader does not support HogQL queries.");
333
- }
334
- return this.reader.queryHogQL(input);
335
- }
336
- }
337
- function buildUsageWhereClause(eventPrefix, input) {
338
- const clauses = [
339
- `event = '${eventPrefix}.usage_recorded'`,
340
- `properties.metricKey = {metricKey}`
341
- ];
342
- if (input.subjectId) {
343
- clauses.push("distinct_id = {subjectId}");
344
- }
345
- if (input.dateRange?.from) {
346
- clauses.push("timestamp >= {dateFrom}");
347
- }
348
- if (input.dateRange?.to) {
349
- clauses.push("timestamp < {dateTo}");
350
- }
351
- return clauses.join(" and ");
352
- }
353
- function buildSummaryWhereClause(eventPrefix, input) {
354
- const clauses = [
355
- `event = '${eventPrefix}.usage_aggregated'`,
356
- `properties.metricKey = {metricKey}`
357
- ];
358
- if (input.subjectId) {
359
- clauses.push("distinct_id = {subjectId}");
360
- }
361
- if (input.periodType) {
362
- clauses.push("properties.periodType = {periodType}");
363
- }
364
- if (input.dateRange?.from) {
365
- clauses.push("timestamp >= {dateFrom}");
366
- }
367
- if (input.dateRange?.to) {
368
- clauses.push("timestamp < {dateTo}");
369
- }
370
- return clauses.join(" and ");
371
- }
372
- function buildUsageValues(input) {
373
- return {
374
- metricKey: input.metricKey,
375
- subjectId: input.subjectId,
376
- dateFrom: toIsoString(input.dateRange?.from),
377
- dateTo: toIsoString(input.dateRange?.to)
378
- };
379
- }
380
- function buildSummaryValues(input) {
381
- return {
382
- metricKey: input.metricKey,
383
- subjectId: input.subjectId,
384
- periodType: input.periodType,
385
- dateFrom: toIsoString(input.dateRange?.from),
386
- dateTo: toIsoString(input.dateRange?.to)
387
- };
388
- }
389
- function bucketExpression(bucket) {
390
- switch (bucket) {
391
- case "hour":
392
- return "toStartOfHour(timestamp)";
393
- case "week":
394
- return "toStartOfWeek(timestamp)";
395
- case "month":
396
- return "toStartOfMonth(timestamp)";
397
- case "day":
398
- default:
399
- return "toStartOfDay(timestamp)";
400
- }
401
- }
402
- function mapUsageRecords(result) {
403
- const rows = mapRows(result);
404
- return rows.flatMap((row) => {
405
- const metricKey = asString(row.metricKey);
406
- const subjectType = asString(row.subjectType);
407
- const subjectId = asString(row.subjectId);
408
- const timestamp = asDate(row.timestamp);
409
- if (!metricKey || !subjectType || !subjectId || !timestamp) {
410
- return [];
411
- }
412
- return [
413
- {
414
- recordId: asOptionalString(row.recordId),
415
- metricKey,
416
- subjectType,
417
- subjectId,
418
- quantity: asNumber(row.quantity),
419
- source: asOptionalString(row.source),
420
- timestamp
421
- }
422
- ];
423
- });
424
- }
425
- function mapUsageSummaries(result) {
426
- const rows = mapRows(result);
427
- return rows.flatMap((row) => {
428
- const metricKey = asString(row.metricKey);
429
- const subjectType = asString(row.subjectType);
430
- const subjectId = asString(row.subjectId);
431
- const periodType = asString(row.periodType);
432
- const periodStart = asDate(row.periodStart);
433
- const periodEnd = asDate(row.periodEnd);
434
- const aggregatedAt = asDate(row.aggregatedAt);
435
- if (!metricKey || !subjectType || !subjectId || !periodType || !periodStart || !periodEnd || !aggregatedAt) {
436
- return [];
437
- }
438
- return [
439
- {
440
- summaryId: asOptionalString(row.summaryId),
441
- metricKey,
442
- subjectType,
443
- subjectId,
444
- periodType,
445
- periodStart,
446
- periodEnd,
447
- totalQuantity: asNumber(row.totalQuantity),
448
- recordCount: asNumber(row.recordCount),
449
- aggregatedAt
450
- }
451
- ];
452
- });
453
- }
454
- function mapUsageTrend(result) {
455
- const rows = mapRows(result);
456
- return rows.flatMap((row) => {
457
- const bucketStart = asString(row.bucketStart);
458
- if (!bucketStart)
459
- return [];
460
- return [
461
- {
462
- bucketStart,
463
- totalQuantity: asNumber(row.totalQuantity),
464
- recordCount: asNumber(row.recordCount)
465
- }
466
- ];
467
- });
468
- }
469
- function mapRows(result) {
470
- if (!Array.isArray(result.results) || !Array.isArray(result.columns)) {
471
- return [];
472
- }
473
- const columns = result.columns;
474
- return result.results.flatMap((row) => {
475
- if (!Array.isArray(row))
476
- return [];
477
- const record = {};
478
- columns.forEach((column, index) => {
479
- record[column] = row[index];
480
- });
481
- return [record];
482
- });
483
- }
484
- function asString(value) {
485
- if (typeof value === "string" && value.trim())
486
- return value;
487
- if (typeof value === "number")
488
- return String(value);
489
- return null;
490
- }
491
- function asOptionalString(value) {
492
- if (typeof value === "string")
493
- return value;
494
- if (typeof value === "number")
495
- return String(value);
496
- return;
497
- }
498
- function asNumber(value) {
499
- if (typeof value === "number" && Number.isFinite(value))
500
- return value;
501
- if (typeof value === "string" && value.trim()) {
502
- const parsed = Number(value);
503
- if (Number.isFinite(parsed))
504
- return parsed;
505
- }
506
- return 0;
507
- }
508
- function asDate(value) {
509
- if (value instanceof Date)
510
- return value;
511
- if (typeof value === "string" || typeof value === "number") {
512
- const date = new Date(value);
513
- if (!Number.isNaN(date.getTime()))
514
- return date;
515
- }
516
- return null;
517
- }
518
- function toIsoString(value) {
519
- if (!value)
520
- return;
521
- return typeof value === "string" ? value : value.toISOString();
522
- }
523
-
524
- // src/analytics/posthog-metering.ts
525
- class PosthogMeteringReporter {
526
- provider;
527
- eventPrefix;
528
- includeSource;
529
- constructor(provider, options = {}) {
530
- this.provider = provider;
531
- this.eventPrefix = options.eventPrefix ?? "metering";
532
- this.includeSource = options.includeSource ?? true;
533
- }
534
- async captureUsageRecorded(record) {
535
- await this.provider.capture({
536
- distinctId: record.subjectId,
537
- event: `${this.eventPrefix}.usage_recorded`,
538
- timestamp: record.timestamp,
539
- properties: {
540
- recordId: record.recordId ?? null,
541
- metricKey: record.metricKey,
542
- subjectType: record.subjectType,
543
- quantity: record.quantity,
544
- ...this.includeSource && record.source ? { source: record.source } : {}
545
- }
546
- });
547
- }
548
- async captureUsageAggregated(summary) {
549
- await this.provider.capture({
550
- distinctId: summary.subjectId,
551
- event: `${this.eventPrefix}.usage_aggregated`,
552
- timestamp: summary.aggregatedAt,
553
- properties: {
554
- summaryId: summary.summaryId ?? null,
555
- metricKey: summary.metricKey,
556
- subjectType: summary.subjectType,
557
- periodType: summary.periodType,
558
- periodStart: summary.periodStart.toISOString(),
559
- periodEnd: summary.periodEnd.toISOString(),
560
- totalQuantity: summary.totalQuantity,
561
- recordCount: summary.recordCount
562
- }
563
- });
564
- }
565
- }
566
-
567
- // src/contracts/index.ts
568
- import { defineCommand, defineQuery } from "@contractspec/lib.contracts-spec";
569
- import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
570
- var OWNERS = ["platform.metering"];
571
- var MetricDefinitionModel = defineSchemaModel({
572
- name: "MetricDefinition",
573
- description: "Represents a metric definition",
574
- fields: {
575
- id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
576
- key: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
577
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
578
- description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
579
- unit: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
580
- aggregationType: {
581
- type: ScalarTypeEnum.String_unsecure(),
582
- isOptional: false
583
- },
584
- resetPeriod: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
585
- category: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
586
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: false },
587
- orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
588
- createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
589
- updatedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
590
- }
591
- });
592
- var UsageRecordModel = defineSchemaModel({
593
- name: "UsageRecord",
594
- description: "Represents a usage record",
595
- fields: {
596
- id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
597
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
598
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
599
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
600
- quantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
601
- source: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
602
- resourceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
603
- resourceType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
604
- metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
605
- timestamp: { type: ScalarTypeEnum.DateTime(), isOptional: false },
606
- createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
607
- }
608
- });
609
- var UsageSummaryModel = defineSchemaModel({
610
- name: "UsageSummary",
611
- description: "Represents aggregated usage",
612
- fields: {
613
- id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
614
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
615
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
616
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
617
- periodType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
618
- periodStart: { type: ScalarTypeEnum.DateTime(), isOptional: false },
619
- periodEnd: { type: ScalarTypeEnum.DateTime(), isOptional: false },
620
- totalQuantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
621
- recordCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
622
- minQuantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
623
- maxQuantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
624
- avgQuantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true }
625
- }
626
- });
627
- var UsageThresholdModel = defineSchemaModel({
628
- name: "UsageThreshold",
629
- description: "Represents a usage threshold",
630
- fields: {
631
- id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
632
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
633
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
634
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
635
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
636
- threshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
637
- warnThreshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
638
- periodType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
639
- action: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
640
- currentValue: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
641
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: false },
642
- createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
643
- }
644
- });
645
- var DefineMetricInput = defineSchemaModel({
646
- name: "DefineMetricInput",
647
- description: "Input for defining a metric",
648
- fields: {
649
- key: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
650
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
651
- description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
652
- unit: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
653
- aggregationType: {
654
- type: ScalarTypeEnum.String_unsecure(),
655
- isOptional: true
656
- },
657
- resetPeriod: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
658
- category: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
659
- orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
660
- metadata: { type: ScalarTypeEnum.JSON(), isOptional: true }
661
- }
662
- });
663
- var UpdateMetricInput = defineSchemaModel({
664
- name: "UpdateMetricInput",
665
- description: "Input for updating a metric",
666
- fields: {
667
- metricId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
668
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
669
- description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
670
- category: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
671
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: true },
672
- metadata: { type: ScalarTypeEnum.JSON(), isOptional: true }
673
- }
674
- });
675
- var DeleteMetricInput = defineSchemaModel({
676
- name: "DeleteMetricInput",
677
- description: "Input for deleting a metric",
678
- fields: {
679
- metricId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
680
- }
681
- });
682
- var GetMetricInput = defineSchemaModel({
683
- name: "GetMetricInput",
684
- description: "Input for getting a metric",
685
- fields: {
686
- key: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
687
- orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
688
- }
689
- });
690
- var ListMetricsInput = defineSchemaModel({
691
- name: "ListMetricsInput",
692
- description: "Input for listing metrics",
693
- fields: {
694
- orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
695
- category: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
696
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: true },
697
- limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
698
- offset: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
699
- }
700
- });
701
- var ListMetricsOutput = defineSchemaModel({
702
- name: "ListMetricsOutput",
703
- description: "Output for listing metrics",
704
- fields: {
705
- metrics: { type: MetricDefinitionModel, isArray: true, isOptional: false },
706
- total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
707
- }
708
- });
709
- var RecordUsageInput = defineSchemaModel({
710
- name: "RecordUsageInput",
711
- description: "Input for recording usage",
712
- fields: {
713
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
714
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
715
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
716
- quantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
717
- timestamp: { type: ScalarTypeEnum.DateTime(), isOptional: true },
718
- source: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
719
- resourceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
720
- resourceType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
721
- metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
722
- idempotencyKey: {
723
- type: ScalarTypeEnum.String_unsecure(),
724
- isOptional: true
725
- }
726
- }
727
- });
728
- var RecordBatchUsageInput = defineSchemaModel({
729
- name: "RecordBatchUsageInput",
730
- description: "Input for recording batch usage",
731
- fields: {
732
- records: { type: RecordUsageInput, isArray: true, isOptional: false }
733
- }
734
- });
735
- var RecordBatchUsageOutput = defineSchemaModel({
736
- name: "RecordBatchUsageOutput",
737
- description: "Output for recording batch usage",
738
- fields: {
739
- recordedCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
740
- skippedCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
741
- recordIds: { type: ScalarTypeEnum.JSON(), isOptional: false }
742
- }
743
- });
744
- var GetUsageInput = defineSchemaModel({
745
- name: "GetUsageInput",
746
- description: "Input for getting usage",
747
- fields: {
748
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
749
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
750
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
751
- startDate: { type: ScalarTypeEnum.DateTime(), isOptional: false },
752
- endDate: { type: ScalarTypeEnum.DateTime(), isOptional: false },
753
- limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
754
- offset: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
755
- }
756
- });
757
- var GetUsageOutput = defineSchemaModel({
758
- name: "GetUsageOutput",
759
- description: "Output for getting usage",
760
- fields: {
761
- records: { type: UsageRecordModel, isArray: true, isOptional: false },
762
- total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
763
- totalQuantity: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false }
764
- }
765
- });
766
- var GetUsageSummaryInput = defineSchemaModel({
767
- name: "GetUsageSummaryInput",
768
- description: "Input for getting usage summary",
769
- fields: {
770
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
771
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
772
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
773
- periodType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
774
- startDate: { type: ScalarTypeEnum.DateTime(), isOptional: false },
775
- endDate: { type: ScalarTypeEnum.DateTime(), isOptional: true }
776
- }
777
- });
778
- var GetUsageSummaryOutput = defineSchemaModel({
779
- name: "GetUsageSummaryOutput",
780
- description: "Output for getting usage summary",
781
- fields: {
782
- summaries: { type: UsageSummaryModel, isArray: true, isOptional: false },
783
- total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
784
- }
785
- });
786
- var CreateThresholdInput = defineSchemaModel({
787
- name: "CreateThresholdInput",
788
- description: "Input for creating a threshold",
789
- fields: {
790
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
791
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
792
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
793
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
794
- threshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
795
- warnThreshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
796
- periodType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
797
- action: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
798
- notifyEmails: { type: ScalarTypeEnum.JSON(), isOptional: true },
799
- notifyWebhook: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
800
- }
801
- });
802
- var UpdateThresholdInput = defineSchemaModel({
803
- name: "UpdateThresholdInput",
804
- description: "Input for updating a threshold",
805
- fields: {
806
- thresholdId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
807
- name: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
808
- threshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
809
- warnThreshold: { type: ScalarTypeEnum.Float_unsecure(), isOptional: true },
810
- action: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
811
- notifyEmails: { type: ScalarTypeEnum.JSON(), isOptional: true },
812
- notifyWebhook: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
813
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: true }
814
- }
815
- });
816
- var DeleteThresholdInput = defineSchemaModel({
817
- name: "DeleteThresholdInput",
818
- description: "Input for deleting a threshold",
819
- fields: {
820
- thresholdId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
821
- }
822
- });
823
- var ListThresholdsInput = defineSchemaModel({
824
- name: "ListThresholdsInput",
825
- description: "Input for listing thresholds",
826
- fields: {
827
- metricKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
828
- subjectType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
829
- subjectId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
830
- isActive: { type: ScalarTypeEnum.Boolean(), isOptional: true }
831
- }
832
- });
833
- var ListThresholdsOutput = defineSchemaModel({
834
- name: "ListThresholdsOutput",
835
- description: "Output for listing thresholds",
836
- fields: {
837
- thresholds: { type: UsageThresholdModel, isArray: true, isOptional: false },
838
- total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
839
- }
840
- });
841
- var SuccessOutput = defineSchemaModel({
842
- name: "SuccessOutput",
843
- description: "Generic success output",
844
- fields: {
845
- success: { type: ScalarTypeEnum.Boolean(), isOptional: false }
846
- }
847
- });
848
- var DefineMetricContract = defineCommand({
849
- meta: {
850
- key: "metric.define",
851
- version: "1.0.0",
852
- stability: "stable",
853
- owners: [...OWNERS],
854
- tags: ["metering", "metric", "define"],
855
- description: "Define a new usage metric.",
856
- goal: "Create a new metric for tracking usage.",
857
- context: "Called when setting up metering."
858
- },
859
- io: {
860
- input: DefineMetricInput,
861
- output: MetricDefinitionModel,
862
- errors: {
863
- METRIC_KEY_EXISTS: {
864
- description: "Metric key already exists",
865
- http: 409,
866
- gqlCode: "METRIC_KEY_EXISTS",
867
- when: "A metric with this key already exists"
868
- }
869
- }
870
- },
871
- policy: {
872
- auth: "admin"
873
- }
874
- });
875
- var UpdateMetricContract = defineCommand({
876
- meta: {
877
- key: "metric.update",
878
- version: "1.0.0",
879
- stability: "stable",
880
- owners: [...OWNERS],
881
- tags: ["metering", "metric", "update"],
882
- description: "Update a metric definition.",
883
- goal: "Modify metric configuration.",
884
- context: "Called when updating metric settings."
885
- },
886
- io: {
887
- input: UpdateMetricInput,
888
- output: MetricDefinitionModel,
889
- errors: {
890
- METRIC_NOT_FOUND: {
891
- description: "Metric does not exist",
892
- http: 404,
893
- gqlCode: "METRIC_NOT_FOUND",
894
- when: "Metric ID is invalid"
895
- }
896
- }
897
- },
898
- policy: {
899
- auth: "admin"
900
- }
901
- });
902
- var DeleteMetricContract = defineCommand({
903
- meta: {
904
- key: "metric.delete",
905
- version: "1.0.0",
906
- stability: "stable",
907
- owners: [...OWNERS],
908
- tags: ["metering", "metric", "delete"],
909
- description: "Delete a metric definition.",
910
- goal: "Remove a metric and its data.",
911
- context: "Called when removing a metric."
912
- },
913
- io: {
914
- input: DeleteMetricInput,
915
- output: SuccessOutput,
916
- errors: {
917
- METRIC_NOT_FOUND: {
918
- description: "Metric does not exist",
919
- http: 404,
920
- gqlCode: "METRIC_NOT_FOUND",
921
- when: "Metric ID is invalid"
922
- }
923
- }
924
- },
925
- policy: {
926
- auth: "admin"
927
- }
928
- });
929
- var GetMetricContract = defineQuery({
930
- meta: {
931
- key: "metric.get",
932
- version: "1.0.0",
933
- stability: "stable",
934
- owners: [...OWNERS],
935
- tags: ["metering", "metric", "get"],
936
- description: "Get a metric by key.",
937
- goal: "Retrieve metric definition.",
938
- context: "Called to inspect metric details."
939
- },
940
- io: {
941
- input: GetMetricInput,
942
- output: MetricDefinitionModel,
943
- errors: {
944
- METRIC_NOT_FOUND: {
945
- description: "Metric does not exist",
946
- http: 404,
947
- gqlCode: "METRIC_NOT_FOUND",
948
- when: "Metric key is invalid"
949
- }
950
- }
951
- },
952
- policy: {
953
- auth: "user"
954
- }
955
- });
956
- var ListMetricsContract = defineQuery({
957
- meta: {
958
- key: "metric.list",
959
- version: "1.0.0",
960
- stability: "stable",
961
- owners: [...OWNERS],
962
- tags: ["metering", "metric", "list"],
963
- description: "List all metrics.",
964
- goal: "View configured metrics.",
965
- context: "Called to browse metrics."
966
- },
967
- io: {
968
- input: ListMetricsInput,
969
- output: ListMetricsOutput
970
- },
971
- policy: {
972
- auth: "user"
973
- }
974
- });
975
- var RecordUsageContract = defineCommand({
976
- meta: {
977
- key: "usage.record",
978
- version: "1.0.0",
979
- stability: "stable",
980
- owners: [...OWNERS],
981
- tags: ["metering", "usage", "record"],
982
- description: "Record a usage event.",
983
- goal: "Track usage for billing and monitoring.",
984
- context: "Called when usage occurs."
985
- },
986
- io: {
987
- input: RecordUsageInput,
988
- output: UsageRecordModel,
989
- errors: {
990
- METRIC_NOT_FOUND: {
991
- description: "Metric does not exist",
992
- http: 404,
993
- gqlCode: "METRIC_NOT_FOUND",
994
- when: "Metric key is invalid"
995
- },
996
- DUPLICATE_RECORD: {
997
- description: "Record already exists",
998
- http: 409,
999
- gqlCode: "DUPLICATE_RECORD",
1000
- when: "Idempotency key already used"
1001
- }
1002
- }
1003
- },
1004
- policy: {
1005
- auth: "admin"
1006
- }
1007
- });
1008
- var RecordBatchUsageContract = defineCommand({
1009
- meta: {
1010
- key: "usage.recordBatch",
1011
- version: "1.0.0",
1012
- stability: "stable",
1013
- owners: [...OWNERS],
1014
- tags: ["metering", "usage", "batch"],
1015
- description: "Record multiple usage events.",
1016
- goal: "Efficiently track bulk usage.",
1017
- context: "Called for batch processing."
1018
- },
1019
- io: {
1020
- input: RecordBatchUsageInput,
1021
- output: RecordBatchUsageOutput
1022
- },
1023
- policy: {
1024
- auth: "admin"
1025
- }
1026
- });
1027
- var GetUsageContract = defineQuery({
1028
- meta: {
1029
- key: "usage.get",
1030
- version: "1.0.0",
1031
- stability: "stable",
1032
- owners: [...OWNERS],
1033
- tags: ["metering", "usage", "get"],
1034
- description: "Get usage records for a subject.",
1035
- goal: "View detailed usage history.",
1036
- context: "Called to analyze usage."
1037
- },
1038
- io: {
1039
- input: GetUsageInput,
1040
- output: GetUsageOutput
1041
- },
1042
- policy: {
1043
- auth: "user"
1044
- }
1045
- });
1046
- var GetUsageSummaryContract = defineQuery({
1047
- meta: {
1048
- key: "usage.getSummary",
1049
- version: "1.0.0",
1050
- stability: "stable",
1051
- owners: [...OWNERS],
1052
- tags: ["metering", "usage", "summary"],
1053
- description: "Get aggregated usage summary.",
1054
- goal: "View usage totals for billing.",
1055
- context: "Called for billing and reporting."
1056
- },
1057
- io: {
1058
- input: GetUsageSummaryInput,
1059
- output: GetUsageSummaryOutput
1060
- },
1061
- policy: {
1062
- auth: "user"
1063
- }
1064
- });
1065
- var CreateThresholdContract = defineCommand({
1066
- meta: {
1067
- key: "threshold.create",
1068
- version: "1.0.0",
1069
- stability: "stable",
1070
- owners: [...OWNERS],
1071
- tags: ["metering", "threshold", "create"],
1072
- description: "Create a usage threshold.",
1073
- goal: "Set up usage limits and alerts.",
1074
- context: "Called when configuring limits."
1075
- },
1076
- io: {
1077
- input: CreateThresholdInput,
1078
- output: UsageThresholdModel,
1079
- errors: {
1080
- METRIC_NOT_FOUND: {
1081
- description: "Metric does not exist",
1082
- http: 404,
1083
- gqlCode: "METRIC_NOT_FOUND",
1084
- when: "Metric key is invalid"
1085
- }
1086
- }
1087
- },
1088
- policy: {
1089
- auth: "admin"
1090
- }
1091
- });
1092
- var UpdateThresholdContract = defineCommand({
1093
- meta: {
1094
- key: "threshold.update",
1095
- version: "1.0.0",
1096
- stability: "stable",
1097
- owners: [...OWNERS],
1098
- tags: ["metering", "threshold", "update"],
1099
- description: "Update a threshold.",
1100
- goal: "Modify threshold configuration.",
1101
- context: "Called when adjusting limits."
1102
- },
1103
- io: {
1104
- input: UpdateThresholdInput,
1105
- output: UsageThresholdModel,
1106
- errors: {
1107
- THRESHOLD_NOT_FOUND: {
1108
- description: "Threshold does not exist",
1109
- http: 404,
1110
- gqlCode: "THRESHOLD_NOT_FOUND",
1111
- when: "Threshold ID is invalid"
1112
- }
1113
- }
1114
- },
1115
- policy: {
1116
- auth: "admin"
1117
- }
1118
- });
1119
- var DeleteThresholdContract = defineCommand({
1120
- meta: {
1121
- key: "threshold.delete",
1122
- version: "1.0.0",
1123
- stability: "stable",
1124
- owners: [...OWNERS],
1125
- tags: ["metering", "threshold", "delete"],
1126
- description: "Delete a threshold.",
1127
- goal: "Remove a usage threshold.",
1128
- context: "Called when removing limits."
1129
- },
1130
- io: {
1131
- input: DeleteThresholdInput,
1132
- output: SuccessOutput,
1133
- errors: {
1134
- THRESHOLD_NOT_FOUND: {
1135
- description: "Threshold does not exist",
1136
- http: 404,
1137
- gqlCode: "THRESHOLD_NOT_FOUND",
1138
- when: "Threshold ID is invalid"
1139
- }
1140
- }
1141
- },
1142
- policy: {
1143
- auth: "admin"
1144
- }
1145
- });
1146
- var ListThresholdsContract = defineQuery({
1147
- meta: {
1148
- key: "threshold.list",
1149
- version: "1.0.0",
1150
- stability: "stable",
1151
- owners: [...OWNERS],
1152
- tags: ["metering", "threshold", "list"],
1153
- description: "List usage thresholds.",
1154
- goal: "View configured limits.",
1155
- context: "Called to browse thresholds."
1156
- },
1157
- io: {
1158
- input: ListThresholdsInput,
1159
- output: ListThresholdsOutput
1160
- },
1161
- policy: {
1162
- auth: "user"
1163
- }
1164
- });
1165
-
1166
- // src/docs/metering.docblock.ts
1167
- import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
1168
- var meteringDocBlocks = [
1169
- {
1170
- id: "docs.metering.usage",
1171
- title: "Usage Metering & Billing Core",
1172
- summary: "Reusable usage/metering layer with metric definitions, usage ingestion, aggregation, thresholds, and alerts for billing or quotas.",
1173
- kind: "reference",
1174
- visibility: "public",
1175
- route: "/docs/metering/usage",
1176
- tags: ["metering", "usage", "billing", "quotas"],
1177
- body: `## Capabilities
1
+ function Z(t,I){let g=new Date(t);switch(I){case"HOURLY":return g.setMinutes(0,0,0),g;case"DAILY":return g.setHours(0,0,0,0),g;case"WEEKLY":{g.setHours(0,0,0,0);let r=g.getDay();return g.setDate(g.getDate()-r),g}case"MONTHLY":return g.setHours(0,0,0,0),g.setDate(1),g;case"YEARLY":return g.setHours(0,0,0,0),g.setMonth(0,1),g}}function N(t,I){let g=Z(t,I);switch(I){case"HOURLY":return new Date(g.getTime()+3600000);case"DAILY":return new Date(g.getTime()+86400000);case"WEEKLY":return new Date(g.getTime()+604800000);case"MONTHLY":{let r=new Date(g);return r.setMonth(r.getMonth()+1),r}case"YEARLY":{let r=new Date(g);return r.setFullYear(r.getFullYear()+1),r}}}function e(t,I){let g=Z(t,I),r=g.getFullYear(),o=String(g.getMonth()+1).padStart(2,"0"),A=String(g.getDate()).padStart(2,"0"),Q=String(g.getHours()).padStart(2,"0");switch(I){case"HOURLY":return`${r}-${o}-${A}T${Q}`;case"DAILY":return`${r}-${o}-${A}`;case"WEEKLY":return`${r}-W${d(g)}`;case"MONTHLY":return`${r}-${o}`;case"YEARLY":return`${r}`}}function d(t){let I=new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate())),g=I.getUTCDay()||7;I.setUTCDate(I.getUTCDate()+4-g);let r=new Date(Date.UTC(I.getUTCFullYear(),0,1)),o=Math.ceil(((I.getTime()-r.getTime())/86400000+1)/7);return String(o).padStart(2,"0")}class T{storage;batchSize;constructor(t){this.storage=t.storage,this.batchSize=t.batchSize||1000}async aggregate(t){let{periodType:I,periodStart:g,metricKey:r}=t,o=t.periodEnd||N(g,I),A={periodType:I,periodStart:g,periodEnd:o,recordsProcessed:0,summariesCreated:0,summariesUpdated:0,errors:[]},Q=await this.storage.getUnaggregatedRecords({metricKey:r,periodStart:g,periodEnd:o,limit:this.batchSize});if(Q.length===0)return A;let k=this.groupRecords(Q,I);for(let[$,O]of k.entries())try{await this.aggregateGroup($,O,I,A)}catch(U){let[X,L,Y]=$.split("::");A.errors.push({metricKey:X??"unknown",subjectType:L??"unknown",subjectId:Y??"unknown",error:U instanceof Error?U.message:String(U)})}let _=Q.map(($)=>$.id);return await this.storage.markRecordsAggregated(_,new Date),A.recordsProcessed=Q.length,A}groupRecords(t,I){let g=new Map;for(let r of t){let o=e(r.timestamp,I),A=`${r.metricKey}::${r.subjectType}::${r.subjectId}::${o}`,Q=g.get(A)||[];Q.push(r),g.set(A,Q)}return g}async aggregateGroup(t,I,g,r){let[o,A,Q]=t.split("::");if(!o||!A||!Q||I.length===0)return;let k=I[0];if(!k)return;let _=Z(k.timestamp,g),$=N(k.timestamp,g),U=(await this.storage.getMetric(o))?.aggregationType||"SUM",X=I.map((Y)=>Y.quantity),L=this.calculateAggregation(X,U);await this.storage.upsertSummary({metricKey:o,subjectType:A,subjectId:Q,periodType:g,periodStart:_,periodEnd:$,totalQuantity:L.total,recordCount:I.length,minQuantity:L.min,maxQuantity:L.max,avgQuantity:L.avg}),r.summariesCreated++}calculateAggregation(t,I){if(t.length===0)return{total:0,min:0,max:0,avg:0};let g=Math.min(...t),r=Math.max(...t),o=t.reduce((_,$)=>_+$,0),A=o/t.length,Q=t.length,k;switch(I){case"COUNT":k=Q;break;case"SUM":k=o;break;case"AVG":k=A;break;case"MAX":k=r;break;case"MIN":k=g;break;case"LAST":k=t[t.length-1]??0;break;default:k=o}return{total:k,min:g,max:r,avg:A}}}class S{records=[];summaries=new Map;metrics=new Map;addRecord(t){this.records.push(t)}addMetric(t){this.metrics.set(t.key,t)}async getUnaggregatedRecords(t){let I=this.records.filter((g)=>{let r=g.timestamp>=t.periodStart&&g.timestamp<t.periodEnd,o=!t.metricKey||g.metricKey===t.metricKey;return r&&o});if(t.limit)I=I.slice(0,t.limit);return I}async markRecordsAggregated(t){this.records=this.records.filter((I)=>!t.includes(I.id))}async upsertSummary(t){let I=`${t.metricKey}::${t.subjectType}::${t.subjectId}::${t.periodType}::${t.periodStart.toISOString()}`,g=this.summaries.get(I);if(g){if(g.totalQuantity+=t.totalQuantity,g.recordCount+=t.recordCount,t.minQuantity!==void 0)g.minQuantity=Math.min(g.minQuantity??1/0,t.minQuantity);if(t.maxQuantity!==void 0)g.maxQuantity=Math.max(g.maxQuantity??-1/0,t.maxQuantity);return g}let r={id:`summary-${Date.now()}-${Math.random().toString(36).slice(2)}`,...t};return this.summaries.set(I,r),r}async getMetric(t){return this.metrics.get(t)||null}async listMetrics(){return Array.from(this.metrics.values())}getSummaries(){return Array.from(this.summaries.values())}clear(){this.records=[],this.summaries.clear(),this.metrics.clear()}}class m{reader;eventPrefix;constructor(t,I={}){this.reader=t,this.eventPrefix=I.eventPrefix??"metering"}async getUsageByMetric(t){let I=await this.queryHogQL({query:["select"," properties.recordId as recordId,"," properties.metricKey as metricKey,"," properties.subjectType as subjectType,"," distinct_id as subjectId,"," properties.quantity as quantity,"," properties.source as source,"," timestamp as timestamp","from events",`where ${W(this.eventPrefix,t)}`,"order by timestamp desc",`limit ${t.limit??200}`].join(`
2
+ `),values:M(t)});return tt(I)}async getUsageSummary(t){let I=await this.queryHogQL({query:["select"," properties.summaryId as summaryId,"," properties.metricKey as metricKey,"," properties.subjectType as subjectType,"," distinct_id as subjectId,"," properties.periodType as periodType,"," properties.periodStart as periodStart,"," properties.periodEnd as periodEnd,"," properties.totalQuantity as totalQuantity,"," properties.recordCount as recordCount,"," timestamp as aggregatedAt","from events",`where ${E(this.eventPrefix,t)}`,"order by timestamp desc",`limit ${t.limit??200}`].join(`
3
+ `),values:l(t)});return st(I)}async getUsageTrend(t){let I=await this.queryHogQL({query:["select",` ${a(t.bucket)} as bucketStart,`," sum(properties.quantity) as totalQuantity,"," count() as recordCount","from events",`where ${W(this.eventPrefix,t)}`,"group by bucketStart","order by bucketStart asc"].join(`
4
+ `),values:M(t)});return gt(I)}async queryHogQL(t){if(!this.reader.queryHogQL)throw Error("Analytics reader does not support HogQL queries.");return this.reader.queryHogQL(t)}}function W(t,I){let g=[`event = '${t}.usage_recorded'`,"properties.metricKey = {metricKey}"];if(I.subjectId)g.push("distinct_id = {subjectId}");if(I.dateRange?.from)g.push("timestamp >= {dateFrom}");if(I.dateRange?.to)g.push("timestamp < {dateTo}");return g.join(" and ")}function E(t,I){let g=[`event = '${t}.usage_aggregated'`,"properties.metricKey = {metricKey}"];if(I.subjectId)g.push("distinct_id = {subjectId}");if(I.periodType)g.push("properties.periodType = {periodType}");if(I.dateRange?.from)g.push("timestamp >= {dateFrom}");if(I.dateRange?.to)g.push("timestamp < {dateTo}");return g.join(" and ")}function M(t){return{metricKey:t.metricKey,subjectId:t.subjectId,dateFrom:z(t.dateRange?.from),dateTo:z(t.dateRange?.to)}}function l(t){return{metricKey:t.metricKey,subjectId:t.subjectId,periodType:t.periodType,dateFrom:z(t.dateRange?.from),dateTo:z(t.dateRange?.to)}}function a(t){switch(t){case"hour":return"toStartOfHour(timestamp)";case"week":return"toStartOfWeek(timestamp)";case"month":return"toStartOfMonth(timestamp)";case"day":default:return"toStartOfDay(timestamp)"}}function tt(t){return G(t).flatMap((g)=>{let r=D(g.metricKey),o=D(g.subjectType),A=D(g.subjectId),Q=i(g.timestamp);if(!r||!o||!A||!Q)return[];return[{recordId:v(g.recordId),metricKey:r,subjectType:o,subjectId:A,quantity:q(g.quantity),source:v(g.source),timestamp:Q}]})}function st(t){return G(t).flatMap((g)=>{let r=D(g.metricKey),o=D(g.subjectType),A=D(g.subjectId),Q=D(g.periodType),k=i(g.periodStart),_=i(g.periodEnd),$=i(g.aggregatedAt);if(!r||!o||!A||!Q||!k||!_||!$)return[];return[{summaryId:v(g.summaryId),metricKey:r,subjectType:o,subjectId:A,periodType:Q,periodStart:k,periodEnd:_,totalQuantity:q(g.totalQuantity),recordCount:q(g.recordCount),aggregatedAt:$}]})}function gt(t){return G(t).flatMap((g)=>{let r=D(g.bucketStart);if(!r)return[];return[{bucketStart:r,totalQuantity:q(g.totalQuantity),recordCount:q(g.recordCount)}]})}function G(t){if(!Array.isArray(t.results)||!Array.isArray(t.columns))return[];let I=t.columns;return t.results.flatMap((g)=>{if(!Array.isArray(g))return[];let r={};return I.forEach((o,A)=>{r[o]=g[A]}),[r]})}function D(t){if(typeof t==="string"&&t.trim())return t;if(typeof t==="number")return String(t);return null}function v(t){if(typeof t==="string")return t;if(typeof t==="number")return String(t);return}function q(t){if(typeof t==="number"&&Number.isFinite(t))return t;if(typeof t==="string"&&t.trim()){let I=Number(t);if(Number.isFinite(I))return I}return 0}function i(t){if(t instanceof Date)return t;if(typeof t==="string"||typeof t==="number"){let I=new Date(t);if(!Number.isNaN(I.getTime()))return I}return null}function z(t){if(!t)return;return typeof t==="string"?t:t.toISOString()}class bt{provider;eventPrefix;includeSource;constructor(t,I={}){this.provider=t,this.eventPrefix=I.eventPrefix??"metering",this.includeSource=I.includeSource??!0}async captureUsageRecorded(t){await this.provider.capture({distinctId:t.subjectId,event:`${this.eventPrefix}.usage_recorded`,timestamp:t.timestamp,properties:{recordId:t.recordId??null,metricKey:t.metricKey,subjectType:t.subjectType,quantity:t.quantity,...this.includeSource&&t.source?{source:t.source}:{}}})}async captureUsageAggregated(t){await this.provider.capture({distinctId:t.subjectId,event:`${this.eventPrefix}.usage_aggregated`,timestamp:t.aggregatedAt,properties:{summaryId:t.summaryId??null,metricKey:t.metricKey,subjectType:t.subjectType,periodType:t.periodType,periodStart:t.periodStart.toISOString(),periodEnd:t.periodEnd.toISOString(),totalQuantity:t.totalQuantity,recordCount:t.recordCount}})}}import{defineCommand as F,defineQuery as V}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as R,ScalarTypeEnum as s}from"@contractspec/lib.schema";var P=["platform.metering"],B=R({name:"MetricDefinition",description:"Represents a metric definition",fields:{id:{type:s.String_unsecure(),isOptional:!1},key:{type:s.String_unsecure(),isOptional:!1},name:{type:s.String_unsecure(),isOptional:!1},description:{type:s.String_unsecure(),isOptional:!0},unit:{type:s.String_unsecure(),isOptional:!1},aggregationType:{type:s.String_unsecure(),isOptional:!1},resetPeriod:{type:s.String_unsecure(),isOptional:!1},category:{type:s.String_unsecure(),isOptional:!0},isActive:{type:s.Boolean(),isOptional:!1},orgId:{type:s.String_unsecure(),isOptional:!0},createdAt:{type:s.DateTime(),isOptional:!1},updatedAt:{type:s.DateTime(),isOptional:!1}}}),h=R({name:"UsageRecord",description:"Represents a usage record",fields:{id:{type:s.String_unsecure(),isOptional:!1},metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!1},subjectId:{type:s.String_unsecure(),isOptional:!1},quantity:{type:s.Float_unsecure(),isOptional:!1},source:{type:s.String_unsecure(),isOptional:!0},resourceId:{type:s.String_unsecure(),isOptional:!0},resourceType:{type:s.String_unsecure(),isOptional:!0},metadata:{type:s.JSON(),isOptional:!0},timestamp:{type:s.DateTime(),isOptional:!1},createdAt:{type:s.DateTime(),isOptional:!1}}}),jt=R({name:"UsageSummary",description:"Represents aggregated usage",fields:{id:{type:s.String_unsecure(),isOptional:!1},metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!1},subjectId:{type:s.String_unsecure(),isOptional:!1},periodType:{type:s.String_unsecure(),isOptional:!1},periodStart:{type:s.DateTime(),isOptional:!1},periodEnd:{type:s.DateTime(),isOptional:!1},totalQuantity:{type:s.Float_unsecure(),isOptional:!1},recordCount:{type:s.Int_unsecure(),isOptional:!1},minQuantity:{type:s.Float_unsecure(),isOptional:!0},maxQuantity:{type:s.Float_unsecure(),isOptional:!0},avgQuantity:{type:s.Float_unsecure(),isOptional:!0}}}),x=R({name:"UsageThreshold",description:"Represents a usage threshold",fields:{id:{type:s.String_unsecure(),isOptional:!1},metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!0},subjectId:{type:s.String_unsecure(),isOptional:!0},name:{type:s.String_unsecure(),isOptional:!1},threshold:{type:s.Float_unsecure(),isOptional:!1},warnThreshold:{type:s.Float_unsecure(),isOptional:!0},periodType:{type:s.String_unsecure(),isOptional:!1},action:{type:s.String_unsecure(),isOptional:!1},currentValue:{type:s.Float_unsecure(),isOptional:!1},isActive:{type:s.Boolean(),isOptional:!1},createdAt:{type:s.DateTime(),isOptional:!1}}}),It=R({name:"DefineMetricInput",description:"Input for defining a metric",fields:{key:{type:s.String_unsecure(),isOptional:!1},name:{type:s.String_unsecure(),isOptional:!1},description:{type:s.String_unsecure(),isOptional:!0},unit:{type:s.String_unsecure(),isOptional:!1},aggregationType:{type:s.String_unsecure(),isOptional:!0},resetPeriod:{type:s.String_unsecure(),isOptional:!0},category:{type:s.String_unsecure(),isOptional:!0},orgId:{type:s.String_unsecure(),isOptional:!0},metadata:{type:s.JSON(),isOptional:!0}}}),rt=R({name:"UpdateMetricInput",description:"Input for updating a metric",fields:{metricId:{type:s.String_unsecure(),isOptional:!1},name:{type:s.String_unsecure(),isOptional:!0},description:{type:s.String_unsecure(),isOptional:!0},category:{type:s.String_unsecure(),isOptional:!0},isActive:{type:s.Boolean(),isOptional:!0},metadata:{type:s.JSON(),isOptional:!0}}}),ot=R({name:"DeleteMetricInput",description:"Input for deleting a metric",fields:{metricId:{type:s.String_unsecure(),isOptional:!1}}}),At=R({name:"GetMetricInput",description:"Input for getting a metric",fields:{key:{type:s.String_unsecure(),isOptional:!1},orgId:{type:s.String_unsecure(),isOptional:!0}}}),Rt=R({name:"ListMetricsInput",description:"Input for listing metrics",fields:{orgId:{type:s.String_unsecure(),isOptional:!0},category:{type:s.String_unsecure(),isOptional:!0},isActive:{type:s.Boolean(),isOptional:!0},limit:{type:s.Int_unsecure(),isOptional:!0},offset:{type:s.Int_unsecure(),isOptional:!0}}}),Qt=R({name:"ListMetricsOutput",description:"Output for listing metrics",fields:{metrics:{type:B,isArray:!0,isOptional:!1},total:{type:s.Int_unsecure(),isOptional:!1}}}),n=R({name:"RecordUsageInput",description:"Input for recording usage",fields:{metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!1},subjectId:{type:s.String_unsecure(),isOptional:!1},quantity:{type:s.Float_unsecure(),isOptional:!1},timestamp:{type:s.DateTime(),isOptional:!0},source:{type:s.String_unsecure(),isOptional:!0},resourceId:{type:s.String_unsecure(),isOptional:!0},resourceType:{type:s.String_unsecure(),isOptional:!0},metadata:{type:s.JSON(),isOptional:!0},idempotencyKey:{type:s.String_unsecure(),isOptional:!0}}}),kt=R({name:"RecordBatchUsageInput",description:"Input for recording batch usage",fields:{records:{type:n,isArray:!0,isOptional:!1}}}),Kt=R({name:"RecordBatchUsageOutput",description:"Output for recording batch usage",fields:{recordedCount:{type:s.Int_unsecure(),isOptional:!1},skippedCount:{type:s.Int_unsecure(),isOptional:!1},recordIds:{type:s.JSON(),isOptional:!1}}}),Pt=R({name:"GetUsageInput",description:"Input for getting usage",fields:{metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!1},subjectId:{type:s.String_unsecure(),isOptional:!1},startDate:{type:s.DateTime(),isOptional:!1},endDate:{type:s.DateTime(),isOptional:!1},limit:{type:s.Int_unsecure(),isOptional:!0},offset:{type:s.Int_unsecure(),isOptional:!0}}}),$t=R({name:"GetUsageOutput",description:"Output for getting usage",fields:{records:{type:h,isArray:!0,isOptional:!1},total:{type:s.Int_unsecure(),isOptional:!1},totalQuantity:{type:s.Float_unsecure(),isOptional:!1}}}),wt=R({name:"GetUsageSummaryInput",description:"Input for getting usage summary",fields:{metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!1},subjectId:{type:s.String_unsecure(),isOptional:!1},periodType:{type:s.String_unsecure(),isOptional:!1},startDate:{type:s.DateTime(),isOptional:!1},endDate:{type:s.DateTime(),isOptional:!0}}}),Ht=R({name:"GetUsageSummaryOutput",description:"Output for getting usage summary",fields:{summaries:{type:jt,isArray:!0,isOptional:!1},total:{type:s.Int_unsecure(),isOptional:!1}}}),_t=R({name:"CreateThresholdInput",description:"Input for creating a threshold",fields:{metricKey:{type:s.String_unsecure(),isOptional:!1},subjectType:{type:s.String_unsecure(),isOptional:!0},subjectId:{type:s.String_unsecure(),isOptional:!0},name:{type:s.String_unsecure(),isOptional:!1},threshold:{type:s.Float_unsecure(),isOptional:!1},warnThreshold:{type:s.Float_unsecure(),isOptional:!0},periodType:{type:s.String_unsecure(),isOptional:!0},action:{type:s.String_unsecure(),isOptional:!0},notifyEmails:{type:s.JSON(),isOptional:!0},notifyWebhook:{type:s.String_unsecure(),isOptional:!0}}}),Dt=R({name:"UpdateThresholdInput",description:"Input for updating a threshold",fields:{thresholdId:{type:s.String_unsecure(),isOptional:!1},name:{type:s.String_unsecure(),isOptional:!0},threshold:{type:s.Float_unsecure(),isOptional:!0},warnThreshold:{type:s.Float_unsecure(),isOptional:!0},action:{type:s.String_unsecure(),isOptional:!0},notifyEmails:{type:s.JSON(),isOptional:!0},notifyWebhook:{type:s.String_unsecure(),isOptional:!0},isActive:{type:s.Boolean(),isOptional:!0}}}),Ft=R({name:"DeleteThresholdInput",description:"Input for deleting a threshold",fields:{thresholdId:{type:s.String_unsecure(),isOptional:!1}}}),Lt=R({name:"ListThresholdsInput",description:"Input for listing thresholds",fields:{metricKey:{type:s.String_unsecure(),isOptional:!0},subjectType:{type:s.String_unsecure(),isOptional:!0},subjectId:{type:s.String_unsecure(),isOptional:!0},isActive:{type:s.Boolean(),isOptional:!0}}}),Ut=R({name:"ListThresholdsOutput",description:"Output for listing thresholds",fields:{thresholds:{type:x,isArray:!0,isOptional:!1},total:{type:s.Int_unsecure(),isOptional:!1}}}),f=R({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:s.Boolean(),isOptional:!1}}}),ts=F({meta:{key:"metric.define",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","metric","define"],description:"Define a new usage metric.",goal:"Create a new metric for tracking usage.",context:"Called when setting up metering."},io:{input:It,output:B,errors:{METRIC_KEY_EXISTS:{description:"Metric key already exists",http:409,gqlCode:"METRIC_KEY_EXISTS",when:"A metric with this key already exists"}}},policy:{auth:"admin"}}),ss=F({meta:{key:"metric.update",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","metric","update"],description:"Update a metric definition.",goal:"Modify metric configuration.",context:"Called when updating metric settings."},io:{input:rt,output:B,errors:{METRIC_NOT_FOUND:{description:"Metric does not exist",http:404,gqlCode:"METRIC_NOT_FOUND",when:"Metric ID is invalid"}}},policy:{auth:"admin"}}),gs=F({meta:{key:"metric.delete",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","metric","delete"],description:"Delete a metric definition.",goal:"Remove a metric and its data.",context:"Called when removing a metric."},io:{input:ot,output:f,errors:{METRIC_NOT_FOUND:{description:"Metric does not exist",http:404,gqlCode:"METRIC_NOT_FOUND",when:"Metric ID is invalid"}}},policy:{auth:"admin"}}),bs=V({meta:{key:"metric.get",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","metric","get"],description:"Get a metric by key.",goal:"Retrieve metric definition.",context:"Called to inspect metric details."},io:{input:At,output:B,errors:{METRIC_NOT_FOUND:{description:"Metric does not exist",http:404,gqlCode:"METRIC_NOT_FOUND",when:"Metric key is invalid"}}},policy:{auth:"user"}}),js=V({meta:{key:"metric.list",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","metric","list"],description:"List all metrics.",goal:"View configured metrics.",context:"Called to browse metrics."},io:{input:Rt,output:Qt},policy:{auth:"user"}}),Is=F({meta:{key:"usage.record",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","usage","record"],description:"Record a usage event.",goal:"Track usage for billing and monitoring.",context:"Called when usage occurs."},io:{input:n,output:h,errors:{METRIC_NOT_FOUND:{description:"Metric does not exist",http:404,gqlCode:"METRIC_NOT_FOUND",when:"Metric key is invalid"},DUPLICATE_RECORD:{description:"Record already exists",http:409,gqlCode:"DUPLICATE_RECORD",when:"Idempotency key already used"}}},policy:{auth:"admin"}}),rs=F({meta:{key:"usage.recordBatch",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","usage","batch"],description:"Record multiple usage events.",goal:"Efficiently track bulk usage.",context:"Called for batch processing."},io:{input:kt,output:Kt},policy:{auth:"admin"}}),os=V({meta:{key:"usage.get",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","usage","get"],description:"Get usage records for a subject.",goal:"View detailed usage history.",context:"Called to analyze usage."},io:{input:Pt,output:$t},policy:{auth:"user"}}),As=V({meta:{key:"usage.getSummary",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","usage","summary"],description:"Get aggregated usage summary.",goal:"View usage totals for billing.",context:"Called for billing and reporting."},io:{input:wt,output:Ht},policy:{auth:"user"}}),Rs=F({meta:{key:"threshold.create",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","threshold","create"],description:"Create a usage threshold.",goal:"Set up usage limits and alerts.",context:"Called when configuring limits."},io:{input:_t,output:x,errors:{METRIC_NOT_FOUND:{description:"Metric does not exist",http:404,gqlCode:"METRIC_NOT_FOUND",when:"Metric key is invalid"}}},policy:{auth:"admin"}}),Qs=F({meta:{key:"threshold.update",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","threshold","update"],description:"Update a threshold.",goal:"Modify threshold configuration.",context:"Called when adjusting limits."},io:{input:Dt,output:x,errors:{THRESHOLD_NOT_FOUND:{description:"Threshold does not exist",http:404,gqlCode:"THRESHOLD_NOT_FOUND",when:"Threshold ID is invalid"}}},policy:{auth:"admin"}}),ks=F({meta:{key:"threshold.delete",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","threshold","delete"],description:"Delete a threshold.",goal:"Remove a usage threshold.",context:"Called when removing limits."},io:{input:Ft,output:f,errors:{THRESHOLD_NOT_FOUND:{description:"Threshold does not exist",http:404,gqlCode:"THRESHOLD_NOT_FOUND",when:"Threshold ID is invalid"}}},policy:{auth:"admin"}}),Ks=V({meta:{key:"threshold.list",version:"1.0.0",stability:"stable",owners:[...P],tags:["metering","threshold","list"],description:"List usage thresholds.",goal:"View configured limits.",context:"Called to browse thresholds."},io:{input:Lt,output:Ut},policy:{auth:"user"}});import{registerDocBlocks as qt}from"@contractspec/lib.contracts-spec/docs";var Vt=[{id:"docs.metering.usage",title:"Usage Metering & Billing Core",summary:"Reusable usage/metering layer with metric definitions, usage ingestion, aggregation, thresholds, and alerts for billing or quotas.",kind:"reference",visibility:"public",route:"/docs/metering/usage",tags:["metering","usage","billing","quotas"],body:`## Capabilities
1178
5
 
1179
6
  - **Entities**: MetricDefinition, UsageRecord, UsageSummary, UsageThreshold, UsageAlert.
1180
7
  - **Contracts**: define/list metrics; record usage (batch + idempotent); retrieve usage by subject; manage thresholds and alerts.
@@ -1199,7 +26,7 @@ var meteringDocBlocks = [
1199
26
 
1200
27
  ## Example
1201
28
 
1202
- ${"```"}ts
29
+ \`\`\`ts
1203
30
  import { meteringSchemaContribution } from '@contractspec/lib.metering';
1204
31
  import { aggregateUsage } from '@contractspec/lib.metering/aggregation';
1205
32
 
@@ -1214,708 +41,11 @@ await aggregateUsage({
1214
41
  usage: usageRepository,
1215
42
  period: 'DAILY',
1216
43
  });
1217
- ${"```"},
44
+ \`\`\`,
1218
45
 
1219
46
  ## Guardrails
1220
47
 
1221
48
  - Keep metric keys stable; store quantities as decimals for currency/units.
1222
49
  - Use idempotency keys for external ingestion; avoid PII in metric metadata.
1223
50
  - Scope by org/user for multi-tenant isolation; emit audit + analytics events on changes.
1224
- `
1225
- }
1226
- ];
1227
- registerDocBlocks(meteringDocBlocks);
1228
- // src/entities/index.ts
1229
- import {
1230
- defineEntity,
1231
- defineEntityEnum,
1232
- field,
1233
- index
1234
- } from "@contractspec/lib.schema";
1235
- var AggregationTypeEnum = defineEntityEnum({
1236
- name: "AggregationType",
1237
- values: ["COUNT", "SUM", "AVG", "MAX", "MIN", "LAST"],
1238
- schema: "lssm_metering",
1239
- description: "How to aggregate metric values."
1240
- });
1241
- var ResetPeriodEnum = defineEntityEnum({
1242
- name: "ResetPeriod",
1243
- values: ["NEVER", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"],
1244
- schema: "lssm_metering",
1245
- description: "When to reset metric counters."
1246
- });
1247
- var PeriodTypeEnum = defineEntityEnum({
1248
- name: "PeriodType",
1249
- values: ["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"],
1250
- schema: "lssm_metering",
1251
- description: "Time period for aggregation."
1252
- });
1253
- var ThresholdActionEnum = defineEntityEnum({
1254
- name: "ThresholdAction",
1255
- values: ["NONE", "ALERT", "WARN", "BLOCK", "DOWNGRADE"],
1256
- schema: "lssm_metering",
1257
- description: "Action to take when threshold is exceeded."
1258
- });
1259
- var MetricDefinitionEntity = defineEntity({
1260
- name: "MetricDefinition",
1261
- description: "Definition of a usage metric.",
1262
- schema: "lssm_metering",
1263
- map: "metric_definition",
1264
- fields: {
1265
- id: field.id({ description: "Unique identifier" }),
1266
- key: field.string({
1267
- isUnique: true,
1268
- description: "Metric key (e.g., api_calls, storage_gb)"
1269
- }),
1270
- name: field.string({ description: "Human-readable name" }),
1271
- description: field.string({
1272
- isOptional: true,
1273
- description: "Metric description"
1274
- }),
1275
- unit: field.string({
1276
- description: "Unit of measurement (calls, bytes, etc.)"
1277
- }),
1278
- aggregationType: field.enum("AggregationType", {
1279
- default: "SUM",
1280
- description: "How to aggregate values"
1281
- }),
1282
- resetPeriod: field.enum("ResetPeriod", {
1283
- default: "MONTHLY",
1284
- description: "When to reset counters"
1285
- }),
1286
- precision: field.int({ default: 2, description: "Decimal precision" }),
1287
- orgId: field.string({
1288
- isOptional: true,
1289
- description: "Organization scope (null = global metric)"
1290
- }),
1291
- category: field.string({
1292
- isOptional: true,
1293
- description: "Category for grouping"
1294
- }),
1295
- displayOrder: field.int({ default: 0, description: "Order for display" }),
1296
- metadata: field.json({
1297
- isOptional: true,
1298
- description: "Additional metadata"
1299
- }),
1300
- isActive: field.boolean({
1301
- default: true,
1302
- description: "Whether metric is active"
1303
- }),
1304
- createdAt: field.createdAt(),
1305
- updatedAt: field.updatedAt(),
1306
- usageRecords: field.hasMany("UsageRecord"),
1307
- usageSummaries: field.hasMany("UsageSummary"),
1308
- thresholds: field.hasMany("UsageThreshold")
1309
- },
1310
- indexes: [
1311
- index.on(["orgId", "key"]),
1312
- index.on(["category"]),
1313
- index.on(["isActive"])
1314
- ],
1315
- enums: [AggregationTypeEnum, ResetPeriodEnum]
1316
- });
1317
- var UsageRecordEntity = defineEntity({
1318
- name: "UsageRecord",
1319
- description: "A single usage event.",
1320
- schema: "lssm_metering",
1321
- map: "usage_record",
1322
- fields: {
1323
- id: field.id({ description: "Unique identifier" }),
1324
- metricKey: field.string({ description: "Metric being recorded" }),
1325
- metricId: field.string({
1326
- isOptional: true,
1327
- description: "Metric ID (for FK)"
1328
- }),
1329
- subjectType: field.string({
1330
- description: "Subject type (org, user, project)"
1331
- }),
1332
- subjectId: field.string({ description: "Subject identifier" }),
1333
- quantity: field.decimal({ description: "Usage quantity" }),
1334
- source: field.string({
1335
- isOptional: true,
1336
- description: "Source of usage (endpoint, feature, etc.)"
1337
- }),
1338
- resourceId: field.string({
1339
- isOptional: true,
1340
- description: "Related resource ID"
1341
- }),
1342
- resourceType: field.string({
1343
- isOptional: true,
1344
- description: "Related resource type"
1345
- }),
1346
- metadata: field.json({
1347
- isOptional: true,
1348
- description: "Additional context"
1349
- }),
1350
- idempotencyKey: field.string({
1351
- isOptional: true,
1352
- description: "Idempotency key for deduplication"
1353
- }),
1354
- timestamp: field.dateTime({ description: "When usage occurred" }),
1355
- createdAt: field.createdAt(),
1356
- aggregated: field.boolean({
1357
- default: false,
1358
- description: "Whether included in summary"
1359
- }),
1360
- aggregatedAt: field.dateTime({
1361
- isOptional: true,
1362
- description: "When aggregated"
1363
- })
1364
- },
1365
- indexes: [
1366
- index.on(["metricKey", "subjectType", "subjectId", "timestamp"]),
1367
- index.on(["subjectType", "subjectId", "timestamp"]),
1368
- index.on(["timestamp"]),
1369
- index.on(["aggregated", "timestamp"]),
1370
- index.unique(["idempotencyKey"], { name: "usage_record_idempotency" })
1371
- ]
1372
- });
1373
- var UsageSummaryEntity = defineEntity({
1374
- name: "UsageSummary",
1375
- description: "Pre-aggregated usage summary.",
1376
- schema: "lssm_metering",
1377
- map: "usage_summary",
1378
- fields: {
1379
- id: field.id({ description: "Unique identifier" }),
1380
- metricKey: field.string({ description: "Metric key" }),
1381
- metricId: field.string({
1382
- isOptional: true,
1383
- description: "Metric ID (for FK)"
1384
- }),
1385
- subjectType: field.string({ description: "Subject type" }),
1386
- subjectId: field.string({ description: "Subject identifier" }),
1387
- periodType: field.enum("PeriodType", { description: "Period type" }),
1388
- periodStart: field.dateTime({ description: "Period start time" }),
1389
- periodEnd: field.dateTime({ description: "Period end time" }),
1390
- totalQuantity: field.decimal({ description: "Total/aggregated quantity" }),
1391
- recordCount: field.int({
1392
- default: 0,
1393
- description: "Number of records aggregated"
1394
- }),
1395
- minQuantity: field.decimal({
1396
- isOptional: true,
1397
- description: "Minimum value"
1398
- }),
1399
- maxQuantity: field.decimal({
1400
- isOptional: true,
1401
- description: "Maximum value"
1402
- }),
1403
- avgQuantity: field.decimal({
1404
- isOptional: true,
1405
- description: "Average value"
1406
- }),
1407
- metadata: field.json({
1408
- isOptional: true,
1409
- description: "Additional metadata"
1410
- }),
1411
- createdAt: field.createdAt(),
1412
- updatedAt: field.updatedAt()
1413
- },
1414
- indexes: [
1415
- index.unique(["metricKey", "subjectType", "subjectId", "periodType", "periodStart"], { name: "usage_summary_unique" }),
1416
- index.on(["subjectType", "subjectId", "periodType", "periodStart"]),
1417
- index.on(["metricKey", "periodType", "periodStart"])
1418
- ],
1419
- enums: [PeriodTypeEnum]
1420
- });
1421
- var UsageThresholdEntity = defineEntity({
1422
- name: "UsageThreshold",
1423
- description: "Usage threshold configuration.",
1424
- schema: "lssm_metering",
1425
- map: "usage_threshold",
1426
- fields: {
1427
- id: field.id({ description: "Unique identifier" }),
1428
- metricKey: field.string({ description: "Metric to monitor" }),
1429
- metricId: field.string({
1430
- isOptional: true,
1431
- description: "Metric ID (for FK)"
1432
- }),
1433
- subjectType: field.string({
1434
- isOptional: true,
1435
- description: "Subject type"
1436
- }),
1437
- subjectId: field.string({
1438
- isOptional: true,
1439
- description: "Subject identifier"
1440
- }),
1441
- name: field.string({ description: "Threshold name" }),
1442
- threshold: field.decimal({ description: "Threshold value" }),
1443
- warnThreshold: field.decimal({
1444
- isOptional: true,
1445
- description: "Warning threshold (e.g., 80%)"
1446
- }),
1447
- periodType: field.enum("PeriodType", {
1448
- default: "MONTHLY",
1449
- description: "Period to evaluate"
1450
- }),
1451
- action: field.enum("ThresholdAction", {
1452
- default: "ALERT",
1453
- description: "Action when exceeded"
1454
- }),
1455
- notifyEmails: field.json({
1456
- isOptional: true,
1457
- description: "Email addresses to notify"
1458
- }),
1459
- notifyWebhook: field.string({
1460
- isOptional: true,
1461
- description: "Webhook URL to call"
1462
- }),
1463
- currentValue: field.decimal({
1464
- default: 0,
1465
- description: "Current usage value"
1466
- }),
1467
- lastCheckedAt: field.dateTime({
1468
- isOptional: true,
1469
- description: "Last threshold check"
1470
- }),
1471
- lastExceededAt: field.dateTime({
1472
- isOptional: true,
1473
- description: "Last time threshold was exceeded"
1474
- }),
1475
- isActive: field.boolean({
1476
- default: true,
1477
- description: "Whether threshold is active"
1478
- }),
1479
- metadata: field.json({
1480
- isOptional: true,
1481
- description: "Additional metadata"
1482
- }),
1483
- createdAt: field.createdAt(),
1484
- updatedAt: field.updatedAt()
1485
- },
1486
- indexes: [
1487
- index.on(["metricKey"]),
1488
- index.on(["subjectType", "subjectId"]),
1489
- index.on(["isActive", "metricKey"])
1490
- ],
1491
- enums: [ThresholdActionEnum]
1492
- });
1493
- var UsageAlertEntity = defineEntity({
1494
- name: "UsageAlert",
1495
- description: "Alert generated when threshold is exceeded.",
1496
- schema: "lssm_metering",
1497
- map: "usage_alert",
1498
- fields: {
1499
- id: field.id({ description: "Unique identifier" }),
1500
- thresholdId: field.foreignKey({
1501
- description: "Threshold that triggered alert"
1502
- }),
1503
- metricKey: field.string({ description: "Metric key" }),
1504
- subjectType: field.string({
1505
- isOptional: true,
1506
- description: "Subject type"
1507
- }),
1508
- subjectId: field.string({
1509
- isOptional: true,
1510
- description: "Subject identifier"
1511
- }),
1512
- alertType: field.string({ description: "Alert type (warn, exceed, etc.)" }),
1513
- threshold: field.decimal({ description: "Threshold value" }),
1514
- actualValue: field.decimal({ description: "Actual usage value" }),
1515
- percentageUsed: field.decimal({
1516
- description: "Percentage of threshold used"
1517
- }),
1518
- status: field.string({
1519
- default: '"pending"',
1520
- description: "Alert status (pending, acknowledged, resolved)"
1521
- }),
1522
- acknowledgedBy: field.string({
1523
- isOptional: true,
1524
- description: "User who acknowledged"
1525
- }),
1526
- acknowledgedAt: field.dateTime({
1527
- isOptional: true,
1528
- description: "When acknowledged"
1529
- }),
1530
- resolvedAt: field.dateTime({
1531
- isOptional: true,
1532
- description: "When resolved"
1533
- }),
1534
- notificationsSent: field.json({
1535
- isOptional: true,
1536
- description: "Notifications sent"
1537
- }),
1538
- triggeredAt: field.dateTime({ description: "When alert was triggered" }),
1539
- createdAt: field.createdAt(),
1540
- thresholdRelation: field.belongsTo("UsageThreshold", ["thresholdId"], ["id"], { onDelete: "Cascade" })
1541
- },
1542
- indexes: [
1543
- index.on(["thresholdId", "status"]),
1544
- index.on(["metricKey", "triggeredAt"]),
1545
- index.on(["status", "triggeredAt"])
1546
- ]
1547
- });
1548
- var meteringEntities = [
1549
- MetricDefinitionEntity,
1550
- UsageRecordEntity,
1551
- UsageSummaryEntity,
1552
- UsageThresholdEntity,
1553
- UsageAlertEntity
1554
- ];
1555
- var meteringSchemaContribution = {
1556
- moduleId: "@contractspec/lib.metering",
1557
- entities: meteringEntities,
1558
- enums: [
1559
- AggregationTypeEnum,
1560
- ResetPeriodEnum,
1561
- PeriodTypeEnum,
1562
- ThresholdActionEnum
1563
- ]
1564
- };
1565
-
1566
- // src/events.ts
1567
- import { defineEvent } from "@contractspec/lib.contracts-spec";
1568
- import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
1569
- var MetricDefinedPayload = defineSchemaModel2({
1570
- name: "MetricDefinedEventPayload",
1571
- description: "Payload when a metric is defined",
1572
- fields: {
1573
- metricId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1574
- key: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1575
- name: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1576
- unit: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1577
- aggregationType: {
1578
- type: ScalarTypeEnum2.String_unsecure(),
1579
- isOptional: false
1580
- },
1581
- orgId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1582
- createdBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1583
- createdAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1584
- }
1585
- });
1586
- var MetricUpdatedPayload = defineSchemaModel2({
1587
- name: "MetricUpdatedEventPayload",
1588
- description: "Payload when a metric is updated",
1589
- fields: {
1590
- metricId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1591
- key: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1592
- changes: { type: ScalarTypeEnum2.JSON(), isOptional: false },
1593
- updatedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1594
- updatedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1595
- }
1596
- });
1597
- var UsageRecordedPayload = defineSchemaModel2({
1598
- name: "UsageRecordedEventPayload",
1599
- description: "Payload when usage is recorded",
1600
- fields: {
1601
- recordId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1602
- metricKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1603
- subjectType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1604
- subjectId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1605
- quantity: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1606
- source: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1607
- timestamp: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1608
- }
1609
- });
1610
- var UsageBatchRecordedPayload = defineSchemaModel2({
1611
- name: "UsageBatchRecordedEventPayload",
1612
- description: "Payload when a batch of usage is recorded",
1613
- fields: {
1614
- recordCount: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
1615
- metricKeys: { type: ScalarTypeEnum2.JSON(), isOptional: false },
1616
- totalQuantity: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1617
- timestamp: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1618
- }
1619
- });
1620
- var UsageAggregatedPayload = defineSchemaModel2({
1621
- name: "UsageAggregatedEventPayload",
1622
- description: "Payload when usage is aggregated",
1623
- fields: {
1624
- summaryId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1625
- metricKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1626
- subjectType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1627
- subjectId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1628
- periodType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1629
- periodStart: { type: ScalarTypeEnum2.DateTime(), isOptional: false },
1630
- periodEnd: { type: ScalarTypeEnum2.DateTime(), isOptional: false },
1631
- totalQuantity: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1632
- recordCount: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
1633
- aggregatedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1634
- }
1635
- });
1636
- var ThresholdCreatedPayload = defineSchemaModel2({
1637
- name: "ThresholdCreatedEventPayload",
1638
- description: "Payload when a threshold is created",
1639
- fields: {
1640
- thresholdId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1641
- metricKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1642
- subjectType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1643
- subjectId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1644
- threshold: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1645
- action: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1646
- createdAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1647
- }
1648
- });
1649
- var ThresholdExceededPayload = defineSchemaModel2({
1650
- name: "ThresholdExceededEventPayload",
1651
- description: "Payload when a threshold is exceeded",
1652
- fields: {
1653
- alertId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1654
- thresholdId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1655
- metricKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1656
- subjectType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1657
- subjectId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1658
- threshold: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1659
- actualValue: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1660
- percentageUsed: {
1661
- type: ScalarTypeEnum2.Float_unsecure(),
1662
- isOptional: false
1663
- },
1664
- action: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1665
- triggeredAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1666
- }
1667
- });
1668
- var ThresholdApproachingPayload = defineSchemaModel2({
1669
- name: "ThresholdApproachingEventPayload",
1670
- description: "Payload when usage is approaching a threshold",
1671
- fields: {
1672
- thresholdId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1673
- metricKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1674
- subjectType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1675
- subjectId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1676
- threshold: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1677
- currentValue: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1678
- percentageUsed: {
1679
- type: ScalarTypeEnum2.Float_unsecure(),
1680
- isOptional: false
1681
- },
1682
- triggeredAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1683
- }
1684
- });
1685
- var MetricDefinedEvent = defineEvent({
1686
- meta: {
1687
- key: "metric.defined",
1688
- version: "1.0.0",
1689
- description: "A metric has been defined.",
1690
- stability: "stable",
1691
- owners: ["@platform.metering"],
1692
- tags: ["metering", "metric"]
1693
- },
1694
- payload: MetricDefinedPayload
1695
- });
1696
- var MetricUpdatedEvent = defineEvent({
1697
- meta: {
1698
- key: "metric.updated",
1699
- version: "1.0.0",
1700
- description: "A metric has been updated.",
1701
- stability: "stable",
1702
- owners: ["@platform.metering"],
1703
- tags: ["metering", "metric"]
1704
- },
1705
- payload: MetricUpdatedPayload
1706
- });
1707
- var UsageRecordedEvent = defineEvent({
1708
- meta: {
1709
- key: "usage.recorded",
1710
- version: "1.0.0",
1711
- description: "Usage has been recorded.",
1712
- stability: "stable",
1713
- owners: ["@platform.metering"],
1714
- tags: ["metering", "usage"]
1715
- },
1716
- payload: UsageRecordedPayload
1717
- });
1718
- var UsageBatchRecordedEvent = defineEvent({
1719
- meta: {
1720
- key: "usage.batch_recorded",
1721
- version: "1.0.0",
1722
- description: "A batch of usage has been recorded.",
1723
- stability: "stable",
1724
- owners: ["@platform.metering"],
1725
- tags: ["metering", "usage"]
1726
- },
1727
- payload: UsageBatchRecordedPayload
1728
- });
1729
- var UsageAggregatedEvent = defineEvent({
1730
- meta: {
1731
- key: "usage.aggregated",
1732
- version: "1.0.0",
1733
- description: "Usage has been aggregated into a summary.",
1734
- stability: "stable",
1735
- owners: ["@platform.metering"],
1736
- tags: ["metering", "usage"]
1737
- },
1738
- payload: UsageAggregatedPayload
1739
- });
1740
- var ThresholdCreatedEvent = defineEvent({
1741
- meta: {
1742
- key: "threshold.created",
1743
- version: "1.0.0",
1744
- description: "A usage threshold has been created.",
1745
- stability: "stable",
1746
- owners: ["@platform.metering"],
1747
- tags: ["metering", "threshold"]
1748
- },
1749
- payload: ThresholdCreatedPayload
1750
- });
1751
- var ThresholdExceededEvent = defineEvent({
1752
- meta: {
1753
- key: "threshold.exceeded",
1754
- version: "1.0.0",
1755
- description: "Usage has exceeded a threshold.",
1756
- stability: "stable",
1757
- owners: ["@platform.metering"],
1758
- tags: ["metering", "threshold"]
1759
- },
1760
- payload: ThresholdExceededPayload
1761
- });
1762
- var ThresholdApproachingEvent = defineEvent({
1763
- meta: {
1764
- key: "threshold.approaching",
1765
- version: "1.0.0",
1766
- description: "Usage is approaching a threshold.",
1767
- stability: "stable",
1768
- owners: ["@platform.metering"],
1769
- tags: ["metering", "threshold"]
1770
- },
1771
- payload: ThresholdApproachingPayload
1772
- });
1773
- var ModelSelectionPayload = defineSchemaModel2({
1774
- name: "ModelSelectionEventPayload",
1775
- description: "Payload when an AI model is selected via ranking",
1776
- fields: {
1777
- modelId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1778
- providerKey: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1779
- dimension: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
1780
- score: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: false },
1781
- reason: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
1782
- alternativesCount: {
1783
- type: ScalarTypeEnum2.Int_unsecure(),
1784
- isOptional: false
1785
- },
1786
- costEstimateInput: {
1787
- type: ScalarTypeEnum2.Float_unsecure(),
1788
- isOptional: true
1789
- },
1790
- costEstimateOutput: {
1791
- type: ScalarTypeEnum2.Float_unsecure(),
1792
- isOptional: true
1793
- },
1794
- selectionDurationMs: {
1795
- type: ScalarTypeEnum2.Float_unsecure(),
1796
- isOptional: true
1797
- },
1798
- timestamp: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
1799
- }
1800
- });
1801
- var ModelSelectionEvent = defineEvent({
1802
- meta: {
1803
- key: "model.selected",
1804
- version: "1.0.0",
1805
- description: "An AI model has been selected via ranking-driven selection.",
1806
- stability: "experimental",
1807
- owners: ["@platform.metering"],
1808
- tags: ["metering", "ai", "model-selection"]
1809
- },
1810
- payload: ModelSelectionPayload
1811
- });
1812
- var MeteringEvents = {
1813
- MetricDefinedEvent,
1814
- MetricUpdatedEvent,
1815
- UsageRecordedEvent,
1816
- UsageBatchRecordedEvent,
1817
- UsageAggregatedEvent,
1818
- ThresholdCreatedEvent,
1819
- ThresholdExceededEvent,
1820
- ThresholdApproachingEvent,
1821
- ModelSelectionEvent
1822
- };
1823
-
1824
- // src/metering.feature.ts
1825
- import { defineFeature } from "@contractspec/lib.contracts-spec";
1826
- var MeteringFeature = defineFeature({
1827
- meta: {
1828
- key: "metrics",
1829
- version: "1.0.0",
1830
- title: "Usage Metering",
1831
- description: "Usage metering, metric definitions, and threshold alerting",
1832
- domain: "platform",
1833
- owners: ["@platform.metering"],
1834
- tags: ["metering", "usage", "billing", "thresholds"],
1835
- stability: "stable"
1836
- },
1837
- operations: [
1838
- { key: "metric.define", version: "1.0.0" },
1839
- { key: "metric.update", version: "1.0.0" },
1840
- { key: "metric.delete", version: "1.0.0" },
1841
- { key: "metric.get", version: "1.0.0" },
1842
- { key: "metric.list", version: "1.0.0" },
1843
- { key: "usage.record", version: "1.0.0" },
1844
- { key: "usage.recordBatch", version: "1.0.0" },
1845
- { key: "usage.get", version: "1.0.0" },
1846
- { key: "usage.getSummary", version: "1.0.0" },
1847
- { key: "threshold.create", version: "1.0.0" },
1848
- { key: "threshold.update", version: "1.0.0" },
1849
- { key: "threshold.delete", version: "1.0.0" },
1850
- { key: "threshold.list", version: "1.0.0" }
1851
- ],
1852
- events: [
1853
- { key: "metric.defined", version: "1.0.0" },
1854
- { key: "metric.updated", version: "1.0.0" },
1855
- { key: "usage.recorded", version: "1.0.0" },
1856
- { key: "usage.batch_recorded", version: "1.0.0" },
1857
- { key: "usage.aggregated", version: "1.0.0" },
1858
- { key: "threshold.created", version: "1.0.0" },
1859
- { key: "threshold.exceeded", version: "1.0.0" },
1860
- { key: "threshold.approaching", version: "1.0.0" },
1861
- { key: "model.selected", version: "1.0.0" }
1862
- ],
1863
- presentations: [],
1864
- opToPresentation: [],
1865
- presentationsTargets: [],
1866
- capabilities: {
1867
- provides: [
1868
- { key: "metering", version: "1.0.0" },
1869
- { key: "thresholds", version: "1.0.0" }
1870
- ],
1871
- requires: []
1872
- }
1873
- });
1874
- export {
1875
- meteringSchemaContribution,
1876
- meteringEntities,
1877
- getPeriodStart,
1878
- getPeriodEnd,
1879
- formatPeriodKey,
1880
- UsageThresholdModel,
1881
- UsageThresholdEntity,
1882
- UsageSummaryModel,
1883
- UsageSummaryEntity,
1884
- UsageRecordedEvent,
1885
- UsageRecordModel,
1886
- UsageRecordEntity,
1887
- UsageBatchRecordedEvent,
1888
- UsageAlertEntity,
1889
- UsageAggregator,
1890
- UsageAggregatedEvent,
1891
- UpdateThresholdContract,
1892
- UpdateMetricContract,
1893
- ThresholdExceededEvent,
1894
- ThresholdCreatedEvent,
1895
- ThresholdApproachingEvent,
1896
- ThresholdActionEnum,
1897
- ResetPeriodEnum,
1898
- RecordUsageContract,
1899
- RecordBatchUsageContract,
1900
- PosthogMeteringReporter,
1901
- PosthogMeteringReader,
1902
- PeriodTypeEnum,
1903
- ModelSelectionEvent,
1904
- MetricUpdatedEvent,
1905
- MetricDefinitionModel,
1906
- MetricDefinitionEntity,
1907
- MetricDefinedEvent,
1908
- MeteringFeature,
1909
- MeteringEvents,
1910
- ListThresholdsContract,
1911
- ListMetricsContract,
1912
- InMemoryUsageStorage,
1913
- GetUsageSummaryContract,
1914
- GetUsageContract,
1915
- GetMetricContract,
1916
- DeleteThresholdContract,
1917
- DeleteMetricContract,
1918
- DefineMetricContract,
1919
- CreateThresholdContract,
1920
- AggregationTypeEnum
1921
- };
51
+ `}];qt(Vt);import{defineEntity as C,defineEntityEnum as J,field as b,index as K}from"@contractspec/lib.schema";var u=J({name:"AggregationType",values:["COUNT","SUM","AVG","MAX","MIN","LAST"],schema:"lssm_metering",description:"How to aggregate metric values."}),c=J({name:"ResetPeriod",values:["NEVER","HOURLY","DAILY","WEEKLY","MONTHLY","YEARLY"],schema:"lssm_metering",description:"When to reset metric counters."}),p=J({name:"PeriodType",values:["HOURLY","DAILY","WEEKLY","MONTHLY","YEARLY"],schema:"lssm_metering",description:"Time period for aggregation."}),y=J({name:"ThresholdAction",values:["NONE","ALERT","WARN","BLOCK","DOWNGRADE"],schema:"lssm_metering",description:"Action to take when threshold is exceeded."}),Ct=C({name:"MetricDefinition",description:"Definition of a usage metric.",schema:"lssm_metering",map:"metric_definition",fields:{id:b.id({description:"Unique identifier"}),key:b.string({isUnique:!0,description:"Metric key (e.g., api_calls, storage_gb)"}),name:b.string({description:"Human-readable name"}),description:b.string({isOptional:!0,description:"Metric description"}),unit:b.string({description:"Unit of measurement (calls, bytes, etc.)"}),aggregationType:b.enum("AggregationType",{default:"SUM",description:"How to aggregate values"}),resetPeriod:b.enum("ResetPeriod",{default:"MONTHLY",description:"When to reset counters"}),precision:b.int({default:2,description:"Decimal precision"}),orgId:b.string({isOptional:!0,description:"Organization scope (null = global metric)"}),category:b.string({isOptional:!0,description:"Category for grouping"}),displayOrder:b.int({default:0,description:"Order for display"}),metadata:b.json({isOptional:!0,description:"Additional metadata"}),isActive:b.boolean({default:!0,description:"Whether metric is active"}),createdAt:b.createdAt(),updatedAt:b.updatedAt(),usageRecords:b.hasMany("UsageRecord"),usageSummaries:b.hasMany("UsageSummary"),thresholds:b.hasMany("UsageThreshold")},indexes:[K.on(["orgId","key"]),K.on(["category"]),K.on(["isActive"])],enums:[u,c]}),it=C({name:"UsageRecord",description:"A single usage event.",schema:"lssm_metering",map:"usage_record",fields:{id:b.id({description:"Unique identifier"}),metricKey:b.string({description:"Metric being recorded"}),metricId:b.string({isOptional:!0,description:"Metric ID (for FK)"}),subjectType:b.string({description:"Subject type (org, user, project)"}),subjectId:b.string({description:"Subject identifier"}),quantity:b.decimal({description:"Usage quantity"}),source:b.string({isOptional:!0,description:"Source of usage (endpoint, feature, etc.)"}),resourceId:b.string({isOptional:!0,description:"Related resource ID"}),resourceType:b.string({isOptional:!0,description:"Related resource type"}),metadata:b.json({isOptional:!0,description:"Additional context"}),idempotencyKey:b.string({isOptional:!0,description:"Idempotency key for deduplication"}),timestamp:b.dateTime({description:"When usage occurred"}),createdAt:b.createdAt(),aggregated:b.boolean({default:!1,description:"Whether included in summary"}),aggregatedAt:b.dateTime({isOptional:!0,description:"When aggregated"})},indexes:[K.on(["metricKey","subjectType","subjectId","timestamp"]),K.on(["subjectType","subjectId","timestamp"]),K.on(["timestamp"]),K.on(["aggregated","timestamp"]),K.unique(["idempotencyKey"],{name:"usage_record_idempotency"})]}),zt=C({name:"UsageSummary",description:"Pre-aggregated usage summary.",schema:"lssm_metering",map:"usage_summary",fields:{id:b.id({description:"Unique identifier"}),metricKey:b.string({description:"Metric key"}),metricId:b.string({isOptional:!0,description:"Metric ID (for FK)"}),subjectType:b.string({description:"Subject type"}),subjectId:b.string({description:"Subject identifier"}),periodType:b.enum("PeriodType",{description:"Period type"}),periodStart:b.dateTime({description:"Period start time"}),periodEnd:b.dateTime({description:"Period end time"}),totalQuantity:b.decimal({description:"Total/aggregated quantity"}),recordCount:b.int({default:0,description:"Number of records aggregated"}),minQuantity:b.decimal({isOptional:!0,description:"Minimum value"}),maxQuantity:b.decimal({isOptional:!0,description:"Maximum value"}),avgQuantity:b.decimal({isOptional:!0,description:"Average value"}),metadata:b.json({isOptional:!0,description:"Additional metadata"}),createdAt:b.createdAt(),updatedAt:b.updatedAt()},indexes:[K.unique(["metricKey","subjectType","subjectId","periodType","periodStart"],{name:"usage_summary_unique"}),K.on(["subjectType","subjectId","periodType","periodStart"]),K.on(["metricKey","periodType","periodStart"])],enums:[p]}),Bt=C({name:"UsageThreshold",description:"Usage threshold configuration.",schema:"lssm_metering",map:"usage_threshold",fields:{id:b.id({description:"Unique identifier"}),metricKey:b.string({description:"Metric to monitor"}),metricId:b.string({isOptional:!0,description:"Metric ID (for FK)"}),subjectType:b.string({isOptional:!0,description:"Subject type"}),subjectId:b.string({isOptional:!0,description:"Subject identifier"}),name:b.string({description:"Threshold name"}),threshold:b.decimal({description:"Threshold value"}),warnThreshold:b.decimal({isOptional:!0,description:"Warning threshold (e.g., 80%)"}),periodType:b.enum("PeriodType",{default:"MONTHLY",description:"Period to evaluate"}),action:b.enum("ThresholdAction",{default:"ALERT",description:"Action when exceeded"}),notifyEmails:b.json({isOptional:!0,description:"Email addresses to notify"}),notifyWebhook:b.string({isOptional:!0,description:"Webhook URL to call"}),currentValue:b.decimal({default:0,description:"Current usage value"}),lastCheckedAt:b.dateTime({isOptional:!0,description:"Last threshold check"}),lastExceededAt:b.dateTime({isOptional:!0,description:"Last time threshold was exceeded"}),isActive:b.boolean({default:!0,description:"Whether threshold is active"}),metadata:b.json({isOptional:!0,description:"Additional metadata"}),createdAt:b.createdAt(),updatedAt:b.updatedAt()},indexes:[K.on(["metricKey"]),K.on(["subjectType","subjectId"]),K.on(["isActive","metricKey"])],enums:[y]}),Jt=C({name:"UsageAlert",description:"Alert generated when threshold is exceeded.",schema:"lssm_metering",map:"usage_alert",fields:{id:b.id({description:"Unique identifier"}),thresholdId:b.foreignKey({description:"Threshold that triggered alert"}),metricKey:b.string({description:"Metric key"}),subjectType:b.string({isOptional:!0,description:"Subject type"}),subjectId:b.string({isOptional:!0,description:"Subject identifier"}),alertType:b.string({description:"Alert type (warn, exceed, etc.)"}),threshold:b.decimal({description:"Threshold value"}),actualValue:b.decimal({description:"Actual usage value"}),percentageUsed:b.decimal({description:"Percentage of threshold used"}),status:b.string({default:'"pending"',description:"Alert status (pending, acknowledged, resolved)"}),acknowledgedBy:b.string({isOptional:!0,description:"User who acknowledged"}),acknowledgedAt:b.dateTime({isOptional:!0,description:"When acknowledged"}),resolvedAt:b.dateTime({isOptional:!0,description:"When resolved"}),notificationsSent:b.json({isOptional:!0,description:"Notifications sent"}),triggeredAt:b.dateTime({description:"When alert was triggered"}),createdAt:b.createdAt(),thresholdRelation:b.belongsTo("UsageThreshold",["thresholdId"],["id"],{onDelete:"Cascade"})},indexes:[K.on(["thresholdId","status"]),K.on(["metricKey","triggeredAt"]),K.on(["status","triggeredAt"])]}),Xt=[Ct,it,zt,Bt,Jt],_s={moduleId:"@contractspec/lib.metering",entities:Xt,enums:[u,c,p,y]};import{defineEvent as w}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as H,ScalarTypeEnum as j}from"@contractspec/lib.schema";var Yt=H({name:"MetricDefinedEventPayload",description:"Payload when a metric is defined",fields:{metricId:{type:j.String_unsecure(),isOptional:!1},key:{type:j.String_unsecure(),isOptional:!1},name:{type:j.String_unsecure(),isOptional:!1},unit:{type:j.String_unsecure(),isOptional:!1},aggregationType:{type:j.String_unsecure(),isOptional:!1},orgId:{type:j.String_unsecure(),isOptional:!0},createdBy:{type:j.String_unsecure(),isOptional:!0},createdAt:{type:j.DateTime(),isOptional:!1}}}),Zt=H({name:"MetricUpdatedEventPayload",description:"Payload when a metric is updated",fields:{metricId:{type:j.String_unsecure(),isOptional:!1},key:{type:j.String_unsecure(),isOptional:!1},changes:{type:j.JSON(),isOptional:!1},updatedBy:{type:j.String_unsecure(),isOptional:!0},updatedAt:{type:j.DateTime(),isOptional:!1}}}),vt=H({name:"UsageRecordedEventPayload",description:"Payload when usage is recorded",fields:{recordId:{type:j.String_unsecure(),isOptional:!1},metricKey:{type:j.String_unsecure(),isOptional:!1},subjectType:{type:j.String_unsecure(),isOptional:!1},subjectId:{type:j.String_unsecure(),isOptional:!1},quantity:{type:j.Float_unsecure(),isOptional:!1},source:{type:j.String_unsecure(),isOptional:!0},timestamp:{type:j.DateTime(),isOptional:!1}}}),Gt=H({name:"UsageBatchRecordedEventPayload",description:"Payload when a batch of usage is recorded",fields:{recordCount:{type:j.Int_unsecure(),isOptional:!1},metricKeys:{type:j.JSON(),isOptional:!1},totalQuantity:{type:j.Float_unsecure(),isOptional:!1},timestamp:{type:j.DateTime(),isOptional:!1}}}),xt=H({name:"UsageAggregatedEventPayload",description:"Payload when usage is aggregated",fields:{summaryId:{type:j.String_unsecure(),isOptional:!1},metricKey:{type:j.String_unsecure(),isOptional:!1},subjectType:{type:j.String_unsecure(),isOptional:!1},subjectId:{type:j.String_unsecure(),isOptional:!1},periodType:{type:j.String_unsecure(),isOptional:!1},periodStart:{type:j.DateTime(),isOptional:!1},periodEnd:{type:j.DateTime(),isOptional:!1},totalQuantity:{type:j.Float_unsecure(),isOptional:!1},recordCount:{type:j.Int_unsecure(),isOptional:!1},aggregatedAt:{type:j.DateTime(),isOptional:!1}}}),Ot=H({name:"ThresholdCreatedEventPayload",description:"Payload when a threshold is created",fields:{thresholdId:{type:j.String_unsecure(),isOptional:!1},metricKey:{type:j.String_unsecure(),isOptional:!1},subjectType:{type:j.String_unsecure(),isOptional:!0},subjectId:{type:j.String_unsecure(),isOptional:!0},threshold:{type:j.Float_unsecure(),isOptional:!1},action:{type:j.String_unsecure(),isOptional:!1},createdAt:{type:j.DateTime(),isOptional:!1}}}),Nt=H({name:"ThresholdExceededEventPayload",description:"Payload when a threshold is exceeded",fields:{alertId:{type:j.String_unsecure(),isOptional:!1},thresholdId:{type:j.String_unsecure(),isOptional:!1},metricKey:{type:j.String_unsecure(),isOptional:!1},subjectType:{type:j.String_unsecure(),isOptional:!0},subjectId:{type:j.String_unsecure(),isOptional:!0},threshold:{type:j.Float_unsecure(),isOptional:!1},actualValue:{type:j.Float_unsecure(),isOptional:!1},percentageUsed:{type:j.Float_unsecure(),isOptional:!1},action:{type:j.String_unsecure(),isOptional:!1},triggeredAt:{type:j.DateTime(),isOptional:!1}}}),Wt=H({name:"ThresholdApproachingEventPayload",description:"Payload when usage is approaching a threshold",fields:{thresholdId:{type:j.String_unsecure(),isOptional:!1},metricKey:{type:j.String_unsecure(),isOptional:!1},subjectType:{type:j.String_unsecure(),isOptional:!0},subjectId:{type:j.String_unsecure(),isOptional:!0},threshold:{type:j.Float_unsecure(),isOptional:!1},currentValue:{type:j.Float_unsecure(),isOptional:!1},percentageUsed:{type:j.Float_unsecure(),isOptional:!1},triggeredAt:{type:j.DateTime(),isOptional:!1}}}),Mt=w({meta:{key:"metric.defined",version:"1.0.0",description:"A metric has been defined.",stability:"stable",owners:["@platform.metering"],tags:["metering","metric"]},payload:Yt}),ht=w({meta:{key:"metric.updated",version:"1.0.0",description:"A metric has been updated.",stability:"stable",owners:["@platform.metering"],tags:["metering","metric"]},payload:Zt}),nt=w({meta:{key:"usage.recorded",version:"1.0.0",description:"Usage has been recorded.",stability:"stable",owners:["@platform.metering"],tags:["metering","usage"]},payload:vt}),ft=w({meta:{key:"usage.batch_recorded",version:"1.0.0",description:"A batch of usage has been recorded.",stability:"stable",owners:["@platform.metering"],tags:["metering","usage"]},payload:Gt}),ut=w({meta:{key:"usage.aggregated",version:"1.0.0",description:"Usage has been aggregated into a summary.",stability:"stable",owners:["@platform.metering"],tags:["metering","usage"]},payload:xt}),ct=w({meta:{key:"threshold.created",version:"1.0.0",description:"A usage threshold has been created.",stability:"stable",owners:["@platform.metering"],tags:["metering","threshold"]},payload:Ot}),pt=w({meta:{key:"threshold.exceeded",version:"1.0.0",description:"Usage has exceeded a threshold.",stability:"stable",owners:["@platform.metering"],tags:["metering","threshold"]},payload:Nt}),yt=w({meta:{key:"threshold.approaching",version:"1.0.0",description:"Usage is approaching a threshold.",stability:"stable",owners:["@platform.metering"],tags:["metering","threshold"]},payload:Wt}),et=H({name:"ModelSelectionEventPayload",description:"Payload when an AI model is selected via ranking",fields:{modelId:{type:j.String_unsecure(),isOptional:!1},providerKey:{type:j.String_unsecure(),isOptional:!1},dimension:{type:j.String_unsecure(),isOptional:!0},score:{type:j.Float_unsecure(),isOptional:!1},reason:{type:j.String_unsecure(),isOptional:!1},alternativesCount:{type:j.Int_unsecure(),isOptional:!1},costEstimateInput:{type:j.Float_unsecure(),isOptional:!0},costEstimateOutput:{type:j.Float_unsecure(),isOptional:!0},selectionDurationMs:{type:j.Float_unsecure(),isOptional:!0},timestamp:{type:j.DateTime(),isOptional:!1}}}),dt=w({meta:{key:"model.selected",version:"1.0.0",description:"An AI model has been selected via ranking-driven selection.",stability:"experimental",owners:["@platform.metering"],tags:["metering","ai","model-selection"]},payload:et}),Us={MetricDefinedEvent:Mt,MetricUpdatedEvent:ht,UsageRecordedEvent:nt,UsageBatchRecordedEvent:ft,UsageAggregatedEvent:ut,ThresholdCreatedEvent:ct,ThresholdExceededEvent:pt,ThresholdApproachingEvent:yt,ModelSelectionEvent:dt};import{defineFeature as Tt}from"@contractspec/lib.contracts-spec";var Cs=Tt({meta:{key:"metrics",version:"1.0.0",title:"Usage Metering",description:"Usage metering, metric definitions, and threshold alerting",domain:"platform",owners:["@platform.metering"],tags:["metering","usage","billing","thresholds"],stability:"stable"},operations:[{key:"metric.define",version:"1.0.0"},{key:"metric.update",version:"1.0.0"},{key:"metric.delete",version:"1.0.0"},{key:"metric.get",version:"1.0.0"},{key:"metric.list",version:"1.0.0"},{key:"usage.record",version:"1.0.0"},{key:"usage.recordBatch",version:"1.0.0"},{key:"usage.get",version:"1.0.0"},{key:"usage.getSummary",version:"1.0.0"},{key:"threshold.create",version:"1.0.0"},{key:"threshold.update",version:"1.0.0"},{key:"threshold.delete",version:"1.0.0"},{key:"threshold.list",version:"1.0.0"}],events:[{key:"metric.defined",version:"1.0.0"},{key:"metric.updated",version:"1.0.0"},{key:"usage.recorded",version:"1.0.0"},{key:"usage.batch_recorded",version:"1.0.0"},{key:"usage.aggregated",version:"1.0.0"},{key:"threshold.created",version:"1.0.0"},{key:"threshold.exceeded",version:"1.0.0"},{key:"threshold.approaching",version:"1.0.0"},{key:"model.selected",version:"1.0.0"}],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{provides:[{key:"metering",version:"1.0.0"},{key:"thresholds",version:"1.0.0"}],requires:[]}});export{_s as meteringSchemaContribution,Xt as meteringEntities,Z as getPeriodStart,N as getPeriodEnd,e as formatPeriodKey,x as UsageThresholdModel,Bt as UsageThresholdEntity,jt as UsageSummaryModel,zt as UsageSummaryEntity,nt as UsageRecordedEvent,h as UsageRecordModel,it as UsageRecordEntity,ft as UsageBatchRecordedEvent,Jt as UsageAlertEntity,T as UsageAggregator,ut as UsageAggregatedEvent,Qs as UpdateThresholdContract,ss as UpdateMetricContract,pt as ThresholdExceededEvent,ct as ThresholdCreatedEvent,yt as ThresholdApproachingEvent,y as ThresholdActionEnum,c as ResetPeriodEnum,Is as RecordUsageContract,rs as RecordBatchUsageContract,bt as PosthogMeteringReporter,m as PosthogMeteringReader,p as PeriodTypeEnum,dt as ModelSelectionEvent,ht as MetricUpdatedEvent,B as MetricDefinitionModel,Ct as MetricDefinitionEntity,Mt as MetricDefinedEvent,Cs as MeteringFeature,Us as MeteringEvents,Ks as ListThresholdsContract,js as ListMetricsContract,S as InMemoryUsageStorage,As as GetUsageSummaryContract,os as GetUsageContract,bs as GetMetricContract,ks as DeleteThresholdContract,gs as DeleteMetricContract,ts as DefineMetricContract,Rs as CreateThresholdContract,u as AggregationTypeEnum};