@contractspec/lib.cost-tracking 3.7.6 → 3.7.8

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.
package/README.md CHANGED
@@ -1,33 +1,63 @@
1
1
  # @contractspec/lib.cost-tracking
2
2
 
3
- Website: https://contractspec.io/
3
+ Website: https://contractspec.io
4
4
 
5
+ **API cost tracking and budgeting.**
5
6
 
6
- Cost attribution, budgeting, and optimization helpers for ContractSpec operations.
7
+ ## What It Provides
7
8
 
8
- ## Features
9
+ - **Layer**: lib.
10
+ - **Consumers**: bundles.
11
+ - Related ContractSpec packages include `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
12
+ - Related ContractSpec packages include `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
9
13
 
10
- - Normalize DB/API/compute metrics into unified cost samples
11
- - Attribute costs per operation, tenant, and feature flag
12
- - Detect budget overruns with configurable alerts
13
- - Generate optimization suggestions (N+1, caching, batching)
14
- - Integrates with telemetry produced via `@contractspec/lib.observability`
14
+ ## Installation
15
15
 
16
- ## Example
16
+ `npm install @contractspec/lib.cost-tracking`
17
17
 
18
- ```ts
19
- import { CostTracker, defaultCostModel } from '@contractspec/lib.cost-tracking';
18
+ or
20
19
 
21
- const tracker = new CostTracker({ costModel: defaultCostModel });
20
+ `bun add @contractspec/lib.cost-tracking`
22
21
 
23
- tracker.recordSample({
24
- operation: 'orders.list',
25
- tenantId: 'acme',
26
- dbReads: 12,
27
- dbWrites: 2,
28
- externalCalls: [{ provider: 'stripe', cost: 0.02 }],
29
- computeMs: 150,
30
- });
22
+ ## Usage
31
23
 
32
- const summary = tracker.getTotals();
33
- ```
24
+ Import the root entrypoint from `@contractspec/lib.cost-tracking`, or choose a documented subpath when you only need one part of the package surface.
25
+
26
+ ## Architecture
27
+
28
+ - `src/budget-alert-manager.ts` is part of the package's public or composition surface.
29
+ - `src/cost-model.ts` is part of the package's public or composition surface.
30
+ - `src/cost-tracker.ts` is part of the package's public or composition surface.
31
+ - `src/index.ts` is the root public barrel and package entrypoint.
32
+ - `src/optimization-recommender.ts` is part of the package's public or composition surface.
33
+ - `src/types.ts` is shared public type definitions.
34
+
35
+ ## Public Entry Points
36
+
37
+ - Export `.` resolves through `./src/index.ts`.
38
+
39
+ ## Local Commands
40
+
41
+ - `bun run dev` — contractspec-bun-build dev
42
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
43
+ - `bun run test` — bun test --pass-with-no-tests
44
+ - `bun run lint` — bun lint:fix
45
+ - `bun run lint:check` — biome check .
46
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
47
+ - `bun run typecheck` — tsc --noEmit
48
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
49
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
50
+ - `bun run clean` — rimraf dist .turbo
51
+ - `bun run build:bundle` — contractspec-bun-build transpile
52
+ - `bun run build:types` — contractspec-bun-build types
53
+ - `bun run prebuild` — contractspec-bun-build prebuild
54
+
55
+ ## Recent Updates
56
+
57
+ - Replace eslint+prettier by biomejs to optimize speed.
58
+
59
+ ## Notes
60
+
61
+ - Cost calculation logic must stay deterministic — no side effects or external calls during computation.
62
+ - Budget threshold types are shared across consumers; changes require coordination.
63
+ - Do not introduce floating-point arithmetic where precision matters; use integer cents or a decimal library.
@@ -1,3 +1,39 @@
1
+ // src/budget-alert-manager.ts
2
+ class BudgetAlertManager {
3
+ options;
4
+ limits = new Map;
5
+ spend = new Map;
6
+ constructor(options) {
7
+ this.options = options;
8
+ for (const budget of options.budgets) {
9
+ this.limits.set(budget.tenantId, budget);
10
+ this.spend.set(budget.tenantId, 0);
11
+ }
12
+ }
13
+ track(summary) {
14
+ if (!summary.tenantId) {
15
+ return;
16
+ }
17
+ const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
18
+ this.spend.set(summary.tenantId, current);
19
+ const budget = this.limits.get(summary.tenantId);
20
+ if (!budget) {
21
+ return;
22
+ }
23
+ const threshold = budget.alertThreshold ?? 0.8;
24
+ if (current >= budget.monthlyLimit * threshold) {
25
+ this.options.onAlert?.({
26
+ tenantId: summary.tenantId,
27
+ limit: budget.monthlyLimit,
28
+ total: current,
29
+ summary
30
+ });
31
+ }
32
+ }
33
+ getSpend(tenantId) {
34
+ return this.spend.get(tenantId) ?? 0;
35
+ }
36
+ }
1
37
  // src/cost-model.ts
