@spirobel/monero-wallet-api 0.5.4 → 0.6.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/dist/actionlog/actionLogOpened.d.ts +96 -1
- package/dist/actionlog/actionLogOpened.js +386 -1
- package/dist/actionlog/actionLogTypes.d.ts +114 -0
- package/dist/actionlog/actionLogTypes.js +198 -0
- package/dist/actionlog/backend/idbBackend.d.ts +13 -0
- package/dist/actionlog/backend/idbBackend.js +109 -0
- package/dist/actionlog/backend/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/backend/jsonlBackend.js +110 -0
- package/dist/actionlog/backend/memoryBackend.d.ts +11 -0
- package/dist/actionlog/backend/memoryBackend.js +31 -0
- package/dist/actionlog/backend/sqliteBackend.d.ts +14 -0
- package/dist/actionlog/backend/sqliteBackend.js +96 -0
- package/dist/actionlog/backend.d.ts +12 -0
- package/dist/actionlog/backend.js +1 -0
- package/dist/actionlog/idbBackend.d.ts +19 -0
- package/dist/actionlog/idbBackend.js +98 -0
- package/dist/actionlog/jsonlBackend.d.ts +19 -0
- package/dist/actionlog/jsonlBackend.js +75 -0
- package/dist/actionlog/memoryBackend.d.ts +14 -0
- package/dist/actionlog/memoryBackend.js +38 -0
- package/dist/actionlog/openMaps.d.ts +11 -0
- package/dist/actionlog/openMaps.js +32 -0
- package/dist/actionlog/sqliteBackend.d.ts +18 -0
- package/dist/actionlog/sqliteBackend.js +105 -0
- package/dist/actionlog/toolContext.d.ts +36 -0
- package/dist/actionlog/toolContext.js +1 -0
- package/dist/actionlog/types.d.ts +62 -0
- package/dist/actionlog/types.js +62 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +3 -0
- package/dist/io/sleep.d.ts +1 -1
- package/dist/io/sleep.js +16 -1
- package/dist/scanning-syncing/backgroundWorker.d.ts +2 -0
- package/dist/scanning-syncing/backgroundWorker.js +92 -10
- package/dist/scanning-syncing/blocksbuffer/blocksBufferFetchLoop.js +33 -2
- package/dist/scanning-syncing/blocksbuffer/blocksbufferCoordination.js +2 -2
- package/dist/scanning-syncing/connectionStatus.d.ts +8 -0
- package/dist/scanning-syncing/connectionStatus.js +50 -0
- package/dist/scanning-syncing/connectionStatusOpened.d.ts +3 -1
- package/dist/scanning-syncing/connectionStatusOpened.js +22 -14
- package/dist/scanning-syncing/openWallet.d.ts +4 -1
- package/dist/scanning-syncing/openWallet.js +3 -0
- package/dist/scanning-syncing/scanresult/scanCache.d.ts +5 -1
- package/dist/scanning-syncing/scanresult/scanCache.js +11 -2
- package/dist/scanning-syncing/scanresult/scanCacheOpened.d.ts +56 -5
- package/dist/scanning-syncing/scanresult/scanCacheOpened.js +422 -83
- package/dist/scanning-syncing/scanresult/scanCoordination.d.ts +7 -3
- package/dist/scanning-syncing/scanresult/scanCoordination.js +126 -28
- package/dist/scanning-syncing/scanresult/scanLoop.d.ts +1 -2
- package/dist/scanning-syncing/scanresult/scanLoop.js +0 -13
- package/dist/scanning-syncing/scanresult/scanResult.js +17 -12
- package/dist/scanning-syncing/scanresult/scanStats.js +8 -5
- package/dist/scanning-syncing/worker-entrypoints/worker.js +1 -1
- package/dist/scanning-syncing/worker-mains/coordinator-main.d.ts +1 -0
- package/dist/scanning-syncing/worker-mains/coordinator-main.js +16 -1
- package/dist/scanning-syncing/worker-mains/cpubound-main.js +3 -2
- package/dist/scanning-syncing/worker-mains/worker.js +45 -2
- package/dist/tools/calls/001.d.ts +20 -0
- package/dist/tools/calls/001.js +236 -0
- package/dist/tools/calls/002-content.d.ts +46 -0
- package/dist/tools/calls/002-content.js +135 -0
- package/dist/tools/calls/002-fail-cleanup.d.ts +27 -0
- package/dist/tools/calls/002-fail-cleanup.js +60 -0
- package/dist/tools/calls/002.d.ts +64 -0
- package/dist/tools/calls/002.js +353 -0
- package/dist/tools/content.d.ts +22 -0
- package/dist/tools/content.js +41 -0
- package/dist/tools/counterparty.d.ts +3 -0
- package/dist/tools/counterparty.js +1 -0
- package/dist/tools/download-public-suffix.d.ts +1 -0
- package/dist/tools/download-public-suffix.js +20 -0
- package/dist/tools/globals.d.ts +196 -0
- package/dist/tools/globals.js +14 -0
- package/dist/tools/monero-tools-content.d.ts +29 -0
- package/dist/tools/monero-tools-content.js +80 -0
- package/dist/tools/monero-tools.d.ts +27 -52
- package/dist/tools/monero-tools.js +127 -222
- package/dist/tools/public-suffix-list.d.ts +1 -0
- package/dist/tools/public-suffix-list.js +10336 -0
- package/dist/tools/runtimeMessages.d.ts +4 -0
- package/dist/tools/runtimeMessages.js +18 -0
- package/dist/tools/toolActionLogContext.d.ts +33 -0
- package/dist/tools/toolActionLogContext.js +1 -0
- package/dist/tools/toolArm.d.ts +47 -0
- package/dist/tools/toolArm.js +1 -0
- package/dist/tools/ui.d.ts +2 -0
- package/dist/tools/ui.js +1 -0
- package/dist/tools/worker.d.ts +1 -0
- package/dist/tools/worker.js +1 -0
- package/dist/wasm-processing/wasmFile.js +2 -2
- package/package.json +8 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export class Invocation {
|
|
2
|
+
invocationId;
|
|
3
|
+
toolId;
|
|
4
|
+
stage;
|
|
5
|
+
lastType;
|
|
6
|
+
valid;
|
|
7
|
+
amount;
|
|
8
|
+
address;
|
|
9
|
+
no_check;
|
|
10
|
+
wallet_slot;
|
|
11
|
+
context_domain;
|
|
12
|
+
destination_domain;
|
|
13
|
+
context_href;
|
|
14
|
+
found_in;
|
|
15
|
+
link;
|
|
16
|
+
linkText;
|
|
17
|
+
timestamp;
|
|
18
|
+
wallet_to_send_from_pa;
|
|
19
|
+
ok;
|
|
20
|
+
error;
|
|
21
|
+
events;
|
|
22
|
+
/** an Invocation from stored fields or from advanceInvo. */
|
|
23
|
+
constructor(init) {
|
|
24
|
+
this.invocationId = init.invocationId;
|
|
25
|
+
this.toolId = init.toolId;
|
|
26
|
+
this.stage = init.stage;
|
|
27
|
+
this.lastType = init.lastType;
|
|
28
|
+
this.events = init.events ?? [];
|
|
29
|
+
if (init.valid !== undefined)
|
|
30
|
+
this.valid = init.valid;
|
|
31
|
+
if (init.amount !== undefined)
|
|
32
|
+
this.amount = init.amount;
|
|
33
|
+
if (init.address !== undefined)
|
|
34
|
+
this.address = init.address;
|
|
35
|
+
if (init.no_check !== undefined)
|
|
36
|
+
this.no_check = init.no_check;
|
|
37
|
+
if (init.wallet_slot !== undefined)
|
|
38
|
+
this.wallet_slot = init.wallet_slot;
|
|
39
|
+
if (init.context_domain !== undefined)
|
|
40
|
+
this.context_domain = init.context_domain;
|
|
41
|
+
if (init.destination_domain !== undefined)
|
|
42
|
+
this.destination_domain = init.destination_domain;
|
|
43
|
+
if (init.context_href !== undefined)
|
|
44
|
+
this.context_href = init.context_href;
|
|
45
|
+
if (init.found_in !== undefined)
|
|
46
|
+
this.found_in = init.found_in;
|
|
47
|
+
if (init.link !== undefined)
|
|
48
|
+
this.link = init.link;
|
|
49
|
+
if (init.linkText !== undefined)
|
|
50
|
+
this.linkText = init.linkText;
|
|
51
|
+
if (init.timestamp !== undefined)
|
|
52
|
+
this.timestamp = init.timestamp;
|
|
53
|
+
if (init.wallet_to_send_from_pa !== undefined)
|
|
54
|
+
this.wallet_to_send_from_pa = init.wallet_to_send_from_pa;
|
|
55
|
+
if (init.ok !== undefined)
|
|
56
|
+
this.ok = init.ok;
|
|
57
|
+
if (init.error !== undefined)
|
|
58
|
+
this.error = init.error;
|
|
59
|
+
}
|
|
60
|
+
/** the label on the list and detail pages. a failed send is failed, not done. */
|
|
61
|
+
get status() {
|
|
62
|
+
if (this.lastType === "execute_result")
|
|
63
|
+
return this.ok === false ? "failed" : "done";
|
|
64
|
+
if (this.lastType === "dismiss")
|
|
65
|
+
return "dismissed";
|
|
66
|
+
if (this.lastType === "aborted")
|
|
67
|
+
return "aborted";
|
|
68
|
+
if (this.lastType === "execute_error")
|
|
69
|
+
return "failed";
|
|
70
|
+
return "in progress";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export const TERMINAL_EVENT_TYPES = new Set([
|
|
74
|
+
"dismiss",
|
|
75
|
+
"execute_result",
|
|
76
|
+
"aborted",
|
|
77
|
+
]);
|
|
78
|
+
// dismiss, execute_result, and aborted end the invocation.
|
|
79
|
+
export function isTerminalEventType(type) {
|
|
80
|
+
return TERMINAL_EVENT_TYPES.has(type);
|
|
81
|
+
}
|
|
82
|
+
export function newEventId() {
|
|
83
|
+
return crypto.randomUUID();
|
|
84
|
+
}
|
|
85
|
+
export function nowTimestamp() {
|
|
86
|
+
return new Date().toISOString();
|
|
87
|
+
}
|
|
88
|
+
/** apply one event to the current invocation state. advances invocation state. */
|
|
89
|
+
export function advanceInvo(prev, event) {
|
|
90
|
+
const next = new Invocation({
|
|
91
|
+
...(prev ?? {
|
|
92
|
+
invocationId: event.invocationId,
|
|
93
|
+
toolId: event.toolId,
|
|
94
|
+
stage: event.stage,
|
|
95
|
+
lastType: event.type,
|
|
96
|
+
events: [],
|
|
97
|
+
}),
|
|
98
|
+
invocationId: event.invocationId,
|
|
99
|
+
toolId: event.toolId,
|
|
100
|
+
stage: event.stage,
|
|
101
|
+
lastType: event.type,
|
|
102
|
+
timestamp: event.timestamp,
|
|
103
|
+
events: [...(prev?.events ?? []), event],
|
|
104
|
+
});
|
|
105
|
+
if (event.valid !== undefined)
|
|
106
|
+
next.valid = event.valid;
|
|
107
|
+
if (event.amount !== undefined)
|
|
108
|
+
next.amount = event.amount;
|
|
109
|
+
if (event.address !== undefined)
|
|
110
|
+
next.address = event.address;
|
|
111
|
+
if (event.no_check !== undefined)
|
|
112
|
+
next.no_check = event.no_check;
|
|
113
|
+
if (event.wallet_slot !== undefined)
|
|
114
|
+
next.wallet_slot = event.wallet_slot;
|
|
115
|
+
if (event.context_domain !== undefined)
|
|
116
|
+
next.context_domain = event.context_domain;
|
|
117
|
+
if (event.destination_domain !== undefined)
|
|
118
|
+
next.destination_domain = event.destination_domain;
|
|
119
|
+
if (event.context_href !== undefined)
|
|
120
|
+
next.context_href = event.context_href;
|
|
121
|
+
if (event.found_in !== undefined)
|
|
122
|
+
next.found_in = event.found_in;
|
|
123
|
+
if (event.link !== undefined)
|
|
124
|
+
next.link = event.link;
|
|
125
|
+
if (event.linkText !== undefined)
|
|
126
|
+
next.linkText = event.linkText;
|
|
127
|
+
if (event.wallet_to_send_from_pa !== undefined)
|
|
128
|
+
next.wallet_to_send_from_pa = event.wallet_to_send_from_pa;
|
|
129
|
+
if (event.ok !== undefined)
|
|
130
|
+
next.ok = event.ok;
|
|
131
|
+
if (event.error !== undefined)
|
|
132
|
+
next.error = event.error;
|
|
133
|
+
return next;
|
|
134
|
+
}
|
|
135
|
+
/** still live. false for dismiss, execute_result, aborted. */
|
|
136
|
+
export function isActive(state) {
|
|
137
|
+
return !isTerminalEventType(state.lastType);
|
|
138
|
+
}
|
|
139
|
+
/** the status column: active, or lastType when the invocation ended. */
|
|
140
|
+
export function invocationStatus(state) {
|
|
141
|
+
return isActive(state) ? "active" : state.lastType;
|
|
142
|
+
}
|
|
143
|
+
// current invos in memory. mininext reads this once per frame.
|
|
144
|
+
export class ActiveInvocations {
|
|
145
|
+
byInvocation = new Map();
|
|
146
|
+
byTool = new Map();
|
|
147
|
+
/** update the invo for this event. byTool keeps one live invo per tool. */
|
|
148
|
+
apply(event) {
|
|
149
|
+
const prev = this.byInvocation.get(event.invocationId) ?? null;
|
|
150
|
+
const next = advanceInvo(prev, event);
|
|
151
|
+
this.byInvocation.set(event.invocationId, next);
|
|
152
|
+
if (isActive(next)) {
|
|
153
|
+
this.byTool.set(event.toolId, event.invocationId);
|
|
154
|
+
return next;
|
|
155
|
+
}
|
|
156
|
+
if (this.byTool.get(event.toolId) === event.invocationId) {
|
|
157
|
+
this.byTool.delete(event.toolId);
|
|
158
|
+
}
|
|
159
|
+
return next;
|
|
160
|
+
}
|
|
161
|
+
/** invocations from sqlite or idb into byInvocation and byTool. */
|
|
162
|
+
loadStates(states) {
|
|
163
|
+
this.clear();
|
|
164
|
+
for (const state of states) {
|
|
165
|
+
const invo = new Invocation({
|
|
166
|
+
...state,
|
|
167
|
+
events: state.events ?? [],
|
|
168
|
+
});
|
|
169
|
+
this.byInvocation.set(invo.invocationId, invo);
|
|
170
|
+
if (isActive(invo))
|
|
171
|
+
this.byTool.set(invo.toolId, invo.invocationId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
clear() {
|
|
175
|
+
this.byInvocation.clear();
|
|
176
|
+
this.byTool.clear();
|
|
177
|
+
}
|
|
178
|
+
/** the invocation for this id. */
|
|
179
|
+
invocation(invocationId) {
|
|
180
|
+
return this.byInvocation.get(invocationId) ?? null;
|
|
181
|
+
}
|
|
182
|
+
/** all invocations, or filter. active reads byTool (one live invo per tool). */
|
|
183
|
+
invocations(status) {
|
|
184
|
+
if (status === "active") {
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const id of this.byTool.values()) {
|
|
187
|
+
const s = this.byInvocation.get(id);
|
|
188
|
+
if (s)
|
|
189
|
+
out.push(s);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
const all = [...this.byInvocation.values()];
|
|
194
|
+
if (!status)
|
|
195
|
+
return all;
|
|
196
|
+
return all.filter((s) => invocationStatus(s) === status);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ActionLogBackend, type ActionLogEvent, type InvocationState, type InvocationStatus } from "../actionLogTypes";
|
|
2
|
+
export declare class ActionLogIdbBackend implements ActionLogBackend {
|
|
3
|
+
private readonly db;
|
|
4
|
+
private active;
|
|
5
|
+
private constructor();
|
|
6
|
+
static open(dbName?: string): Promise<ActionLogIdbBackend>;
|
|
7
|
+
private loadInvocations;
|
|
8
|
+
private readInvocations;
|
|
9
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
10
|
+
invocation(invocationId: string): Promise<InvocationState | null>;
|
|
11
|
+
invocations(status?: InvocationStatus): Promise<InvocationState[]>;
|
|
12
|
+
reload(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ActiveInvocations, Invocation, invocationStatus, } from "../actionLogTypes";
|
|
2
|
+
const DB_NAME_DEFAULT = "actionlog";
|
|
3
|
+
const STORE_INVOCATIONS = "invocations";
|
|
4
|
+
const DB_VERSION = 1;
|
|
5
|
+
export class ActionLogIdbBackend {
|
|
6
|
+
db;
|
|
7
|
+
active = new ActiveInvocations();
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
static async open(dbName = DB_NAME_DEFAULT) {
|
|
12
|
+
if (typeof indexedDB === "undefined") {
|
|
13
|
+
throw new Error("IndexedDB not available");
|
|
14
|
+
}
|
|
15
|
+
const db = await openDb(dbName);
|
|
16
|
+
const backend = new ActionLogIdbBackend(db);
|
|
17
|
+
await backend.loadInvocations();
|
|
18
|
+
return backend;
|
|
19
|
+
}
|
|
20
|
+
async loadInvocations() {
|
|
21
|
+
const rows = await this.readInvocations();
|
|
22
|
+
if (!rows.length) {
|
|
23
|
+
this.active.clear();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.active.loadStates(rows);
|
|
27
|
+
}
|
|
28
|
+
readInvocations() {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
if (!this.db.objectStoreNames.contains(STORE_INVOCATIONS)) {
|
|
31
|
+
resolve([]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const tx = this.db.transaction(STORE_INVOCATIONS, "readonly");
|
|
35
|
+
const req = tx.objectStore(STORE_INVOCATIONS).getAll();
|
|
36
|
+
req.onerror = () => reject(req.error);
|
|
37
|
+
req.onsuccess = () => {
|
|
38
|
+
resolve(req.result ?? []);
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async append(event) {
|
|
43
|
+
const next = this.active.apply(event);
|
|
44
|
+
await new Promise((resolve, reject) => {
|
|
45
|
+
const tx = this.db.transaction(STORE_INVOCATIONS, "readwrite");
|
|
46
|
+
tx.objectStore(STORE_INVOCATIONS).put({
|
|
47
|
+
...next,
|
|
48
|
+
status: invocationStatus(next),
|
|
49
|
+
});
|
|
50
|
+
tx.oncomplete = () => resolve();
|
|
51
|
+
tx.onerror = () => reject(tx.error);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async invocation(invocationId) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (!this.db.objectStoreNames.contains(STORE_INVOCATIONS)) {
|
|
57
|
+
resolve(this.active.invocation(invocationId));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const tx = this.db.transaction(STORE_INVOCATIONS, "readonly");
|
|
61
|
+
const req = tx.objectStore(STORE_INVOCATIONS).get(invocationId);
|
|
62
|
+
req.onerror = () => reject(req.error);
|
|
63
|
+
req.onsuccess = () => {
|
|
64
|
+
const row = req.result;
|
|
65
|
+
resolve(row ? new Invocation({ ...row, events: row.events ?? [] }) : null);
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async invocations(status) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
if (!this.db.objectStoreNames.contains(STORE_INVOCATIONS)) {
|
|
72
|
+
resolve(this.active.invocations(status));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const tx = this.db.transaction(STORE_INVOCATIONS, "readonly");
|
|
76
|
+
const store = tx.objectStore(STORE_INVOCATIONS);
|
|
77
|
+
const req = status
|
|
78
|
+
? store.index("status").getAll(status)
|
|
79
|
+
: store.getAll();
|
|
80
|
+
req.onerror = () => reject(req.error);
|
|
81
|
+
req.onsuccess = () => {
|
|
82
|
+
const rows = req.result ?? [];
|
|
83
|
+
resolve(rows.map((s) => new Invocation({
|
|
84
|
+
...s,
|
|
85
|
+
events: s.events ?? [],
|
|
86
|
+
})));
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async reload() {
|
|
91
|
+
await this.loadInvocations();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function openDb(name) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const req = indexedDB.open(name, DB_VERSION);
|
|
97
|
+
req.onerror = () => reject(req.error);
|
|
98
|
+
req.onupgradeneeded = () => {
|
|
99
|
+
const db = req.result;
|
|
100
|
+
if (!db.objectStoreNames.contains(STORE_INVOCATIONS)) {
|
|
101
|
+
const inv = db.createObjectStore(STORE_INVOCATIONS, {
|
|
102
|
+
keyPath: "invocationId",
|
|
103
|
+
});
|
|
104
|
+
inv.createIndex("status", "status", { unique: false });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
req.onsuccess = () => resolve(req.result);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ActionLogBackend, type ActionLogEvent, type InvocationState, type InvocationStatus } from "../actionLogTypes";
|
|
2
|
+
export declare class ActionLogJsonlBackend implements ActionLogBackend {
|
|
3
|
+
private readonly path;
|
|
4
|
+
private eventLog;
|
|
5
|
+
private active;
|
|
6
|
+
constructor(path: string);
|
|
7
|
+
static open(path: string): Promise<ActionLogJsonlBackend>;
|
|
8
|
+
private invocationsPath;
|
|
9
|
+
private load;
|
|
10
|
+
private loadInvocationsFile;
|
|
11
|
+
private persistEvents;
|
|
12
|
+
private persistInvocations;
|
|
13
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
14
|
+
events(invocationId: string): Promise<ActionLogEvent[]>;
|
|
15
|
+
invocation(invocationId: string): Promise<InvocationState | null>;
|
|
16
|
+
invocations(status?: InvocationStatus): Promise<InvocationState[]>;
|
|
17
|
+
feed(events: ActionLogEvent[]): Promise<void>;
|
|
18
|
+
reload(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { atomicWrite } from "../../io/atomicWrite";
|
|
2
|
+
import { ActiveInvocations, invocationStatus, } from "../actionLogTypes";
|
|
3
|
+
export class ActionLogJsonlBackend {
|
|
4
|
+
path;
|
|
5
|
+
eventLog = [];
|
|
6
|
+
active = new ActiveInvocations();
|
|
7
|
+
constructor(path) {
|
|
8
|
+
this.path = path;
|
|
9
|
+
}
|
|
10
|
+
static async open(path) {
|
|
11
|
+
const backend = new ActionLogJsonlBackend(path);
|
|
12
|
+
await backend.load();
|
|
13
|
+
return backend;
|
|
14
|
+
}
|
|
15
|
+
invocationsPath() {
|
|
16
|
+
const p = this.path;
|
|
17
|
+
const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
18
|
+
const dir = i >= 0 ? p.slice(0, i) : ".";
|
|
19
|
+
return `${dir}/invocations.jsonl`;
|
|
20
|
+
}
|
|
21
|
+
async load() {
|
|
22
|
+
const loaded = await this.loadInvocationsFile();
|
|
23
|
+
const text = await Bun.file(this.path)
|
|
24
|
+
.text()
|
|
25
|
+
.catch(() => "");
|
|
26
|
+
this.eventLog = [];
|
|
27
|
+
if (text.trim()) {
|
|
28
|
+
for (const line of text.split("\n")) {
|
|
29
|
+
const t = line.trim();
|
|
30
|
+
if (!t)
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
this.eventLog.push(JSON.parse(t));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// skip bad line
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (loaded)
|
|
41
|
+
return;
|
|
42
|
+
this.active.clear();
|
|
43
|
+
for (const event of this.eventLog)
|
|
44
|
+
this.active.apply(event);
|
|
45
|
+
await this.persistInvocations();
|
|
46
|
+
}
|
|
47
|
+
async loadInvocationsFile() {
|
|
48
|
+
const text = await Bun.file(this.invocationsPath())
|
|
49
|
+
.text()
|
|
50
|
+
.catch(() => "");
|
|
51
|
+
if (!text.trim())
|
|
52
|
+
return false;
|
|
53
|
+
const states = [];
|
|
54
|
+
for (const line of text.split("\n")) {
|
|
55
|
+
const t = line.trim();
|
|
56
|
+
if (!t)
|
|
57
|
+
continue;
|
|
58
|
+
try {
|
|
59
|
+
states.push(JSON.parse(t));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// skip bad line
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!states.length)
|
|
66
|
+
return false;
|
|
67
|
+
this.active.loadStates(states);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
async persistEvents() {
|
|
71
|
+
const body = this.eventLog.map((e) => JSON.stringify(e)).join("\n") +
|
|
72
|
+
(this.eventLog.length ? "\n" : "");
|
|
73
|
+
await atomicWrite(this.path, body);
|
|
74
|
+
}
|
|
75
|
+
async persistInvocations() {
|
|
76
|
+
const rows = this.active.invocations();
|
|
77
|
+
const body = rows
|
|
78
|
+
.map((s) => JSON.stringify({ ...s, status: invocationStatus(s) }))
|
|
79
|
+
.join("\n") + (rows.length ? "\n" : "");
|
|
80
|
+
await atomicWrite(this.invocationsPath(), body);
|
|
81
|
+
}
|
|
82
|
+
async append(event) {
|
|
83
|
+
this.eventLog.push(event);
|
|
84
|
+
this.active.apply(event);
|
|
85
|
+
await this.persistEvents();
|
|
86
|
+
await this.persistInvocations();
|
|
87
|
+
}
|
|
88
|
+
async events(invocationId) {
|
|
89
|
+
return this.eventLog.filter((e) => e.invocationId === invocationId);
|
|
90
|
+
}
|
|
91
|
+
async invocation(invocationId) {
|
|
92
|
+
return this.active.invocation(invocationId);
|
|
93
|
+
}
|
|
94
|
+
async invocations(status) {
|
|
95
|
+
return this.active.invocations(status);
|
|
96
|
+
}
|
|
97
|
+
async feed(events) {
|
|
98
|
+
for (const event of events) {
|
|
99
|
+
if (this.eventLog.some((e) => e.id === event.id))
|
|
100
|
+
continue;
|
|
101
|
+
this.eventLog.push(event);
|
|
102
|
+
this.active.apply(event);
|
|
103
|
+
}
|
|
104
|
+
await this.persistEvents();
|
|
105
|
+
await this.persistInvocations();
|
|
106
|
+
}
|
|
107
|
+
async reload() {
|
|
108
|
+
await this.load();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ActionLogBackend, type ActionLogEvent, type InvocationState, type InvocationStatus } from "../actionLogTypes";
|
|
2
|
+
export declare class ActionLogMemoryBackend implements ActionLogBackend {
|
|
3
|
+
private eventLog;
|
|
4
|
+
private active;
|
|
5
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
6
|
+
events(invocationId: string): Promise<ActionLogEvent[]>;
|
|
7
|
+
invocation(invocationId: string): Promise<InvocationState | null>;
|
|
8
|
+
invocations(status?: InvocationStatus): Promise<InvocationState[]>;
|
|
9
|
+
feed(events: ActionLogEvent[]): Promise<void>;
|
|
10
|
+
reload(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ActiveInvocations, } from "../actionLogTypes";
|
|
2
|
+
export class ActionLogMemoryBackend {
|
|
3
|
+
eventLog = [];
|
|
4
|
+
active = new ActiveInvocations();
|
|
5
|
+
async append(event) {
|
|
6
|
+
this.eventLog.push(event);
|
|
7
|
+
this.active.apply(event);
|
|
8
|
+
}
|
|
9
|
+
async events(invocationId) {
|
|
10
|
+
return this.eventLog.filter((e) => e.invocationId === invocationId);
|
|
11
|
+
}
|
|
12
|
+
async invocation(invocationId) {
|
|
13
|
+
return this.active.invocation(invocationId);
|
|
14
|
+
}
|
|
15
|
+
async invocations(status) {
|
|
16
|
+
return this.active.invocations(status);
|
|
17
|
+
}
|
|
18
|
+
async feed(events) {
|
|
19
|
+
for (const event of events) {
|
|
20
|
+
if (this.eventLog.some((e) => e.id === event.id))
|
|
21
|
+
continue;
|
|
22
|
+
this.eventLog.push(event);
|
|
23
|
+
this.active.apply(event);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async reload() {
|
|
27
|
+
this.active.clear();
|
|
28
|
+
for (const event of this.eventLog)
|
|
29
|
+
this.active.apply(event);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ActionLogBackend, type ActionLogEvent, type InvocationState, type InvocationStatus } from "../actionLogTypes";
|
|
2
|
+
export declare class ActionLogSqliteBackend implements ActionLogBackend {
|
|
3
|
+
private active;
|
|
4
|
+
private sql;
|
|
5
|
+
private constructor();
|
|
6
|
+
static open(filename: string): Promise<ActionLogSqliteBackend>;
|
|
7
|
+
private loadInvocations;
|
|
8
|
+
/** body is the invocation as json. events[] is the state transitions. */
|
|
9
|
+
private upsertInvocation;
|
|
10
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
11
|
+
invocation(invocationId: string): Promise<InvocationState | null>;
|
|
12
|
+
invocations(status?: InvocationStatus): Promise<InvocationState[]>;
|
|
13
|
+
reload(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ActiveInvocations, Invocation, invocationStatus, } from "../actionLogTypes";
|
|
2
|
+
export class ActionLogSqliteBackend {
|
|
3
|
+
active = new ActiveInvocations();
|
|
4
|
+
sql;
|
|
5
|
+
constructor(sql) {
|
|
6
|
+
this.sql = sql;
|
|
7
|
+
}
|
|
8
|
+
static async open(filename) {
|
|
9
|
+
// specifier is not a literal so browser bun build does not fail on builtin bun
|
|
10
|
+
const spec = "bun";
|
|
11
|
+
const { SQL: SqlCtor } = (await import(spec));
|
|
12
|
+
const sql = new SqlCtor({
|
|
13
|
+
adapter: "sqlite",
|
|
14
|
+
filename,
|
|
15
|
+
create: true,
|
|
16
|
+
});
|
|
17
|
+
await sql `PRAGMA journal_mode = WAL`;
|
|
18
|
+
await sql `
|
|
19
|
+
CREATE TABLE IF NOT EXISTS actionlog_invocations (
|
|
20
|
+
invocationId TEXT PRIMARY KEY,
|
|
21
|
+
status TEXT NOT NULL,
|
|
22
|
+
timestamp TEXT,
|
|
23
|
+
toolId TEXT NOT NULL,
|
|
24
|
+
body TEXT NOT NULL
|
|
25
|
+
)
|
|
26
|
+
`;
|
|
27
|
+
await sql `
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_actionlog_invo_status
|
|
29
|
+
ON actionlog_invocations (status)
|
|
30
|
+
`;
|
|
31
|
+
const backend = new ActionLogSqliteBackend(sql);
|
|
32
|
+
await backend.loadInvocations();
|
|
33
|
+
return backend;
|
|
34
|
+
}
|
|
35
|
+
async loadInvocations() {
|
|
36
|
+
const rows = (await this.sql `
|
|
37
|
+
SELECT body FROM actionlog_invocations
|
|
38
|
+
`);
|
|
39
|
+
if (!Array.isArray(rows) || !rows.length) {
|
|
40
|
+
this.active.clear();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.active.loadStates(rows.map((r) => JSON.parse(r.body)));
|
|
44
|
+
}
|
|
45
|
+
/** body is the invocation as json. events[] is the state transitions. */
|
|
46
|
+
async upsertInvocation(state) {
|
|
47
|
+
const body = JSON.stringify(state);
|
|
48
|
+
const status = invocationStatus(state);
|
|
49
|
+
await this.sql `
|
|
50
|
+
INSERT INTO actionlog_invocations (invocationId, status, timestamp, toolId, body)
|
|
51
|
+
VALUES (
|
|
52
|
+
${state.invocationId},
|
|
53
|
+
${status},
|
|
54
|
+
${state.timestamp ?? ""},
|
|
55
|
+
${state.toolId},
|
|
56
|
+
${body}
|
|
57
|
+
)
|
|
58
|
+
ON CONFLICT(invocationId) DO UPDATE SET
|
|
59
|
+
status = ${status},
|
|
60
|
+
timestamp = ${state.timestamp ?? ""},
|
|
61
|
+
toolId = ${state.toolId},
|
|
62
|
+
body = ${body}
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
async append(event) {
|
|
66
|
+
const next = this.active.apply(event);
|
|
67
|
+
await this.upsertInvocation(next);
|
|
68
|
+
}
|
|
69
|
+
async invocation(invocationId) {
|
|
70
|
+
const rows = (await this.sql `
|
|
71
|
+
SELECT body FROM actionlog_invocations
|
|
72
|
+
WHERE invocationId = ${invocationId}
|
|
73
|
+
`);
|
|
74
|
+
if (!Array.isArray(rows) || !rows[0])
|
|
75
|
+
return null;
|
|
76
|
+
const s = JSON.parse(rows[0].body);
|
|
77
|
+
return new Invocation({ ...s, events: s.events ?? [] });
|
|
78
|
+
}
|
|
79
|
+
async invocations(status) {
|
|
80
|
+
const rows = (status
|
|
81
|
+
? await this.sql `
|
|
82
|
+
SELECT body FROM actionlog_invocations
|
|
83
|
+
WHERE status = ${status}
|
|
84
|
+
`
|
|
85
|
+
: await this.sql `SELECT body FROM actionlog_invocations`);
|
|
86
|
+
if (!Array.isArray(rows))
|
|
87
|
+
return [];
|
|
88
|
+
return rows.map((r) => {
|
|
89
|
+
const s = JSON.parse(r.body);
|
|
90
|
+
return new Invocation({ ...s, events: s.events ?? [] });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async reload() {
|
|
94
|
+
await this.loadInvocations();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ToolId } from "../tools/monero-tools";
|
|
2
|
+
import type { ActionLogEvent, OpenInvocationState } from "./types";
|
|
3
|
+
export type ActionLogBackend = {
|
|
4
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
5
|
+
getBranch(invocationId: string): Promise<ActionLogEvent[]>;
|
|
6
|
+
getTip(invocationId: string): Promise<ActionLogEvent | null>;
|
|
7
|
+
getOpenByToolId(toolId: ToolId): Promise<OpenInvocationState | null>;
|
|
8
|
+
getOpenInvocations(): Promise<OpenInvocationState[]>;
|
|
9
|
+
feed?(events: ActionLogEvent[]): Promise<void>;
|
|
10
|
+
reload?(): Promise<void>;
|
|
11
|
+
close?(): Promise<void>;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ActionLogBackend } from "./backend";
|
|
2
|
+
import type { ActionLogEvent, OpenInvocationState } from "./types";
|
|
3
|
+
import type { ToolId } from "../tools/monero-tools";
|
|
4
|
+
export declare class ActionLogIdbBackend implements ActionLogBackend {
|
|
5
|
+
private readonly db;
|
|
6
|
+
private open;
|
|
7
|
+
private constructor();
|
|
8
|
+
static open(dbName?: string): Promise<ActionLogIdbBackend>;
|
|
9
|
+
private rebuildOpenMaps;
|
|
10
|
+
private getAllEventsOrdered;
|
|
11
|
+
append(event: ActionLogEvent): Promise<void>;
|
|
12
|
+
getBranch(invocationId: string): Promise<ActionLogEvent[]>;
|
|
13
|
+
getTip(invocationId: string): Promise<ActionLogEvent | null>;
|
|
14
|
+
getOpenByToolId(toolId: ToolId): Promise<OpenInvocationState | null>;
|
|
15
|
+
getOpenInvocations(): Promise<OpenInvocationState[]>;
|
|
16
|
+
feed(events: ActionLogEvent[]): Promise<void>;
|
|
17
|
+
reload(): Promise<void>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|