@contractspec/lib.cost-tracking 1.57.0 → 1.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ // src/cost-model.ts
2
+ var defaultCostModel = {
3
+ dbReadCost: 0.000002,
4
+ dbWriteCost: 0.00001,
5
+ computeMsCost: 0.00000015,
6
+ memoryMbMsCost: 0.00000002
7
+ };
8
+ function calculateSampleCost(sample, model) {
9
+ const external = (sample.externalCalls ?? []).reduce((sum, call) => sum + (call.cost ?? 0), 0);
10
+ return {
11
+ dbReads: (sample.dbReads ?? 0) * model.dbReadCost,
12
+ dbWrites: (sample.dbWrites ?? 0) * model.dbWriteCost,
13
+ compute: (sample.computeMs ?? 0) * model.computeMsCost,
14
+ memory: (sample.memoryMbMs ?? 0) * model.memoryMbMsCost,
15
+ external,
16
+ custom: sample.customCost ?? 0
17
+ };
18
+ }
19
+ // src/cost-tracker.ts
20
+ class CostTracker {
21
+ options;
22
+ totals = new Map;
23
+ costModel;
24
+ constructor(options = {}) {
25
+ this.options = options;
26
+ this.costModel = options.costModel ?? defaultCostModel;
27
+ }
28
+ recordSample(sample) {
29
+ const breakdown = calculateSampleCost(sample, this.costModel);
30
+ const total = breakdown.dbReads + breakdown.dbWrites + breakdown.compute + breakdown.memory + breakdown.external + breakdown.custom;
31
+ const key = this.buildKey(sample.operation, sample.tenantId);
32
+ const existing = this.totals.get(key);
33
+ const summary = existing ? {
34
+ ...existing,
35
+ total: existing.total + total,
36
+ breakdown: {
37
+ dbReads: existing.breakdown.dbReads + breakdown.dbReads,
38
+ dbWrites: existing.breakdown.dbWrites + breakdown.dbWrites,
39
+ compute: existing.breakdown.compute + breakdown.compute,
40
+ memory: existing.breakdown.memory + breakdown.memory,
41
+ external: existing.breakdown.external + breakdown.external,
42
+ custom: existing.breakdown.custom + breakdown.custom
43
+ },
44
+ samples: existing.samples + 1
45
+ } : {
46
+ operation: sample.operation,
47
+ tenantId: sample.tenantId,
48
+ total,
49
+ breakdown: {
50
+ dbReads: breakdown.dbReads,
51
+ dbWrites: breakdown.dbWrites,
52
+ compute: breakdown.compute,
53
+ memory: breakdown.memory,
54
+ external: breakdown.external,
55
+ custom: breakdown.custom
56
+ },
57
+ samples: 1
58
+ };
59
+ this.totals.set(key, summary);
60
+ this.options.onSampleRecorded?.(sample, total);
61
+ return summary;
62
+ }
63
+ getTotals(filter) {
64
+ const items = Array.from(this.totals.values());
65
+ if (!filter?.tenantId) {
66
+ return items;
67
+ }
68
+ return items.filter((item) => item.tenantId === filter.tenantId);
69
+ }
70
+ reset() {
71
+ this.totals.clear();
72
+ }
73
+ buildKey(operation, tenantId) {
74
+ return tenantId ? `${tenantId}:${operation}` : operation;
75
+ }
76
+ }
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
+ // src/optimization-recommender.ts
114
+ class OptimizationRecommender {
115
+ generate(summary) {
116
+ const suggestions = [];
117
+ const avgCost = summary.total / summary.samples;
118
+ if (summary.breakdown.dbReads / summary.samples > 1000) {
119
+ suggestions.push({
120
+ operation: summary.operation,
121
+ tenantId: summary.tenantId,
122
+ category: "n_plus_one",
123
+ message: "High average DB read count detected. Consider batching queries or adding pagination.",
124
+ evidence: { avgReads: summary.breakdown.dbReads / summary.samples }
125
+ });
126
+ }
127
+ if (summary.breakdown.compute / summary.total > 0.6) {
128
+ suggestions.push({
129
+ operation: summary.operation,
130
+ tenantId: summary.tenantId,
131
+ category: "batching",
132
+ message: "Compute dominates cost. Investigate hot loops or move heavy logic to background jobs.",
133
+ evidence: { computeShare: summary.breakdown.compute / summary.total }
134
+ });
135
+ }
136
+ if (summary.breakdown.external > avgCost * 0.5) {
137
+ suggestions.push({
138
+ operation: summary.operation,
139
+ tenantId: summary.tenantId,
140
+ category: "external",
141
+ message: "External provider spend is high. Reuse results or enable caching.",
142
+ evidence: { externalCost: summary.breakdown.external }
143
+ });
144
+ }
145
+ if (summary.breakdown.memory > summary.breakdown.compute * 1.2) {
146
+ suggestions.push({
147
+ operation: summary.operation,
148
+ tenantId: summary.tenantId,
149
+ category: "caching",
150
+ message: "Memory utilization suggests cached payloads linger. Tune TTL or stream responses.",
151
+ evidence: { memoryCost: summary.breakdown.memory }
152
+ });
153
+ }
154
+ return suggestions;
155
+ }
156
+ }
157
+ export {
158
+ defaultCostModel,
159
+ calculateSampleCost,
160
+ OptimizationRecommender,
161
+ CostTracker,
162
+ BudgetAlertManager
163
+ };
@@ -1,23 +1,19 @@
1
- import { OperationCostSummary, TenantBudget } from "./types.js";
2
-
3
- //#region src/budget-alert-manager.d.ts
4
- interface BudgetAlertManagerOptions {
5
- budgets: TenantBudget[];
6
- onAlert?: (payload: {
7
- tenantId: string;
8
- limit: number;
9
- total: number;
10
- summary: OperationCostSummary;
11
- }) => void;
1
+ import type { OperationCostSummary, TenantBudget } from './types';
2
+ export interface BudgetAlertManagerOptions {
3
+ budgets: TenantBudget[];
4
+ onAlert?: (payload: {
5
+ tenantId: string;
6
+ limit: number;
7
+ total: number;
8
+ summary: OperationCostSummary;
9
+ }) => void;
12
10
  }
13
- declare class BudgetAlertManager {
14
- private readonly options;
15
- private readonly limits;
16
- private readonly spend;
17
- constructor(options: BudgetAlertManagerOptions);
18
- track(summary: OperationCostSummary): void;
19
- getSpend(tenantId: string): number;
11
+ export declare class BudgetAlertManager {
12
+ private readonly options;
13
+ private readonly limits;
14
+ private readonly spend;
15
+ constructor(options: BudgetAlertManagerOptions);
16
+ track(summary: OperationCostSummary): void;
17
+ getSpend(tenantId: string): number;
20
18
  }
21
- //#endregion
22
- export { BudgetAlertManager, BudgetAlertManagerOptions };
23
19
  //# sourceMappingURL=budget-alert-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"budget-alert-manager.d.ts","names":[],"sources":["../src/budget-alert-manager.ts"],"mappings":";;;UAEiB,yBAAA;EACf,OAAA,EAAS,YAAA;EACT,OAAA,IAAW,OAAA;IACT,QAAA;IACA,KAAA;IACA,KAAA;IACA,OAAA,EAAS,oBAAA;EAAA;AAAA;AAAA,cAIA,kBAAA;EAAA,iBAIkB,OAAA;EAAA,iBAHZ,MAAA;EAAA,iBACA,KAAA;cAEY,OAAA,EAAS,yBAAA;EAOtC,KAAA,CAAM,OAAA,EAAS,oBAAA;EAwBf,QAAA,CAAS,QAAA;AAAA"}
1
+ {"version":3,"file":"budget-alert-manager.d.ts","sourceRoot":"","sources":["../src/budget-alert-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAElE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,oBAAoB,CAAC;KAC/B,KAAK,IAAI,CAAC;CACZ;AAED,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;gBAEtB,OAAO,EAAE,yBAAyB;IAO/D,KAAK,CAAC,OAAO,EAAE,oBAAoB;IAwBnC,QAAQ,CAAC,QAAQ,EAAE,MAAM;CAG1B"}
@@ -1,15 +1,11 @@
1
- import { CostModel, CostSample } from "./types.js";
2
-
3
- //#region src/cost-model.d.ts
4
- declare const defaultCostModel: CostModel;
5
- declare function calculateSampleCost(sample: CostSample, model: CostModel): {
6
- dbReads: number;
7
- dbWrites: number;
8
- compute: number;
9
- memory: number;
10
- external: number;
11
- custom: number;
1
+ import type { CostModel, CostSample } from './types';
2
+ export declare const defaultCostModel: CostModel;
3
+ export declare function calculateSampleCost(sample: CostSample, model: CostModel): {
4
+ dbReads: number;
5
+ dbWrites: number;
6
+ compute: number;
7
+ memory: number;
8
+ external: number;
9
+ custom: number;
12
10
  };
13
- //#endregion
14
- export { calculateSampleCost, defaultCostModel };
15
11
  //# sourceMappingURL=cost-model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost-model.d.ts","names":[],"sources":["../src/cost-model.ts"],"mappings":";;;cAEa,gBAAA,EAAkB,SAAA;AAAA,iBAOf,mBAAA,CAAoB,MAAA,EAAQ,UAAA,EAAY,KAAA,EAAO,SAAA"}
1
+ {"version":3,"file":"cost-model.d.ts","sourceRoot":"","sources":["../src/cost-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD,eAAO,MAAM,gBAAgB,EAAE,SAK9B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS;;;;;;;EAcvE"}
@@ -1,22 +1,18 @@
1
- import { CostModel, CostSample, OperationCostSummary } from "./types.js";
2
-
3
- //#region src/cost-tracker.d.ts
4
- interface CostTrackerOptions {
5
- costModel?: CostModel;
6
- onSampleRecorded?: (sample: CostSample, total: number) => void;
1
+ import type { CostModel, CostSample, OperationCostSummary } from './types';
2
+ export interface CostTrackerOptions {
3
+ costModel?: CostModel;
4
+ onSampleRecorded?: (sample: CostSample, total: number) => void;
7
5
  }
8
- declare class CostTracker {
9
- private readonly options;
10
- private readonly totals;
11
- private readonly costModel;
12
- constructor(options?: CostTrackerOptions);
13
- recordSample(sample: CostSample): OperationCostSummary;
14
- getTotals(filter?: {
15
- tenantId?: string;
16
- }): OperationCostSummary[];
17
- reset(): void;
18
- private buildKey;
6
+ export declare class CostTracker {
7
+ private readonly options;
8
+ private readonly totals;
9
+ private readonly costModel;
10
+ constructor(options?: CostTrackerOptions);
11
+ recordSample(sample: CostSample): OperationCostSummary;
12
+ getTotals(filter?: {
13
+ tenantId?: string;
14
+ }): OperationCostSummary[];
15
+ reset(): void;
16
+ private buildKey;
19
17
  }
20
- //#endregion
21
- export { CostTracker, CostTrackerOptions };
22
18
  //# sourceMappingURL=cost-tracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost-tracker.d.ts","names":[],"sources":["../src/cost-tracker.ts"],"mappings":";;;UAGiB,kBAAA;EACf,SAAA,GAAY,SAAA;EACZ,gBAAA,IAAoB,MAAA,EAAQ,UAAA,EAAY,KAAA;AAAA;AAAA,cAG7B,WAAA;EAAA,iBAIkB,OAAA;EAAA,iBAHZ,MAAA;EAAA,iBACA,SAAA;cAEY,OAAA,GAAS,kBAAA;EAItC,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,oBAAA;EA+ClC,SAAA,CAAU,MAAA;IAAW,QAAA;EAAA,IAAmB,oBAAA;EAQxC,KAAA,CAAA;EAAA,QAIQ,QAAA;AAAA"}
1
+ {"version":3,"file":"cost-tracker.d.ts","sourceRoot":"","sources":["../src/cost-tracker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChE;AAED,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAClE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAET,OAAO,GAAE,kBAAuB;IAI7D,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,oBAAoB;IA+CtD,SAAS,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAQxC,KAAK;IAIL,OAAO,CAAC,QAAQ;CAGjB"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { BudgetAlert, CostModel, CostSample, ExternalCallCost, OperationCostSummary, OptimizationSuggestion, TenantBudget } from "./types.js";
2
- import { calculateSampleCost, defaultCostModel } from "./cost-model.js";
3
- import { CostTracker, CostTrackerOptions } from "./cost-tracker.js";
4
- import { BudgetAlertManager, BudgetAlertManagerOptions } from "./budget-alert-manager.js";
5
- import { OptimizationRecommender } from "./optimization-recommender.js";
6
- export { BudgetAlert, BudgetAlertManager, BudgetAlertManagerOptions, CostModel, CostSample, CostTracker, CostTrackerOptions, ExternalCallCost, OperationCostSummary, OptimizationRecommender, OptimizationSuggestion, TenantBudget, calculateSampleCost, defaultCostModel };
1
+ export * from './types';
2
+ export * from './cost-model';
3
+ export * from './cost-tracker';
4
+ export * from './budget-alert-manager';
5
+ export * from './optimization-recommender';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,164 @@
1
- import { calculateSampleCost, defaultCostModel } from "./cost-model.js";
2
- import { CostTracker } from "./cost-tracker.js";
3
- import { BudgetAlertManager } from "./budget-alert-manager.js";
4
- import { OptimizationRecommender } from "./optimization-recommender.js";
5
-
6
- export { BudgetAlertManager, CostTracker, OptimizationRecommender, calculateSampleCost, defaultCostModel };
1
+ // @bun
2
+ // src/cost-model.ts
3
+ var defaultCostModel = {
4
+ dbReadCost: 0.000002,
5
+ dbWriteCost: 0.00001,
6
+ computeMsCost: 0.00000015,
7
+ memoryMbMsCost: 0.00000002
8
+ };
9
+ function calculateSampleCost(sample, model) {
10
+ const external = (sample.externalCalls ?? []).reduce((sum, call) => sum + (call.cost ?? 0), 0);
11
+ return {
12
+ dbReads: (sample.dbReads ?? 0) * model.dbReadCost,
13
+ dbWrites: (sample.dbWrites ?? 0) * model.dbWriteCost,
14
+ compute: (sample.computeMs ?? 0) * model.computeMsCost,
15
+ memory: (sample.memoryMbMs ?? 0) * model.memoryMbMsCost,
16
+ external,
17
+ custom: sample.customCost ?? 0
18
+ };
19
+ }
20
+ // src/cost-tracker.ts
21
+ class CostTracker {
22
+ options;
23
+ totals = new Map;
24
+ costModel;
25
+ constructor(options = {}) {
26
+ this.options = options;
27
+ this.costModel = options.costModel ?? defaultCostModel;
28
+ }
29
+ recordSample(sample) {
30
+ const breakdown = calculateSampleCost(sample, this.costModel);
31
+ const total = breakdown.dbReads + breakdown.dbWrites + breakdown.compute + breakdown.memory + breakdown.external + breakdown.custom;
32
+ const key = this.buildKey(sample.operation, sample.tenantId);
33
+ const existing = this.totals.get(key);
34
+ const summary = existing ? {
35
+ ...existing,
36
+ total: existing.total + total,
37
+ breakdown: {
38
+ dbReads: existing.breakdown.dbReads + breakdown.dbReads,
39
+ dbWrites: existing.breakdown.dbWrites + breakdown.dbWrites,
40
+ compute: existing.breakdown.compute + breakdown.compute,
41
+ memory: existing.breakdown.memory + breakdown.memory,
42
+ external: existing.breakdown.external + breakdown.external,
43
+ custom: existing.breakdown.custom + breakdown.custom
44
+ },
45
+ samples: existing.samples + 1
46
+ } : {
47
+ operation: sample.operation,
48
+ tenantId: sample.tenantId,
49
+ total,
50
+ breakdown: {
51
+ dbReads: breakdown.dbReads,
52
+ dbWrites: breakdown.dbWrites,
53
+ compute: breakdown.compute,
54
+ memory: breakdown.memory,
55
+ external: breakdown.external,
56
+ custom: breakdown.custom
57
+ },
58
+ samples: 1
59
+ };
60
+ this.totals.set(key, summary);
61
+ this.options.onSampleRecorded?.(sample, total);
62
+ return summary;
63
+ }
64
+ getTotals(filter) {
65
+ const items = Array.from(this.totals.values());
66
+ if (!filter?.tenantId) {
67
+ return items;
68
+ }
69
+ return items.filter((item) => item.tenantId === filter.tenantId);
70
+ }
71
+ reset() {
72
+ this.totals.clear();
73
+ }
74
+ buildKey(operation, tenantId) {
75
+ return tenantId ? `${tenantId}:${operation}` : operation;
76
+ }
77
+ }
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
+ // src/optimization-recommender.ts
115
+ class OptimizationRecommender {
116
+ generate(summary) {
117
+ const suggestions = [];
118
+ const avgCost = summary.total / summary.samples;
119
+ if (summary.breakdown.dbReads / summary.samples > 1000) {
120
+ suggestions.push({
121
+ operation: summary.operation,
122
+ tenantId: summary.tenantId,
123
+ category: "n_plus_one",
124
+ message: "High average DB read count detected. Consider batching queries or adding pagination.",
125
+ evidence: { avgReads: summary.breakdown.dbReads / summary.samples }
126
+ });
127
+ }
128
+ if (summary.breakdown.compute / summary.total > 0.6) {
129
+ suggestions.push({
130
+ operation: summary.operation,
131
+ tenantId: summary.tenantId,
132
+ category: "batching",
133
+ message: "Compute dominates cost. Investigate hot loops or move heavy logic to background jobs.",
134
+ evidence: { computeShare: summary.breakdown.compute / summary.total }
135
+ });
136
+ }
137
+ if (summary.breakdown.external > avgCost * 0.5) {
138
+ suggestions.push({
139
+ operation: summary.operation,
140
+ tenantId: summary.tenantId,
141
+ category: "external",
142
+ message: "External provider spend is high. Reuse results or enable caching.",
143
+ evidence: { externalCost: summary.breakdown.external }
144
+ });
145
+ }
146
+ if (summary.breakdown.memory > summary.breakdown.compute * 1.2) {
147
+ suggestions.push({
148
+ operation: summary.operation,
149
+ tenantId: summary.tenantId,
150
+ category: "caching",
151
+ message: "Memory utilization suggests cached payloads linger. Tune TTL or stream responses.",
152
+ evidence: { memoryCost: summary.breakdown.memory }
153
+ });
154
+ }
155
+ return suggestions;
156
+ }
157
+ }
158
+ export {
159
+ defaultCostModel,
160
+ calculateSampleCost,
161
+ OptimizationRecommender,
162
+ CostTracker,
163
+ BudgetAlertManager
164
+ };
@@ -0,0 +1,163 @@
1
+ // src/cost-model.ts
2
+ var defaultCostModel = {
3
+ dbReadCost: 0.000002,
4
+ dbWriteCost: 0.00001,
5
+ computeMsCost: 0.00000015,
6
+ memoryMbMsCost: 0.00000002
7
+ };
8
+ function calculateSampleCost(sample, model) {
9
+ const external = (sample.externalCalls ?? []).reduce((sum, call) => sum + (call.cost ?? 0), 0);
10
+ return {
11
+ dbReads: (sample.dbReads ?? 0) * model.dbReadCost,
12
+ dbWrites: (sample.dbWrites ?? 0) * model.dbWriteCost,
13
+ compute: (sample.computeMs ?? 0) * model.computeMsCost,
14
+ memory: (sample.memoryMbMs ?? 0) * model.memoryMbMsCost,
15
+ external,
16
+ custom: sample.customCost ?? 0
17
+ };
18
+ }
19
+ // src/cost-tracker.ts
20
+ class CostTracker {
21
+ options;
22
+ totals = new Map;
23
+ costModel;
24
+ constructor(options = {}) {
25
+ this.options = options;
26
+ this.costModel = options.costModel ?? defaultCostModel;
27
+ }
28
+ recordSample(sample) {
29
+ const breakdown = calculateSampleCost(sample, this.costModel);
30
+ const total = breakdown.dbReads + breakdown.dbWrites + breakdown.compute + breakdown.memory + breakdown.external + breakdown.custom;
31
+ const key = this.buildKey(sample.operation, sample.tenantId);
32
+ const existing = this.totals.get(key);
33
+ const summary = existing ? {
34
+ ...existing,
35
+ total: existing.total + total,
36
+ breakdown: {
37
+ dbReads: existing.breakdown.dbReads + breakdown.dbReads,
38
+ dbWrites: existing.breakdown.dbWrites + breakdown.dbWrites,
39
+ compute: existing.breakdown.compute + breakdown.compute,
40
+ memory: existing.breakdown.memory + breakdown.memory,
41
+ external: existing.breakdown.external + breakdown.external,
42
+ custom: existing.breakdown.custom + breakdown.custom
43
+ },
44
+ samples: existing.samples + 1
45
+ } : {
46
+ operation: sample.operation,
47
+ tenantId: sample.tenantId,
48
+ total,
49
+ breakdown: {
50
+ dbReads: breakdown.dbReads,
51
+ dbWrites: breakdown.dbWrites,
52
+ compute: breakdown.compute,
53
+ memory: breakdown.memory,
54
+ external: breakdown.external,
55
+ custom: breakdown.custom
56
+ },
57
+ samples: 1
58
+ };
59
+ this.totals.set(key, summary);
60
+ this.options.onSampleRecorded?.(sample, total);
61
+ return summary;
62
+ }
63
+ getTotals(filter) {
64
+ const items = Array.from(this.totals.values());
65
+ if (!filter?.tenantId) {
66
+ return items;
67
+ }
68
+ return items.filter((item) => item.tenantId === filter.tenantId);
69
+ }
70
+ reset() {
71
+ this.totals.clear();
72
+ }
73
+ buildKey(operation, tenantId) {
74
+ return tenantId ? `${tenantId}:${operation}` : operation;
75
+ }
76
+ }
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
+ // src/optimization-recommender.ts
114
+ class OptimizationRecommender {
115
+ generate(summary) {
116
+ const suggestions = [];
117
+ const avgCost = summary.total / summary.samples;
118
+ if (summary.breakdown.dbReads / summary.samples > 1000) {
119
+ suggestions.push({
120
+ operation: summary.operation,
121
+ tenantId: summary.tenantId,
122
+ category: "n_plus_one",
123
+ message: "High average DB read count detected. Consider batching queries or adding pagination.",
124
+ evidence: { avgReads: summary.breakdown.dbReads / summary.samples }
125
+ });
126
+ }
127
+ if (summary.breakdown.compute / summary.total > 0.6) {
128
+ suggestions.push({
129
+ operation: summary.operation,
130
+ tenantId: summary.tenantId,
131
+ category: "batching",
132
+ message: "Compute dominates cost. Investigate hot loops or move heavy logic to background jobs.",
133
+ evidence: { computeShare: summary.breakdown.compute / summary.total }
134
+ });
135
+ }
136
+ if (summary.breakdown.external > avgCost * 0.5) {
137
+ suggestions.push({
138
+ operation: summary.operation,
139
+ tenantId: summary.tenantId,
140
+ category: "external",
141
+ message: "External provider spend is high. Reuse results or enable caching.",
142
+ evidence: { externalCost: summary.breakdown.external }
143
+ });
144
+ }
145
+ if (summary.breakdown.memory > summary.breakdown.compute * 1.2) {
146
+ suggestions.push({
147
+ operation: summary.operation,
148
+ tenantId: summary.tenantId,
149
+ category: "caching",
150
+ message: "Memory utilization suggests cached payloads linger. Tune TTL or stream responses.",
151
+ evidence: { memoryCost: summary.breakdown.memory }
152
+ });
153
+ }
154
+ return suggestions;
155
+ }
156
+ }
157
+ export {
158
+ defaultCostModel,
159
+ calculateSampleCost,
160
+ OptimizationRecommender,
161
+ CostTracker,
162
+ BudgetAlertManager
163
+ };
@@ -1,9 +1,5 @@
1
- import { OperationCostSummary, OptimizationSuggestion } from "./types.js";
2
-
3
- //#region src/optimization-recommender.d.ts
4
- declare class OptimizationRecommender {
5
- generate(summary: OperationCostSummary): OptimizationSuggestion[];
1
+ import type { OperationCostSummary, OptimizationSuggestion } from './types';
2
+ export declare class OptimizationRecommender {
3
+ generate(summary: OperationCostSummary): OptimizationSuggestion[];
6
4
  }
7
- //#endregion
8
- export { OptimizationRecommender };
9
5
  //# sourceMappingURL=optimization-recommender.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"optimization-recommender.d.ts","names":[],"sources":["../src/optimization-recommender.ts"],"mappings":";;;cAEa,uBAAA;EACX,QAAA,CAAS,OAAA,EAAS,oBAAA,GAAuB,sBAAA;AAAA"}
1
+ {"version":3,"file":"optimization-recommender.d.ts","sourceRoot":"","sources":["../src/optimization-recommender.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAE5E,qBAAa,uBAAuB;IAClC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,sBAAsB,EAAE;CAkDlE"}
package/dist/types.d.ts CHANGED
@@ -1,63 +1,60 @@
1
- //#region src/types.d.ts
2
- interface ExternalCallCost {
3
- provider: string;
4
- cost?: number;
5
- durationMs?: number;
6
- requestCount?: number;
1
+ export interface ExternalCallCost {
2
+ provider: string;
3
+ cost?: number;
4
+ durationMs?: number;
5
+ requestCount?: number;
7
6
  }
8
- interface CostSample {
9
- operation: string;
10
- tenantId?: string;
11
- environment?: 'dev' | 'staging' | 'prod';
12
- dbReads?: number;
13
- dbWrites?: number;
14
- computeMs?: number;
15
- memoryMbMs?: number;
16
- externalCalls?: ExternalCallCost[];
17
- customCost?: number;
18
- timestamp?: Date;
19
- metadata?: Record<string, unknown>;
7
+ export interface CostSample {
8
+ operation: string;
9
+ tenantId?: string;
10
+ environment?: 'dev' | 'staging' | 'prod';
11
+ dbReads?: number;
12
+ dbWrites?: number;
13
+ computeMs?: number;
14
+ memoryMbMs?: number;
15
+ externalCalls?: ExternalCallCost[];
16
+ customCost?: number;
17
+ timestamp?: Date;
18
+ metadata?: Record<string, unknown>;
20
19
  }
21
- interface CostModel {
22
- dbReadCost: number;
23
- dbWriteCost: number;
24
- computeMsCost: number;
25
- memoryMbMsCost: number;
20
+ export interface CostModel {
21
+ dbReadCost: number;
22
+ dbWriteCost: number;
23
+ computeMsCost: number;
24
+ memoryMbMsCost: number;
26
25
  }
27
- interface OperationCostSummary {
28
- operation: string;
29
- tenantId?: string;
30
- total: number;
31
- breakdown: {
32
- dbReads: number;
33
- dbWrites: number;
34
- compute: number;
35
- memory: number;
36
- external: number;
37
- custom: number;
38
- };
39
- samples: number;
26
+ export interface OperationCostSummary {
27
+ operation: string;
28
+ tenantId?: string;
29
+ total: number;
30
+ breakdown: {
31
+ dbReads: number;
32
+ dbWrites: number;
33
+ compute: number;
34
+ memory: number;
35
+ external: number;
36
+ custom: number;
37
+ };
38
+ samples: number;
40
39
  }
41
- interface TenantBudget {
42
- tenantId: string;
43
- monthlyLimit: number;
44
- currency?: string;
45
- alertThreshold?: number;
46
- currentSpend?: number;
40
+ export interface TenantBudget {
41
+ tenantId: string;
42
+ monthlyLimit: number;
43
+ currency?: string;
44
+ alertThreshold?: number;
45
+ currentSpend?: number;
47
46
  }
48
- interface OptimizationSuggestion {
49
- operation: string;
50
- tenantId?: string;
51
- category: 'n_plus_one' | 'caching' | 'batching' | 'external';
52
- message: string;
53
- evidence: Record<string, unknown>;
47
+ export interface OptimizationSuggestion {
48
+ operation: string;
49
+ tenantId?: string;
50
+ category: 'n_plus_one' | 'caching' | 'batching' | 'external';
51
+ message: string;
52
+ evidence: Record<string, unknown>;
54
53
  }
55
- interface BudgetAlert {
56
- tenantId: string;
57
- limit: number;
58
- actual: number;
59
- triggeredAt: Date;
54
+ export interface BudgetAlert {
55
+ tenantId: string;
56
+ limit: number;
57
+ actual: number;
58
+ triggeredAt: Date;
60
59
  }
61
- //#endregion
62
- export { BudgetAlert, CostModel, CostSample, ExternalCallCost, OperationCostSummary, OptimizationSuggestion, TenantBudget };
63
60
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";UAAiB,gBAAA;EACf,QAAA;EACA,IAAA;EACA,UAAA;EACA,YAAA;AAAA;AAAA,UAGe,UAAA;EACf,SAAA;EACA,QAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,aAAA,GAAgB,gBAAA;EAChB,UAAA;EACA,SAAA,GAAY,IAAA;EACZ,QAAA,GAAW,MAAA;AAAA;AAAA,UAGI,SAAA;EACf,UAAA;EACA,WAAA;EACA,aAAA;EACA,cAAA;AAAA;AAAA,UAGe,oBAAA;EACf,SAAA;EACA,QAAA;EACA,KAAA;EACA,SAAA;IACE,OAAA;IACA,QAAA;IACA,OAAA;IACA,MAAA;IACA,QAAA;IACA,MAAA;EAAA;EAEF,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,QAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,YAAA;AAAA;AAAA,UAGe,sBAAA;EACf,SAAA;EACA,QAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA,EAAU,MAAA;AAAA;AAAA,UAGK,WAAA;EACf,QAAA;EACA,KAAA;EACA,MAAA;EACA,WAAA,EAAa,IAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.cost-tracking",
3
- "version": "1.57.0",
3
+ "version": "1.59.0",
4
4
  "description": "API cost tracking and budgeting",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -19,31 +19,36 @@
19
19
  "scripts": {
20
20
  "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
21
21
  "publish:pkg:canary": "bun publish:pkg --tag canary",
22
- "build": "bun build:types && bun build:bundle",
23
- "build:bundle": "tsdown",
24
- "build:types": "tsc --noEmit",
25
- "dev": "bun build:bundle --watch",
22
+ "build": "bun run prebuild && bun run build:bundle && bun run build:types",
23
+ "build:bundle": "contractspec-bun-build transpile",
24
+ "build:types": "contractspec-bun-build types",
25
+ "dev": "contractspec-bun-build dev",
26
26
  "clean": "rimraf dist .turbo",
27
27
  "lint": "bun lint:fix",
28
28
  "lint:fix": "eslint src --fix",
29
29
  "lint:check": "eslint src",
30
- "test": "bun test"
30
+ "test": "bun test",
31
+ "prebuild": "contractspec-bun-build prebuild",
32
+ "typecheck": "tsc --noEmit"
31
33
  },
32
34
  "devDependencies": {
33
- "@contractspec/tool.tsdown": "1.57.0",
34
- "@contractspec/tool.typescript": "1.57.0",
35
- "tsdown": "^0.20.3",
36
- "typescript": "^5.9.3"
35
+ "@contractspec/tool.typescript": "1.59.0",
36
+ "typescript": "^5.9.3",
37
+ "@contractspec/tool.bun": "1.58.0"
37
38
  },
38
39
  "exports": {
39
- ".": "./dist/index.js",
40
- "./*": "./*"
40
+ ".": "./src/index.ts"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public",
44
44
  "exports": {
45
- ".": "./dist/index.js",
46
- "./*": "./*"
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "bun": "./dist/index.js",
48
+ "node": "./dist/node/index.mjs",
49
+ "browser": "./dist/browser/index.js",
50
+ "default": "./dist/index.js"
51
+ }
47
52
  },
48
53
  "registry": "https://registry.npmjs.org/"
49
54
  },
@@ -1,33 +0,0 @@
1
- //#region src/budget-alert-manager.ts
2
- var BudgetAlertManager = class {
3
- limits = /* @__PURE__ */ new Map();
4
- spend = /* @__PURE__ */ new Map();
5
- constructor(options) {
6
- this.options = options;
7
- for (const budget of options.budgets) {
8
- this.limits.set(budget.tenantId, budget);
9
- this.spend.set(budget.tenantId, 0);
10
- }
11
- }
12
- track(summary) {
13
- if (!summary.tenantId) return;
14
- const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;
15
- this.spend.set(summary.tenantId, current);
16
- const budget = this.limits.get(summary.tenantId);
17
- if (!budget) return;
18
- const threshold = budget.alertThreshold ?? .8;
19
- if (current >= budget.monthlyLimit * threshold) this.options.onAlert?.({
20
- tenantId: summary.tenantId,
21
- limit: budget.monthlyLimit,
22
- total: current,
23
- summary
24
- });
25
- }
26
- getSpend(tenantId) {
27
- return this.spend.get(tenantId) ?? 0;
28
- }
29
- };
30
-
31
- //#endregion
32
- export { BudgetAlertManager };
33
- //# sourceMappingURL=budget-alert-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"budget-alert-manager.js","names":[],"sources":["../src/budget-alert-manager.ts"],"sourcesContent":["import type { OperationCostSummary, TenantBudget } from './types';\n\nexport interface BudgetAlertManagerOptions {\n budgets: TenantBudget[];\n onAlert?: (payload: {\n tenantId: string;\n limit: number;\n total: number;\n summary: OperationCostSummary;\n }) => void;\n}\n\nexport class BudgetAlertManager {\n private readonly limits = new Map<string, TenantBudget>();\n private readonly spend = new Map<string, number>();\n\n constructor(private readonly options: BudgetAlertManagerOptions) {\n for (const budget of options.budgets) {\n this.limits.set(budget.tenantId, budget);\n this.spend.set(budget.tenantId, 0);\n }\n }\n\n track(summary: OperationCostSummary) {\n if (!summary.tenantId) {\n return;\n }\n\n const current = (this.spend.get(summary.tenantId) ?? 0) + summary.total;\n this.spend.set(summary.tenantId, current);\n\n const budget = this.limits.get(summary.tenantId);\n if (!budget) {\n return;\n }\n\n const threshold = budget.alertThreshold ?? 0.8;\n if (current >= budget.monthlyLimit * threshold) {\n this.options.onAlert?.({\n tenantId: summary.tenantId,\n limit: budget.monthlyLimit,\n total: current,\n summary,\n });\n }\n }\n\n getSpend(tenantId: string) {\n return this.spend.get(tenantId) ?? 0;\n }\n}\n"],"mappings":";AAYA,IAAa,qBAAb,MAAgC;CAC9B,AAAiB,yBAAS,IAAI,KAA2B;CACzD,AAAiB,wBAAQ,IAAI,KAAqB;CAElD,YAAY,AAAiB,SAAoC;EAApC;AAC3B,OAAK,MAAM,UAAU,QAAQ,SAAS;AACpC,QAAK,OAAO,IAAI,OAAO,UAAU,OAAO;AACxC,QAAK,MAAM,IAAI,OAAO,UAAU,EAAE;;;CAItC,MAAM,SAA+B;AACnC,MAAI,CAAC,QAAQ,SACX;EAGF,MAAM,WAAW,KAAK,MAAM,IAAI,QAAQ,SAAS,IAAI,KAAK,QAAQ;AAClE,OAAK,MAAM,IAAI,QAAQ,UAAU,QAAQ;EAEzC,MAAM,SAAS,KAAK,OAAO,IAAI,QAAQ,SAAS;AAChD,MAAI,CAAC,OACH;EAGF,MAAM,YAAY,OAAO,kBAAkB;AAC3C,MAAI,WAAW,OAAO,eAAe,UACnC,MAAK,QAAQ,UAAU;GACrB,UAAU,QAAQ;GAClB,OAAO,OAAO;GACd,OAAO;GACP;GACD,CAAC;;CAIN,SAAS,UAAkB;AACzB,SAAO,KAAK,MAAM,IAAI,SAAS,IAAI"}
@@ -1,22 +0,0 @@
1
- //#region src/cost-model.ts
2
- const defaultCostModel = {
3
- dbReadCost: 2e-6,
4
- dbWriteCost: 1e-5,
5
- computeMsCost: 15e-8,
6
- memoryMbMsCost: 2e-8
7
- };
8
- function calculateSampleCost(sample, model) {
9
- const external = (sample.externalCalls ?? []).reduce((sum, call) => sum + (call.cost ?? 0), 0);
10
- return {
11
- dbReads: (sample.dbReads ?? 0) * model.dbReadCost,
12
- dbWrites: (sample.dbWrites ?? 0) * model.dbWriteCost,
13
- compute: (sample.computeMs ?? 0) * model.computeMsCost,
14
- memory: (sample.memoryMbMs ?? 0) * model.memoryMbMsCost,
15
- external,
16
- custom: sample.customCost ?? 0
17
- };
18
- }
19
-
20
- //#endregion
21
- export { calculateSampleCost, defaultCostModel };
22
- //# sourceMappingURL=cost-model.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cost-model.js","names":[],"sources":["../src/cost-model.ts"],"sourcesContent":["import type { CostModel, CostSample } from './types';\n\nexport const defaultCostModel: CostModel = {\n dbReadCost: 0.000002,\n dbWriteCost: 0.00001,\n computeMsCost: 0.00000015,\n memoryMbMsCost: 0.00000002,\n};\n\nexport function calculateSampleCost(sample: CostSample, model: CostModel) {\n const external = (sample.externalCalls ?? []).reduce(\n (sum, call) => sum + (call.cost ?? 0),\n 0\n );\n\n return {\n dbReads: (sample.dbReads ?? 0) * model.dbReadCost,\n dbWrites: (sample.dbWrites ?? 0) * model.dbWriteCost,\n compute: (sample.computeMs ?? 0) * model.computeMsCost,\n memory: (sample.memoryMbMs ?? 0) * model.memoryMbMsCost,\n external,\n custom: sample.customCost ?? 0,\n };\n}\n"],"mappings":";AAEA,MAAa,mBAA8B;CACzC,YAAY;CACZ,aAAa;CACb,eAAe;CACf,gBAAgB;CACjB;AAED,SAAgB,oBAAoB,QAAoB,OAAkB;CACxE,MAAM,YAAY,OAAO,iBAAiB,EAAE,EAAE,QAC3C,KAAK,SAAS,OAAO,KAAK,QAAQ,IACnC,EACD;AAED,QAAO;EACL,UAAU,OAAO,WAAW,KAAK,MAAM;EACvC,WAAW,OAAO,YAAY,KAAK,MAAM;EACzC,UAAU,OAAO,aAAa,KAAK,MAAM;EACzC,SAAS,OAAO,cAAc,KAAK,MAAM;EACzC;EACA,QAAQ,OAAO,cAAc;EAC9B"}
@@ -1,61 +0,0 @@
1
- import { calculateSampleCost, defaultCostModel } from "./cost-model.js";
2
-
3
- //#region src/cost-tracker.ts
4
- var CostTracker = class {
5
- totals = /* @__PURE__ */ new Map();
6
- costModel;
7
- constructor(options = {}) {
8
- this.options = options;
9
- this.costModel = options.costModel ?? defaultCostModel;
10
- }
11
- recordSample(sample) {
12
- const breakdown = calculateSampleCost(sample, this.costModel);
13
- const total = breakdown.dbReads + breakdown.dbWrites + breakdown.compute + breakdown.memory + breakdown.external + breakdown.custom;
14
- const key = this.buildKey(sample.operation, sample.tenantId);
15
- const existing = this.totals.get(key);
16
- const summary = existing ? {
17
- ...existing,
18
- total: existing.total + total,
19
- breakdown: {
20
- dbReads: existing.breakdown.dbReads + breakdown.dbReads,
21
- dbWrites: existing.breakdown.dbWrites + breakdown.dbWrites,
22
- compute: existing.breakdown.compute + breakdown.compute,
23
- memory: existing.breakdown.memory + breakdown.memory,
24
- external: existing.breakdown.external + breakdown.external,
25
- custom: existing.breakdown.custom + breakdown.custom
26
- },
27
- samples: existing.samples + 1
28
- } : {
29
- operation: sample.operation,
30
- tenantId: sample.tenantId,
31
- total,
32
- breakdown: {
33
- dbReads: breakdown.dbReads,
34
- dbWrites: breakdown.dbWrites,
35
- compute: breakdown.compute,
36
- memory: breakdown.memory,
37
- external: breakdown.external,
38
- custom: breakdown.custom
39
- },
40
- samples: 1
41
- };
42
- this.totals.set(key, summary);
43
- this.options.onSampleRecorded?.(sample, total);
44
- return summary;
45
- }
46
- getTotals(filter) {
47
- const items = Array.from(this.totals.values());
48
- if (!filter?.tenantId) return items;
49
- return items.filter((item) => item.tenantId === filter.tenantId);
50
- }
51
- reset() {
52
- this.totals.clear();
53
- }
54
- buildKey(operation, tenantId) {
55
- return tenantId ? `${tenantId}:${operation}` : operation;
56
- }
57
- };
58
-
59
- //#endregion
60
- export { CostTracker };
61
- //# sourceMappingURL=cost-tracker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cost-tracker.js","names":[],"sources":["../src/cost-tracker.ts"],"sourcesContent":["import { calculateSampleCost, defaultCostModel } from './cost-model';\nimport type { CostModel, CostSample, OperationCostSummary } from './types';\n\nexport interface CostTrackerOptions {\n costModel?: CostModel;\n onSampleRecorded?: (sample: CostSample, total: number) => void;\n}\n\nexport class CostTracker {\n private readonly totals = new Map<string, OperationCostSummary>();\n private readonly costModel: CostModel;\n\n constructor(private readonly options: CostTrackerOptions = {}) {\n this.costModel = options.costModel ?? defaultCostModel;\n }\n\n recordSample(sample: CostSample): OperationCostSummary {\n const breakdown = calculateSampleCost(sample, this.costModel);\n const total =\n breakdown.dbReads +\n breakdown.dbWrites +\n breakdown.compute +\n breakdown.memory +\n breakdown.external +\n breakdown.custom;\n\n const key = this.buildKey(sample.operation, sample.tenantId);\n const existing = this.totals.get(key);\n\n const summary: OperationCostSummary = existing\n ? {\n ...existing,\n total: existing.total + total,\n breakdown: {\n dbReads: existing.breakdown.dbReads + breakdown.dbReads,\n dbWrites: existing.breakdown.dbWrites + breakdown.dbWrites,\n compute: existing.breakdown.compute + breakdown.compute,\n memory: existing.breakdown.memory + breakdown.memory,\n external: existing.breakdown.external + breakdown.external,\n custom: existing.breakdown.custom + breakdown.custom,\n },\n samples: existing.samples + 1,\n }\n : {\n operation: sample.operation,\n tenantId: sample.tenantId,\n total,\n breakdown: {\n dbReads: breakdown.dbReads,\n dbWrites: breakdown.dbWrites,\n compute: breakdown.compute,\n memory: breakdown.memory,\n external: breakdown.external,\n custom: breakdown.custom,\n },\n samples: 1,\n };\n\n this.totals.set(key, summary);\n this.options.onSampleRecorded?.(sample, total);\n return summary;\n }\n\n getTotals(filter?: { tenantId?: string }) {\n const items = Array.from(this.totals.values());\n if (!filter?.tenantId) {\n return items;\n }\n return items.filter((item) => item.tenantId === filter.tenantId);\n }\n\n reset() {\n this.totals.clear();\n }\n\n private buildKey(operation: string, tenantId?: string) {\n return tenantId ? `${tenantId}:${operation}` : operation;\n }\n}\n"],"mappings":";;;AAQA,IAAa,cAAb,MAAyB;CACvB,AAAiB,yBAAS,IAAI,KAAmC;CACjE,AAAiB;CAEjB,YAAY,AAAiB,UAA8B,EAAE,EAAE;EAAlC;AAC3B,OAAK,YAAY,QAAQ,aAAa;;CAGxC,aAAa,QAA0C;EACrD,MAAM,YAAY,oBAAoB,QAAQ,KAAK,UAAU;EAC7D,MAAM,QACJ,UAAU,UACV,UAAU,WACV,UAAU,UACV,UAAU,SACV,UAAU,WACV,UAAU;EAEZ,MAAM,MAAM,KAAK,SAAS,OAAO,WAAW,OAAO,SAAS;EAC5D,MAAM,WAAW,KAAK,OAAO,IAAI,IAAI;EAErC,MAAM,UAAgC,WAClC;GACE,GAAG;GACH,OAAO,SAAS,QAAQ;GACxB,WAAW;IACT,SAAS,SAAS,UAAU,UAAU,UAAU;IAChD,UAAU,SAAS,UAAU,WAAW,UAAU;IAClD,SAAS,SAAS,UAAU,UAAU,UAAU;IAChD,QAAQ,SAAS,UAAU,SAAS,UAAU;IAC9C,UAAU,SAAS,UAAU,WAAW,UAAU;IAClD,QAAQ,SAAS,UAAU,SAAS,UAAU;IAC/C;GACD,SAAS,SAAS,UAAU;GAC7B,GACD;GACE,WAAW,OAAO;GAClB,UAAU,OAAO;GACjB;GACA,WAAW;IACT,SAAS,UAAU;IACnB,UAAU,UAAU;IACpB,SAAS,UAAU;IACnB,QAAQ,UAAU;IAClB,UAAU,UAAU;IACpB,QAAQ,UAAU;IACnB;GACD,SAAS;GACV;AAEL,OAAK,OAAO,IAAI,KAAK,QAAQ;AAC7B,OAAK,QAAQ,mBAAmB,QAAQ,MAAM;AAC9C,SAAO;;CAGT,UAAU,QAAgC;EACxC,MAAM,QAAQ,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAC;AAC9C,MAAI,CAAC,QAAQ,SACX,QAAO;AAET,SAAO,MAAM,QAAQ,SAAS,KAAK,aAAa,OAAO,SAAS;;CAGlE,QAAQ;AACN,OAAK,OAAO,OAAO;;CAGrB,AAAQ,SAAS,WAAmB,UAAmB;AACrD,SAAO,WAAW,GAAG,SAAS,GAAG,cAAc"}
@@ -1,40 +0,0 @@
1
- //#region src/optimization-recommender.ts
2
- var OptimizationRecommender = class {
3
- generate(summary) {
4
- const suggestions = [];
5
- const avgCost = summary.total / summary.samples;
6
- if (summary.breakdown.dbReads / summary.samples > 1e3) suggestions.push({
7
- operation: summary.operation,
8
- tenantId: summary.tenantId,
9
- category: "n_plus_one",
10
- message: "High average DB read count detected. Consider batching queries or adding pagination.",
11
- evidence: { avgReads: summary.breakdown.dbReads / summary.samples }
12
- });
13
- if (summary.breakdown.compute / summary.total > .6) suggestions.push({
14
- operation: summary.operation,
15
- tenantId: summary.tenantId,
16
- category: "batching",
17
- message: "Compute dominates cost. Investigate hot loops or move heavy logic to background jobs.",
18
- evidence: { computeShare: summary.breakdown.compute / summary.total }
19
- });
20
- if (summary.breakdown.external > avgCost * .5) suggestions.push({
21
- operation: summary.operation,
22
- tenantId: summary.tenantId,
23
- category: "external",
24
- message: "External provider spend is high. Reuse results or enable caching.",
25
- evidence: { externalCost: summary.breakdown.external }
26
- });
27
- if (summary.breakdown.memory > summary.breakdown.compute * 1.2) suggestions.push({
28
- operation: summary.operation,
29
- tenantId: summary.tenantId,
30
- category: "caching",
31
- message: "Memory utilization suggests cached payloads linger. Tune TTL or stream responses.",
32
- evidence: { memoryCost: summary.breakdown.memory }
33
- });
34
- return suggestions;
35
- }
36
- };
37
-
38
- //#endregion
39
- export { OptimizationRecommender };
40
- //# sourceMappingURL=optimization-recommender.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"optimization-recommender.js","names":[],"sources":["../src/optimization-recommender.ts"],"sourcesContent":["import type { OperationCostSummary, OptimizationSuggestion } from './types';\n\nexport class OptimizationRecommender {\n generate(summary: OperationCostSummary): OptimizationSuggestion[] {\n const suggestions: OptimizationSuggestion[] = [];\n const avgCost = summary.total / summary.samples;\n\n if (summary.breakdown.dbReads / summary.samples > 1000) {\n suggestions.push({\n operation: summary.operation,\n tenantId: summary.tenantId,\n category: 'n_plus_one',\n message:\n 'High average DB read count detected. Consider batching queries or adding pagination.',\n evidence: { avgReads: summary.breakdown.dbReads / summary.samples },\n });\n }\n\n if (summary.breakdown.compute / summary.total > 0.6) {\n suggestions.push({\n operation: summary.operation,\n tenantId: summary.tenantId,\n category: 'batching',\n message:\n 'Compute dominates cost. Investigate hot loops or move heavy logic to background jobs.',\n evidence: { computeShare: summary.breakdown.compute / summary.total },\n });\n }\n\n if (summary.breakdown.external > avgCost * 0.5) {\n suggestions.push({\n operation: summary.operation,\n tenantId: summary.tenantId,\n category: 'external',\n message:\n 'External provider spend is high. Reuse results or enable caching.',\n evidence: { externalCost: summary.breakdown.external },\n });\n }\n\n if (summary.breakdown.memory > summary.breakdown.compute * 1.2) {\n suggestions.push({\n operation: summary.operation,\n tenantId: summary.tenantId,\n category: 'caching',\n message:\n 'Memory utilization suggests cached payloads linger. Tune TTL or stream responses.',\n evidence: { memoryCost: summary.breakdown.memory },\n });\n }\n\n return suggestions;\n }\n}\n"],"mappings":";AAEA,IAAa,0BAAb,MAAqC;CACnC,SAAS,SAAyD;EAChE,MAAM,cAAwC,EAAE;EAChD,MAAM,UAAU,QAAQ,QAAQ,QAAQ;AAExC,MAAI,QAAQ,UAAU,UAAU,QAAQ,UAAU,IAChD,aAAY,KAAK;GACf,WAAW,QAAQ;GACnB,UAAU,QAAQ;GAClB,UAAU;GACV,SACE;GACF,UAAU,EAAE,UAAU,QAAQ,UAAU,UAAU,QAAQ,SAAS;GACpE,CAAC;AAGJ,MAAI,QAAQ,UAAU,UAAU,QAAQ,QAAQ,GAC9C,aAAY,KAAK;GACf,WAAW,QAAQ;GACnB,UAAU,QAAQ;GAClB,UAAU;GACV,SACE;GACF,UAAU,EAAE,cAAc,QAAQ,UAAU,UAAU,QAAQ,OAAO;GACtE,CAAC;AAGJ,MAAI,QAAQ,UAAU,WAAW,UAAU,GACzC,aAAY,KAAK;GACf,WAAW,QAAQ;GACnB,UAAU,QAAQ;GAClB,UAAU;GACV,SACE;GACF,UAAU,EAAE,cAAc,QAAQ,UAAU,UAAU;GACvD,CAAC;AAGJ,MAAI,QAAQ,UAAU,SAAS,QAAQ,UAAU,UAAU,IACzD,aAAY,KAAK;GACf,WAAW,QAAQ;GACnB,UAAU,QAAQ;GAClB,UAAU;GACV,SACE;GACF,UAAU,EAAE,YAAY,QAAQ,UAAU,QAAQ;GACnD,CAAC;AAGJ,SAAO"}