2
38
  var defaultCostModel = {
3
39
  dbReadCost: 0.000002,
@@ -74,42 +110,6 @@ class CostTracker {
74
110
  return tenantId ? `${tenantId}:${operation}` : operation;
75
111
  }
76
112
  }
77
- // src/budget-alert-manager.ts
78
- class BudgetAlertManager {
79
- options;
80
- limits = new Map;
81
- spend = new Map;
82
- constructor(options) {
83
- this.options = options;
84
- for (const budget of options.budgets) {
85
- this.limits.set(budget.tenantId, budget);
86
- this.spend.set(budget.tenantId, 0);
87
- }
88
- }
89
- track(summary) {
90
- if (!summary.tenantId) {
91
- return;
92
- }
93
- const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
94
- this.spend.set(summary.tenantId, current);
95
- const budget = this.limits.get(summary.tenantId);
96
- if (!budget) {
97
- return;
98
- }
99
- const threshold = budget.alertThreshold ?? 0.8;
100
- if (current >= budget.monthlyLimit * threshold) {
101
- this.options.onAlert?.({
102
- tenantId: summary.tenantId,
103
- limit: budget.monthlyLimit,
104
- total: current,
105
- summary
106
- });
107
- }
108
- }
109
- getSpend(tenantId) {
110
- return this.spend.get(tenantId) ?? 0;
111
- }
112
- }
113
113
  // src/optimization-recommender.ts
