@ocap/indexdb-sqlite 1.29.8 → 1.29.10
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/esm/db/base.d.mts +3 -1
- package/esm/db/base.mjs +94 -47
- package/esm/db/index.mjs +2 -1
- package/esm/interfaces.d.mts +12 -1
- package/esm/migrations/002-composite-indexes.d.mts +12 -0
- package/esm/migrations/002-composite-indexes.mjs +73 -0
- package/esm/migrations/index.mjs +5 -1
- package/esm/table/account.d.mts +31 -0
- package/esm/table/account.mjs +58 -0
- package/esm/table/transaction.d.mts +7 -1
- package/esm/table/transaction.mjs +31 -2
- package/lib/db/base.cjs +94 -47
- package/lib/db/base.d.cts +3 -1
- package/lib/db/index.cjs +2 -1
- package/lib/interfaces.d.cts +12 -1
- package/lib/migrations/002-composite-indexes.cjs +80 -0
- package/lib/migrations/002-composite-indexes.d.cts +12 -0
- package/lib/migrations/index.cjs +5 -1
- package/lib/table/account.cjs +60 -0
- package/lib/table/account.d.cts +31 -0
- package/lib/table/transaction.cjs +31 -2
- package/lib/table/transaction.d.cts +7 -1
- package/package.json +6 -6
package/esm/db/base.d.mts
CHANGED
|
@@ -36,7 +36,9 @@ declare class SqliteBaseIndexDB extends BaseIndexDB implements IIndexDB {
|
|
|
36
36
|
*/
|
|
37
37
|
listAssets(params?: Partial<TRequestListAssets>): Promise<IListAssetsResult>;
|
|
38
38
|
/**
|
|
39
|
-
* List top accounts by token balance
|
|
39
|
+
* List top accounts by token balance.
|
|
40
|
+
* For tokenAddress filter: uses balance table JOIN for indexed lookup.
|
|
41
|
+
* Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
|
|
40
42
|
*/
|
|
41
43
|
listTopAccounts(params?: Partial<TRequestListTopAccounts>): Promise<IListAccountsResult>;
|
|
42
44
|
/**
|
package/esm/db/base.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
1
2
|
import { BaseIndexDB, formatDelegationAfterRead, formatNextPagination, formatPagination, parseDateTime } from "@ocap/indexdb";
|
|
2
3
|
import { toChecksumAddress } from "@arcblock/did/lib/type";
|
|
3
|
-
import { BN } from "@ocap/util";
|
|
4
4
|
import { DEFAULT_TOKEN_DECIMAL } from "@ocap/util/lib/constant";
|
|
5
5
|
import debugFactory from "debug";
|
|
6
6
|
import omit from "lodash/omit.js";
|
|
@@ -55,8 +55,8 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
55
55
|
includeItxData
|
|
56
56
|
});
|
|
57
57
|
let qb = this.db.selectFrom("tx").selectAll();
|
|
58
|
-
|
|
59
|
-
else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
|
|
58
|
+
const isMutual = !!(sender && receiver && direction === "MUTUAL");
|
|
59
|
+
if (sender && receiver) if (direction === "MUTUAL") {} else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
|
|
60
60
|
else qb = qb.where((eb) => eb.or([eb("sender", "=", sender), eb("receiver", "=", receiver)]));
|
|
61
61
|
else if (sender) qb = qb.where("sender", "=", sender);
|
|
62
62
|
else if (receiver) qb = qb.where("receiver", "=", receiver);
|
|
@@ -71,18 +71,68 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
71
71
|
const { validity } = validityFilter;
|
|
72
72
|
if (validity === "VALID") qb = qb.where("valid", "=", 1);
|
|
73
73
|
else if (validity === "INVALID") qb = qb.where("valid", "=", 0);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
const txRefTypeMap = {
|
|
75
|
+
factories: "factory",
|
|
76
|
+
tokens: "token",
|
|
77
|
+
assets: "asset",
|
|
78
|
+
accounts: "account",
|
|
79
|
+
rollups: "rollup",
|
|
80
|
+
stakes: "stake",
|
|
81
|
+
delegations: "delegation",
|
|
82
|
+
tokenFactories: "tokenFactory"
|
|
83
|
+
};
|
|
84
|
+
const applyJsonArrayFilter = (column, values) => {
|
|
85
|
+
const refType = txRefTypeMap[column];
|
|
86
|
+
qb = qb.where("hash", "in", this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values));
|
|
87
|
+
};
|
|
88
|
+
if (factories.length) applyJsonArrayFilter("factories", factories);
|
|
89
|
+
if (assets.length) applyJsonArrayFilter("assets", assets);
|
|
90
|
+
if (tokens.length) applyJsonArrayFilter("tokens", tokens);
|
|
91
|
+
if (accounts.length) applyJsonArrayFilter("accounts", accounts);
|
|
92
|
+
if (rollups.length) applyJsonArrayFilter("rollups", rollups);
|
|
93
|
+
if (stakes.length) applyJsonArrayFilter("stakes", stakes);
|
|
94
|
+
if (delegations.length) applyJsonArrayFilter("delegations", delegations);
|
|
95
|
+
if (tokenFactories.length) applyJsonArrayFilter("tokenFactories", tokenFactories);
|
|
96
|
+
let total;
|
|
97
|
+
const activeJsonFilters = [];
|
|
98
|
+
if (factories.length) activeJsonFilters.push(["factory", factories]);
|
|
99
|
+
if (assets.length) activeJsonFilters.push(["asset", assets]);
|
|
100
|
+
if (tokens.length) activeJsonFilters.push(["token", tokens]);
|
|
101
|
+
if (accounts.length) activeJsonFilters.push(["account", accounts]);
|
|
102
|
+
if (rollups.length) activeJsonFilters.push(["rollup", rollups]);
|
|
103
|
+
if (stakes.length) activeJsonFilters.push(["stake", stakes]);
|
|
104
|
+
if (delegations.length) activeJsonFilters.push(["delegation", delegations]);
|
|
105
|
+
if (tokenFactories.length) activeJsonFilters.push(["tokenFactory", tokenFactories]);
|
|
106
|
+
const hasNonJsonFilters = !!(sender || receiver || types.length || txs.length || parsedStart || parsedEnd || validity);
|
|
107
|
+
if (isMutual) {
|
|
108
|
+
const [c1, c2] = await Promise.all([this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).where("receiver", "=", receiver).executeTakeFirst(), this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", receiver).where("receiver", "=", sender).executeTakeFirst()]);
|
|
109
|
+
total = (c1?.count ?? 0) + (c2?.count ?? 0);
|
|
110
|
+
} else if (!hasNonJsonFilters && activeJsonFilters.length === 1) {
|
|
111
|
+
const [refType, values] = activeJsonFilters[0];
|
|
112
|
+
total = (await this.db.selectFrom("tx_ref").select(this.db.fn.countAll().as("count")).where("type", "=", refType).where("value", "in", values).executeTakeFirst())?.count ?? 0;
|
|
113
|
+
} else if (sender && receiver && direction !== "ONE_WAY") {
|
|
114
|
+
const [senderCount, receiverCount] = await Promise.all([this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).executeTakeFirst(), this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("receiver", "=", receiver).executeTakeFirst()]);
|
|
115
|
+
total = (senderCount?.count ?? 0) + (receiverCount?.count ?? 0);
|
|
116
|
+
} else total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
83
117
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
84
|
-
|
|
85
|
-
|
|
118
|
+
let rawResults;
|
|
119
|
+
if (isMutual) {
|
|
120
|
+
const q1 = this.db.selectFrom("tx").selectAll().where("sender", "=", sender).where("receiver", "=", receiver);
|
|
121
|
+
const q2 = this.db.selectFrom("tx").selectAll().where("sender", "=", receiver).where("receiver", "=", sender);
|
|
122
|
+
rawResults = await q1.unionAll(q2).orderBy(pagination.order.field, orderDir).limit(pagination.size).offset(pagination.cursor).execute();
|
|
123
|
+
} else if (!hasNonJsonFilters && activeJsonFilters.length === 1 && pagination.order.field === "time") {
|
|
124
|
+
const [refType, values] = activeJsonFilters[0];
|
|
125
|
+
const hashes = (await this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values).orderBy("time", orderDir).limit(pagination.size).offset(pagination.cursor).execute()).map((r) => r.hash);
|
|
126
|
+
if (hashes.length > 0) {
|
|
127
|
+
const dataRows = await this.db.selectFrom("tx").selectAll().where("hash", "in", hashes).execute();
|
|
128
|
+
const hashOrder = new Map(hashes.map((h, i) => [h, i]));
|
|
129
|
+
rawResults = [...dataRows].sort((a, b) => (hashOrder.get(a.hash) ?? 0) - (hashOrder.get(b.hash) ?? 0));
|
|
130
|
+
} else rawResults = [];
|
|
131
|
+
} else {
|
|
132
|
+
qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
|
|
133
|
+
rawResults = await qb.execute();
|
|
134
|
+
}
|
|
135
|
+
let transactions = rawResults.map((row) => {
|
|
86
136
|
const tx = { ...row };
|
|
87
137
|
[
|
|
88
138
|
"receipts",
|
|
@@ -212,7 +262,9 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
212
262
|
};
|
|
213
263
|
}
|
|
214
264
|
/**
|
|
215
|
-
* List top accounts by token balance
|
|
265
|
+
* List top accounts by token balance.
|
|
266
|
+
* For tokenAddress filter: uses balance table JOIN for indexed lookup.
|
|
267
|
+
* Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
|
|
216
268
|
*/
|
|
217
269
|
async listTopAccounts(params = {}) {
|
|
218
270
|
const { paging, tokenAddress } = params;
|
|
@@ -230,12 +282,8 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
230
282
|
paging,
|
|
231
283
|
pagination
|
|
232
284
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
236
|
-
qb = qb.orderBy(pagination.order.field, orderDir);
|
|
237
|
-
}
|
|
238
|
-
let accounts = (await qb.execute()).map((row) => {
|
|
285
|
+
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
286
|
+
const parseAccount = (row) => {
|
|
239
287
|
const account = { ...row };
|
|
240
288
|
[
|
|
241
289
|
"stake",
|
|
@@ -251,31 +299,21 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
251
299
|
return token;
|
|
252
300
|
});
|
|
253
301
|
return account;
|
|
254
|
-
}
|
|
255
|
-
if (tokenAddress)
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const descending = pagination.order.type === "desc";
|
|
263
|
-
accounts.sort((a, b) => {
|
|
264
|
-
const tokensA = a.tokens;
|
|
265
|
-
const tokensB = b.tokens;
|
|
266
|
-
const tokenA = tokensA?.find((t) => t.address === tokenAddress);
|
|
267
|
-
const tokenB = tokensB?.find((t) => t.address === tokenAddress);
|
|
268
|
-
const balanceA = new BN(tokenA ? tokenA.balance : "0");
|
|
269
|
-
const balanceB = new BN(tokenB ? tokenB.balance : "0");
|
|
270
|
-
if (balanceB.gt(balanceA)) return descending ? 1 : -1;
|
|
271
|
-
if (balanceB.eq(balanceA)) return 0;
|
|
272
|
-
return descending ? -1 : 1;
|
|
273
|
-
});
|
|
302
|
+
};
|
|
303
|
+
if (tokenAddress) {
|
|
304
|
+
const total$1 = (await this.db.selectFrom("balance").select(this.db.fn.countAll().as("count")).where("tokenAddress", "=", tokenAddress).where("balance", "!=", "0").executeTakeFirst())?.count ?? 0;
|
|
305
|
+
const sortField$1 = pagination.order.field === "balance" ? "b.balanceNum" : `a.${pagination.order.field}`;
|
|
306
|
+
return {
|
|
307
|
+
accounts: (await this.db.selectFrom("balance as b").innerJoin("account as a", "a.address", "b.address").selectAll("a").where("b.tokenAddress", "=", tokenAddress).where("b.balance", "!=", "0").orderBy(sortField$1, orderDir).limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
|
|
308
|
+
paging: formatNextPagination(total$1, pagination)
|
|
309
|
+
};
|
|
274
310
|
}
|
|
275
|
-
|
|
276
|
-
|
|
311
|
+
let qb = this.db.selectFrom("account").selectAll();
|
|
312
|
+
const sortField = pagination.order.field === "balance" ? "balanceNum" : pagination.order.field;
|
|
313
|
+
qb = qb.orderBy(sortField, orderDir);
|
|
314
|
+
const total = (await this.db.selectFrom("account").select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
277
315
|
return {
|
|
278
|
-
accounts,
|
|
316
|
+
accounts: (await qb.limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
|
|
279
317
|
paging: formatNextPagination(total, pagination)
|
|
280
318
|
};
|
|
281
319
|
}
|
|
@@ -457,7 +495,10 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
457
495
|
if (parsedStart && parsedEnd) qb = qb.where("renaissanceTime", ">", parsedStart).where("renaissanceTime", "<=", parsedEnd);
|
|
458
496
|
else if (parsedStart) qb = qb.where("renaissanceTime", ">", parsedStart);
|
|
459
497
|
else if (parsedEnd) qb = qb.where("renaissanceTime", "<=", parsedEnd);
|
|
460
|
-
if (assets.length) qb = qb.where((eb) =>
|
|
498
|
+
if (assets.length) qb = qb.where((eb) => {
|
|
499
|
+
const conditions = assets.map((v) => eb(sql`INSTR(${sql.ref("assets")}, ${`"${v}"`})`, ">", 0));
|
|
500
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
501
|
+
});
|
|
461
502
|
const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
462
503
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
463
504
|
qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
|
|
@@ -550,8 +591,14 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
|
|
|
550
591
|
if (parsedStart && parsedEnd) qb = qb.where("genesisTime", ">", parsedStart).where("genesisTime", "<=", parsedEnd);
|
|
551
592
|
else if (parsedStart) qb = qb.where("genesisTime", ">", parsedStart);
|
|
552
593
|
else if (parsedEnd) qb = qb.where("genesisTime", "<=", parsedEnd);
|
|
553
|
-
if (txs.length) qb = qb.where((eb) =>
|
|
554
|
-
|
|
594
|
+
if (txs.length) qb = qb.where((eb) => {
|
|
595
|
+
const conditions = txs.map((v) => eb(sql`INSTR(${sql.ref("txs")}, ${`"${v}"`})`, ">", 0));
|
|
596
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
597
|
+
});
|
|
598
|
+
if (validators.length) qb = qb.where((eb) => {
|
|
599
|
+
const conditions = validators.map((v) => eb(sql`INSTR(${sql.ref("validators")}, ${`"${v}"`})`, ">", 0));
|
|
600
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
601
|
+
});
|
|
555
602
|
if (tokenAddress) qb = qb.where((eb) => eb(eb.fn("json_extract", [eb.ref("tokenInfo"), eb.val("$.address")]), "=", tokenAddress));
|
|
556
603
|
const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
557
604
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
package/esm/db/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createKysely } from "../kysely.mjs";
|
|
2
2
|
import { runMigrations } from "../migrations/index.mjs";
|
|
3
3
|
import base_default from "../table/base.mjs";
|
|
4
|
+
import account_default from "../table/account.mjs";
|
|
4
5
|
import transaction_default from "../table/transaction.mjs";
|
|
5
6
|
import base_default$1 from "./base.mjs";
|
|
6
7
|
import package_default from "../package.mjs";
|
|
@@ -96,7 +97,7 @@ var SqliteIndexDB = class extends base_default$1 {
|
|
|
96
97
|
]
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
|
-
this.account = new
|
|
100
|
+
this.account = new account_default({
|
|
100
101
|
name: "account",
|
|
101
102
|
uniqIndex: "address",
|
|
102
103
|
db: this.db,
|
package/esm/interfaces.d.mts
CHANGED
|
@@ -20,6 +20,7 @@ interface Database {
|
|
|
20
20
|
tokenDistribution: TokenDistributionTable;
|
|
21
21
|
tokenFactory: TokenFactoryTable;
|
|
22
22
|
balance: BalanceTable;
|
|
23
|
+
tx_ref: TxRefTable;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Transaction table for indexed transactions
|
|
@@ -318,6 +319,16 @@ interface BalanceTable {
|
|
|
318
319
|
balanceNum: string | null;
|
|
319
320
|
data: string | null;
|
|
320
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Junction table for fast tx JSON array lookups.
|
|
324
|
+
* Normalizes the JSON array columns (factories, tokens, etc.) into indexed rows.
|
|
325
|
+
*/
|
|
326
|
+
interface TxRefTable {
|
|
327
|
+
hash: string;
|
|
328
|
+
type: string;
|
|
329
|
+
value: string;
|
|
330
|
+
time: string | null;
|
|
331
|
+
}
|
|
321
332
|
/**
|
|
322
333
|
* SQLite-specific operation context with optional transaction
|
|
323
334
|
*/
|
|
@@ -325,4 +336,4 @@ interface SqliteOperationContext {
|
|
|
325
336
|
txn?: Transaction<Database>;
|
|
326
337
|
}
|
|
327
338
|
//#endregion
|
|
328
|
-
export { AccountTable, AssetTable, BalanceTable, Database, DelegationTable, FactoryTable, RollupBlockTable, RollupTable, RollupValidatorTable, SqliteOperationContext, StakeTable, TokenDistributionTable, TokenFactoryTable, TokenTable, TxTable };
|
|
339
|
+
export { AccountTable, AssetTable, BalanceTable, Database, DelegationTable, FactoryTable, RollupBlockTable, RollupTable, RollupValidatorTable, SqliteOperationContext, StakeTable, TokenDistributionTable, TokenFactoryTable, TokenTable, TxRefTable, TxTable };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
|
|
3
|
+
//#region src/migrations/002-composite-indexes.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add composite indexes for covering WHERE + ORDER BY query patterns.
|
|
7
|
+
* These eliminate filesort when filtering by one column and sorting by another.
|
|
8
|
+
*/
|
|
9
|
+
declare function up(db: Kysely<unknown>): Promise<void>;
|
|
10
|
+
declare function down(db: Kysely<unknown>): Promise<void>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { down, up };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/rolldown_runtime.mjs";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
//#region src/migrations/002-composite-indexes.ts
|
|
5
|
+
var _002_composite_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
down: () => down,
|
|
7
|
+
up: () => up
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Add composite indexes for covering WHERE + ORDER BY query patterns.
|
|
11
|
+
* These eliminate filesort when filtering by one column and sorting by another.
|
|
12
|
+
*/
|
|
13
|
+
async function up(db) {
|
|
14
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_sender_time ON tx(sender, time DESC)`.execute(db);
|
|
15
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_receiver_time ON tx(receiver, time DESC)`.execute(db);
|
|
16
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_type_time ON tx(type, time DESC)`.execute(db);
|
|
17
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_valid_time ON tx(valid, time DESC)`.execute(db);
|
|
18
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_asset_owner_renaissance ON asset(owner, renaissanceTime DESC)`.execute(db);
|
|
19
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_asset_parent_renaissance ON asset(parent, renaissanceTime DESC)`.execute(db);
|
|
20
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_stake_sender_renaissance ON stake(sender, renaissanceTime DESC)`.execute(db);
|
|
21
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_stake_receiver_renaissance ON stake(receiver, renaissanceTime DESC)`.execute(db);
|
|
22
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_delegation_from_renaissance ON delegation(from_, renaissanceTime DESC)`.execute(db);
|
|
23
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_delegation_to_renaissance ON delegation(to_, renaissanceTime DESC)`.execute(db);
|
|
24
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_rollupblock_rollup_genesis ON rollupBlock(rollup, genesisTime DESC)`.execute(db);
|
|
25
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_rollupvalidator_rollup_genesis ON rollupValidator(rollup, genesisTime DESC)`.execute(db);
|
|
26
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_factory_owner_renaissance ON factory(owner, renaissanceTime DESC)`.execute(db);
|
|
27
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_token_issuer_renaissance ON token(issuer, renaissanceTime DESC)`.execute(db);
|
|
28
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tokenfactory_owner_renaissance ON tokenFactory(owner, renaissanceTime DESC)`.execute(db);
|
|
29
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_sender_receiver_time ON tx(sender, receiver, time DESC)`.execute(db);
|
|
30
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_balance_token_amount ON balance(tokenAddress, balanceNum DESC)`.execute(db);
|
|
31
|
+
await sql`CREATE TABLE IF NOT EXISTS tx_ref (hash TEXT NOT NULL, type TEXT NOT NULL, value TEXT NOT NULL, time TEXT)`.execute(db);
|
|
32
|
+
await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_tx_ref_pk ON tx_ref(hash, type, value)`.execute(db);
|
|
33
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_ref_lookup ON tx_ref(type, value)`.execute(db);
|
|
34
|
+
await sql`CREATE INDEX IF NOT EXISTS idx_tx_ref_time_lookup ON tx_ref(type, value, time DESC)`.execute(db);
|
|
35
|
+
for (const [type, column] of [
|
|
36
|
+
["factory", "factories"],
|
|
37
|
+
["token", "tokens"],
|
|
38
|
+
["asset", "assets"],
|
|
39
|
+
["account", "accounts"],
|
|
40
|
+
["rollup", "rollups"],
|
|
41
|
+
["stake", "stakes"],
|
|
42
|
+
["delegation", "delegations"],
|
|
43
|
+
["tokenFactory", "tokenFactories"]
|
|
44
|
+
]) await sql.raw(`INSERT OR IGNORE INTO tx_ref (hash, type, value, time) SELECT tx.hash, '${type}', je.value, tx.time FROM tx, json_each(tx.${column}) AS je WHERE tx.${column} IS NOT NULL AND tx.${column} != '[]'`).execute(db);
|
|
45
|
+
}
|
|
46
|
+
async function down(db) {
|
|
47
|
+
for (const idx of [
|
|
48
|
+
"idx_tx_sender_time",
|
|
49
|
+
"idx_tx_receiver_time",
|
|
50
|
+
"idx_tx_type_time",
|
|
51
|
+
"idx_tx_valid_time",
|
|
52
|
+
"idx_asset_owner_renaissance",
|
|
53
|
+
"idx_asset_parent_renaissance",
|
|
54
|
+
"idx_stake_sender_renaissance",
|
|
55
|
+
"idx_stake_receiver_renaissance",
|
|
56
|
+
"idx_delegation_from_renaissance",
|
|
57
|
+
"idx_delegation_to_renaissance",
|
|
58
|
+
"idx_rollupblock_rollup_genesis",
|
|
59
|
+
"idx_rollupvalidator_rollup_genesis",
|
|
60
|
+
"idx_factory_owner_renaissance",
|
|
61
|
+
"idx_token_issuer_renaissance",
|
|
62
|
+
"idx_tokenfactory_owner_renaissance",
|
|
63
|
+
"idx_tx_sender_receiver_time",
|
|
64
|
+
"idx_balance_token_amount",
|
|
65
|
+
"idx_tx_ref_pk",
|
|
66
|
+
"idx_tx_ref_lookup",
|
|
67
|
+
"idx_tx_ref_time_lookup"
|
|
68
|
+
]) await sql.raw(`DROP INDEX IF EXISTS ${idx}`).execute(db);
|
|
69
|
+
await sql`DROP TABLE IF EXISTS tx_ref`.execute(db);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { _002_composite_indexes_exports, down, up };
|
package/esm/migrations/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _001_genesis_exports } from "./001-genesis.mjs";
|
|
2
|
+
import { _002_composite_indexes_exports } from "./002-composite-indexes.mjs";
|
|
2
3
|
import { Migrator } from "kysely";
|
|
3
4
|
|
|
4
5
|
//#region src/migrations/index.ts
|
|
@@ -8,7 +9,10 @@ import { Migrator } from "kysely";
|
|
|
8
9
|
*/
|
|
9
10
|
var InMemoryMigrationProvider = class {
|
|
10
11
|
async getMigrations() {
|
|
11
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
"001-genesis": _001_genesis_exports,
|
|
14
|
+
"002-composite-indexes": _002_composite_indexes_exports
|
|
15
|
+
};
|
|
12
16
|
}
|
|
13
17
|
};
|
|
14
18
|
/**
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import SqliteTable from "./base.mjs";
|
|
2
|
+
import { TIndexedAccountState } from "@ocap/types";
|
|
3
|
+
|
|
4
|
+
//#region src/table/account.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Account table for SQLite IndexDB
|
|
8
|
+
* Extends base table to maintain the balance junction table
|
|
9
|
+
* for fast listTopAccounts queries with tokenAddress filter.
|
|
10
|
+
*/
|
|
11
|
+
declare class AccountTable extends SqliteTable<TIndexedAccountState> {
|
|
12
|
+
/**
|
|
13
|
+
* Override _insert to sync balance table after inserting an account.
|
|
14
|
+
*/
|
|
15
|
+
_insert(row: TIndexedAccountState | Record<string, unknown>): Promise<TIndexedAccountState>;
|
|
16
|
+
/**
|
|
17
|
+
* Override _update to sync balance table after updating an account.
|
|
18
|
+
*/
|
|
19
|
+
_update(key: string | Record<string, unknown>, updates: Partial<TIndexedAccountState>): Promise<TIndexedAccountState>;
|
|
20
|
+
/**
|
|
21
|
+
* Sync the balance table from account tokens.
|
|
22
|
+
* Deletes existing entries for this address, then inserts current token balances.
|
|
23
|
+
*/
|
|
24
|
+
private syncBalance;
|
|
25
|
+
/**
|
|
26
|
+
* Override _reset to also clear the balance table
|
|
27
|
+
*/
|
|
28
|
+
_reset(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { AccountTable as default };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import base_default from "./base.mjs";
|
|
2
|
+
import { sql } from "kysely";
|
|
3
|
+
|
|
4
|
+
//#region src/table/account.ts
|
|
5
|
+
/**
|
|
6
|
+
* Account table for SQLite IndexDB
|
|
7
|
+
* Extends base table to maintain the balance junction table
|
|
8
|
+
* for fast listTopAccounts queries with tokenAddress filter.
|
|
9
|
+
*/
|
|
10
|
+
var AccountTable = class extends base_default {
|
|
11
|
+
/**
|
|
12
|
+
* Override _insert to sync balance table after inserting an account.
|
|
13
|
+
*/
|
|
14
|
+
async _insert(row) {
|
|
15
|
+
const result = await super._insert(row);
|
|
16
|
+
await this.syncBalance(row);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Override _update to sync balance table after updating an account.
|
|
21
|
+
*/
|
|
22
|
+
async _update(key, updates) {
|
|
23
|
+
const result = await super._update(key, updates);
|
|
24
|
+
await this.syncBalance(result);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sync the balance table from account tokens.
|
|
29
|
+
* Deletes existing entries for this address, then inserts current token balances.
|
|
30
|
+
*/
|
|
31
|
+
async syncBalance(data) {
|
|
32
|
+
const address = data.address;
|
|
33
|
+
if (!address) return;
|
|
34
|
+
let tokens = data.tokens;
|
|
35
|
+
if (typeof tokens === "string") try {
|
|
36
|
+
tokens = JSON.parse(tokens);
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(tokens)) return;
|
|
41
|
+
await sql`DELETE FROM balance WHERE address = ${address}`.execute(this.db);
|
|
42
|
+
for (const t of tokens) if (t.address && t.balance && t.balance !== "0") {
|
|
43
|
+
const balanceNum = (t.balance.replace(/^0+/, "") || "0").padStart(40, "0");
|
|
44
|
+
await sql`INSERT INTO balance (address, tokenAddress, balance, balanceNum) VALUES (${address}, ${t.address}, ${t.balance}, ${balanceNum})`.execute(this.db);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Override _reset to also clear the balance table
|
|
49
|
+
*/
|
|
50
|
+
async _reset() {
|
|
51
|
+
await super._reset();
|
|
52
|
+
await this.db.deleteFrom("balance").execute();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var account_default = AccountTable;
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { account_default as default };
|
|
@@ -6,13 +6,19 @@ import { TIndexedTransaction } from "@ocap/types";
|
|
|
6
6
|
/**
|
|
7
7
|
* Transaction table for SQLite IndexDB
|
|
8
8
|
* Extends base table to call formatTxBeforeInsert on insert
|
|
9
|
+
* and maintain the tx_ref junction table for fast JSON array lookups
|
|
9
10
|
*/
|
|
10
11
|
declare class TransactionTable extends SqliteTable<TIndexedTransaction> {
|
|
11
12
|
/**
|
|
12
13
|
* Override _insert to format transaction before inserting
|
|
13
|
-
* This populates assets, tokens, factories, etc. from itxJson
|
|
14
|
+
* This populates assets, tokens, factories, etc. from itxJson,
|
|
15
|
+
* then inserts corresponding rows into tx_ref for indexed lookups.
|
|
14
16
|
*/
|
|
15
17
|
_insert(row: TIndexedTransaction | Record<string, unknown>): Promise<TIndexedTransaction>;
|
|
18
|
+
/**
|
|
19
|
+
* Override _reset to also clear the tx_ref junction table
|
|
20
|
+
*/
|
|
21
|
+
_reset(): Promise<void>;
|
|
16
22
|
}
|
|
17
23
|
//#endregion
|
|
18
24
|
export { TransactionTable as default };
|
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
import base_default from "./base.mjs";
|
|
2
|
+
import { sql } from "kysely";
|
|
2
3
|
import { formatTxBeforeInsert } from "@ocap/indexdb";
|
|
3
4
|
|
|
4
5
|
//#region src/table/transaction.ts
|
|
6
|
+
const TX_REF_TYPES = [
|
|
7
|
+
["factory", "factories"],
|
|
8
|
+
["token", "tokens"],
|
|
9
|
+
["asset", "assets"],
|
|
10
|
+
["account", "accounts"],
|
|
11
|
+
["rollup", "rollups"],
|
|
12
|
+
["stake", "stakes"],
|
|
13
|
+
["delegation", "delegations"],
|
|
14
|
+
["tokenFactory", "tokenFactories"]
|
|
15
|
+
];
|
|
5
16
|
/**
|
|
6
17
|
* Transaction table for SQLite IndexDB
|
|
7
18
|
* Extends base table to call formatTxBeforeInsert on insert
|
|
19
|
+
* and maintain the tx_ref junction table for fast JSON array lookups
|
|
8
20
|
*/
|
|
9
21
|
var TransactionTable = class extends base_default {
|
|
10
22
|
/**
|
|
11
23
|
* Override _insert to format transaction before inserting
|
|
12
|
-
* This populates assets, tokens, factories, etc. from itxJson
|
|
24
|
+
* This populates assets, tokens, factories, etc. from itxJson,
|
|
25
|
+
* then inserts corresponding rows into tx_ref for indexed lookups.
|
|
13
26
|
*/
|
|
14
27
|
async _insert(row) {
|
|
15
28
|
const formatted = formatTxBeforeInsert(row);
|
|
16
|
-
|
|
29
|
+
const result = await super._insert(formatted);
|
|
30
|
+
const data = formatted;
|
|
31
|
+
const hash = data.hash;
|
|
32
|
+
const time = data.time || null;
|
|
33
|
+
for (const [type, field] of TX_REF_TYPES) {
|
|
34
|
+
const values = data[field];
|
|
35
|
+
if (!Array.isArray(values) || values.length === 0) continue;
|
|
36
|
+
for (const v of values) if (typeof v === "string" && v) await sql`INSERT OR IGNORE INTO tx_ref (hash, type, value, time) VALUES (${hash}, ${type}, ${v}, ${time})`.execute(this.db);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Override _reset to also clear the tx_ref junction table
|
|
42
|
+
*/
|
|
43
|
+
async _reset() {
|
|
44
|
+
await super._reset();
|
|
45
|
+
await this.db.deleteFrom("tx_ref").execute();
|
|
17
46
|
}
|
|
18
47
|
};
|
|
19
48
|
var transaction_default = TransactionTable;
|
package/lib/db/base.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
3
|
+
let kysely = require("kysely");
|
|
3
4
|
let _ocap_indexdb = require("@ocap/indexdb");
|
|
4
5
|
let _arcblock_did_lib_type = require("@arcblock/did/lib/type");
|
|
5
|
-
let _ocap_util = require("@ocap/util");
|
|
6
6
|
let _ocap_util_lib_constant = require("@ocap/util/lib/constant");
|
|
7
7
|
let debug = require("debug");
|
|
8
8
|
debug = require_rolldown_runtime.__toESM(debug);
|
|
@@ -59,8 +59,8 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
59
59
|
includeItxData
|
|
60
60
|
});
|
|
61
61
|
let qb = this.db.selectFrom("tx").selectAll();
|
|
62
|
-
|
|
63
|
-
else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
|
|
62
|
+
const isMutual = !!(sender && receiver && direction === "MUTUAL");
|
|
63
|
+
if (sender && receiver) if (direction === "MUTUAL") {} else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
|
|
64
64
|
else qb = qb.where((eb) => eb.or([eb("sender", "=", sender), eb("receiver", "=", receiver)]));
|
|
65
65
|
else if (sender) qb = qb.where("sender", "=", sender);
|
|
66
66
|
else if (receiver) qb = qb.where("receiver", "=", receiver);
|
|
@@ -75,18 +75,68 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
75
75
|
const { validity } = validityFilter;
|
|
76
76
|
if (validity === "VALID") qb = qb.where("valid", "=", 1);
|
|
77
77
|
else if (validity === "INVALID") qb = qb.where("valid", "=", 0);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
const txRefTypeMap = {
|
|
79
|
+
factories: "factory",
|
|
80
|
+
tokens: "token",
|
|
81
|
+
assets: "asset",
|
|
82
|
+
accounts: "account",
|
|
83
|
+
rollups: "rollup",
|
|
84
|
+
stakes: "stake",
|
|
85
|
+
delegations: "delegation",
|
|
86
|
+
tokenFactories: "tokenFactory"
|
|
87
|
+
};
|
|
88
|
+
const applyJsonArrayFilter = (column, values) => {
|
|
89
|
+
const refType = txRefTypeMap[column];
|
|
90
|
+
qb = qb.where("hash", "in", this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values));
|
|
91
|
+
};
|
|
92
|
+
if (factories.length) applyJsonArrayFilter("factories", factories);
|
|
93
|
+
if (assets.length) applyJsonArrayFilter("assets", assets);
|
|
94
|
+
if (tokens.length) applyJsonArrayFilter("tokens", tokens);
|
|
95
|
+
if (accounts.length) applyJsonArrayFilter("accounts", accounts);
|
|
96
|
+
if (rollups.length) applyJsonArrayFilter("rollups", rollups);
|
|
97
|
+
if (stakes.length) applyJsonArrayFilter("stakes", stakes);
|
|
98
|
+
if (delegations.length) applyJsonArrayFilter("delegations", delegations);
|
|
99
|
+
if (tokenFactories.length) applyJsonArrayFilter("tokenFactories", tokenFactories);
|
|
100
|
+
let total;
|
|
101
|
+
const activeJsonFilters = [];
|
|
102
|
+
if (factories.length) activeJsonFilters.push(["factory", factories]);
|
|
103
|
+
if (assets.length) activeJsonFilters.push(["asset", assets]);
|
|
104
|
+
if (tokens.length) activeJsonFilters.push(["token", tokens]);
|
|
105
|
+
if (accounts.length) activeJsonFilters.push(["account", accounts]);
|
|
106
|
+
if (rollups.length) activeJsonFilters.push(["rollup", rollups]);
|
|
107
|
+
if (stakes.length) activeJsonFilters.push(["stake", stakes]);
|
|
108
|
+
if (delegations.length) activeJsonFilters.push(["delegation", delegations]);
|
|
109
|
+
if (tokenFactories.length) activeJsonFilters.push(["tokenFactory", tokenFactories]);
|
|
110
|
+
const hasNonJsonFilters = !!(sender || receiver || types.length || txs.length || parsedStart || parsedEnd || validity);
|
|
111
|
+
if (isMutual) {
|
|
112
|
+
const [c1, c2] = await Promise.all([this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).where("receiver", "=", receiver).executeTakeFirst(), this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", receiver).where("receiver", "=", sender).executeTakeFirst()]);
|
|
113
|
+
total = (c1?.count ?? 0) + (c2?.count ?? 0);
|
|
114
|
+
} else if (!hasNonJsonFilters && activeJsonFilters.length === 1) {
|
|
115
|
+
const [refType, values] = activeJsonFilters[0];
|
|
116
|
+
total = (await this.db.selectFrom("tx_ref").select(this.db.fn.countAll().as("count")).where("type", "=", refType).where("value", "in", values).executeTakeFirst())?.count ?? 0;
|
|
117
|
+
} else if (sender && receiver && direction !== "ONE_WAY") {
|
|
118
|
+
const [senderCount, receiverCount] = await Promise.all([this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).executeTakeFirst(), this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("receiver", "=", receiver).executeTakeFirst()]);
|
|
119
|
+
total = (senderCount?.count ?? 0) + (receiverCount?.count ?? 0);
|
|
120
|
+
} else total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
87
121
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
88
|
-
|
|
89
|
-
|
|
122
|
+
let rawResults;
|
|
123
|
+
if (isMutual) {
|
|
124
|
+
const q1 = this.db.selectFrom("tx").selectAll().where("sender", "=", sender).where("receiver", "=", receiver);
|
|
125
|
+
const q2 = this.db.selectFrom("tx").selectAll().where("sender", "=", receiver).where("receiver", "=", sender);
|
|
126
|
+
rawResults = await q1.unionAll(q2).orderBy(pagination.order.field, orderDir).limit(pagination.size).offset(pagination.cursor).execute();
|
|
127
|
+
} else if (!hasNonJsonFilters && activeJsonFilters.length === 1 && pagination.order.field === "time") {
|
|
128
|
+
const [refType, values] = activeJsonFilters[0];
|
|
129
|
+
const hashes = (await this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values).orderBy("time", orderDir).limit(pagination.size).offset(pagination.cursor).execute()).map((r) => r.hash);
|
|
130
|
+
if (hashes.length > 0) {
|
|
131
|
+
const dataRows = await this.db.selectFrom("tx").selectAll().where("hash", "in", hashes).execute();
|
|
132
|
+
const hashOrder = new Map(hashes.map((h, i) => [h, i]));
|
|
133
|
+
rawResults = [...dataRows].sort((a, b) => (hashOrder.get(a.hash) ?? 0) - (hashOrder.get(b.hash) ?? 0));
|
|
134
|
+
} else rawResults = [];
|
|
135
|
+
} else {
|
|
136
|
+
qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
|
|
137
|
+
rawResults = await qb.execute();
|
|
138
|
+
}
|
|
139
|
+
let transactions = rawResults.map((row) => {
|
|
90
140
|
const tx = { ...row };
|
|
91
141
|
[
|
|
92
142
|
"receipts",
|
|
@@ -216,7 +266,9 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
216
266
|
};
|
|
217
267
|
}
|
|
218
268
|
/**
|
|
219
|
-
* List top accounts by token balance
|
|
269
|
+
* List top accounts by token balance.
|
|
270
|
+
* For tokenAddress filter: uses balance table JOIN for indexed lookup.
|
|
271
|
+
* Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
|
|
220
272
|
*/
|
|
221
273
|
async listTopAccounts(params = {}) {
|
|
222
274
|
const { paging, tokenAddress } = params;
|
|
@@ -234,12 +286,8 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
234
286
|
paging,
|
|
235
287
|
pagination
|
|
236
288
|
});
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
240
|
-
qb = qb.orderBy(pagination.order.field, orderDir);
|
|
241
|
-
}
|
|
242
|
-
let accounts = (await qb.execute()).map((row) => {
|
|
289
|
+
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
290
|
+
const parseAccount = (row) => {
|
|
243
291
|
const account = { ...row };
|
|
244
292
|
[
|
|
245
293
|
"stake",
|
|
@@ -255,31 +303,21 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
255
303
|
return token;
|
|
256
304
|
});
|
|
257
305
|
return account;
|
|
258
|
-
}
|
|
259
|
-
if (tokenAddress)
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const descending = pagination.order.type === "desc";
|
|
267
|
-
accounts.sort((a, b) => {
|
|
268
|
-
const tokensA = a.tokens;
|
|
269
|
-
const tokensB = b.tokens;
|
|
270
|
-
const tokenA = tokensA?.find((t) => t.address === tokenAddress);
|
|
271
|
-
const tokenB = tokensB?.find((t) => t.address === tokenAddress);
|
|
272
|
-
const balanceA = new _ocap_util.BN(tokenA ? tokenA.balance : "0");
|
|
273
|
-
const balanceB = new _ocap_util.BN(tokenB ? tokenB.balance : "0");
|
|
274
|
-
if (balanceB.gt(balanceA)) return descending ? 1 : -1;
|
|
275
|
-
if (balanceB.eq(balanceA)) return 0;
|
|
276
|
-
return descending ? -1 : 1;
|
|
277
|
-
});
|
|
306
|
+
};
|
|
307
|
+
if (tokenAddress) {
|
|
308
|
+
const total$1 = (await this.db.selectFrom("balance").select(this.db.fn.countAll().as("count")).where("tokenAddress", "=", tokenAddress).where("balance", "!=", "0").executeTakeFirst())?.count ?? 0;
|
|
309
|
+
const sortField$1 = pagination.order.field === "balance" ? "b.balanceNum" : `a.${pagination.order.field}`;
|
|
310
|
+
return {
|
|
311
|
+
accounts: (await this.db.selectFrom("balance as b").innerJoin("account as a", "a.address", "b.address").selectAll("a").where("b.tokenAddress", "=", tokenAddress).where("b.balance", "!=", "0").orderBy(sortField$1, orderDir).limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
|
|
312
|
+
paging: (0, _ocap_indexdb.formatNextPagination)(total$1, pagination)
|
|
313
|
+
};
|
|
278
314
|
}
|
|
279
|
-
|
|
280
|
-
|
|
315
|
+
let qb = this.db.selectFrom("account").selectAll();
|
|
316
|
+
const sortField = pagination.order.field === "balance" ? "balanceNum" : pagination.order.field;
|
|
317
|
+
qb = qb.orderBy(sortField, orderDir);
|
|
318
|
+
const total = (await this.db.selectFrom("account").select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
281
319
|
return {
|
|
282
|
-
accounts,
|
|
320
|
+
accounts: (await qb.limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
|
|
283
321
|
paging: (0, _ocap_indexdb.formatNextPagination)(total, pagination)
|
|
284
322
|
};
|
|
285
323
|
}
|
|
@@ -461,7 +499,10 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
461
499
|
if (parsedStart && parsedEnd) qb = qb.where("renaissanceTime", ">", parsedStart).where("renaissanceTime", "<=", parsedEnd);
|
|
462
500
|
else if (parsedStart) qb = qb.where("renaissanceTime", ">", parsedStart);
|
|
463
501
|
else if (parsedEnd) qb = qb.where("renaissanceTime", "<=", parsedEnd);
|
|
464
|
-
if (assets.length) qb = qb.where((eb) =>
|
|
502
|
+
if (assets.length) qb = qb.where((eb) => {
|
|
503
|
+
const conditions = assets.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("assets")}, ${`"${v}"`})`, ">", 0));
|
|
504
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
505
|
+
});
|
|
465
506
|
const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
466
507
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
|
467
508
|
qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
|
|
@@ -554,8 +595,14 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
|
|
|
554
595
|
if (parsedStart && parsedEnd) qb = qb.where("genesisTime", ">", parsedStart).where("genesisTime", "<=", parsedEnd);
|
|
555
596
|
else if (parsedStart) qb = qb.where("genesisTime", ">", parsedStart);
|
|
556
597
|
else if (parsedEnd) qb = qb.where("genesisTime", "<=", parsedEnd);
|
|
557
|
-
if (txs.length) qb = qb.where((eb) =>
|
|
558
|
-
|
|
598
|
+
if (txs.length) qb = qb.where((eb) => {
|
|
599
|
+
const conditions = txs.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("txs")}, ${`"${v}"`})`, ">", 0));
|
|
600
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
601
|
+
});
|
|
602
|
+
if (validators.length) qb = qb.where((eb) => {
|
|
603
|
+
const conditions = validators.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("validators")}, ${`"${v}"`})`, ">", 0));
|
|
604
|
+
return conditions.length === 1 ? conditions[0] : eb.or(conditions);
|
|
605
|
+
});
|
|
559
606
|
if (tokenAddress) qb = qb.where((eb) => eb(eb.fn("json_extract", [eb.ref("tokenInfo"), eb.val("$.address")]), "=", tokenAddress));
|
|
560
607
|
const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
|
|
561
608
|
const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
|
package/lib/db/base.d.cts
CHANGED
|
@@ -36,7 +36,9 @@ declare class SqliteBaseIndexDB extends BaseIndexDB implements IIndexDB {
|
|
|
36
36
|
*/
|
|
37
37
|
listAssets(params?: Partial<TRequestListAssets>): Promise<IListAssetsResult>;
|
|
38
38
|
/**
|
|
39
|
-
* List top accounts by token balance
|
|
39
|
+
* List top accounts by token balance.
|
|
40
|
+
* For tokenAddress filter: uses balance table JOIN for indexed lookup.
|
|
41
|
+
* Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
|
|
40
42
|
*/
|
|
41
43
|
listTopAccounts(params?: Partial<TRequestListTopAccounts>): Promise<IListAccountsResult>;
|
|
42
44
|
/**
|
package/lib/db/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
const require_kysely = require('../kysely.cjs');
|
|
3
3
|
const require_migrations_index = require('../migrations/index.cjs');
|
|
4
4
|
const require_table_base = require('../table/base.cjs');
|
|
5
|
+
const require_table_account = require('../table/account.cjs');
|
|
5
6
|
const require_table_transaction = require('../table/transaction.cjs');
|
|
6
7
|
const require_db_base = require('./base.cjs');
|
|
7
8
|
const require_package = require('../package.cjs');
|
|
@@ -97,7 +98,7 @@ var SqliteIndexDB = class extends require_db_base.default {
|
|
|
97
98
|
]
|
|
98
99
|
}
|
|
99
100
|
});
|
|
100
|
-
this.account = new
|
|
101
|
+
this.account = new require_table_account.default({
|
|
101
102
|
name: "account",
|
|
102
103
|
uniqIndex: "address",
|
|
103
104
|
db: this.db,
|
package/lib/interfaces.d.cts
CHANGED
|
@@ -20,6 +20,7 @@ interface Database {
|
|
|
20
20
|
tokenDistribution: TokenDistributionTable;
|
|
21
21
|
tokenFactory: TokenFactoryTable;
|
|
22
22
|
balance: BalanceTable;
|
|
23
|
+
tx_ref: TxRefTable;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Transaction table for indexed transactions
|
|
@@ -318,6 +319,16 @@ interface BalanceTable {
|
|
|
318
319
|
balanceNum: string | null;
|
|
319
320
|
data: string | null;
|
|
320
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Junction table for fast tx JSON array lookups.
|
|
324
|
+
* Normalizes the JSON array columns (factories, tokens, etc.) into indexed rows.
|
|
325
|
+
*/
|
|
326
|
+
interface TxRefTable {
|
|
327
|
+
hash: string;
|
|
328
|
+
type: string;
|
|
329
|
+
value: string;
|
|
330
|
+
time: string | null;
|
|
331
|
+
}
|
|
321
332
|
/**
|
|
322
333
|
* SQLite-specific operation context with optional transaction
|
|
323
334
|
*/
|
|
@@ -325,4 +336,4 @@ interface SqliteOperationContext {
|
|
|
325
336
|
txn?: Transaction<Database>;
|
|
326
337
|
}
|
|
327
338
|
//#endregion
|
|
328
|
-
export { AccountTable, AssetTable, BalanceTable, Database, DelegationTable, FactoryTable, RollupBlockTable, RollupTable, RollupValidatorTable, SqliteOperationContext, StakeTable, TokenDistributionTable, TokenFactoryTable, TokenTable, TxTable };
|
|
339
|
+
export { AccountTable, AssetTable, BalanceTable, Database, DelegationTable, FactoryTable, RollupBlockTable, RollupTable, RollupValidatorTable, SqliteOperationContext, StakeTable, TokenDistributionTable, TokenFactoryTable, TokenTable, TxRefTable, TxTable };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let kysely = require("kysely");
|
|
3
|
+
|
|
4
|
+
//#region src/migrations/002-composite-indexes.ts
|
|
5
|
+
var _002_composite_indexes_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
6
|
+
down: () => down,
|
|
7
|
+
up: () => up
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Add composite indexes for covering WHERE + ORDER BY query patterns.
|
|
11
|
+
* These eliminate filesort when filtering by one column and sorting by another.
|
|
12
|
+
*/
|
|
13
|
+
async function up(db) {
|
|
14
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_sender_time ON tx(sender, time DESC)`.execute(db);
|
|
15
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_receiver_time ON tx(receiver, time DESC)`.execute(db);
|
|
16
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_type_time ON tx(type, time DESC)`.execute(db);
|
|
17
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_valid_time ON tx(valid, time DESC)`.execute(db);
|
|
18
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_asset_owner_renaissance ON asset(owner, renaissanceTime DESC)`.execute(db);
|
|
19
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_asset_parent_renaissance ON asset(parent, renaissanceTime DESC)`.execute(db);
|
|
20
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_stake_sender_renaissance ON stake(sender, renaissanceTime DESC)`.execute(db);
|
|
21
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_stake_receiver_renaissance ON stake(receiver, renaissanceTime DESC)`.execute(db);
|
|
22
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_delegation_from_renaissance ON delegation(from_, renaissanceTime DESC)`.execute(db);
|
|
23
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_delegation_to_renaissance ON delegation(to_, renaissanceTime DESC)`.execute(db);
|
|
24
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_rollupblock_rollup_genesis ON rollupBlock(rollup, genesisTime DESC)`.execute(db);
|
|
25
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_rollupvalidator_rollup_genesis ON rollupValidator(rollup, genesisTime DESC)`.execute(db);
|
|
26
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_factory_owner_renaissance ON factory(owner, renaissanceTime DESC)`.execute(db);
|
|
27
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_token_issuer_renaissance ON token(issuer, renaissanceTime DESC)`.execute(db);
|
|
28
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tokenfactory_owner_renaissance ON tokenFactory(owner, renaissanceTime DESC)`.execute(db);
|
|
29
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_sender_receiver_time ON tx(sender, receiver, time DESC)`.execute(db);
|
|
30
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_balance_token_amount ON balance(tokenAddress, balanceNum DESC)`.execute(db);
|
|
31
|
+
await kysely.sql`CREATE TABLE IF NOT EXISTS tx_ref (hash TEXT NOT NULL, type TEXT NOT NULL, value TEXT NOT NULL, time TEXT)`.execute(db);
|
|
32
|
+
await kysely.sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_tx_ref_pk ON tx_ref(hash, type, value)`.execute(db);
|
|
33
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_ref_lookup ON tx_ref(type, value)`.execute(db);
|
|
34
|
+
await kysely.sql`CREATE INDEX IF NOT EXISTS idx_tx_ref_time_lookup ON tx_ref(type, value, time DESC)`.execute(db);
|
|
35
|
+
for (const [type, column] of [
|
|
36
|
+
["factory", "factories"],
|
|
37
|
+
["token", "tokens"],
|
|
38
|
+
["asset", "assets"],
|
|
39
|
+
["account", "accounts"],
|
|
40
|
+
["rollup", "rollups"],
|
|
41
|
+
["stake", "stakes"],
|
|
42
|
+
["delegation", "delegations"],
|
|
43
|
+
["tokenFactory", "tokenFactories"]
|
|
44
|
+
]) await kysely.sql.raw(`INSERT OR IGNORE INTO tx_ref (hash, type, value, time) SELECT tx.hash, '${type}', je.value, tx.time FROM tx, json_each(tx.${column}) AS je WHERE tx.${column} IS NOT NULL AND tx.${column} != '[]'`).execute(db);
|
|
45
|
+
}
|
|
46
|
+
async function down(db) {
|
|
47
|
+
for (const idx of [
|
|
48
|
+
"idx_tx_sender_time",
|
|
49
|
+
"idx_tx_receiver_time",
|
|
50
|
+
"idx_tx_type_time",
|
|
51
|
+
"idx_tx_valid_time",
|
|
52
|
+
"idx_asset_owner_renaissance",
|
|
53
|
+
"idx_asset_parent_renaissance",
|
|
54
|
+
"idx_stake_sender_renaissance",
|
|
55
|
+
"idx_stake_receiver_renaissance",
|
|
56
|
+
"idx_delegation_from_renaissance",
|
|
57
|
+
"idx_delegation_to_renaissance",
|
|
58
|
+
"idx_rollupblock_rollup_genesis",
|
|
59
|
+
"idx_rollupvalidator_rollup_genesis",
|
|
60
|
+
"idx_factory_owner_renaissance",
|
|
61
|
+
"idx_token_issuer_renaissance",
|
|
62
|
+
"idx_tokenfactory_owner_renaissance",
|
|
63
|
+
"idx_tx_sender_receiver_time",
|
|
64
|
+
"idx_balance_token_amount",
|
|
65
|
+
"idx_tx_ref_pk",
|
|
66
|
+
"idx_tx_ref_lookup",
|
|
67
|
+
"idx_tx_ref_time_lookup"
|
|
68
|
+
]) await kysely.sql.raw(`DROP INDEX IF EXISTS ${idx}`).execute(db);
|
|
69
|
+
await kysely.sql`DROP TABLE IF EXISTS tx_ref`.execute(db);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
Object.defineProperty(exports, '_002_composite_indexes_exports', {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
get: function () {
|
|
76
|
+
return _002_composite_indexes_exports;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
exports.down = down;
|
|
80
|
+
exports.up = up;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
|
|
3
|
+
//#region src/migrations/002-composite-indexes.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add composite indexes for covering WHERE + ORDER BY query patterns.
|
|
7
|
+
* These eliminate filesort when filtering by one column and sorting by another.
|
|
8
|
+
*/
|
|
9
|
+
declare function up(db: Kysely<unknown>): Promise<void>;
|
|
10
|
+
declare function down(db: Kysely<unknown>): Promise<void>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { down, up };
|
package/lib/migrations/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
2
|
const require_migrations_001_genesis = require('./001-genesis.cjs');
|
|
3
|
+
const require_migrations_002_composite_indexes = require('./002-composite-indexes.cjs');
|
|
3
4
|
let kysely = require("kysely");
|
|
4
5
|
|
|
5
6
|
//#region src/migrations/index.ts
|
|
@@ -9,7 +10,10 @@ let kysely = require("kysely");
|
|
|
9
10
|
*/
|
|
10
11
|
var InMemoryMigrationProvider = class {
|
|
11
12
|
async getMigrations() {
|
|
12
|
-
return {
|
|
13
|
+
return {
|
|
14
|
+
"001-genesis": require_migrations_001_genesis._001_genesis_exports,
|
|
15
|
+
"002-composite-indexes": require_migrations_002_composite_indexes._002_composite_indexes_exports
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
18
|
};
|
|
15
19
|
/**
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
3
|
+
const require_table_base = require('./base.cjs');
|
|
4
|
+
let kysely = require("kysely");
|
|
5
|
+
|
|
6
|
+
//#region src/table/account.ts
|
|
7
|
+
/**
|
|
8
|
+
* Account table for SQLite IndexDB
|
|
9
|
+
* Extends base table to maintain the balance junction table
|
|
10
|
+
* for fast listTopAccounts queries with tokenAddress filter.
|
|
11
|
+
*/
|
|
12
|
+
var AccountTable = class extends require_table_base.default {
|
|
13
|
+
/**
|
|
14
|
+
* Override _insert to sync balance table after inserting an account.
|
|
15
|
+
*/
|
|
16
|
+
async _insert(row) {
|
|
17
|
+
const result = await super._insert(row);
|
|
18
|
+
await this.syncBalance(row);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Override _update to sync balance table after updating an account.
|
|
23
|
+
*/
|
|
24
|
+
async _update(key, updates) {
|
|
25
|
+
const result = await super._update(key, updates);
|
|
26
|
+
await this.syncBalance(result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sync the balance table from account tokens.
|
|
31
|
+
* Deletes existing entries for this address, then inserts current token balances.
|
|
32
|
+
*/
|
|
33
|
+
async syncBalance(data) {
|
|
34
|
+
const address = data.address;
|
|
35
|
+
if (!address) return;
|
|
36
|
+
let tokens = data.tokens;
|
|
37
|
+
if (typeof tokens === "string") try {
|
|
38
|
+
tokens = JSON.parse(tokens);
|
|
39
|
+
} catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(tokens)) return;
|
|
43
|
+
await kysely.sql`DELETE FROM balance WHERE address = ${address}`.execute(this.db);
|
|
44
|
+
for (const t of tokens) if (t.address && t.balance && t.balance !== "0") {
|
|
45
|
+
const balanceNum = (t.balance.replace(/^0+/, "") || "0").padStart(40, "0");
|
|
46
|
+
await kysely.sql`INSERT INTO balance (address, tokenAddress, balance, balanceNum) VALUES (${address}, ${t.address}, ${t.balance}, ${balanceNum})`.execute(this.db);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Override _reset to also clear the balance table
|
|
51
|
+
*/
|
|
52
|
+
async _reset() {
|
|
53
|
+
await super._reset();
|
|
54
|
+
await this.db.deleteFrom("balance").execute();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var account_default = AccountTable;
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
exports.default = account_default;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import SqliteTable from "./base.cjs";
|
|
2
|
+
import { TIndexedAccountState } from "@ocap/types";
|
|
3
|
+
|
|
4
|
+
//#region src/table/account.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Account table for SQLite IndexDB
|
|
8
|
+
* Extends base table to maintain the balance junction table
|
|
9
|
+
* for fast listTopAccounts queries with tokenAddress filter.
|
|
10
|
+
*/
|
|
11
|
+
declare class AccountTable extends SqliteTable<TIndexedAccountState> {
|
|
12
|
+
/**
|
|
13
|
+
* Override _insert to sync balance table after inserting an account.
|
|
14
|
+
*/
|
|
15
|
+
_insert(row: TIndexedAccountState | Record<string, unknown>): Promise<TIndexedAccountState>;
|
|
16
|
+
/**
|
|
17
|
+
* Override _update to sync balance table after updating an account.
|
|
18
|
+
*/
|
|
19
|
+
_update(key: string | Record<string, unknown>, updates: Partial<TIndexedAccountState>): Promise<TIndexedAccountState>;
|
|
20
|
+
/**
|
|
21
|
+
* Sync the balance table from account tokens.
|
|
22
|
+
* Deletes existing entries for this address, then inserts current token balances.
|
|
23
|
+
*/
|
|
24
|
+
private syncBalance;
|
|
25
|
+
/**
|
|
26
|
+
* Override _reset to also clear the balance table
|
|
27
|
+
*/
|
|
28
|
+
_reset(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { AccountTable as default };
|
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
3
3
|
const require_table_base = require('./base.cjs');
|
|
4
|
+
let kysely = require("kysely");
|
|
4
5
|
let _ocap_indexdb = require("@ocap/indexdb");
|
|
5
6
|
|
|
6
7
|
//#region src/table/transaction.ts
|
|
8
|
+
const TX_REF_TYPES = [
|
|
9
|
+
["factory", "factories"],
|
|
10
|
+
["token", "tokens"],
|
|
11
|
+
["asset", "assets"],
|
|
12
|
+
["account", "accounts"],
|
|
13
|
+
["rollup", "rollups"],
|
|
14
|
+
["stake", "stakes"],
|
|
15
|
+
["delegation", "delegations"],
|
|
16
|
+
["tokenFactory", "tokenFactories"]
|
|
17
|
+
];
|
|
7
18
|
/**
|
|
8
19
|
* Transaction table for SQLite IndexDB
|
|
9
20
|
* Extends base table to call formatTxBeforeInsert on insert
|
|
21
|
+
* and maintain the tx_ref junction table for fast JSON array lookups
|
|
10
22
|
*/
|
|
11
23
|
var TransactionTable = class extends require_table_base.default {
|
|
12
24
|
/**
|
|
13
25
|
* Override _insert to format transaction before inserting
|
|
14
|
-
* This populates assets, tokens, factories, etc. from itxJson
|
|
26
|
+
* This populates assets, tokens, factories, etc. from itxJson,
|
|
27
|
+
* then inserts corresponding rows into tx_ref for indexed lookups.
|
|
15
28
|
*/
|
|
16
29
|
async _insert(row) {
|
|
17
30
|
const formatted = (0, _ocap_indexdb.formatTxBeforeInsert)(row);
|
|
18
|
-
|
|
31
|
+
const result = await super._insert(formatted);
|
|
32
|
+
const data = formatted;
|
|
33
|
+
const hash = data.hash;
|
|
34
|
+
const time = data.time || null;
|
|
35
|
+
for (const [type, field] of TX_REF_TYPES) {
|
|
36
|
+
const values = data[field];
|
|
37
|
+
if (!Array.isArray(values) || values.length === 0) continue;
|
|
38
|
+
for (const v of values) if (typeof v === "string" && v) await kysely.sql`INSERT OR IGNORE INTO tx_ref (hash, type, value, time) VALUES (${hash}, ${type}, ${v}, ${time})`.execute(this.db);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Override _reset to also clear the tx_ref junction table
|
|
44
|
+
*/
|
|
45
|
+
async _reset() {
|
|
46
|
+
await super._reset();
|
|
47
|
+
await this.db.deleteFrom("tx_ref").execute();
|
|
19
48
|
}
|
|
20
49
|
};
|
|
21
50
|
var transaction_default = TransactionTable;
|
|
@@ -6,13 +6,19 @@ import { TIndexedTransaction } from "@ocap/types";
|
|
|
6
6
|
/**
|
|
7
7
|
* Transaction table for SQLite IndexDB
|
|
8
8
|
* Extends base table to call formatTxBeforeInsert on insert
|
|
9
|
+
* and maintain the tx_ref junction table for fast JSON array lookups
|
|
9
10
|
*/
|
|
10
11
|
declare class TransactionTable extends SqliteTable<TIndexedTransaction> {
|
|
11
12
|
/**
|
|
12
13
|
* Override _insert to format transaction before inserting
|
|
13
|
-
* This populates assets, tokens, factories, etc. from itxJson
|
|
14
|
+
* This populates assets, tokens, factories, etc. from itxJson,
|
|
15
|
+
* then inserts corresponding rows into tx_ref for indexed lookups.
|
|
14
16
|
*/
|
|
15
17
|
_insert(row: TIndexedTransaction | Record<string, unknown>): Promise<TIndexedTransaction>;
|
|
18
|
+
/**
|
|
19
|
+
* Override _reset to also clear the tx_ref junction table
|
|
20
|
+
*/
|
|
21
|
+
_reset(): Promise<void>;
|
|
16
22
|
}
|
|
17
23
|
//#endregion
|
|
18
24
|
export { TransactionTable as default };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ocap/indexdb-sqlite",
|
|
3
3
|
"description": "OCAP indexdb adapter that uses SQLite as backend",
|
|
4
|
-
"version": "1.29.
|
|
4
|
+
"version": "1.29.10",
|
|
5
5
|
"author": "wangshijun <shijun@arcblock.io> (https://www.arcblock.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/ArcBlock/blockchain/issues",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"wangshijun <shijun@arcblock.io> (https://www.arcblock.io)"
|
|
15
15
|
],
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@ocap/indexdb-test": "1.29.
|
|
17
|
+
"@ocap/indexdb-test": "1.29.10"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://github.com/ArcBlock/blockchain/tree/master/indexdb/sqlite",
|
|
20
20
|
"keywords": [
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"clean": "rm -rf lib esm"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@arcblock/did": "1.29.
|
|
67
|
-
"@ocap/indexdb": "1.29.
|
|
68
|
-
"@ocap/types": "1.29.
|
|
69
|
-
"@ocap/util": "1.29.
|
|
66
|
+
"@arcblock/did": "1.29.10",
|
|
67
|
+
"@ocap/indexdb": "1.29.10",
|
|
68
|
+
"@ocap/types": "1.29.10",
|
|
69
|
+
"@ocap/util": "1.29.10",
|
|
70
70
|
"debug": "^4.4.3",
|
|
71
71
|
"better-sqlite3": "^11.8.1",
|
|
72
72
|
"kysely": "^0.27.0",
|