@aigne/afs-cost-base 1.11.0-beta.12

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 (40) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/annotations.cjs +61 -0
  5. package/dist/annotations.d.cts +22 -0
  6. package/dist/annotations.d.cts.map +1 -0
  7. package/dist/annotations.d.mts +22 -0
  8. package/dist/annotations.d.mts.map +1 -0
  9. package/dist/annotations.mjs +62 -0
  10. package/dist/annotations.mjs.map +1 -0
  11. package/dist/base-provider.cjs +431 -0
  12. package/dist/base-provider.d.cts +77 -0
  13. package/dist/base-provider.d.cts.map +1 -0
  14. package/dist/base-provider.d.mts +77 -0
  15. package/dist/base-provider.d.mts.map +1 -0
  16. package/dist/base-provider.mjs +432 -0
  17. package/dist/base-provider.mjs.map +1 -0
  18. package/dist/cache.cjs +43 -0
  19. package/dist/cache.d.cts +22 -0
  20. package/dist/cache.d.cts.map +1 -0
  21. package/dist/cache.d.mts +22 -0
  22. package/dist/cache.d.mts.map +1 -0
  23. package/dist/cache.mjs +43 -0
  24. package/dist/cache.mjs.map +1 -0
  25. package/dist/index.cjs +9 -0
  26. package/dist/index.d.cts +6 -0
  27. package/dist/index.d.mts +6 -0
  28. package/dist/index.mjs +6 -0
  29. package/dist/mock-adapter.cjs +38 -0
  30. package/dist/mock-adapter.d.cts +25 -0
  31. package/dist/mock-adapter.d.cts.map +1 -0
  32. package/dist/mock-adapter.d.mts +25 -0
  33. package/dist/mock-adapter.d.mts.map +1 -0
  34. package/dist/mock-adapter.mjs +38 -0
  35. package/dist/mock-adapter.mjs.map +1 -0
  36. package/dist/types.d.cts +37 -0
  37. package/dist/types.d.cts.map +1 -0
  38. package/dist/types.d.mts +37 -0
  39. package/dist/types.d.mts.map +1 -0
  40. package/package.json +57 -0