114
114
  class OptimizationRecommender {
115
115
  generate(summary) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './types';
1
+ export * from './budget-alert-manager';
2
2
  export * from './cost-model';
3
3
  export * from './cost-tracker';
4
- export * from './budget-alert-manager';
5
4
  export * from './optimization-recommender';
5
+ export * from './types';
package/dist/index.js CHANGED
@@ -1,4 +1,40 @@
1
1
  // @bun
2
+ // src/budget-alert-manager.ts
3
+ class BudgetAlertManager {
4
+ options;
5
+ limits = new Map;
6
+ spend = new Map;
7
+ constructor(options) {
8
+ this.options = options;
9
+ for (const budget of options.budgets) {
10
+ this.limits.set(budget.tenantId, budget);
11
+ this.spend.set(budget.tenantId, 0);
12
+ }
13
+ }
14
+ track(summary) {
15
+ if (!summary.tenantId) {
16
+ return;
17
+ }
18
+ const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
19
+ this.spend.set(summary.tenantId, current);
20
+ const budget = this.limits.get(summary.tenantId);
21
+ if (!budget) {
22
+ return;
23
+ }
24
+ const threshold = budget.alertThreshold ?? 0.8;
25
+ if (current >= budget.monthlyLimit * threshold) {
26
+ this.options.onAlert?.({
27
+ tenantId: summary.tenantId,
28
+ limit: budget.monthlyLimit,
29
+ total: current,
30
+ summary
31
+ });
32
+ }
33
+ }
34
+ getSpend(tenantId) {
35
+ return this.spend.get(tenantId) ?? 0;
36
+ }
37
+ }
2
38
  // src/cost-model.ts
3
39
  var defaultCostModel = {
4
40
  dbReadCost: 0.000002,
@@ -75,42 +111,6 @@ class CostTracker {
75
111
  return tenantId ? `${tenantId}:${operation}` : operation;
76
112
  }
77
113
  }
78
- // src/budget-alert-manager.ts
79
- class BudgetAlertManager {
80
- options;
81
- limits = new Map;
82
- spend = new Map;
83
- constructor(options) {
84
- this.options = options;
85
- for (const budget of options.budgets) {
86
- this.limits.set(budget.tenantId, budget);
87
- this.spend.set(budget.tenantId, 0);
88
- }
89
- }
90
- track(summary) {
91
- if (!summary.tenantId) {
92
- return;
93
- }
94
- const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
95
- this.spend.set(summary.tenantId, current);
96
- const budget = this.limits.get(summary.tenantId);
97
- if (!budget) {
98
- return;
99
- }
100
- const threshold = budget.alertThreshold ?? 0.8;
101
- if (current >= budget.monthlyLimit * threshold) {
102
- this.options.onAlert?.({
103
- tenantId: summary.tenantId,
104
- limit: budget.monthlyLimit,
105
- total: current,
106
- summary
107
- });
108
- }
109
- }
110
- getSpend(tenantId) {
111
- return this.spend.get(tenantId) ?? 0;
112
- }
113
- }
114
114
  // src/optimization-recommender.ts
115
115
  class OptimizationRecommender {
116
116
  generate(summary) {
@@ -1,3 +1,39 @@
1
+ // src/budget-alert-manager.ts
2
+ class BudgetAlertManager {
3
+ options;
4
+ limits = new Map;
5
+ spend = new Map;
6
+ constructor(options) {
7
+ this.options = options;
8
+ for (const budget of options.budgets) {
9
+ this.limits.set(budget.tenantId, budget);
10
+ this.spend.set(budget.tenantId, 0);
11
+ }
12
+ }
13
+ track(summary) {
14
+ if (!summary.tenantId) {
15
+ return;
16
+ }
17
+ const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
18
+ this.spend.set(summary.tenantId, current);
19
+ const budget = this.limits.get(summary.tenantId);
20
+ if (!budget) {
21
+ return;
22
+ }
23
+ const threshold = budget.alertThreshold ?? 0.8;
24
+ if (current >= budget.monthlyLimit * threshold) {
25
+ this.options.onAlert?.({
26
+ tenantId: summary.tenantId,
27
+ limit: budget.monthlyLimit,
28
+ total: current,
29
+ summary
30
+ });
31
+ }
32
+ }
33
+ getSpend(tenantId) {
34
+ return this.spend.get(tenantId) ?? 0;
35
+ }
36
+ }
1
37
  // src/cost-model.ts
2
38
  var defaultCostModel = {
3
39
  dbReadCost: 0.000002,
@@ -74,42 +110,6 @@ class CostTracker {
74
110
  return tenantId ? `${tenantId}:${operation}` : operation;
75
111
  }
76
112
  }
77
- // src/budget-alert-manager.ts
78
- class BudgetAlertManager {
79
- options;
80
- limits = new Map;
81
- spend = new Map;
82
- constructor(options) {
83
- this.options = options;
84
- for (const budget of options.budgets) {
85
- this.limits.set(budget.tenantId, budget);
86
- this.spend.set(budget.tenantId, 0);
87
- }
88
- }
89
- track(summary) {
90
- if (!summary.tenantId) {
91
- return;
92
- }
93
- const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
94
- this.spend.set(summary.tenantId, current);
95
- const budget = this.limits.get(summary.tenantId);
96
- if (!budget) {
97
- return;
98
- }
99
- const threshold = budget.alertThreshold ?? 0.8;
100
- if (current >= budget.monthlyLimit * threshold) {
101
- this.options.onAlert?.({
102
- tenantId: summary.tenantId,
103
- limit: budget.monthlyLimit,
104
- total: current,
105
- summary
106
- });
107
- }
108
- }
109
- getSpend(tenantId) {
110
- return this.spend.get(tenantId) ?? 0;
111
- }
112
- }
113
113
  // src/optimization-recommender.ts
114
114
  class OptimizationRecommender {
115
115
  generate(summary) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.cost-tracking",
3
- "version": "3.7.6",
3
+ "version": "3.7.8",
4
4
  "description": "API cost tracking and budgeting",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -25,16 +25,16 @@
25
25
  "dev": "contractspec-bun-build dev",
26
26
  "clean": "rimraf dist .turbo",
27
27
  "lint": "bun lint:fix",
28
- "lint:fix": "eslint src --fix",
29
- "lint:check": "eslint src",
28
+ "lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
29
+ "lint:check": "biome check .",
30
30
  "test": "bun test --pass-with-no-tests",
31
31
  "prebuild": "contractspec-bun-build prebuild",
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "devDependencies": {
35
- "@contractspec/tool.typescript": "3.7.6",
35
+ "@contractspec/tool.typescript": "3.7.8",
36
36
  "typescript": "^5.9.3",
37
- "@contractspec/tool.bun": "3.7.6"
37
+ "@contractspec/tool.bun": "3.7.8"
38
38
  },
39
39
  "exports": {
40
40
  ".": {