@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.
- package/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/annotations.cjs +61 -0
- package/dist/annotations.d.cts +22 -0
- package/dist/annotations.d.cts.map +1 -0
- package/dist/annotations.d.mts +22 -0
- package/dist/annotations.d.mts.map +1 -0
- package/dist/annotations.mjs +62 -0
- package/dist/annotations.mjs.map +1 -0
- package/dist/base-provider.cjs +431 -0
- package/dist/base-provider.d.cts +77 -0
- package/dist/base-provider.d.cts.map +1 -0
- package/dist/base-provider.d.mts +77 -0
- package/dist/base-provider.d.mts.map +1 -0
- package/dist/base-provider.mjs +432 -0
- package/dist/base-provider.mjs.map +1 -0
- package/dist/cache.cjs +43 -0
- package/dist/cache.d.cts +22 -0
- package/dist/cache.d.cts.map +1 -0
- package/dist/cache.d.mts +22 -0
- package/dist/cache.d.mts.map +1 -0
- package/dist/cache.mjs +43 -0
- package/dist/cache.mjs.map +1 -0
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +6 -0
- package/dist/mock-adapter.cjs +38 -0
- package/dist/mock-adapter.d.cts +25 -0
- package/dist/mock-adapter.d.cts.map +1 -0
- package/dist/mock-adapter.d.mts +25 -0
- package/dist/mock-adapter.d.mts.map +1 -0
- package/dist/mock-adapter.mjs +38 -0
- package/dist/mock-adapter.mjs.map +1 -0
- package/dist/types.d.cts +37 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +37 -0
- package/dist/types.d.mts.map +1 -0
- 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;
|