package/LICENSE.md ADDED
@@ -0,0 +1,26 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. Unauthorized copying, modification, distribution, or use of
7
+ this Software, via any medium, is strictly prohibited.
8
+
9
+ The Software is provided for internal use only within ArcBlock, Inc. and its
10
+ authorized affiliates.
11
+
12
+ ## No License Granted
13
+
14
+ No license, express or implied, is granted to any party for any purpose.
15
+ All rights are reserved by ArcBlock, Inc.
16
+
17
+ ## Public Artifact Distribution
18
+
19
+ Portions of this Software may be released publicly under separate open-source
20
+ licenses (such as MIT License) through designated public repositories. Such
21
+ public releases are governed by their respective licenses and do not affect
22
+ the proprietary nature of this repository.
23
+
24
+ ## Contact
25
+
26
+ For licensing inquiries, contact: legal@arcblock.io
@@ -0,0 +1,11 @@
1
+
2
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
3
+ function __decorate(decorators, target, key, desc) {
4
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
8
+ }
9
+
10
+ //#endregion
11
+ exports.__decorate = __decorate;
@@ -0,0 +1,10 @@
1
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
2
+ function __decorate(decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ }
8
+
9
+ //#endregion
10
+ export { __decorate };
@@ -0,0 +1,61 @@
1
+ let node_fs = require("node:fs");
2
+ let node_path = require("node:path");
3
+
4
+ //#region src/annotations.ts
5
+ /**
6
+ * In-memory annotation store with optional file persistence.
7
+ * Keys are formatted as `{cloud}:{service?}:{date?}`.
8
+ */
9
+ var AnnotationStore = class {
10
+ data = /* @__PURE__ */ new Map();
11
+ dataDir;
12
+ filePath;
13
+ constructor(options) {
14
+ if (options?.dataDir) {
15
+ this.dataDir = options.dataDir;
16
+ this.filePath = (0, node_path.join)(options.dataDir, "annotations.json");
17
+ this.loadFromFile();
18
+ }
19
+ }
20
+ get(key) {
21
+ return this.data.get(key);
22
+ }
23
+ set(key, value) {
24
+ const existing = this.data.get(key) ?? {};
25
+ this.data.set(key, {
26
+ ...existing,
27
+ ...value
28
+ });
29
+ this.persistToFile();
30
+ }
31
+ /** Build annotation key from cloud + optional service/date. */
32
+ static key(cloud, service, date) {
33
+ return [
34
+ cloud,
35
+ service,
36
+ date
37
+ ].filter(Boolean).join(":");
38
+ }
39
+ loadFromFile() {
40
+ if (!this.filePath || !(0, node_fs.existsSync)(this.filePath)) return;
41
+ try {
42
+ const raw = (0, node_fs.readFileSync)(this.filePath, "utf-8");
43
+ const parsed = JSON.parse(raw);
44
+ if (parsed && typeof parsed === "object") {
45
+ for (const [k, v] of Object.entries(parsed)) if (v && typeof v === "object" && !Array.isArray(v)) this.data.set(k, v);
46
+ }
47
+ } catch {}
48
+ }
49
+ persistToFile() {
50
+ if (!this.filePath || !this.dataDir) return;
51
+ try {
52
+ (0, node_fs.mkdirSync)(this.dataDir, { recursive: true });
53
+ const obj = {};
54
+ for (const [k, v] of this.data) obj[k] = v;
55
+ (0, node_fs.writeFileSync)(this.filePath, JSON.stringify(obj, null, 2), "utf-8");
56
+ } catch {}
57
+ }
58
+ };
59
+
60
+ //#endregion
61
+ exports.AnnotationStore = AnnotationStore;
@@ -0,0 +1,22 @@
1
+ //#region src/annotations.d.ts
2
+ /**
3
+ * In-memory annotation store with optional file persistence.
4
+ * Keys are formatted as `{cloud}:{service?}:{date?}`.
5
+ */
6
+ declare class AnnotationStore {
7
+ private data;
8
+ private dataDir?;
9
+ private filePath?;
10
+ constructor(options?: {
11
+ dataDir?: string;
12
+ });
13
+ get(key: string): Record<string, unknown> | undefined;
14
+ set(key: string, value: Record<string, unknown>): void;
15
+ /** Build annotation key from cloud + optional service/date. */
16
+ static key(cloud: string, service?: string, date?: string): string;
17
+ private loadFromFile;
18
+ private persistToFile;
19
+ }
20
+ //#endregion
21
+ export { AnnotationStore };
22
+ //# sourceMappingURL=annotations.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.cts","names":[],"sources":["../src/annotations.ts"],"mappings":";;AAOA;;;cAAa,eAAA;EAAA,QACH,IAAA;EAAA,QACA,OAAA;EAAA,QACA,QAAA;cAEI,OAAA;IAAY,OAAA;EAAA;EAQxB,GAAA,CAAI,GAAA,WAAc,MAAA;EAIlB,GAAA,CAAI,GAAA,UAAa,KAAA,EAAO,MAAA;EAJN;EAAA,OAWX,GAAA,CAAI,KAAA,UAAe,OAAA,WAAkB,IAAA;EAAA,QAIpC,YAAA;EAAA,QAiBA,aAAA;AAAA"}
@@ -0,0 +1,22 @@
1
+ //#region src/annotations.d.ts
2
+ /**
3
+ * In-memory annotation store with optional file persistence.
4
+ * Keys are formatted as `{cloud}:{service?}:{date?}`.
5
+ */
6
+ declare class AnnotationStore {
7
+ private data;
8
+ private dataDir?;
9
+ private filePath?;
10
+ constructor(options?: {
11
+ dataDir?: string;
12
+ });
13
+ get(key: string): Record<string, unknown> | undefined;
14
+ set(key: string, value: Record<string, unknown>): void;
15
+ /** Build annotation key from cloud + optional service/date. */
16
+ static key(cloud: string, service?: string, date?: string): string;
17
+ private loadFromFile;
18
+ private persistToFile;
19
+ }
20
+ //#endregion
21
+ export { AnnotationStore };
22
+ //# sourceMappingURL=annotations.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.mts","names":[],"sources":["../src/annotations.ts"],"mappings":";;AAOA;;;cAAa,eAAA;EAAA,QACH,IAAA;EAAA,QACA,OAAA;EAAA,QACA,QAAA;cAEI,OAAA;IAAY,OAAA;EAAA;EAQxB,GAAA,CAAI,GAAA,WAAc,MAAA;EAIlB,GAAA,CAAI,GAAA,UAAa,KAAA,EAAO,MAAA;EAJN;EAAA,OAWX,GAAA,CAAI,KAAA,UAAe,OAAA,WAAkB,IAAA;EAAA,QAIpC,YAAA;EAAA,QAiBA,aAAA;AAAA"}
@@ -0,0 +1,62 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ //#region src/annotations.ts
5
+ /**
6
+ * In-memory annotation store with optional file persistence.
7
+ * Keys are formatted as `{cloud}:{service?}:{date?}`.
8
+ */
9
+ var AnnotationStore = class {
10
+ data = /* @__PURE__ */ new Map();
11
+ dataDir;
12
+ filePath;
13
+ constructor(options) {
14
+ if (options?.dataDir) {
15
+ this.dataDir = options.dataDir;
16
+ this.filePath = join(options.dataDir, "annotations.json");
17
+ this.loadFromFile();
18
+ }
19
+ }
20
+ get(key) {
21
+ return this.data.get(key);
22
+ }
23
+ set(key, value) {
24
+ const existing = this.data.get(key) ?? {};
25
+ this.data.set(key, {
26
+ ...existing,
27
+ ...value
28
+ });
29
+ this.persistToFile();
30
+ }
31
+ /** Build annotation key from cloud + optional service/date. */
32
+ static key(cloud, service, date) {
33
+ return [
34
+ cloud,
35
+ service,
36
+ date
37
+ ].filter(Boolean).join(":");
38
+ }
39
+ loadFromFile() {
40
+ if (!this.filePath || !existsSync(this.filePath)) return;
41
+ try {
42
+ const raw = readFileSync(this.filePath, "utf-8");
43
+ const parsed = JSON.parse(raw);
44
+ if (parsed && typeof parsed === "object") {
45
+ for (const [k, v] of Object.entries(parsed)) if (v && typeof v === "object" && !Array.isArray(v)) this.data.set(k, v);
46
+ }
47
+ } catch {}
48
+ }
49
+ persistToFile() {
50
+ if (!this.filePath || !this.dataDir) return;
51
+ try {
52
+ mkdirSync(this.dataDir, { recursive: true });
53
+ const obj = {};
54
+ for (const [k, v] of this.data) obj[k] = v;
55
+ writeFileSync(this.filePath, JSON.stringify(obj, null, 2), "utf-8");
56
+ } catch {}
57
+ }
58
+ };
59
+
60
+ //#endregion
61
+ export { AnnotationStore };
62
+ //# sourceMappingURL=annotations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.mjs","names":[],"sources":["../src/annotations.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * In-memory annotation store with optional file persistence.\n * Keys are formatted as `{cloud}:{service?}:{date?}`.\n */\nexport class AnnotationStore {\n private data = new Map<string, Record<string, unknown>>();\n private dataDir?: string;\n private filePath?: string;\n\n constructor(options?: { dataDir?: string }) {\n if (options?.dataDir) {\n this.dataDir = options.dataDir;\n this.filePath = join(options.dataDir, \"annotations.json\");\n this.loadFromFile();\n }\n }\n\n get(key: string): Record<string, unknown> | undefined {\n return this.data.get(key);\n }\n\n set(key: string, value: Record<string, unknown>): void {\n const existing = this.data.get(key) ?? {};\n this.data.set(key, { ...existing, ...value });\n this.persistToFile();\n }\n\n /** Build annotation key from cloud + optional service/date. */\n static key(cloud: string, service?: string, date?: string): string {\n return [cloud, service, date].filter(Boolean).join(\":\");\n }\n\n private loadFromFile(): void {\n if (!this.filePath || !existsSync(this.filePath)) return;\n try {\n const raw = readFileSync(this.filePath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n for (const [k, v] of Object.entries(parsed)) {\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n this.data.set(k, v as Record<string, unknown>);\n }\n }\n }\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n private persistToFile(): void {\n if (!this.filePath || !this.dataDir) return;\n try {\n mkdirSync(this.dataDir, { recursive: true });\n const obj: Record<string, Record<string, unknown>> = {};\n for (const [k, v] of this.data) {\n obj[k] = v;\n }\n writeFileSync(this.filePath, JSON.stringify(obj, null, 2), \"utf-8\");\n } catch {\n // Best-effort persistence — don't crash on write failure\n }\n }\n}\n"],"mappings":";;;;;;;;AAOA,IAAa,kBAAb,MAA6B;CAC3B,AAAQ,uBAAO,IAAI,KAAsC;CACzD,AAAQ;CACR,AAAQ;CAER,YAAY,SAAgC;AAC1C,MAAI,SAAS,SAAS;AACpB,QAAK,UAAU,QAAQ;AACvB,QAAK,WAAW,KAAK,QAAQ,SAAS,mBAAmB;AACzD,QAAK,cAAc;;;CAIvB,IAAI,KAAkD;AACpD,SAAO,KAAK,KAAK,IAAI,IAAI;;CAG3B,IAAI,KAAa,OAAsC;EACrD,MAAM,WAAW,KAAK,KAAK,IAAI,IAAI,IAAI,EAAE;AACzC,OAAK,KAAK,IAAI,KAAK;GAAE,GAAG;GAAU,GAAG;GAAO,CAAC;AAC7C,OAAK,eAAe;;;CAItB,OAAO,IAAI,OAAe,SAAkB,MAAuB;AACjE,SAAO;GAAC;GAAO;GAAS;GAAK,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;;CAGzD,AAAQ,eAAqB;AAC3B,MAAI,CAAC,KAAK,YAAY,CAAC,WAAW,KAAK,SAAS,CAAE;AAClD,MAAI;GACF,MAAM,MAAM,aAAa,KAAK,UAAU,QAAQ;GAChD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,OAAI,UAAU,OAAO,WAAW,UAC9B;SAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,CACzC,KAAI,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,EAAE,CACjD,MAAK,KAAK,IAAI,GAAG,EAA6B;;UAI9C;;CAKV,AAAQ,gBAAsB;AAC5B,MAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAAS;AACrC,MAAI;AACF,aAAU,KAAK,SAAS,EAAE,WAAW,MAAM,CAAC;GAC5C,MAAM,MAA+C,EAAE;AACvD,QAAK,MAAM,CAAC,GAAG,MAAM,KAAK,KACxB,KAAI,KAAK;AAEX,iBAAc,KAAK,UAAU,KAAK,UAAU,KAAK,MAAM,EAAE,EAAE,QAAQ;UAC7D"}
@@ -0,0 +1,431 @@
1
+ const require_annotations = require('./annotations.cjs');
2
+ const require_cache = require('./cache.cjs');
3
+ const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
4
+ let _aigne_afs = require("@aigne/afs");
5
+ let _aigne_afs_provider = require("@aigne/afs/provider");
6
+ let _aigne_afs_utils_camelize = require("@aigne/afs/utils/camelize");
7
+ let ufo = require("ufo");
8
+
9
+ //#region src/base-provider.ts
10
+ /** Get date string N days ago from today */
11
+ function daysAgo(n) {
12
+ const d = /* @__PURE__ */ new Date();
13
+ d.setDate(d.getDate() - n);
14
+ return d.toISOString().slice(0, 10);
15
+ }
16
+ /** Today's date string */
17
+ function today() {
18
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19
+ }
20
+ /**
21
+ * Parse the path into its logical segments.
22
+ * No /{cloud} level — root is the cloud itself.
23
+ * Returns { view, key } where view is "by-service" or "by-date" and key is the leaf.
24
+ */
25
+ function parseCostPath(path) {
26
+ const segments = path.split("/").filter(Boolean);
27
+ return {
28
+ view: segments[0],
29
+ key: segments[1]
30
+ };
31
+ }
32
+ var AFSCostBaseProvider = class extends _aigne_afs_provider.AFSBaseProvider {
33
+ name;
34
+ description;
35
+ accessMode;
36
+ timeout;
37
+ adapter;
38
+ startDate;
39
+ endDate;
40
+ cache;
41
+ annotationStore;
42
+ constructor(rawOptions) {
43
+ super();
44
+ const injectedAdapter = rawOptions._adapter;
45
+ const options = (0, _aigne_afs_utils_camelize.camelize)(rawOptions);
46
+ this.name = options.name ?? `${this.cloudName}-cost`;
47
+ this.description = options.description;
48
+ this.accessMode = options.accessMode ?? "readwrite";
49
+ this.timeout = options.timeout ?? 3e4;
50
+ this.startDate = options.startDate ?? daysAgo(30);
51
+ this.endDate = options.endDate ?? today();
52
+ this.cache = new require_cache.CostCache(options.cacheTTL ?? 36e5);
53
+ this.annotationStore = new require_annotations.AnnotationStore(options.dataDir ? { dataDir: options.dataDir } : void 0);
54
+ if (injectedAdapter) this.adapter = injectedAdapter;
55
+ else this.adapter = this.createAdapter(options);
56
+ }
57
+ kindRoot() {
58
+ return `${this.cloudName}-cost:root`;
59
+ }
60
+ kindView() {
61
+ return `${this.cloudName}-cost:view`;
62
+ }
63
+ kindServiceSummary() {
64
+ return `${this.cloudName}-cost:service-summary`;
65
+ }
66
+ kindDateSummary() {
67
+ return `${this.cloudName}-cost:date-summary`;
68
+ }
69
+ async getRecords() {
70
+ const cacheKey = require_cache.CostCache.key(this.cloudName, this.startDate, this.endDate);
71
+ const cached = this.cache.get(cacheKey);
72
+ if (cached) return cached;
73
+ const records = await this.adapter.fetchCosts({
74
+ startDate: this.startDate,
75
+ endDate: this.endDate
76
+ });
77
+ this.cache.set(cacheKey, records);
78
+ return records;
79
+ }
80
+ aggregateByService(records) {
81
+ const map = /* @__PURE__ */ new Map();
82
+ for (const r of records) {
83
+ const arr = map.get(r.service) ?? [];
84
+ arr.push(r);
85
+ map.set(r.service, arr);
86
+ }
87
+ return map;
88
+ }
89
+ aggregateByDate(records) {
90
+ const map = /* @__PURE__ */ new Map();
91
+ for (const r of records) {
92
+ const arr = map.get(r.date) ?? [];
93
+ arr.push(r);
94
+ map.set(r.date, arr);
95
+ }
96
+ return map;
97
+ }
98
+ sumAmount(records) {
99
+ return records.reduce((acc, r) => acc + (r.amount ?? 0), 0);
100
+ }
101
+ annotationKey(service, date) {
102
+ return require_annotations.AnnotationStore.key(this.cloudName, service, date);
103
+ }
104
+ buildByServiceViewEntry(records, byService) {
105
+ const caps = this.adapter.capabilities();
106
+ const total = Math.round(this.sumAmount(records) * 100) / 100;
107
+ const rows = [];
108
+ for (const [service, recs] of byService) {
109
+ const serviceAmount = Math.round(this.sumAmount(recs) * 100) / 100;
110
+ const row = { service };
111
+ if (caps.supportsAmount) {
112
+ row.amount = serviceAmount;
113
+ row.currency = recs.find((r) => r.currency)?.currency ?? "USD";
114
+ row.pct = total > 0 ? Math.round(serviceAmount / total * 1e4) / 100 : 0;
115
+ }
116
+ rows.push(row);
117
+ }
118
+ const content = {
119
+ cloud: this.cloudName,
120
+ view: "by-service",
121
+ rows
122
+ };
123
+ if (caps.supportsAmount) {
124
+ content.total = total;
125
+ content.currency = records.find((r) => r.currency)?.currency ?? "USD";
126
+ }
127
+ return this.buildEntry("/by-service", {
128
+ content,
129
+ meta: {
130
+ kind: this.kindView(),
131
+ childrenCount: byService.size
132
+ }
133
+ });
134
+ }
135
+ buildByDateViewEntry(records, byDate) {
136
+ const caps = this.adapter.capabilities();
137
+ const total = Math.round(this.sumAmount(records) * 100) / 100;
138
+ const rows = [];
139
+ for (const [date, recs] of byDate) {
140
+ const dateAmount = Math.round(this.sumAmount(recs) * 100) / 100;
141
+ const row = {
142
+ date,
143
+ serviceCount: new Set(recs.map((r) => r.service)).size
144
+ };
145
+ if (caps.supportsAmount) {
146
+ row.amount = dateAmount;
147
+ row.currency = recs.find((r) => r.currency)?.currency ?? "USD";
148
+ }
149
+ rows.push(row);
150
+ }
151
+ const content = {
152
+ cloud: this.cloudName,
153
+ view: "by-date",
154
+ rows
155
+ };
156
+ if (caps.supportsAmount) {
157
+ content.total = total;
158
+ content.currency = records.find((r) => r.currency)?.currency ?? "USD";
159
+ }
160
+ return this.buildEntry("/by-date", {
161
+ content,
162
+ meta: {
163
+ kind: this.kindView(),
164
+ childrenCount: byDate.size
165
+ }
166
+ });
167
+ }
168
+ buildServiceSummaryEntry(service, records) {
169
+ const caps = this.adapter.capabilities();
170
+ const path = (0, ufo.joinURL)("/by-service", service);
171
+ const annKey = this.annotationKey(service);
172
+ const annotation = this.annotationStore.get(annKey);
173
+ const content = {
174
+ service,
175
+ cloud: this.cloudName,
176
+ startDate: this.startDate,
177
+ endDate: this.endDate
178
+ };
179
+ if (caps.supportsAmount) {
180
+ content.amount = Math.round(this.sumAmount(records) * 100) / 100;
181
+ content.currency = records.find((r) => r.currency)?.currency ?? "USD";
182
+ }
183
+ const byDate = this.aggregateByDate(records);
184
+ const daily = [];
185
+ for (const [d, dateRecs] of byDate) {
186
+ const entry = { date: d };
187
+ if (caps.supportsAmount) entry.amount = Math.round(this.sumAmount(dateRecs) * 100) / 100;
188
+ daily.push(entry);
189
+ }
190
+ content.daily = daily;
191
+ const meta = { kind: this.kindServiceSummary() };
192
+ if (annotation) Object.assign(meta, annotation);
193
+ return this.buildEntry(path, {
194
+ content,
195
+ meta
196
+ });
197
+ }
198
+ buildDateSummaryEntry(date, records) {
199
+ const caps = this.adapter.capabilities();
200
+ const path = (0, ufo.joinURL)("/by-date", date);
201
+ const annKey = this.annotationKey(void 0, date);
202
+ const annotation = this.annotationStore.get(annKey);
203
+ const content = {
204
+ date,
205
+ cloud: this.cloudName,
206
+ serviceCount: new Set(records.map((r) => r.service)).size
207
+ };
208
+ if (caps.supportsAmount) {
209
+ content.totalAmount = Math.round(this.sumAmount(records) * 100) / 100;
210
+ content.currency = records.find((r) => r.currency)?.currency ?? "USD";
211
+ }
212
+ const byService = this.aggregateByService(records);
213
+ const services = [];
214
+ for (const [svc, svcRecs] of byService) {
215
+ const entry = { service: svc };
216
+ if (caps.supportsAmount) entry.amount = Math.round(this.sumAmount(svcRecs) * 100) / 100;
217
+ services.push(entry);
218
+ }
219
+ content.services = services;
220
+ const meta = { kind: this.kindDateSummary() };
221
+ if (annotation) Object.assign(meta, annotation);
222
+ return this.buildEntry(path, {
223
+ content,
224
+ meta
225
+ });
226
+ }
227
+ async handleList(ctx) {
228
+ const path = ctx.path || "/";
229
+ const parsed = parseCostPath(path);
230
+ const maxDepth = ctx.options?.maxDepth ?? 1;
231
+ if (!parsed.view) {
232
+ const records = await this.getRecords();
233
+ const byService = this.aggregateByService(records);
234
+ const byDate = this.aggregateByDate(records);
235
+ const entries = [this.buildByServiceViewEntry(records, byService), this.buildByDateViewEntry(records, byDate)];
236
+ if (maxDepth > 1) {
237
+ for (const [service, recs] of byService) entries.push(this.buildServiceSummaryEntry(service, recs));
238
+ for (const [date, recs] of byDate) entries.push(this.buildDateSummaryEntry(date, recs));
239
+ }
240
+ return { data: entries };
241
+ }
242
+ if (parsed.view === "by-service" && !parsed.key) {
243
+ const records = await this.getRecords();
244
+ const byService = this.aggregateByService(records);
245
+ const entries = [];
246
+ for (const [service, recs] of byService) entries.push(this.buildServiceSummaryEntry(service, recs));
247
+ return { data: entries };
248
+ }
249
+ if (parsed.view === "by-date" && !parsed.key) {
250
+ const records = await this.getRecords();
251
+ const byDate = this.aggregateByDate(records);
252
+ const entries = [];
253
+ for (const [date, recs] of byDate) entries.push(this.buildDateSummaryEntry(date, recs));
254
+ return { data: entries };
255
+ }
256
+ if ((parsed.view === "by-service" || parsed.view === "by-date") && parsed.key) return { data: [] };
257
+ throw new _aigne_afs.AFSNotFoundError(path);
258
+ }
259
+ async handleCapabilities(_ctx) {
260
+ const ops = this.getOperationsDeclaration();
261
+ return {
262
+ id: "/.meta/.capabilities",
263
+ path: "/.meta/.capabilities",
264
+ content: {
265
+ schemaVersion: 1,
266
+ provider: this.name,
267
+ description: this.description,
268
+ tools: [],
269
+ actions: [],
270
+ operations: ops
271
+ },
272
+ meta: { kind: "afs:capabilities" }
273
+ };
274
+ }
275
+ async handleRead(ctx) {
276
+ const path = ctx.path || "/";
277
+ const parsed = parseCostPath(path);
278
+ if (!parsed.view) return this.buildEntry("/", {
279
+ content: {
280
+ cloud: this.cloudName,
281
+ startDate: this.startDate,
282
+ endDate: this.endDate
283
+ },
284
+ meta: {
285
+ kind: this.kindRoot(),
286
+ childrenCount: 2
287
+ }
288
+ });
289
+ const records = await this.getRecords();
290
+ if (parsed.view === "by-service" && !parsed.key) {
291
+ const byService = this.aggregateByService(records);
292
+ return this.buildByServiceViewEntry(records, byService);
293
+ }
294
+ if (parsed.view === "by-date" && !parsed.key) {
295
+ const byDate = this.aggregateByDate(records);
296
+ return this.buildByDateViewEntry(records, byDate);
297
+ }
298
+ if (parsed.view === "by-service" && parsed.key) {
299
+ const recs = this.aggregateByService(records).get(parsed.key);
300
+ if (!recs) throw new _aigne_afs.AFSNotFoundError(path);
301
+ return this.buildServiceSummaryEntry(parsed.key, recs);
302
+ }
303
+ if (parsed.view === "by-date" && parsed.key) {
304
+ const recs = this.aggregateByDate(records).get(parsed.key);
305
+ if (!recs) throw new _aigne_afs.AFSNotFoundError(path);
306
+ return this.buildDateSummaryEntry(parsed.key, recs);
307
+ }
308
+ throw new _aigne_afs.AFSNotFoundError(path);
309
+ }
310
+ async handleMeta(ctx) {
311
+ const metaPath = ctx.path || "/";
312
+ const resourcePath = metaPath.replace(/\/?\.meta$/, "") || "/";
313
+ const fakeCtx = {
314
+ ...ctx,
315
+ path: resourcePath,
316
+ params: { path: resourcePath === "/" ? void 0 : resourcePath.slice(1) }
317
+ };
318
+ const entry = await this.handleRead(fakeCtx);
319
+ if (!entry) return void 0;
320
+ return this.buildEntry(metaPath, {
321
+ content: entry.meta ?? {},
322
+ meta: entry.meta ?? void 0
323
+ });
324
+ }
325
+ async handleStat(ctx) {
326
+ const entry = await this.handleRead(ctx);
327
+ if (!entry) return {};
328
+ const { content: _, ...rest } = entry;
329
+ return { data: rest };
330
+ }
331
+ async handleExplain(ctx) {
332
+ const path = ctx.path || "/";
333
+ const parsed = parseCostPath(path);
334
+ const cloud = this.cloudName;
335
+ if (!parsed.view) return {
336
+ content: `Root of the ${cloud} cost provider. Contains two views: by-service (cost/usage per service) and by-date (cost/usage per day).`,
337
+ format: "text"
338
+ };
339
+ if (parsed.view === "by-service" && !parsed.key) return {
340
+ content: `Complete table of all ${cloud} services with amounts, currency, and percentage. Reading returns all rows in one request — suitable for AUP table binding.`,
341
+ format: "text"
342
+ };
343
+ if (parsed.view === "by-date" && !parsed.key) return {
344
+ content: `Complete time series of daily cost/usage for ${cloud}. Reading returns all date rows in one request — suitable for AUP chart binding.`,
345
+ format: "text"
346
+ };
347
+ if (parsed.view === "by-service" && parsed.key) return {
348
+ content: `Cost/usage summary for ${cloud} service "${parsed.key}" with daily breakdown over the configured date range.`,
349
+ format: "text"
350
+ };
351
+ if (parsed.view === "by-date" && parsed.key) return {
352
+ content: `Cost/usage breakdown for ${cloud} on ${parsed.key}, with per-service detail.`,
353
+ format: "text"
354
+ };
355
+ return {
356
+ content: `Cloud cost data at path: ${path}`,
357
+ format: "text"
358
+ };
359
+ }
360
+ async handleSearch(ctx, query, options) {
361
+ if (!query) return { data: [] };
362
+ if (query.length > 1e3) return { data: [] };
363
+ const parsed = parseCostPath(ctx.path || "/");
364
+ const lowerQuery = query.toLowerCase();
365
+ const limit = options?.limit;
366
+ if (parsed.view && parsed.key) return { data: [] };
367
+ const records = await this.getRecords();
368
+ const byService = this.aggregateByService(records);
369
+ const results = [];
370
+ const matched = /* @__PURE__ */ new Set();
371
+ for (const r of records) {
372
+ if (limit !== void 0 && results.length >= limit) break;
373
+ if (matched.has(r.service)) continue;
374
+ if (r.service.toLowerCase().includes(lowerQuery) || r.tags && (Object.keys(r.tags).some((k) => k.toLowerCase().includes(lowerQuery)) || Object.values(r.tags).some((v) => v.toLowerCase().includes(lowerQuery)))) {
375
+ matched.add(r.service);
376
+ const recs = byService.get(r.service);
377
+ if (recs) results.push(this.buildServiceSummaryEntry(r.service, recs));
378
+ }
379
+ }
380
+ return { data: limit !== void 0 ? results.slice(0, limit) : results };
381
+ }
382
+ async handleWrite(ctx, payload) {
383
+ if (payload.content !== void 0) throw new _aigne_afs.AFSReadonlyError("Cost data is read-only. Use meta field for annotations.");
384
+ const path = ctx.path || "/";
385
+ const parsed = parseCostPath(path);
386
+ const records = await this.getRecords();
387
+ if (parsed.view === "by-service" && parsed.key) {
388
+ if (!this.aggregateByService(records).has(parsed.key)) throw new _aigne_afs.AFSNotFoundError(path);
389
+ } else if (parsed.view === "by-date" && parsed.key) {
390
+ if (!this.aggregateByDate(records).has(parsed.key)) throw new _aigne_afs.AFSNotFoundError(path);
391
+ }
392
+ if (payload.meta) {
393
+ const annKey = this.annotationKey(parsed.view === "by-service" ? parsed.key : void 0, parsed.view === "by-date" ? parsed.key : void 0);
394
+ this.annotationStore.set(annKey, payload.meta);
395
+ }
396
+ return { data: await this.handleRead(ctx) };
397
+ }
398
+ async handleRootActions(_ctx) {
399
+ return { data: [this.buildEntry("/.actions/refresh", {
400
+ content: {
401
+ name: "refresh",
402
+ description: "Invalidate cache and re-fetch",
403
+ inputSchema: {}
404
+ },
405
+ meta: {
406
+ kind: "action",
407
+ description: "Invalidate cache and re-fetch"
408
+ }
409
+ })] };
410
+ }
411
+ async handleRefresh(_ctx, _args) {
412
+ this.cache.invalidate();
413
+ return {
414
+ success: true,
415
+ data: { message: "Cache invalidated" }
416
+ };
417
+ }
418
+ };
419
+ require_decorate.__decorate([(0, _aigne_afs_provider.List)("/:path*", { handleDepth: true })], AFSCostBaseProvider.prototype, "handleList", null);
420
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/.meta/.capabilities")], AFSCostBaseProvider.prototype, "handleCapabilities", null);
421
+ require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/:path*")], AFSCostBaseProvider.prototype, "handleRead", null);
422
+ require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/:path*")], AFSCostBaseProvider.prototype, "handleMeta", null);
423
+ require_decorate.__decorate([(0, _aigne_afs_provider.Stat)("/:path*")], AFSCostBaseProvider.prototype, "handleStat", null);
424
+ require_decorate.__decorate([(0, _aigne_afs_provider.Explain)("/:path*")], AFSCostBaseProvider.prototype, "handleExplain", null);
425
+ require_decorate.__decorate([(0, _aigne_afs_provider.Search)("/:path*")], AFSCostBaseProvider.prototype, "handleSearch", null);
426
+ require_decorate.__decorate([(0, _aigne_afs_provider.Write)("/:path*")], AFSCostBaseProvider.prototype, "handleWrite", null);
427
+ require_decorate.__decorate([(0, _aigne_afs_provider.Actions)("/")], AFSCostBaseProvider.prototype, "handleRootActions", null);
428
+ require_decorate.__decorate([_aigne_afs_provider.Actions.Exec("/", "refresh")], AFSCostBaseProvider.prototype, "handleRefresh", null);
429
+
430
+ //#endregion
431
+ exports.AFSCostBaseProvider = AFSCostBaseProvider;