@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 +52 -22
- package/dist/browser/index.js +36 -36
- package/dist/index.d.ts +2 -2
- package/dist/index.js +36 -36
- package/dist/node/index.js +36 -36
- package/package.json +5 -5
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
|
-
|
|
7
|
+
## What It Provides
|
|
7
8
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
`npm install @contractspec/lib.cost-tracking`
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
import { CostTracker, defaultCostModel } from '@contractspec/lib.cost-tracking';
|
|
18
|
+
or
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
`bun add @contractspec/lib.cost-tracking`
|
|
22
21
|
|
|
23
|
-
|
|
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
|
-
|
|
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.
|
package/dist/browser/index.js
CHANGED
|
@@ -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
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) {
|
package/dist/node/index.js
CHANGED
|
@@ -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.
|
|
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": "
|
|
29
|
-
"lint:check": "
|
|
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.
|
|
35
|
+
"@contractspec/tool.typescript": "3.7.8",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
|
-
"@contractspec/tool.bun": "3.7.
|
|
37
|
+
"@contractspec/tool.bun": "3.7.8"
|
|
38
38
|
},
|
|
39
39
|
"exports": {
|
|
40
40
|
".": {
|