sorokeep 1.0.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.
- package/CONTRIBUTING.md +296 -0
- package/LICENSE +21 -0
- package/README.md +677 -0
- package/dist/alerts/alerts.d.ts +21 -0
- package/dist/alerts/alerts.d.ts.map +1 -0
- package/dist/alerts/alerts.js +18 -0
- package/dist/alerts/alerts.js.map +1 -0
- package/dist/alerts/discord.d.ts +3 -0
- package/dist/alerts/discord.d.ts.map +1 -0
- package/dist/alerts/discord.js +199 -0
- package/dist/alerts/discord.js.map +1 -0
- package/dist/alerts/dispatcher.d.ts +13 -0
- package/dist/alerts/dispatcher.d.ts.map +1 -0
- package/dist/alerts/dispatcher.js +91 -0
- package/dist/alerts/dispatcher.js.map +1 -0
- package/dist/alerts/keys.d.ts +24 -0
- package/dist/alerts/keys.d.ts.map +1 -0
- package/dist/alerts/keys.js +44 -0
- package/dist/alerts/keys.js.map +1 -0
- package/dist/alerts/pagerduty.d.ts +8 -0
- package/dist/alerts/pagerduty.d.ts.map +1 -0
- package/dist/alerts/pagerduty.js +164 -0
- package/dist/alerts/pagerduty.js.map +1 -0
- package/dist/alerts/resource.d.ts +23 -0
- package/dist/alerts/resource.d.ts.map +1 -0
- package/dist/alerts/resource.js +167 -0
- package/dist/alerts/resource.js.map +1 -0
- package/dist/alerts/slack.d.ts +7 -0
- package/dist/alerts/slack.d.ts.map +1 -0
- package/dist/alerts/slack.js +184 -0
- package/dist/alerts/slack.js.map +1 -0
- package/dist/alerts/telegram.d.ts +9 -0
- package/dist/alerts/telegram.d.ts.map +1 -0
- package/dist/alerts/telegram.js +133 -0
- package/dist/alerts/telegram.js.map +1 -0
- package/dist/alerts/templates.d.ts +109 -0
- package/dist/alerts/templates.d.ts.map +1 -0
- package/dist/alerts/templates.js +139 -0
- package/dist/alerts/templates.js.map +1 -0
- package/dist/alerts/types.d.ts +113 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +87 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/alerts/webhook.d.ts +13 -0
- package/dist/alerts/webhook.d.ts.map +1 -0
- package/dist/alerts/webhook.js +56 -0
- package/dist/alerts/webhook.js.map +1 -0
- package/dist/cli/schemaFormatter.d.ts +11 -0
- package/dist/cli/schemaFormatter.d.ts.map +1 -0
- package/dist/cli/schemaFormatter.js +74 -0
- package/dist/cli/schemaFormatter.js.map +1 -0
- package/dist/commands/alerts.d.ts +3 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +256 -0
- package/dist/commands/alerts.js.map +1 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.d.ts.map +1 -0
- package/dist/commands/budget.js +47 -0
- package/dist/commands/budget.js.map +1 -0
- package/dist/commands/channels.d.ts +3 -0
- package/dist/commands/channels.d.ts.map +1 -0
- package/dist/commands/channels.js +84 -0
- package/dist/commands/channels.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +58 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +36 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/costs.d.ts +15 -0
- package/dist/commands/costs.d.ts.map +1 -0
- package/dist/commands/costs.js +280 -0
- package/dist/commands/costs.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +93 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +140 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/guard.d.ts +3 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +181 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +57 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/inspect.d.ts +3 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +82 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +20 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pause.d.ts +3 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +32 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/resources.d.ts +3 -0
- package/dist/commands/resources.d.ts.map +1 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/resources.js.map +1 -0
- package/dist/commands/restore.d.ts +3 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +92 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/resume.d.ts +3 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +31 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +63 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +163 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/aws_secrets.d.ts +13 -0
- package/dist/core/aws_secrets.d.ts.map +1 -0
- package/dist/core/aws_secrets.js +34 -0
- package/dist/core/aws_secrets.js.map +1 -0
- package/dist/core/budget.d.ts +10 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +31 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/channels.d.ts +67 -0
- package/dist/core/channels.d.ts.map +1 -0
- package/dist/core/channels.js +136 -0
- package/dist/core/channels.js.map +1 -0
- package/dist/core/check.d.ts +25 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +85 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/completion.d.ts +9 -0
- package/dist/core/completion.d.ts.map +1 -0
- package/dist/core/completion.js +68 -0
- package/dist/core/completion.js.map +1 -0
- package/dist/core/costs.d.ts +90 -0
- package/dist/core/costs.d.ts.map +1 -0
- package/dist/core/costs.js +147 -0
- package/dist/core/costs.js.map +1 -0
- package/dist/core/decoder.d.ts +7 -0
- package/dist/core/decoder.d.ts.map +1 -0
- package/dist/core/decoder.js +38 -0
- package/dist/core/decoder.js.map +1 -0
- package/dist/core/discovery.d.ts +38 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +208 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/extension.d.ts +83 -0
- package/dist/core/extension.d.ts.map +1 -0
- package/dist/core/extension.js +429 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/inspect.d.ts +62 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +303 -0
- package/dist/core/inspect.js.map +1 -0
- package/dist/core/introspection.d.ts +8 -0
- package/dist/core/introspection.d.ts.map +1 -0
- package/dist/core/introspection.js +8 -0
- package/dist/core/introspection.js.map +1 -0
- package/dist/core/monitor.d.ts +42 -0
- package/dist/core/monitor.d.ts.map +1 -0
- package/dist/core/monitor.js +188 -0
- package/dist/core/monitor.js.map +1 -0
- package/dist/core/rent_projection.d.ts +153 -0
- package/dist/core/rent_projection.d.ts.map +1 -0
- package/dist/core/rent_projection.js +146 -0
- package/dist/core/rent_projection.js.map +1 -0
- package/dist/core/scvalTranslator.d.ts +11 -0
- package/dist/core/scvalTranslator.d.ts.map +1 -0
- package/dist/core/scvalTranslator.js +36 -0
- package/dist/core/scvalTranslator.js.map +1 -0
- package/dist/core/state_diff.d.ts +45 -0
- package/dist/core/state_diff.d.ts.map +1 -0
- package/dist/core/state_diff.js +112 -0
- package/dist/core/state_diff.js.map +1 -0
- package/dist/core/status.d.ts +24 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +57 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/vault.d.ts +37 -0
- package/dist/core/vault.d.ts.map +1 -0
- package/dist/core/vault.js +179 -0
- package/dist/core/vault.js.map +1 -0
- package/dist/core/watch.d.ts +36 -0
- package/dist/core/watch.d.ts.map +1 -0
- package/dist/core/watch.js +176 -0
- package/dist/core/watch.js.map +1 -0
- package/dist/daemon/loop.d.ts +38 -0
- package/dist/daemon/loop.d.ts.map +1 -0
- package/dist/daemon/loop.js +169 -0
- package/dist/daemon/loop.js.map +1 -0
- package/dist/db/backup.d.ts +12 -0
- package/dist/db/backup.d.ts.map +1 -0
- package/dist/db/backup.js +132 -0
- package/dist/db/backup.js.map +1 -0
- package/dist/db/budget.d.ts +17 -0
- package/dist/db/budget.d.ts.map +1 -0
- package/dist/db/budget.js +31 -0
- package/dist/db/budget.js.map +1 -0
- package/dist/db/database.d.ts +6 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +134 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrations/.gitkeep +1 -0
- package/dist/db/migrations/001_resource_usage_logs.sql +28 -0
- package/dist/db/migrator.d.ts +28 -0
- package/dist/db/migrator.d.ts.map +1 -0
- package/dist/db/migrator.js +75 -0
- package/dist/db/migrator.js.map +1 -0
- package/dist/db/repositories.d.ts +474 -0
- package/dist/db/repositories.d.ts.map +1 -0
- package/dist/db/repositories.js +809 -0
- package/dist/db/repositories.js.map +1 -0
- package/dist/db/schema.sql +207 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +20 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +16 -0
- package/dist/lib.js.map +1 -0
- package/dist/logging/index.d.ts +22 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +34 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +14 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +135 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +10 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +14 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +51 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-extension-costs.d.ts +4 -0
- package/dist/mcp/tools/get-extension-costs.d.ts.map +1 -0
- package/dist/mcp/tools/get-extension-costs.js +33 -0
- package/dist/mcp/tools/get-extension-costs.js.map +1 -0
- package/dist/mcp/tools/get_contract_status.d.ts +8 -0
- package/dist/mcp/tools/get_contract_status.d.ts.map +1 -0
- package/dist/mcp/tools/get_contract_status.js +33 -0
- package/dist/mcp/tools/get_contract_status.js.map +1 -0
- package/dist/rpc/client.d.ts +154 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +766 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/utils/config.d.ts +51 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +86 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +66 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/watch-config.d.ts +13 -0
- package/dist/utils/watch-config.d.ts.map +1 -0
- package/dist/utils/watch-config.js +32 -0
- package/dist/utils/watch-config.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
export { upsertBudget, getBudget, addBudgetSpent } from "./budget.js";
|
|
2
|
+
// ---------------------------- Database Access Functions For Schema: Contract ----------------------------
|
|
3
|
+
export function insertContract(db, contract) {
|
|
4
|
+
db.prepare(`
|
|
5
|
+
INSERT INTO contracts (id, name, network, wasm_hash, tags, poll_interval_seconds, active)
|
|
6
|
+
VALUES (@id, @name, @network, @wasm_hash, @tags, @poll_interval_seconds, @active)
|
|
7
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
8
|
+
name = excluded.name,
|
|
9
|
+
network = excluded.network,
|
|
10
|
+
wasm_hash = excluded.wasm_hash,
|
|
11
|
+
tags = excluded.tags,
|
|
12
|
+
poll_interval_seconds = COALESCE(excluded.poll_interval_seconds, contracts.poll_interval_seconds),
|
|
13
|
+
active = COALESCE(excluded.active, contracts.active)
|
|
14
|
+
`).run({
|
|
15
|
+
id: contract.id,
|
|
16
|
+
name: contract.name ?? null,
|
|
17
|
+
network: contract.network,
|
|
18
|
+
wasm_hash: contract.wasm_hash ?? null,
|
|
19
|
+
tags: contract.tags ?? null,
|
|
20
|
+
poll_interval_seconds: contract.poll_interval_seconds ?? null,
|
|
21
|
+
active: contract.active ?? 1,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function getContract(db, id) {
|
|
25
|
+
return db.prepare("SELECT * FROM contracts WHERE id = ?").get(id);
|
|
26
|
+
}
|
|
27
|
+
export function getAllContracts(db) {
|
|
28
|
+
return db.prepare("SELECT * FROM contracts").all();
|
|
29
|
+
}
|
|
30
|
+
export function updateLastCheckedLedger(db, contractId, ledger) {
|
|
31
|
+
db.prepare("UPDATE contracts SET last_checked_ledger = ? WHERE id = ?").run(ledger, contractId);
|
|
32
|
+
}
|
|
33
|
+
export function deleteContract(db, id) {
|
|
34
|
+
db.prepare("DELETE FROM contracts WHERE id = ?").run(id);
|
|
35
|
+
}
|
|
36
|
+
export function updateContractPollInterval(db, contractId, pollIntervalSeconds) {
|
|
37
|
+
db.prepare("UPDATE contracts SET poll_interval_seconds = ? WHERE id = ?").run(pollIntervalSeconds, contractId);
|
|
38
|
+
}
|
|
39
|
+
export function getContractPollInterval(db, contractId) {
|
|
40
|
+
const row = db.prepare("SELECT poll_interval_seconds FROM contracts WHERE id = ?").get(contractId);
|
|
41
|
+
return row?.poll_interval_seconds ?? null;
|
|
42
|
+
}
|
|
43
|
+
export function setContractActiveStatus(db, contractId, active) {
|
|
44
|
+
db.prepare("UPDATE contracts SET active = ? WHERE id = ?").run(active ? 1 : 0, contractId);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Record that a successful contract introspection (instance/WASM key discovery)
|
|
48
|
+
* was performed at the given timestamp. Accepts an ISO-8601 string so callers
|
|
49
|
+
* can control the clock in tests.
|
|
50
|
+
*/
|
|
51
|
+
export function updateLastIntrospectedAt(db, contractId, isoTimestamp) {
|
|
52
|
+
db.prepare("UPDATE contracts SET last_introspected_at = ? WHERE id = ?").run(isoTimestamp, contractId);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Return true when the introspection cache for the given contract is still
|
|
56
|
+
* valid — i.e. `last_introspected_at` is not NULL and the timestamp is
|
|
57
|
+
* strictly less than `maxAgeMs` milliseconds ago.
|
|
58
|
+
*
|
|
59
|
+
* The default max-age is 24 hours (86 400 000 ms).
|
|
60
|
+
* The boundary is *exclusive on the valid side*: exactly 24 h ago is expired.
|
|
61
|
+
*/
|
|
62
|
+
export function isIntrospectionCacheValid(db, contractId, maxAgeMs = 24 * 60 * 60 * 1_000) {
|
|
63
|
+
const row = db
|
|
64
|
+
.prepare("SELECT last_introspected_at FROM contracts WHERE id = ?")
|
|
65
|
+
.get(contractId);
|
|
66
|
+
if (!row || row.last_introspected_at === null)
|
|
67
|
+
return false;
|
|
68
|
+
const introspectedAt = new Date(row.last_introspected_at).getTime();
|
|
69
|
+
const ageMs = Date.now() - introspectedAt;
|
|
70
|
+
// strictly less than → at exactly 24 h the cache is expired
|
|
71
|
+
return ageMs < maxAgeMs;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------- Database Access Functions For Schema: ContractEntry ----------------------------
|
|
74
|
+
export function upsertEntry(db, entry) {
|
|
75
|
+
db.prepare(`
|
|
76
|
+
INSERT INTO contract_entries (contract_id, entry_key_xdr, entry_type, label, live_until_ledger, last_modified_ledger, discovery_source, last_checked_at)
|
|
77
|
+
VALUES (@contract_id, @entry_key_xdr, @entry_type, @label, @live_until_ledger, @last_modified_ledger, @discovery_source, datetime('now'))
|
|
78
|
+
ON CONFLICT(contract_id, entry_key_xdr) DO UPDATE SET
|
|
79
|
+
live_until_ledger = @live_until_ledger,
|
|
80
|
+
last_modified_ledger = @last_modified_ledger,
|
|
81
|
+
last_checked_at = datetime('now')
|
|
82
|
+
`).run({
|
|
83
|
+
contract_id: entry.contract_id,
|
|
84
|
+
entry_key_xdr: entry.entry_key_xdr,
|
|
85
|
+
entry_type: entry.entry_type,
|
|
86
|
+
label: entry.label ?? null,
|
|
87
|
+
live_until_ledger: entry.live_until_ledger ?? null,
|
|
88
|
+
last_modified_ledger: entry.last_modified_ledger ?? null,
|
|
89
|
+
discovery_source: entry.discovery_source ?? "deterministic",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export function getEntriesForContract(db, contractId) {
|
|
93
|
+
return db.prepare("SELECT * FROM contract_entries WHERE contract_id = ?").all(contractId);
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------- Database Access Functions For Other Schema: ExtensionPolicy----------------------------
|
|
96
|
+
export function upsertExtensionPolicy(db, policy) {
|
|
97
|
+
db.prepare(`
|
|
98
|
+
INSERT INTO extension_policies (contract_id, enabled, target_ttl_ledgers, extend_when_below_ledgers, keypair_public, keypair_source)
|
|
99
|
+
VALUES (@contract_id, @enabled, @target_ttl_ledgers, @extend_when_below_ledgers, @keypair_public, @keypair_source)
|
|
100
|
+
ON CONFLICT(contract_id) DO UPDATE SET
|
|
101
|
+
enabled = @enabled,
|
|
102
|
+
target_ttl_ledgers = @target_ttl_ledgers,
|
|
103
|
+
extend_when_below_ledgers = @extend_when_below_ledgers,
|
|
104
|
+
keypair_public = @keypair_public,
|
|
105
|
+
keypair_source = @keypair_source
|
|
106
|
+
`).run({
|
|
107
|
+
contract_id: policy.contract_id,
|
|
108
|
+
enabled: policy.enabled !== false ? 1 : 0,
|
|
109
|
+
target_ttl_ledgers: policy.target_ttl_ledgers,
|
|
110
|
+
extend_when_below_ledgers: policy.extend_when_below_ledgers,
|
|
111
|
+
keypair_public: policy.keypair_public ?? null,
|
|
112
|
+
keypair_source: policy.keypair_source ?? null,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export function getExtensionPolicy(db, contractId) {
|
|
116
|
+
return db.prepare("SELECT * FROM extension_policies WHERE contract_id = ?").get(contractId);
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------- Database Access Functions For Other Schema: AlertConfig----------------------------
|
|
119
|
+
export function insertAlertConfig(db, config) {
|
|
120
|
+
db.prepare(`
|
|
121
|
+
INSERT INTO alert_configs (contract_id, channel_type, channel_target, threshold_ledgers, webhook_secret)
|
|
122
|
+
VALUES (@contract_id, @channel_type, @channel_target, @threshold_ledgers, @webhook_secret)
|
|
123
|
+
`).run({
|
|
124
|
+
...config,
|
|
125
|
+
webhook_secret: config.webhook_secret ?? null,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
export function getAlertConfigById(db, id) {
|
|
129
|
+
return db.prepare("SELECT * FROM alert_configs WHERE id = ?").get(id);
|
|
130
|
+
}
|
|
131
|
+
export function getAlertConfigsForContract(db, contractId) {
|
|
132
|
+
return db.prepare("SELECT * FROM alert_configs WHERE contract_id = ?").all(contractId);
|
|
133
|
+
}
|
|
134
|
+
export function deleteAlertConfig(db, id) {
|
|
135
|
+
db.prepare("DELETE FROM alert_configs WHERE id = ?").run(id);
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------- Database Access Functions For Other Schema: AlertFired----------------------------
|
|
138
|
+
export function recordAlertFired(db, alert) {
|
|
139
|
+
db.prepare(`
|
|
140
|
+
INSERT INTO alerts_fired (alert_config_id, contract_entry_id, fired_at_ledger, ttl_at_fire)
|
|
141
|
+
VALUES (@alert_config_id, @contract_entry_id, @fired_at_ledger, @ttl_at_fire)
|
|
142
|
+
`).run(alert);
|
|
143
|
+
}
|
|
144
|
+
export function hasUnresolvedAlert(db, alertConfigId, entryId) {
|
|
145
|
+
const row = db.prepare(`
|
|
146
|
+
SELECT 1 FROM alerts_fired
|
|
147
|
+
WHERE alert_config_id = ? AND contract_entry_id = ? AND resolved = 0
|
|
148
|
+
LIMIT 1
|
|
149
|
+
`).get(alertConfigId, entryId);
|
|
150
|
+
return row !== undefined;
|
|
151
|
+
}
|
|
152
|
+
export function resolveAlerts(db, entryId, alertConfigId) {
|
|
153
|
+
const rows = db.prepare(`
|
|
154
|
+
SELECT alert_config_id FROM alerts_fired
|
|
155
|
+
WHERE contract_entry_id = ? AND alert_config_id = ? AND resolved = 0
|
|
156
|
+
`).all(entryId, alertConfigId);
|
|
157
|
+
if (rows.length > 0) {
|
|
158
|
+
db.prepare(`
|
|
159
|
+
UPDATE alerts_fired SET resolved = 1, resolved_at = datetime('now')
|
|
160
|
+
WHERE contract_entry_id = ? AND alert_config_id = ? AND resolved = 0
|
|
161
|
+
`).run(entryId, alertConfigId);
|
|
162
|
+
}
|
|
163
|
+
return rows.map(r => r.alert_config_id);
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------- Database Access Functions For Other Schema: ExtensionRecord----------------------------
|
|
166
|
+
export function recordExtension(db, record) {
|
|
167
|
+
db.prepare(`
|
|
168
|
+
INSERT INTO extension_history (contract_id, contract_entry_id, old_ttl_ledgers, new_ttl_ledgers, tx_hash, cost_xlm, cpu_insns, mem_bytes, is_anomaly, executed_at_ledger)
|
|
169
|
+
VALUES (@contract_id, @contract_entry_id, @old_ttl_ledgers, @new_ttl_ledgers, @tx_hash, @cost_xlm, @cpu_insns, @mem_bytes, @is_anomaly, @executed_at_ledger)
|
|
170
|
+
`).run({
|
|
171
|
+
...record,
|
|
172
|
+
cost_xlm: record.cost_xlm ?? null,
|
|
173
|
+
cpu_insns: record.cpu_insns ?? null,
|
|
174
|
+
mem_bytes: record.mem_bytes ?? null,
|
|
175
|
+
is_anomaly: record.is_anomaly ? 1 : 0,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export function getExtensionHistory(db, contractId, days) {
|
|
179
|
+
if (days) {
|
|
180
|
+
return db.prepare(`
|
|
181
|
+
SELECT * FROM extension_history
|
|
182
|
+
WHERE contract_id = ? AND executed_at >= datetime('now', ?)
|
|
183
|
+
ORDER BY executed_at DESC
|
|
184
|
+
`).all(contractId, `-${days} days`);
|
|
185
|
+
}
|
|
186
|
+
return db.prepare(`
|
|
187
|
+
SELECT * FROM extension_history WHERE contract_id = ? ORDER BY executed_at DESC
|
|
188
|
+
`).all(contractId);
|
|
189
|
+
}
|
|
190
|
+
export function aggregateDailyCostSnapshots(db) {
|
|
191
|
+
const rows = db.prepare(`
|
|
192
|
+
SELECT
|
|
193
|
+
eh.contract_id AS contract_id,
|
|
194
|
+
date(eh.executed_at) AS snapshot_date,
|
|
195
|
+
COUNT(*) AS total_extensions,
|
|
196
|
+
SUM(COALESCE(eh.cost_xlm, 0.0)) AS total_cost_xlm,
|
|
197
|
+
SUM(CASE WHEN ce.entry_type = 'instance' THEN 1 ELSE 0 END) AS instance_extensions,
|
|
198
|
+
SUM(CASE WHEN ce.entry_type = 'instance' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END) AS instance_cost_xlm,
|
|
199
|
+
SUM(CASE WHEN ce.entry_type = 'wasm' THEN 1 ELSE 0 END) AS wasm_extensions,
|
|
200
|
+
SUM(CASE WHEN ce.entry_type = 'wasm' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END) AS wasm_cost_xlm,
|
|
201
|
+
SUM(CASE WHEN ce.entry_type = 'persistent' THEN 1 ELSE 0 END) AS persistent_extensions,
|
|
202
|
+
SUM(CASE WHEN ce.entry_type = 'persistent' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END) AS persistent_cost_xlm,
|
|
203
|
+
SUM(CASE WHEN ce.entry_type = 'temporary' THEN 1 ELSE 0 END) AS temporary_extensions,
|
|
204
|
+
SUM(CASE WHEN ce.entry_type = 'temporary' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END) AS temporary_cost_xlm
|
|
205
|
+
FROM extension_history eh
|
|
206
|
+
JOIN contract_entries ce ON ce.id = eh.contract_entry_id
|
|
207
|
+
WHERE date(eh.executed_at) < date('now')
|
|
208
|
+
AND date(eh.executed_at) >= date('now', '-7 days')
|
|
209
|
+
GROUP BY eh.contract_id, date(eh.executed_at)
|
|
210
|
+
`).all();
|
|
211
|
+
const upsert = db.prepare(`
|
|
212
|
+
INSERT INTO cost_daily_snapshots (
|
|
213
|
+
contract_id, snapshot_date,
|
|
214
|
+
total_extensions, total_cost_xlm,
|
|
215
|
+
instance_extensions, instance_cost_xlm,
|
|
216
|
+
wasm_extensions, wasm_cost_xlm,
|
|
217
|
+
persistent_extensions, persistent_cost_xlm,
|
|
218
|
+
temporary_extensions, temporary_cost_xlm
|
|
219
|
+
) VALUES (
|
|
220
|
+
@contract_id, @snapshot_date,
|
|
221
|
+
@total_extensions, @total_cost_xlm,
|
|
222
|
+
@instance_extensions, @instance_cost_xlm,
|
|
223
|
+
@wasm_extensions, @wasm_cost_xlm,
|
|
224
|
+
@persistent_extensions, @persistent_cost_xlm,
|
|
225
|
+
@temporary_extensions, @temporary_cost_xlm
|
|
226
|
+
)
|
|
227
|
+
ON CONFLICT(contract_id, snapshot_date) DO UPDATE SET
|
|
228
|
+
total_extensions = excluded.total_extensions,
|
|
229
|
+
total_cost_xlm = excluded.total_cost_xlm,
|
|
230
|
+
instance_extensions = excluded.instance_extensions,
|
|
231
|
+
instance_cost_xlm = excluded.instance_cost_xlm,
|
|
232
|
+
wasm_extensions = excluded.wasm_extensions,
|
|
233
|
+
wasm_cost_xlm = excluded.wasm_cost_xlm,
|
|
234
|
+
persistent_extensions = excluded.persistent_extensions,
|
|
235
|
+
persistent_cost_xlm = excluded.persistent_cost_xlm,
|
|
236
|
+
temporary_extensions = excluded.temporary_extensions,
|
|
237
|
+
temporary_cost_xlm = excluded.temporary_cost_xlm
|
|
238
|
+
`);
|
|
239
|
+
const transaction = db.transaction((snapshotRows) => {
|
|
240
|
+
for (const row of snapshotRows) {
|
|
241
|
+
upsert.run(row);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
transaction(rows);
|
|
245
|
+
}
|
|
246
|
+
export function getCostDailySnapshots(db, contractId, days) {
|
|
247
|
+
if (days) {
|
|
248
|
+
return db.prepare(`
|
|
249
|
+
SELECT * FROM cost_daily_snapshots
|
|
250
|
+
WHERE contract_id = ? AND snapshot_date >= date('now', ?)
|
|
251
|
+
ORDER BY snapshot_date DESC
|
|
252
|
+
`).all(contractId, `-${Math.max(days - 1, 0)} days`);
|
|
253
|
+
}
|
|
254
|
+
return db.prepare(`
|
|
255
|
+
SELECT * FROM cost_daily_snapshots
|
|
256
|
+
WHERE contract_id = ?
|
|
257
|
+
ORDER BY snapshot_date DESC
|
|
258
|
+
`).all(contractId);
|
|
259
|
+
}
|
|
260
|
+
export function getContractCostSummary(db, contractId, days) {
|
|
261
|
+
const snapshotParams = days ? [`-${Math.max(days - 1, 0)} days`] : [];
|
|
262
|
+
const snapshotRow = days
|
|
263
|
+
? db.prepare(`
|
|
264
|
+
SELECT
|
|
265
|
+
COALESCE(SUM(total_extensions), 0) AS total_extensions,
|
|
266
|
+
COALESCE(SUM(total_cost_xlm), 0.0) AS total_cost_xlm,
|
|
267
|
+
COALESCE(SUM(instance_extensions), 0) AS instance_extensions,
|
|
268
|
+
COALESCE(SUM(instance_cost_xlm), 0.0) AS instance_cost_xlm,
|
|
269
|
+
COALESCE(SUM(wasm_extensions), 0) AS wasm_extensions,
|
|
270
|
+
COALESCE(SUM(wasm_cost_xlm), 0.0) AS wasm_cost_xlm,
|
|
271
|
+
COALESCE(SUM(persistent_extensions), 0) AS persistent_extensions,
|
|
272
|
+
COALESCE(SUM(persistent_cost_xlm), 0.0) AS persistent_cost_xlm,
|
|
273
|
+
COALESCE(SUM(temporary_extensions), 0) AS temporary_extensions,
|
|
274
|
+
COALESCE(SUM(temporary_cost_xlm), 0.0) AS temporary_cost_xlm
|
|
275
|
+
FROM cost_daily_snapshots
|
|
276
|
+
WHERE contract_id = ? AND snapshot_date >= date('now', ?)
|
|
277
|
+
`).get(contractId, ...snapshotParams)
|
|
278
|
+
: db.prepare(`
|
|
279
|
+
SELECT
|
|
280
|
+
COALESCE(SUM(total_extensions), 0) AS total_extensions,
|
|
281
|
+
COALESCE(SUM(total_cost_xlm), 0.0) AS total_cost_xlm,
|
|
282
|
+
COALESCE(SUM(instance_extensions), 0) AS instance_extensions,
|
|
283
|
+
COALESCE(SUM(instance_cost_xlm), 0.0) AS instance_cost_xlm,
|
|
284
|
+
COALESCE(SUM(wasm_extensions), 0) AS wasm_extensions,
|
|
285
|
+
COALESCE(SUM(wasm_cost_xlm), 0.0) AS wasm_cost_xlm,
|
|
286
|
+
COALESCE(SUM(persistent_extensions), 0) AS persistent_extensions,
|
|
287
|
+
COALESCE(SUM(persistent_cost_xlm), 0.0) AS persistent_cost_xlm,
|
|
288
|
+
COALESCE(SUM(temporary_extensions), 0) AS temporary_extensions,
|
|
289
|
+
COALESCE(SUM(temporary_cost_xlm), 0.0) AS temporary_cost_xlm
|
|
290
|
+
FROM cost_daily_snapshots
|
|
291
|
+
WHERE contract_id = ?
|
|
292
|
+
`).get(contractId);
|
|
293
|
+
const currentDayRow = db.prepare(`
|
|
294
|
+
SELECT
|
|
295
|
+
COUNT(*) AS total_extensions,
|
|
296
|
+
COALESCE(SUM(COALESCE(eh.cost_xlm, 0.0)), 0.0) AS total_cost_xlm,
|
|
297
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'instance' THEN 1 ELSE 0 END), 0) AS instance_extensions,
|
|
298
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'instance' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END), 0.0) AS instance_cost_xlm,
|
|
299
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'wasm' THEN 1 ELSE 0 END), 0) AS wasm_extensions,
|
|
300
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'wasm' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END), 0.0) AS wasm_cost_xlm,
|
|
301
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'persistent' THEN 1 ELSE 0 END), 0) AS persistent_extensions,
|
|
302
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'persistent' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END), 0.0) AS persistent_cost_xlm,
|
|
303
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'temporary' THEN 1 ELSE 0 END), 0) AS temporary_extensions,
|
|
304
|
+
COALESCE(SUM(CASE WHEN ce.entry_type = 'temporary' THEN COALESCE(eh.cost_xlm, 0.0) ELSE 0 END), 0.0) AS temporary_cost_xlm
|
|
305
|
+
FROM extension_history eh
|
|
306
|
+
JOIN contract_entries ce ON ce.id = eh.contract_entry_id
|
|
307
|
+
WHERE eh.contract_id = ?
|
|
308
|
+
AND date(eh.executed_at) = date('now')
|
|
309
|
+
`).get(contractId);
|
|
310
|
+
return {
|
|
311
|
+
contract_id: contractId,
|
|
312
|
+
total_extensions: Number((snapshotRow.total_extensions ?? 0) + (currentDayRow.total_extensions ?? 0)),
|
|
313
|
+
total_cost_xlm: Number((snapshotRow.total_cost_xlm ?? 0) + (currentDayRow.total_cost_xlm ?? 0)),
|
|
314
|
+
byType: {
|
|
315
|
+
instance: {
|
|
316
|
+
count: Number((snapshotRow.instance_extensions ?? 0) + (currentDayRow.instance_extensions ?? 0)),
|
|
317
|
+
cost_xlm: Number((snapshotRow.instance_cost_xlm ?? 0) + (currentDayRow.instance_cost_xlm ?? 0)),
|
|
318
|
+
},
|
|
319
|
+
wasm: {
|
|
320
|
+
count: Number((snapshotRow.wasm_extensions ?? 0) + (currentDayRow.wasm_extensions ?? 0)),
|
|
321
|
+
cost_xlm: Number((snapshotRow.wasm_cost_xlm ?? 0) + (currentDayRow.wasm_cost_xlm ?? 0)),
|
|
322
|
+
},
|
|
323
|
+
persistent: {
|
|
324
|
+
count: Number((snapshotRow.persistent_extensions ?? 0) + (currentDayRow.persistent_extensions ?? 0)),
|
|
325
|
+
cost_xlm: Number((snapshotRow.persistent_cost_xlm ?? 0) + (currentDayRow.persistent_cost_xlm ?? 0)),
|
|
326
|
+
},
|
|
327
|
+
temporary: {
|
|
328
|
+
count: Number((snapshotRow.temporary_extensions ?? 0) + (currentDayRow.temporary_extensions ?? 0)),
|
|
329
|
+
cost_xlm: Number((snapshotRow.temporary_cost_xlm ?? 0) + (currentDayRow.temporary_cost_xlm ?? 0)),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Count the number of auto-extension transactions that were executed for the
|
|
336
|
+
* given contract within the last hour.
|
|
337
|
+
*
|
|
338
|
+
* Used by the rate limiter to enforce a maximum of N extensions per hour
|
|
339
|
+
* per contract (issue #142).
|
|
340
|
+
*
|
|
341
|
+
* @param db - The SQLite database connection.
|
|
342
|
+
* @param contractId - The contract to check.
|
|
343
|
+
* @returns The number of extensions recorded in the last 60 minutes.
|
|
344
|
+
*/
|
|
345
|
+
export function countExtensionsInLastHour(db, contractId) {
|
|
346
|
+
const row = db.prepare(`
|
|
347
|
+
SELECT COUNT(*) AS cnt
|
|
348
|
+
FROM extension_history
|
|
349
|
+
WHERE contract_id = ?
|
|
350
|
+
AND datetime(executed_at) >= datetime('now', '-1 hour')
|
|
351
|
+
`).get(contractId);
|
|
352
|
+
return row?.cnt ?? 0;
|
|
353
|
+
}
|
|
354
|
+
export function getAverageResourceUsage(db, contractId, limit) {
|
|
355
|
+
const params = [contractId];
|
|
356
|
+
let query = `
|
|
357
|
+
SELECT cpu_insns, mem_bytes
|
|
358
|
+
FROM extension_history
|
|
359
|
+
WHERE contract_id = ? AND cpu_insns IS NOT NULL AND mem_bytes IS NOT NULL
|
|
360
|
+
ORDER BY executed_at DESC, id DESC
|
|
361
|
+
`;
|
|
362
|
+
if (limit !== undefined) {
|
|
363
|
+
query += ` LIMIT ?`;
|
|
364
|
+
params.push(limit);
|
|
365
|
+
}
|
|
366
|
+
const rows = db.prepare(query).all(...params);
|
|
367
|
+
if (rows.length === 0)
|
|
368
|
+
return null;
|
|
369
|
+
const sumCpu = rows.reduce((acc, row) => acc + row.cpu_insns, 0);
|
|
370
|
+
const sumMem = rows.reduce((acc, row) => acc + row.mem_bytes, 0);
|
|
371
|
+
return {
|
|
372
|
+
avg_cpu_insns: sumCpu / rows.length,
|
|
373
|
+
avg_mem_bytes: sumMem / rows.length,
|
|
374
|
+
count: rows.length
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/** Maximum number of delivery attempts before giving up on an alert. */
|
|
378
|
+
export const MAX_RETRY_COUNT = 5;
|
|
379
|
+
/**
|
|
380
|
+
* Return all undelivered (delivered = 0) alerts for the given network,
|
|
381
|
+
* joining alerts_fired → alert_configs → contract_entries → contracts.
|
|
382
|
+
* Alerts that have exceeded MAX_RETRY_COUNT are excluded.
|
|
383
|
+
*/
|
|
384
|
+
export function getUndeliveredAlerts(db, network) {
|
|
385
|
+
const rows = db.prepare(`
|
|
386
|
+
SELECT
|
|
387
|
+
af.id AS alertFiredId,
|
|
388
|
+
ac.id AS alertConfigId,
|
|
389
|
+
c.id AS contractId,
|
|
390
|
+
c.name AS contractName,
|
|
391
|
+
c.network AS network,
|
|
392
|
+
ce.id AS entryId,
|
|
393
|
+
ce.entry_key_xdr AS entryKeyXdr,
|
|
394
|
+
ce.entry_type AS entryType,
|
|
395
|
+
ce.label AS entryLabel,
|
|
396
|
+
ac.channel_type AS channelType,
|
|
397
|
+
ac.channel_target AS channelTarget,
|
|
398
|
+
ac.threshold_ledgers AS thresholdLedgers,
|
|
399
|
+
ac.webhook_secret AS webhookSecret,
|
|
400
|
+
af.ttl_at_fire AS remainingTTL,
|
|
401
|
+
af.fired_at_ledger AS firedAtLedger,
|
|
402
|
+
af.fired_at AS firedAt,
|
|
403
|
+
af.retry_count AS retryCount
|
|
404
|
+
FROM alerts_fired af
|
|
405
|
+
JOIN alert_configs ac ON ac.id = af.alert_config_id
|
|
406
|
+
JOIN contract_entries ce ON ce.id = af.contract_entry_id
|
|
407
|
+
JOIN contracts c ON c.id = ce.contract_id
|
|
408
|
+
WHERE af.delivered = 0
|
|
409
|
+
AND af.retry_count < ?
|
|
410
|
+
AND c.network = ?
|
|
411
|
+
ORDER BY af.fired_at ASC
|
|
412
|
+
`).all(MAX_RETRY_COUNT, network);
|
|
413
|
+
return rows;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Mark a single alerts_fired record as delivered.
|
|
417
|
+
* Idempotent — safe to call more than once.
|
|
418
|
+
*/
|
|
419
|
+
export function markAlertDelivered(db, alertFiredId) {
|
|
420
|
+
db.prepare(`
|
|
421
|
+
UPDATE alerts_fired
|
|
422
|
+
SET delivered = 1, delivered_at = datetime('now')
|
|
423
|
+
WHERE id = ?
|
|
424
|
+
`).run(alertFiredId);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Count the number of undelivered alerts for the given network.
|
|
428
|
+
* Uses the same filtering logic as getUndeliveredAlerts:
|
|
429
|
+
* - delivered = 0
|
|
430
|
+
* - retry_count < MAX_RETRY_COUNT
|
|
431
|
+
* - matches the specified network
|
|
432
|
+
*/
|
|
433
|
+
export function countUndeliveredAlerts(db, network) {
|
|
434
|
+
const row = db.prepare(`
|
|
435
|
+
SELECT COUNT(*) as count
|
|
436
|
+
FROM alerts_fired af
|
|
437
|
+
JOIN alert_configs ac ON ac.id = af.alert_config_id
|
|
438
|
+
JOIN contract_entries ce ON ce.id = af.contract_entry_id
|
|
439
|
+
JOIN contracts c ON c.id = ce.contract_id
|
|
440
|
+
WHERE af.delivered = 0
|
|
441
|
+
AND af.retry_count < ?
|
|
442
|
+
AND c.network = ?
|
|
443
|
+
`).get(MAX_RETRY_COUNT, network);
|
|
444
|
+
return row.count;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Increment the retry count for a failed alert delivery.
|
|
448
|
+
*/
|
|
449
|
+
export function incrementRetryCount(db, alertFiredId) {
|
|
450
|
+
db.prepare(`
|
|
451
|
+
UPDATE alerts_fired
|
|
452
|
+
SET retry_count = retry_count + 1
|
|
453
|
+
WHERE id = ?
|
|
454
|
+
`).run(alertFiredId);
|
|
455
|
+
}
|
|
456
|
+
export function getAlertHistory(db, contractId, limit) {
|
|
457
|
+
const sql = `
|
|
458
|
+
SELECT
|
|
459
|
+
af.id AS alertFiredId,
|
|
460
|
+
ac.channel_type AS channelType,
|
|
461
|
+
ac.channel_target AS channelTarget,
|
|
462
|
+
ce.entry_key_xdr AS entryKeyXdr,
|
|
463
|
+
ce.entry_type AS entryType,
|
|
464
|
+
ce.label AS entryLabel,
|
|
465
|
+
ac.threshold_ledgers AS thresholdLedgers,
|
|
466
|
+
af.ttl_at_fire AS ttlAtFire,
|
|
467
|
+
af.fired_at_ledger AS firedAtLedger,
|
|
468
|
+
af.fired_at AS firedAt,
|
|
469
|
+
af.resolved AS resolved,
|
|
470
|
+
af.resolved_at AS resolvedAt,
|
|
471
|
+
af.delivered AS delivered,
|
|
472
|
+
af.delivered_at AS deliveredAt,
|
|
473
|
+
af.retry_count AS retryCount
|
|
474
|
+
FROM alerts_fired af
|
|
475
|
+
JOIN alert_configs ac ON ac.id = af.alert_config_id
|
|
476
|
+
JOIN contract_entries ce ON ce.id = af.contract_entry_id
|
|
477
|
+
WHERE ac.contract_id = ?
|
|
478
|
+
ORDER BY af.fired_at DESC
|
|
479
|
+
${limit ? "LIMIT ?" : ""}
|
|
480
|
+
`;
|
|
481
|
+
return (limit
|
|
482
|
+
? db.prepare(sql).all(contractId, limit)
|
|
483
|
+
: db.prepare(sql).all(contractId));
|
|
484
|
+
}
|
|
485
|
+
export function insertChannelAccount(db, account) {
|
|
486
|
+
db.prepare(`
|
|
487
|
+
INSERT INTO channel_accounts (public_key, label, network)
|
|
488
|
+
VALUES (@public_key, @label, @network)
|
|
489
|
+
`).run({
|
|
490
|
+
public_key: account.public_key,
|
|
491
|
+
label: account.label ?? null,
|
|
492
|
+
network: account.network,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
export function upsertChannelAccount(db, account) {
|
|
496
|
+
db.prepare(`
|
|
497
|
+
INSERT INTO channel_accounts (public_key, keypair_source, network)
|
|
498
|
+
VALUES (@public_key, @keypair_source, @network)
|
|
499
|
+
ON CONFLICT(public_key) DO UPDATE SET
|
|
500
|
+
keypair_source = @keypair_source,
|
|
501
|
+
network = @network
|
|
502
|
+
`).run(account);
|
|
503
|
+
}
|
|
504
|
+
export function getChannelAccounts(db, network) {
|
|
505
|
+
return db.prepare("SELECT * FROM channel_accounts WHERE network = ? ORDER BY id ASC")
|
|
506
|
+
.all(network);
|
|
507
|
+
}
|
|
508
|
+
export function updateChannelBalance(db, publicKey, balanceXlm) {
|
|
509
|
+
db.prepare(`
|
|
510
|
+
UPDATE channel_accounts
|
|
511
|
+
SET balance_xlm = ?, balance_checked_at = datetime('now')
|
|
512
|
+
WHERE public_key = ?
|
|
513
|
+
`).run(balanceXlm, publicKey);
|
|
514
|
+
}
|
|
515
|
+
export function deleteChannelAccount(db, publicKey) {
|
|
516
|
+
db.prepare("DELETE FROM channel_accounts WHERE public_key = ?").run(publicKey);
|
|
517
|
+
}
|
|
518
|
+
export function markChannelFunded(db, publicKey) {
|
|
519
|
+
db.prepare("UPDATE channel_accounts SET funded = 1 WHERE public_key = ?").run(publicKey);
|
|
520
|
+
}
|
|
521
|
+
// ---------------------------- Database Access Functions For Schema: StateSnapshot ----------------------------
|
|
522
|
+
export function insertStateSnapshot(db, snapshot) {
|
|
523
|
+
const result = db.prepare(`
|
|
524
|
+
INSERT INTO state_snapshots (contract_entry_id, snapshot_ledger, value_hash, value_xdr)
|
|
525
|
+
VALUES (@contract_entry_id, @snapshot_ledger, @value_hash, @value_xdr)
|
|
526
|
+
`).run(snapshot);
|
|
527
|
+
return result.lastInsertRowid;
|
|
528
|
+
}
|
|
529
|
+
export function getLatestSnapshot(db, contractEntryId) {
|
|
530
|
+
return db.prepare(`
|
|
531
|
+
SELECT * FROM state_snapshots
|
|
532
|
+
WHERE contract_entry_id = ?
|
|
533
|
+
ORDER BY snapshot_ledger DESC, id DESC
|
|
534
|
+
LIMIT 1
|
|
535
|
+
`).get(contractEntryId);
|
|
536
|
+
}
|
|
537
|
+
// ---------------------------- Database Access Functions For Schema: StateChange ----------------------------
|
|
538
|
+
export function insertStateChange(db, change) {
|
|
539
|
+
const result = db.prepare(`
|
|
540
|
+
INSERT INTO state_changes (contract_entry_id, old_snapshot_id, new_snapshot_id, diff_type, diff_json, detected_at_ledger)
|
|
541
|
+
VALUES (@contract_entry_id, @old_snapshot_id, @new_snapshot_id, @diff_type, @diff_json, @detected_at_ledger)
|
|
542
|
+
`).run({
|
|
543
|
+
...change,
|
|
544
|
+
old_snapshot_id: change.old_snapshot_id ?? null,
|
|
545
|
+
new_snapshot_id: change.new_snapshot_id ?? null,
|
|
546
|
+
});
|
|
547
|
+
return result.lastInsertRowid;
|
|
548
|
+
}
|
|
549
|
+
export function getStateChanges(db, contractEntryId, limit) {
|
|
550
|
+
let sql = "SELECT * FROM state_changes WHERE contract_entry_id = ? ORDER BY detected_at_ledger DESC, id DESC";
|
|
551
|
+
if (limit !== undefined) {
|
|
552
|
+
if (limit < 0) {
|
|
553
|
+
throw new Error("limit must be non-negative");
|
|
554
|
+
}
|
|
555
|
+
sql += " LIMIT ?";
|
|
556
|
+
return db.prepare(sql).all(contractEntryId, limit);
|
|
557
|
+
}
|
|
558
|
+
return db.prepare(sql).all(contractEntryId);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Return a rich history view of state changes for a contract,
|
|
562
|
+
* joining state_changes → contract_entries → state_snapshots.
|
|
563
|
+
*/
|
|
564
|
+
export function getStateChangeHistory(db, contractId, opts) {
|
|
565
|
+
const limit = opts?.limit;
|
|
566
|
+
const entryKeyXdr = opts?.entryKeyXdr;
|
|
567
|
+
let sql = `
|
|
568
|
+
SELECT
|
|
569
|
+
sc.id AS changeId,
|
|
570
|
+
sc.contract_entry_id AS contractEntryId,
|
|
571
|
+
ce.entry_key_xdr AS entryKeyXdr,
|
|
572
|
+
ce.entry_type AS entryType,
|
|
573
|
+
ce.label AS entryLabel,
|
|
574
|
+
sc.diff_type AS diffType,
|
|
575
|
+
sc.diff_json AS diffJson,
|
|
576
|
+
sc.detected_at_ledger AS detectedAtLedger,
|
|
577
|
+
sc.created_at AS createdAt,
|
|
578
|
+
old_snap.value_xdr AS oldValueXdr,
|
|
579
|
+
new_snap.value_xdr AS newValueXdr
|
|
580
|
+
FROM state_changes sc
|
|
581
|
+
JOIN contract_entries ce ON ce.id = sc.contract_entry_id
|
|
582
|
+
LEFT JOIN state_snapshots old_snap ON old_snap.id = sc.old_snapshot_id
|
|
583
|
+
LEFT JOIN state_snapshots new_snap ON new_snap.id = sc.new_snapshot_id
|
|
584
|
+
WHERE ce.contract_id = ?
|
|
585
|
+
`;
|
|
586
|
+
const params = [contractId];
|
|
587
|
+
if (entryKeyXdr) {
|
|
588
|
+
sql += " AND ce.entry_key_xdr = ?";
|
|
589
|
+
params.push(entryKeyXdr);
|
|
590
|
+
}
|
|
591
|
+
sql += " ORDER BY sc.detected_at_ledger DESC, sc.id DESC";
|
|
592
|
+
if (limit !== undefined && limit >= 0) {
|
|
593
|
+
sql += " LIMIT ?";
|
|
594
|
+
params.push(limit);
|
|
595
|
+
}
|
|
596
|
+
return db.prepare(sql).all(...params);
|
|
597
|
+
}
|
|
598
|
+
export function insertResourceAlertConfig(db, config) {
|
|
599
|
+
db.prepare(`
|
|
600
|
+
INSERT INTO resource_alert_configs (contract_id, channel_type, channel_target, cpu_limit, mem_limit, webhook_secret)
|
|
601
|
+
VALUES (@contract_id, @channel_type, @channel_target, @cpu_limit, @mem_limit, @webhook_secret)
|
|
602
|
+
`).run({
|
|
603
|
+
contract_id: config.contract_id,
|
|
604
|
+
channel_type: config.channel_type,
|
|
605
|
+
channel_target: config.channel_target,
|
|
606
|
+
cpu_limit: config.cpu_limit,
|
|
607
|
+
mem_limit: config.mem_limit,
|
|
608
|
+
webhook_secret: config.webhook_secret ?? null,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
export function getResourceAlertConfigsForContract(db, contractId) {
|
|
612
|
+
return db.prepare("SELECT * FROM resource_alert_configs WHERE contract_id = ?").all(contractId);
|
|
613
|
+
}
|
|
614
|
+
export function getResourceAlertConfigById(db, id) {
|
|
615
|
+
return db.prepare("SELECT * FROM resource_alert_configs WHERE id = ?").get(id);
|
|
616
|
+
}
|
|
617
|
+
export function deleteResourceAlertConfig(db, id) {
|
|
618
|
+
db.prepare("DELETE FROM resource_alert_configs WHERE id = ?").run(id);
|
|
619
|
+
}
|
|
620
|
+
export function recordResourceAlertFired(db, alert) {
|
|
621
|
+
const result = db.prepare(`
|
|
622
|
+
INSERT INTO resource_alerts_fired (resource_alert_config_id, resource_type, usage, "limit", usage_percent, fired_at_ledger)
|
|
623
|
+
VALUES (@resource_alert_config_id, @resource_type, @usage, @limit, @usage_percent, @fired_at_ledger)
|
|
624
|
+
`).run({
|
|
625
|
+
resource_alert_config_id: alert.resource_alert_config_id,
|
|
626
|
+
resource_type: alert.resource_type,
|
|
627
|
+
usage: alert.usage,
|
|
628
|
+
limit: alert.limit,
|
|
629
|
+
usage_percent: alert.usage_percent,
|
|
630
|
+
fired_at_ledger: alert.fired_at_ledger ?? null,
|
|
631
|
+
});
|
|
632
|
+
return result.lastInsertRowid;
|
|
633
|
+
}
|
|
634
|
+
export function getResourceUsageHistory(db, contractId, days) {
|
|
635
|
+
if (days) {
|
|
636
|
+
return db.prepare(`
|
|
637
|
+
SELECT raf.resource_type AS resourceType,
|
|
638
|
+
raf.usage,
|
|
639
|
+
raf.usage_percent AS usagePercent,
|
|
640
|
+
raf.fired_at AS firedAt
|
|
641
|
+
FROM resource_alerts_fired raf
|
|
642
|
+
JOIN resource_alert_configs rac ON rac.id = raf.resource_alert_config_id
|
|
643
|
+
WHERE rac.contract_id = ?
|
|
644
|
+
AND raf.fired_at >= datetime('now', ?)
|
|
645
|
+
ORDER BY raf.fired_at DESC
|
|
646
|
+
`).all(contractId, `-${days} days`);
|
|
647
|
+
}
|
|
648
|
+
return db.prepare(`
|
|
649
|
+
SELECT raf.resource_type AS resourceType,
|
|
650
|
+
raf.usage,
|
|
651
|
+
raf.usage_percent AS usagePercent,
|
|
652
|
+
raf.fired_at AS firedAt
|
|
653
|
+
FROM resource_alerts_fired raf
|
|
654
|
+
JOIN resource_alert_configs rac ON rac.id = raf.resource_alert_config_id
|
|
655
|
+
WHERE rac.contract_id = ?
|
|
656
|
+
ORDER BY raf.fired_at DESC
|
|
657
|
+
`).all(contractId);
|
|
658
|
+
}
|
|
659
|
+
export function getUndeliveredResourceAlerts(db, network) {
|
|
660
|
+
return db.prepare(`
|
|
661
|
+
SELECT
|
|
662
|
+
raf.id AS alertFiredId,
|
|
663
|
+
raf.resource_alert_config_id AS resourceAlertConfigId,
|
|
664
|
+
c.id AS contractId,
|
|
665
|
+
c.name AS contractName,
|
|
666
|
+
c.network,
|
|
667
|
+
raf.resource_type AS resourceType,
|
|
668
|
+
raf.usage,
|
|
669
|
+
raf."limit",
|
|
670
|
+
raf.usage_percent AS usagePercent,
|
|
671
|
+
rac.channel_type AS channelType,
|
|
672
|
+
rac.channel_target AS channelTarget,
|
|
673
|
+
rac.webhook_secret AS webhookSecret,
|
|
674
|
+
raf.retry_count AS retryCount,
|
|
675
|
+
raf.fired_at_ledger AS firedAtLedger
|
|
676
|
+
FROM resource_alerts_fired raf
|
|
677
|
+
JOIN resource_alert_configs rac ON rac.id = raf.resource_alert_config_id
|
|
678
|
+
JOIN contracts c ON c.id = rac.contract_id
|
|
679
|
+
WHERE c.network = ? AND raf.delivered = 0 AND raf.retry_count < ?
|
|
680
|
+
ORDER BY raf.fired_at DESC
|
|
681
|
+
`).all(network, MAX_RETRY_COUNT);
|
|
682
|
+
}
|
|
683
|
+
export function markResourceAlertDelivered(db, alertFiredId) {
|
|
684
|
+
db.prepare(`
|
|
685
|
+
UPDATE resource_alerts_fired SET delivered = 1, delivered_at = datetime('now') WHERE id = ?
|
|
686
|
+
`).run(alertFiredId);
|
|
687
|
+
}
|
|
688
|
+
export function incrementResourceAlertRetryCount(db, alertFiredId) {
|
|
689
|
+
db.prepare("UPDATE resource_alerts_fired SET retry_count = retry_count + 1 WHERE id = ?").run(alertFiredId);
|
|
690
|
+
}
|
|
691
|
+
export function hasUnresolvedResourceAlert(db, configId, resourceType, currentUsagePercent) {
|
|
692
|
+
if (typeof currentUsagePercent === "undefined") {
|
|
693
|
+
const result = db.prepare(`
|
|
694
|
+
SELECT 1 FROM resource_alerts_fired
|
|
695
|
+
WHERE resource_alert_config_id = ? AND resource_type = ? AND resolved = 0
|
|
696
|
+
LIMIT 1
|
|
697
|
+
`).get(configId, resourceType);
|
|
698
|
+
return !!result;
|
|
699
|
+
}
|
|
700
|
+
// If we have a current usage percent, only consider an existing unresolved
|
|
701
|
+
// alert to be blocking if its recorded usage_percent is greater than or
|
|
702
|
+
// equal to the current usage (i.e., no increase). If the current usage
|
|
703
|
+
// is higher, allow firing a new alert.
|
|
704
|
+
const row = db.prepare(`
|
|
705
|
+
SELECT usage_percent FROM resource_alerts_fired
|
|
706
|
+
WHERE resource_alert_config_id = ? AND resource_type = ? AND resolved = 0
|
|
707
|
+
ORDER BY fired_at DESC
|
|
708
|
+
LIMIT 1
|
|
709
|
+
`).get(configId, resourceType);
|
|
710
|
+
if (!row || typeof row.usage_percent === "undefined")
|
|
711
|
+
return false;
|
|
712
|
+
return row.usage_percent >= currentUsagePercent;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Insert a new resource-usage log entry.
|
|
716
|
+
*
|
|
717
|
+
* @returns The auto-assigned row id of the inserted entry.
|
|
718
|
+
*/
|
|
719
|
+
export function insertResourceUsageLog(db, log) {
|
|
720
|
+
const result = db.prepare(`
|
|
721
|
+
INSERT INTO resource_usage_logs (
|
|
722
|
+
contract_id,
|
|
723
|
+
cpu_insns,
|
|
724
|
+
mem_bytes,
|
|
725
|
+
fee_instructions,
|
|
726
|
+
fee_read_ledger_entries,
|
|
727
|
+
fee_write_ledger_entries,
|
|
728
|
+
fee_read_bytes,
|
|
729
|
+
fee_write_bytes,
|
|
730
|
+
fee_transaction_size,
|
|
731
|
+
fee_historical_ledger,
|
|
732
|
+
fee_rent_ledger,
|
|
733
|
+
fee_refundable,
|
|
734
|
+
recorded_at
|
|
735
|
+
) VALUES (
|
|
736
|
+
@contract_id,
|
|
737
|
+
@cpu_insns,
|
|
738
|
+
@mem_bytes,
|
|
739
|
+
@fee_instructions,
|
|
740
|
+
@fee_read_ledger_entries,
|
|
741
|
+
@fee_write_ledger_entries,
|
|
742
|
+
@fee_read_bytes,
|
|
743
|
+
@fee_write_bytes,
|
|
744
|
+
@fee_transaction_size,
|
|
745
|
+
@fee_historical_ledger,
|
|
746
|
+
@fee_rent_ledger,
|
|
747
|
+
@fee_refundable,
|
|
748
|
+
COALESCE(@recorded_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
749
|
+
)
|
|
750
|
+
`).run({
|
|
751
|
+
contract_id: log.contract_id,
|
|
752
|
+
cpu_insns: log.cpu_insns,
|
|
753
|
+
mem_bytes: log.mem_bytes,
|
|
754
|
+
fee_instructions: log.fee_instructions ?? null,
|
|
755
|
+
fee_read_ledger_entries: log.fee_read_ledger_entries ?? null,
|
|
756
|
+
fee_write_ledger_entries: log.fee_write_ledger_entries ?? null,
|
|
757
|
+
fee_read_bytes: log.fee_read_bytes ?? null,
|
|
758
|
+
fee_write_bytes: log.fee_write_bytes ?? null,
|
|
759
|
+
fee_transaction_size: log.fee_transaction_size ?? null,
|
|
760
|
+
fee_historical_ledger: log.fee_historical_ledger ?? null,
|
|
761
|
+
fee_rent_ledger: log.fee_rent_ledger ?? null,
|
|
762
|
+
fee_refundable: log.fee_refundable ?? null,
|
|
763
|
+
recorded_at: log.recorded_at ?? null,
|
|
764
|
+
});
|
|
765
|
+
return result.lastInsertRowid;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Return resource-usage logs for a contract, newest first.
|
|
769
|
+
*
|
|
770
|
+
* @param options.limit Maximum number of rows to return (must be ≥ 0).
|
|
771
|
+
* @param options.since ISO-8601 lower-bound for recorded_at (inclusive).
|
|
772
|
+
*/
|
|
773
|
+
export function getResourceUsageLogs(db, contractId, options = {}) {
|
|
774
|
+
const { limit, since } = options;
|
|
775
|
+
if (limit !== undefined && limit < 0) {
|
|
776
|
+
throw new Error("limit must be non-negative");
|
|
777
|
+
}
|
|
778
|
+
const conditions = ["contract_id = @contractId"];
|
|
779
|
+
if (since !== undefined) {
|
|
780
|
+
conditions.push("recorded_at >= @since");
|
|
781
|
+
}
|
|
782
|
+
const where = conditions.join(" AND ");
|
|
783
|
+
const params = { contractId, since: since ?? null };
|
|
784
|
+
let query = `
|
|
785
|
+
SELECT *
|
|
786
|
+
FROM resource_usage_logs
|
|
787
|
+
WHERE ${where}
|
|
788
|
+
ORDER BY recorded_at DESC, id DESC
|
|
789
|
+
`;
|
|
790
|
+
if (limit !== undefined) {
|
|
791
|
+
query += ` LIMIT @limit`;
|
|
792
|
+
params.limit = limit;
|
|
793
|
+
}
|
|
794
|
+
return db.prepare(query).all(params);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Return the single most-recent resource-usage log for a contract,
|
|
798
|
+
* or `undefined` if none exist.
|
|
799
|
+
*/
|
|
800
|
+
export function getLatestResourceUsageLog(db, contractId) {
|
|
801
|
+
return db.prepare(`
|
|
802
|
+
SELECT *
|
|
803
|
+
FROM resource_usage_logs
|
|
804
|
+
WHERE contract_id = ?
|
|
805
|
+
ORDER BY recorded_at DESC, id DESC
|
|
806
|
+
LIMIT 1
|
|
807
|
+
`).get(contractId);
|
|
808
|
+
}
|
|
809
|
+
//# sourceMappingURL=repositories.js.map
|