@arkade-os/swap 0.0.12 → 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/node/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ configDir: () => configDir,
24
+ createNodeSqlExecutor: () => createNodeSqlExecutor,
25
+ nodeSwapRepository: () => nodeSwapRepository,
26
+ swapDatabasePath: () => swapDatabasePath
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/node/paths.ts
31
+ var import_node_os = require("node:os");
32
+ var import_node_path = require("node:path");
33
+ var NETWORK_PATH_SEGMENT = /^[a-z][a-z0-9-]{0,32}$/;
34
+ var assertSafeNetworkPathSegment = (network) => {
35
+ if (NETWORK_PATH_SEGMENT.test(network)) return;
36
+ throw new Error(
37
+ `Invalid network name for swap database path: ${JSON.stringify(network)}. Use lowercase letters, digits, and hyphens, starting with a letter.`
38
+ );
39
+ };
40
+ var configDir = (env = process.env) => {
41
+ if (process.platform === "win32") {
42
+ return env.APPDATA ?? (0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Roaming");
43
+ }
44
+ if (process.platform === "darwin") {
45
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support");
46
+ }
47
+ const xdg = env.XDG_CONFIG_HOME;
48
+ return xdg && xdg.startsWith("/") ? xdg : (0, import_node_path.join)((0, import_node_os.homedir)(), ".config");
49
+ };
50
+ var swapDatabasePath = (network, env = process.env) => {
51
+ assertSafeNetworkPathSegment(network);
52
+ return (0, import_node_path.join)(configDir(env), "arkade", "swaps", `swaps-${network}.sqlite`);
53
+ };
54
+
55
+ // src/node/executor.ts
56
+ var import_node_sqlite = require("node:sqlite");
57
+ var import_node_fs = require("node:fs");
58
+ var import_node_path2 = require("node:path");
59
+
60
+ // src/repositories/sqlite/repository.ts
61
+ var import_sqlite = require("@arkade-os/sdk/repositories/sqlite");
62
+
63
+ // src/repository.ts
64
+ var marketsCacheKey = (network, registry) => `arkade-intents-markets-${network}-${registry}`;
65
+
66
+ // src/repositories/sqlite/repository.ts
67
+ var DEFAULT_PREFIX = "arkade_";
68
+ var INSERT_CHUNK = 500;
69
+ var SQLiteAssetSwapRepository = class {
70
+ constructor(db, options) {
71
+ this.db = db;
72
+ this.prefix = (0, import_sqlite.sanitizeTablePrefix)(options?.prefix ?? DEFAULT_PREFIX);
73
+ this.swaps = `${this.prefix}asset_swaps`;
74
+ this.rfqSwaps = `${this.prefix}rfq_swaps`;
75
+ this.swapRecords = `${this.prefix}swap_records`;
76
+ this.scanned = `${this.prefix}asset_swap_scanned_txids`;
77
+ this.markets = `${this.prefix}asset_swap_markets`;
78
+ }
79
+ db;
80
+ version = 5;
81
+ initPromise = null;
82
+ prefix;
83
+ swaps;
84
+ rfqSwaps;
85
+ swapRecords;
86
+ scanned;
87
+ markets;
88
+ /** A rejected init is not cached. The DDL runs in a transaction on a shared
89
+ * connection, so it can fail for reasons that pass — a `SQLITE_BUSY` on
90
+ * `BEGIN IMMEDIATE`, a neighbour's rollback — and caching that rejection
91
+ * would strand the instance unusable for the life of the process. Same rule
92
+ * as the IndexedDB backend's `ensureDb`. */
93
+ ensureInit() {
94
+ return this.initPromise ??= this.init().catch((err) => {
95
+ this.initPromise = null;
96
+ throw err;
97
+ });
98
+ }
99
+ /** The DDL is transactional in SQLite too: run raw on a shared connection it
100
+ * would join a neighbour's open transaction and vanish with its rollback,
101
+ * while `initPromise` stayed resolved and every later statement failed with
102
+ * `no such table`. */
103
+ async init() {
104
+ await this.withTx(async () => {
105
+ await this.db.run(`CREATE TABLE IF NOT EXISTS ${this.swaps} (
106
+ id TEXT PRIMARY KEY,
107
+ status TEXT NOT NULL,
108
+ created_at INTEGER NOT NULL,
109
+ data TEXT NOT NULL
110
+ )`);
111
+ await this.db.run(
112
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}asset_swaps_status ON ${this.swaps} (status)`
113
+ );
114
+ await this.db.run(
115
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}asset_swaps_created_at ON ${this.swaps} (created_at)`
116
+ );
117
+ await this.db.run(`CREATE TABLE IF NOT EXISTS ${this.rfqSwaps} (
118
+ rfq_id TEXT PRIMARY KEY,
119
+ state TEXT NOT NULL,
120
+ updated_at INTEGER NOT NULL,
121
+ data TEXT NOT NULL
122
+ )`);
123
+ await this.db.run(
124
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}rfq_swaps_state ON ${this.rfqSwaps} (state)`
125
+ );
126
+ await this.db.run(`CREATE TABLE IF NOT EXISTS ${this.swapRecords} (
127
+ id TEXT PRIMARY KEY,
128
+ family TEXT NOT NULL,
129
+ updated_at INTEGER NOT NULL,
130
+ data TEXT NOT NULL
131
+ )`);
132
+ await this.db.run(
133
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}swap_records_family ON ${this.swapRecords} (family)`
134
+ );
135
+ await this.db.run(`CREATE TABLE IF NOT EXISTS ${this.scanned} (txid TEXT PRIMARY KEY)`);
136
+ await this.db.run(
137
+ `CREATE TABLE IF NOT EXISTS ${this.markets} (cache_key TEXT PRIMARY KEY, data TEXT NOT NULL)`
138
+ );
139
+ });
140
+ }
141
+ /** Every write, including the single-statement ones. The chain serializes
142
+ * transaction *blocks*, not bare statements: one issued between a
143
+ * neighbour's BEGIN IMMEDIATE and its COMMIT becomes part of that
144
+ * transaction and dies with it. `fn` must issue only raw db calls — no
145
+ * repository method, no `ensureInit` — since `runInTransaction` cannot
146
+ * nest. */
147
+ withTx(fn) {
148
+ return (0, import_sqlite.runInTransaction)(this.db, fn);
149
+ }
150
+ async saveSwap(swap) {
151
+ await this.ensureInit();
152
+ await this.withTx(async () => {
153
+ await this.db.run(
154
+ `INSERT OR REPLACE INTO ${this.swaps} (id, status, created_at, data)
155
+ VALUES (?, ?, ?, ?)`,
156
+ [swap.id, swap.status, swap.createdAt, JSON.stringify(swap)]
157
+ );
158
+ });
159
+ }
160
+ async getAllSwaps() {
161
+ await this.ensureInit();
162
+ const rows = await this.db.all(`SELECT data FROM ${this.swaps}`);
163
+ return rows.map((r) => JSON.parse(r.data));
164
+ }
165
+ async saveRfqSwap(record) {
166
+ await this.ensureInit();
167
+ await this.withTx(async () => {
168
+ await this.db.run(
169
+ `INSERT OR REPLACE INTO ${this.rfqSwaps} (rfq_id, state, updated_at, data)
170
+ VALUES (?, ?, ?, ?)`,
171
+ [record.rfqId, record.state, record.updatedAt, JSON.stringify(record)]
172
+ );
173
+ });
174
+ }
175
+ async getRfqSwap(rfqId) {
176
+ await this.ensureInit();
177
+ const row = await this.db.get(
178
+ `SELECT data FROM ${this.rfqSwaps} WHERE rfq_id = ?`,
179
+ [rfqId]
180
+ );
181
+ return row ? JSON.parse(row.data) : void 0;
182
+ }
183
+ async getAllRfqSwaps() {
184
+ await this.ensureInit();
185
+ const rows = await this.db.all(`SELECT data FROM ${this.rfqSwaps}`);
186
+ return rows.map((r) => JSON.parse(r.data));
187
+ }
188
+ async removeRfqSwap(rfqId) {
189
+ await this.ensureInit();
190
+ await this.withTx(async () => {
191
+ await this.db.run(`DELETE FROM ${this.rfqSwaps} WHERE rfq_id = ?`, [rfqId]);
192
+ });
193
+ }
194
+ async saveSwapRecord(record) {
195
+ await this.ensureInit();
196
+ await this.withTx(async () => {
197
+ await this.db.run(
198
+ `INSERT OR REPLACE INTO ${this.swapRecords} (id, family, updated_at, data)
199
+ VALUES (?, ?, ?, ?)`,
200
+ [record.id, record.family, record.updatedAt, JSON.stringify(record)]
201
+ );
202
+ });
203
+ }
204
+ async getSwapRecord(id) {
205
+ await this.ensureInit();
206
+ const row = await this.db.get(
207
+ `SELECT data FROM ${this.swapRecords} WHERE id = ?`,
208
+ [id]
209
+ );
210
+ return row ? JSON.parse(row.data) : void 0;
211
+ }
212
+ async getAllSwapRecords() {
213
+ await this.ensureInit();
214
+ const rows = await this.db.all(`SELECT data FROM ${this.swapRecords}`);
215
+ return rows.map((r) => JSON.parse(r.data));
216
+ }
217
+ async removeSwapRecord(id) {
218
+ await this.ensureInit();
219
+ await this.withTx(async () => {
220
+ await this.db.run(`DELETE FROM ${this.swapRecords} WHERE id = ?`, [id]);
221
+ });
222
+ }
223
+ async getScannedTxids() {
224
+ await this.ensureInit();
225
+ const rows = await this.db.all(`SELECT txid FROM ${this.scanned}`);
226
+ return new Set(rows.map((r) => r.txid));
227
+ }
228
+ async markTxidsScanned(txids) {
229
+ await this.ensureInit();
230
+ const all = [...txids];
231
+ if (all.length === 0) return;
232
+ await this.withTx(async () => {
233
+ for (let i = 0; i < all.length; i += INSERT_CHUNK) {
234
+ const chunk = all.slice(i, i + INSERT_CHUNK);
235
+ await this.db.run(
236
+ `INSERT OR IGNORE INTO ${this.scanned} (txid) VALUES ${chunk.map(() => "(?)").join(", ")}`,
237
+ chunk
238
+ );
239
+ }
240
+ });
241
+ }
242
+ async getCachedMarkets(network, registry) {
243
+ await this.ensureInit();
244
+ const row = await this.db.get(
245
+ `SELECT data FROM ${this.markets} WHERE cache_key = ?`,
246
+ [marketsCacheKey(network, registry)]
247
+ );
248
+ return row ? JSON.parse(row.data) : void 0;
249
+ }
250
+ async saveCachedMarkets(network, registry, entry) {
251
+ await this.ensureInit();
252
+ await this.withTx(async () => {
253
+ await this.db.run(
254
+ `INSERT OR REPLACE INTO ${this.markets} (cache_key, data) VALUES (?, ?)`,
255
+ [marketsCacheKey(network, registry), JSON.stringify(entry)]
256
+ );
257
+ });
258
+ }
259
+ /** Every table in one transaction: clearing swaps but keeping scanned txids
260
+ * would leave the restore scan permanently skipping those funding txs, so a
261
+ * partial clear must not be observable. */
262
+ async clear() {
263
+ await this.ensureInit();
264
+ await this.withTx(async () => {
265
+ await this.db.run(`DELETE FROM ${this.swaps}`);
266
+ await this.db.run(`DELETE FROM ${this.rfqSwaps}`);
267
+ await this.db.run(`DELETE FROM ${this.swapRecords}`);
268
+ await this.db.run(`DELETE FROM ${this.scanned}`);
269
+ await this.db.run(`DELETE FROM ${this.markets}`);
270
+ });
271
+ }
272
+ async [Symbol.asyncDispose]() {
273
+ }
274
+ };
275
+
276
+ // src/node/executor.ts
277
+ var bindable = (params) => (params ?? []).map((p) => p === void 0 ? null : p);
278
+ var createNodeSqlExecutor = (path) => {
279
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(path), { recursive: true });
280
+ const db = new import_node_sqlite.DatabaseSync(path);
281
+ let open = true;
282
+ return {
283
+ async run(sql, params) {
284
+ db.prepare(sql).run(...bindable(params));
285
+ },
286
+ async get(sql, params) {
287
+ return db.prepare(sql).get(...bindable(params)) ?? void 0;
288
+ },
289
+ async all(sql, params) {
290
+ return db.prepare(sql).all(...bindable(params));
291
+ },
292
+ async close() {
293
+ if (!open) return;
294
+ open = false;
295
+ db.close();
296
+ }
297
+ };
298
+ };
299
+ var nodeSwapRepository = (options) => {
300
+ const executor = createNodeSqlExecutor(options.path ?? swapDatabasePath(options.network));
301
+ return new NodeSwapRepository(
302
+ executor,
303
+ options.prefix === void 0 ? void 0 : { prefix: options.prefix }
304
+ );
305
+ };
306
+ var NodeSwapRepository = class extends SQLiteAssetSwapRepository {
307
+ constructor(executor, options) {
308
+ super(executor, options);
309
+ this.executor = executor;
310
+ }
311
+ executor;
312
+ async [Symbol.asyncDispose]() {
313
+ await this.executor.close();
314
+ }
315
+ };
316
+ // Annotate the CommonJS export names for ESM import in node:
317
+ 0 && (module.exports = {
318
+ configDir,
319
+ createNodeSqlExecutor,
320
+ nodeSwapRepository,
321
+ swapDatabasePath
322
+ });