@ocap/indexdb-sqlite 1.29.9 → 1.29.11

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 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,9 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
55
55
  includeItxData
56
56
  });
57
57
  let qb = this.db.selectFrom("tx").selectAll();
58
- if (sender && receiver) if (direction === "MUTUAL") qb = qb.where((eb) => eb.or([eb.and([eb("sender", "=", sender), eb("receiver", "=", receiver)]), eb.and([eb("sender", "=", receiver), eb("receiver", "=", sender)])]));
59
- else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
58
+ const isMutual = !!(sender && receiver && direction === "MUTUAL");
59
+ const isUnion = !!(sender && receiver && direction !== "MUTUAL" && direction !== "ONE_WAY");
60
+ if (sender && receiver) if (direction === "MUTUAL") {} else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
60
61
  else qb = qb.where((eb) => eb.or([eb("sender", "=", sender), eb("receiver", "=", receiver)]));
61
62
  else if (sender) qb = qb.where("sender", "=", sender);
62
63
  else if (receiver) qb = qb.where("receiver", "=", receiver);
@@ -71,18 +72,98 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
71
72
  const { validity } = validityFilter;
72
73
  if (validity === "VALID") qb = qb.where("valid", "=", 1);
73
74
  else if (validity === "INVALID") qb = qb.where("valid", "=", 0);
74
- if (factories.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("factories")]).as("je")).select("je.value").where("je.value", "in", factories)));
75
- if (assets.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("assets")]).as("je")).select("je.value").where("je.value", "in", assets)));
76
- if (tokens.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("tokens")]).as("je")).select("je.value").where("je.value", "in", tokens)));
77
- if (accounts.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("accounts")]).as("je")).select("je.value").where("je.value", "in", accounts)));
78
- if (rollups.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("rollups")]).as("je")).select("je.value").where("je.value", "in", rollups)));
79
- if (stakes.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("stakes")]).as("je")).select("je.value").where("je.value", "in", stakes)));
80
- if (delegations.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("delegations")]).as("je")).select("je.value").where("je.value", "in", delegations)));
81
- if (tokenFactories.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("tokenFactories")]).as("je")).select("je.value").where("je.value", "in", tokenFactories)));
82
- const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
75
+ const txRefTypeMap = {
76
+ factories: "factory",
77
+ tokens: "token",
78
+ assets: "asset",
79
+ accounts: "account",
80
+ rollups: "rollup",
81
+ stakes: "stake",
82
+ delegations: "delegation",
83
+ tokenFactories: "tokenFactory"
84
+ };
85
+ const applyJsonArrayFilter = (column, values) => {
86
+ const refType = txRefTypeMap[column];
87
+ qb = qb.where("hash", "in", this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values));
88
+ };
89
+ if (factories.length) applyJsonArrayFilter("factories", factories);
90
+ if (assets.length) applyJsonArrayFilter("assets", assets);
91
+ if (tokens.length) applyJsonArrayFilter("tokens", tokens);
92
+ if (accounts.length) applyJsonArrayFilter("accounts", accounts);
93
+ if (rollups.length) applyJsonArrayFilter("rollups", rollups);
94
+ if (stakes.length) applyJsonArrayFilter("stakes", stakes);
95
+ if (delegations.length) applyJsonArrayFilter("delegations", delegations);
96
+ if (tokenFactories.length) applyJsonArrayFilter("tokenFactories", tokenFactories);
97
+ let total;
98
+ const activeJsonFilters = [];
99
+ if (factories.length) activeJsonFilters.push(["factory", factories]);
100
+ if (assets.length) activeJsonFilters.push(["asset", assets]);
101
+ if (tokens.length) activeJsonFilters.push(["token", tokens]);
102
+ if (accounts.length) activeJsonFilters.push(["account", accounts]);
103
+ if (rollups.length) activeJsonFilters.push(["rollup", rollups]);
104
+ if (stakes.length) activeJsonFilters.push(["stake", stakes]);
105
+ if (delegations.length) activeJsonFilters.push(["delegation", delegations]);
106
+ if (tokenFactories.length) activeJsonFilters.push(["tokenFactory", tokenFactories]);
107
+ const hasNonJsonFilters = !!(sender || receiver || types.length || txs.length || parsedStart || parsedEnd || validity);
108
+ const isUnionOnly = isUnion && !types.length && !txs.length && !parsedStart && !parsedEnd && !validity && activeJsonFilters.length === 0;
109
+ const isMultiTypeOnly = types.length > 1 && !sender && !receiver && !txs.length && !parsedStart && !parsedEnd && !validity && activeJsonFilters.length === 0;
110
+ if (isMutual) {
111
+ 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()]);
112
+ total = (c1?.count ?? 0) + (c2?.count ?? 0);
113
+ } else if (!hasNonJsonFilters && activeJsonFilters.length === 1) {
114
+ const [refType, values] = activeJsonFilters[0];
115
+ total = (await this.db.selectFrom("tx_ref").select(this.db.fn.countAll().as("count")).where("type", "=", refType).where("value", "in", values).executeTakeFirst())?.count ?? 0;
116
+ } else if (isUnionOnly) {
117
+ const [senderCount, receiverCount, overlapCount] = await Promise.all([
118
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).executeTakeFirst(),
119
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("receiver", "=", receiver).executeTakeFirst(),
120
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).where("receiver", "=", receiver).executeTakeFirst()
121
+ ]);
122
+ total = (senderCount?.count ?? 0) + (receiverCount?.count ?? 0) - (overlapCount?.count ?? 0);
123
+ } else if (isMultiTypeOnly) total = (await Promise.all(types.map((t) => this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("type", "=", t).executeTakeFirst()))).reduce((sum, c) => sum + (c?.count ?? 0), 0);
124
+ else if (!hasNonJsonFilters && activeJsonFilters.length === 0) total = (await sql`SELECT max(rowid) as c FROM tx`.execute(this.db)).rows[0]?.c ?? 0;
125
+ else total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
83
126
  const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
84
- qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
85
- let transactions = (await qb.execute()).map((row) => {
127
+ let rawResults;
128
+ if (isMutual) {
129
+ const q1 = this.db.selectFrom("tx").selectAll().where("sender", "=", sender).where("receiver", "=", receiver);
130
+ const q2 = this.db.selectFrom("tx").selectAll().where("sender", "=", receiver).where("receiver", "=", sender);
131
+ rawResults = await q1.unionAll(q2).orderBy(pagination.order.field, orderDir).limit(pagination.size).offset(pagination.cursor).execute();
132
+ } else if (isUnionOnly) {
133
+ const topN = pagination.cursor + pagination.size;
134
+ const dirRaw = sql.raw(orderDir === "desc" ? "DESC" : "ASC");
135
+ const colRef = sql.ref(pagination.order.field);
136
+ rawResults = (await sql`
137
+ SELECT * FROM (
138
+ SELECT * FROM (SELECT * FROM tx WHERE sender = ${sender} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})
139
+ UNION
140
+ SELECT * FROM (SELECT * FROM tx WHERE receiver = ${receiver} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})
141
+ ) ORDER BY ${colRef} ${dirRaw}
142
+ LIMIT ${pagination.size} OFFSET ${pagination.cursor}
143
+ `.execute(this.db)).rows;
144
+ } else if (isMultiTypeOnly) {
145
+ const topN = pagination.cursor + pagination.size;
146
+ const dirRaw = sql.raw(orderDir === "desc" ? "DESC" : "ASC");
147
+ const colRef = sql.ref(pagination.order.field);
148
+ let unionQuery = sql`SELECT * FROM (SELECT * FROM tx WHERE type = ${types[0]} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})`;
149
+ for (let i = 1; i < types.length; i++) unionQuery = sql`${unionQuery} UNION ALL SELECT * FROM (SELECT * FROM tx WHERE type = ${types[i]} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})`;
150
+ rawResults = (await sql`
151
+ SELECT * FROM (${unionQuery}) ORDER BY ${colRef} ${dirRaw}
152
+ LIMIT ${pagination.size} OFFSET ${pagination.cursor}
153
+ `.execute(this.db)).rows;
154
+ } else if (!hasNonJsonFilters && activeJsonFilters.length === 1 && pagination.order.field === "time") {
155
+ const [refType, values] = activeJsonFilters[0];
156
+ 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);
157
+ if (hashes.length > 0) {
158
+ const dataRows = await this.db.selectFrom("tx").selectAll().where("hash", "in", hashes).execute();
159
+ const hashOrder = new Map(hashes.map((h, i) => [h, i]));
160
+ rawResults = [...dataRows].sort((a, b) => (hashOrder.get(a.hash) ?? 0) - (hashOrder.get(b.hash) ?? 0));
161
+ } else rawResults = [];
162
+ } else {
163
+ qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
164
+ rawResults = await qb.execute();
165
+ }
166
+ let transactions = rawResults.map((row) => {
86
167
  const tx = { ...row };
87
168
  [
88
169
  "receipts",
@@ -212,7 +293,9 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
212
293
  };
213
294
  }
214
295
  /**
215
- * List top accounts by token balance
296
+ * List top accounts by token balance.
297
+ * For tokenAddress filter: uses balance table JOIN for indexed lookup.
298
+ * Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
216
299
  */
217
300
  async listTopAccounts(params = {}) {
218
301
  const { paging, tokenAddress } = params;
@@ -230,12 +313,8 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
230
313
  paging,
231
314
  pagination
232
315
  });
233
- let qb = this.db.selectFrom("account").selectAll();
234
- if (pagination.order.field !== "balance") {
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) => {
316
+ const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
317
+ const parseAccount = (row) => {
239
318
  const account = { ...row };
240
319
  [
241
320
  "stake",
@@ -251,31 +330,21 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
251
330
  return token;
252
331
  });
253
332
  return account;
254
- });
255
- if (tokenAddress) accounts = accounts.filter((acc) => {
256
- const tokens = acc.tokens;
257
- if (!Array.isArray(tokens)) return false;
258
- const owned = tokens.find((t) => t.address === tokenAddress);
259
- return owned ? owned.balance > "0" : false;
260
- });
261
- if (pagination.order.field === "balance" && tokenAddress) {
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
- });
333
+ };
334
+ if (tokenAddress) {
335
+ const total$1 = (await this.db.selectFrom("balance").select(this.db.fn.countAll().as("count")).where("tokenAddress", "=", tokenAddress).where("balance", "!=", "0").executeTakeFirst())?.count ?? 0;
336
+ const sortField$1 = pagination.order.field === "balance" ? "b.balanceNum" : `a.${pagination.order.field}`;
337
+ return {
338
+ 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),
339
+ paging: formatNextPagination(total$1, pagination)
340
+ };
274
341
  }
275
- const total = accounts.length;
276
- accounts = accounts.slice(pagination.cursor, pagination.cursor + pagination.size);
342
+ let qb = this.db.selectFrom("account").selectAll();
343
+ const sortField = pagination.order.field === "balance" ? "balanceNum" : pagination.order.field;
344
+ qb = qb.orderBy(sortField, orderDir);
345
+ const total = (await this.db.selectFrom("account").select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
277
346
  return {
278
- accounts,
347
+ accounts: (await qb.limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
279
348
  paging: formatNextPagination(total, pagination)
280
349
  };
281
350
  }
@@ -457,7 +526,10 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
457
526
  if (parsedStart && parsedEnd) qb = qb.where("renaissanceTime", ">", parsedStart).where("renaissanceTime", "<=", parsedEnd);
458
527
  else if (parsedStart) qb = qb.where("renaissanceTime", ">", parsedStart);
459
528
  else if (parsedEnd) qb = qb.where("renaissanceTime", "<=", parsedEnd);
460
- if (assets.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("assets")]).as("je")).select("je.value").where("je.value", "in", assets)));
529
+ if (assets.length) qb = qb.where((eb) => {
530
+ const conditions = assets.map((v) => eb(sql`INSTR(${sql.ref("assets")}, ${`"${v}"`})`, ">", 0));
531
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
532
+ });
461
533
  const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
462
534
  const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
463
535
  qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
@@ -550,8 +622,14 @@ var SqliteBaseIndexDB = class extends BaseIndexDB {
550
622
  if (parsedStart && parsedEnd) qb = qb.where("genesisTime", ">", parsedStart).where("genesisTime", "<=", parsedEnd);
551
623
  else if (parsedStart) qb = qb.where("genesisTime", ">", parsedStart);
552
624
  else if (parsedEnd) qb = qb.where("genesisTime", "<=", parsedEnd);
553
- if (txs.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("txs")]).as("je")).select("je.value").where("je.value", "in", txs)));
554
- if (validators.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("validators")]).as("je")).select("je.value").where("je.value", "in", validators)));
625
+ if (txs.length) qb = qb.where((eb) => {
626
+ const conditions = txs.map((v) => eb(sql`INSTR(${sql.ref("txs")}, ${`"${v}"`})`, ">", 0));
627
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
628
+ });
629
+ if (validators.length) qb = qb.where((eb) => {
630
+ const conditions = validators.map((v) => eb(sql`INSTR(${sql.ref("validators")}, ${`"${v}"`})`, ">", 0));
631
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
632
+ });
555
633
  if (tokenAddress) qb = qb.where((eb) => eb(eb.fn("json_extract", [eb.ref("tokenInfo"), eb.val("$.address")]), "=", tokenAddress));
556
634
  const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
557
635
  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 base_default({
100
+ this.account = new account_default({
100
101
  name: "account",
101
102
  uniqIndex: "address",
102
103
  db: this.db,
@@ -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 };
@@ -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 { "001-genesis": _001_genesis_exports };
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
- return super._insert(formatted);
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,9 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
59
59
  includeItxData
60
60
  });
61
61
  let qb = this.db.selectFrom("tx").selectAll();
62
- if (sender && receiver) if (direction === "MUTUAL") qb = qb.where((eb) => eb.or([eb.and([eb("sender", "=", sender), eb("receiver", "=", receiver)]), eb.and([eb("sender", "=", receiver), eb("receiver", "=", sender)])]));
63
- else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
62
+ const isMutual = !!(sender && receiver && direction === "MUTUAL");
63
+ const isUnion = !!(sender && receiver && direction !== "MUTUAL" && direction !== "ONE_WAY");
64
+ if (sender && receiver) if (direction === "MUTUAL") {} else if (direction === "ONE_WAY") qb = qb.where("sender", "=", sender).where("receiver", "=", receiver);
64
65
  else qb = qb.where((eb) => eb.or([eb("sender", "=", sender), eb("receiver", "=", receiver)]));
65
66
  else if (sender) qb = qb.where("sender", "=", sender);
66
67
  else if (receiver) qb = qb.where("receiver", "=", receiver);
@@ -75,18 +76,98 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
75
76
  const { validity } = validityFilter;
76
77
  if (validity === "VALID") qb = qb.where("valid", "=", 1);
77
78
  else if (validity === "INVALID") qb = qb.where("valid", "=", 0);
78
- if (factories.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("factories")]).as("je")).select("je.value").where("je.value", "in", factories)));
79
- if (assets.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("assets")]).as("je")).select("je.value").where("je.value", "in", assets)));
80
- if (tokens.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("tokens")]).as("je")).select("je.value").where("je.value", "in", tokens)));
81
- if (accounts.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("accounts")]).as("je")).select("je.value").where("je.value", "in", accounts)));
82
- if (rollups.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("rollups")]).as("je")).select("je.value").where("je.value", "in", rollups)));
83
- if (stakes.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("stakes")]).as("je")).select("je.value").where("je.value", "in", stakes)));
84
- if (delegations.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("delegations")]).as("je")).select("je.value").where("je.value", "in", delegations)));
85
- if (tokenFactories.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("tokenFactories")]).as("je")).select("je.value").where("je.value", "in", tokenFactories)));
86
- const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
79
+ const txRefTypeMap = {
80
+ factories: "factory",
81
+ tokens: "token",
82
+ assets: "asset",
83
+ accounts: "account",
84
+ rollups: "rollup",
85
+ stakes: "stake",
86
+ delegations: "delegation",
87
+ tokenFactories: "tokenFactory"
88
+ };
89
+ const applyJsonArrayFilter = (column, values) => {
90
+ const refType = txRefTypeMap[column];
91
+ qb = qb.where("hash", "in", this.db.selectFrom("tx_ref").select("hash").where("type", "=", refType).where("value", "in", values));
92
+ };
93
+ if (factories.length) applyJsonArrayFilter("factories", factories);
94
+ if (assets.length) applyJsonArrayFilter("assets", assets);
95
+ if (tokens.length) applyJsonArrayFilter("tokens", tokens);
96
+ if (accounts.length) applyJsonArrayFilter("accounts", accounts);
97
+ if (rollups.length) applyJsonArrayFilter("rollups", rollups);
98
+ if (stakes.length) applyJsonArrayFilter("stakes", stakes);
99
+ if (delegations.length) applyJsonArrayFilter("delegations", delegations);
100
+ if (tokenFactories.length) applyJsonArrayFilter("tokenFactories", tokenFactories);
101
+ let total;
102
+ const activeJsonFilters = [];
103
+ if (factories.length) activeJsonFilters.push(["factory", factories]);
104
+ if (assets.length) activeJsonFilters.push(["asset", assets]);
105
+ if (tokens.length) activeJsonFilters.push(["token", tokens]);
106
+ if (accounts.length) activeJsonFilters.push(["account", accounts]);
107
+ if (rollups.length) activeJsonFilters.push(["rollup", rollups]);
108
+ if (stakes.length) activeJsonFilters.push(["stake", stakes]);
109
+ if (delegations.length) activeJsonFilters.push(["delegation", delegations]);
110
+ if (tokenFactories.length) activeJsonFilters.push(["tokenFactory", tokenFactories]);
111
+ const hasNonJsonFilters = !!(sender || receiver || types.length || txs.length || parsedStart || parsedEnd || validity);
112
+ const isUnionOnly = isUnion && !types.length && !txs.length && !parsedStart && !parsedEnd && !validity && activeJsonFilters.length === 0;
113
+ const isMultiTypeOnly = types.length > 1 && !sender && !receiver && !txs.length && !parsedStart && !parsedEnd && !validity && activeJsonFilters.length === 0;
114
+ if (isMutual) {
115
+ 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()]);
116
+ total = (c1?.count ?? 0) + (c2?.count ?? 0);
117
+ } else if (!hasNonJsonFilters && activeJsonFilters.length === 1) {
118
+ const [refType, values] = activeJsonFilters[0];
119
+ total = (await this.db.selectFrom("tx_ref").select(this.db.fn.countAll().as("count")).where("type", "=", refType).where("value", "in", values).executeTakeFirst())?.count ?? 0;
120
+ } else if (isUnionOnly) {
121
+ const [senderCount, receiverCount, overlapCount] = await Promise.all([
122
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).executeTakeFirst(),
123
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("receiver", "=", receiver).executeTakeFirst(),
124
+ this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("sender", "=", sender).where("receiver", "=", receiver).executeTakeFirst()
125
+ ]);
126
+ total = (senderCount?.count ?? 0) + (receiverCount?.count ?? 0) - (overlapCount?.count ?? 0);
127
+ } else if (isMultiTypeOnly) total = (await Promise.all(types.map((t) => this.db.selectFrom("tx").select(this.db.fn.countAll().as("count")).where("type", "=", t).executeTakeFirst()))).reduce((sum, c) => sum + (c?.count ?? 0), 0);
128
+ else if (!hasNonJsonFilters && activeJsonFilters.length === 0) total = (await kysely.sql`SELECT max(rowid) as c FROM tx`.execute(this.db)).rows[0]?.c ?? 0;
129
+ else total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
87
130
  const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
88
- qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
89
- let transactions = (await qb.execute()).map((row) => {
131
+ let rawResults;
132
+ if (isMutual) {
133
+ const q1 = this.db.selectFrom("tx").selectAll().where("sender", "=", sender).where("receiver", "=", receiver);
134
+ const q2 = this.db.selectFrom("tx").selectAll().where("sender", "=", receiver).where("receiver", "=", sender);
135
+ rawResults = await q1.unionAll(q2).orderBy(pagination.order.field, orderDir).limit(pagination.size).offset(pagination.cursor).execute();
136
+ } else if (isUnionOnly) {
137
+ const topN = pagination.cursor + pagination.size;
138
+ const dirRaw = kysely.sql.raw(orderDir === "desc" ? "DESC" : "ASC");
139
+ const colRef = kysely.sql.ref(pagination.order.field);
140
+ rawResults = (await kysely.sql`
141
+ SELECT * FROM (
142
+ SELECT * FROM (SELECT * FROM tx WHERE sender = ${sender} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})
143
+ UNION
144
+ SELECT * FROM (SELECT * FROM tx WHERE receiver = ${receiver} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})
145
+ ) ORDER BY ${colRef} ${dirRaw}
146
+ LIMIT ${pagination.size} OFFSET ${pagination.cursor}
147
+ `.execute(this.db)).rows;
148
+ } else if (isMultiTypeOnly) {
149
+ const topN = pagination.cursor + pagination.size;
150
+ const dirRaw = kysely.sql.raw(orderDir === "desc" ? "DESC" : "ASC");
151
+ const colRef = kysely.sql.ref(pagination.order.field);
152
+ let unionQuery = kysely.sql`SELECT * FROM (SELECT * FROM tx WHERE type = ${types[0]} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})`;
153
+ for (let i = 1; i < types.length; i++) unionQuery = kysely.sql`${unionQuery} UNION ALL SELECT * FROM (SELECT * FROM tx WHERE type = ${types[i]} ORDER BY ${colRef} ${dirRaw} LIMIT ${topN})`;
154
+ rawResults = (await kysely.sql`
155
+ SELECT * FROM (${unionQuery}) ORDER BY ${colRef} ${dirRaw}
156
+ LIMIT ${pagination.size} OFFSET ${pagination.cursor}
157
+ `.execute(this.db)).rows;
158
+ } else if (!hasNonJsonFilters && activeJsonFilters.length === 1 && pagination.order.field === "time") {
159
+ const [refType, values] = activeJsonFilters[0];
160
+ 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);
161
+ if (hashes.length > 0) {
162
+ const dataRows = await this.db.selectFrom("tx").selectAll().where("hash", "in", hashes).execute();
163
+ const hashOrder = new Map(hashes.map((h, i) => [h, i]));
164
+ rawResults = [...dataRows].sort((a, b) => (hashOrder.get(a.hash) ?? 0) - (hashOrder.get(b.hash) ?? 0));
165
+ } else rawResults = [];
166
+ } else {
167
+ qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
168
+ rawResults = await qb.execute();
169
+ }
170
+ let transactions = rawResults.map((row) => {
90
171
  const tx = { ...row };
91
172
  [
92
173
  "receipts",
@@ -216,7 +297,9 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
216
297
  };
217
298
  }
218
299
  /**
219
- * List top accounts by token balance
300
+ * List top accounts by token balance.
301
+ * For tokenAddress filter: uses balance table JOIN for indexed lookup.
302
+ * Without tokenAddress: uses SQL ORDER BY + LIMIT directly.
220
303
  */
221
304
  async listTopAccounts(params = {}) {
222
305
  const { paging, tokenAddress } = params;
@@ -234,12 +317,8 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
234
317
  paging,
235
318
  pagination
236
319
  });
237
- let qb = this.db.selectFrom("account").selectAll();
238
- if (pagination.order.field !== "balance") {
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) => {
320
+ const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
321
+ const parseAccount = (row) => {
243
322
  const account = { ...row };
244
323
  [
245
324
  "stake",
@@ -255,31 +334,21 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
255
334
  return token;
256
335
  });
257
336
  return account;
258
- });
259
- if (tokenAddress) accounts = accounts.filter((acc) => {
260
- const tokens = acc.tokens;
261
- if (!Array.isArray(tokens)) return false;
262
- const owned = tokens.find((t) => t.address === tokenAddress);
263
- return owned ? owned.balance > "0" : false;
264
- });
265
- if (pagination.order.field === "balance" && tokenAddress) {
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
- });
337
+ };
338
+ if (tokenAddress) {
339
+ const total$1 = (await this.db.selectFrom("balance").select(this.db.fn.countAll().as("count")).where("tokenAddress", "=", tokenAddress).where("balance", "!=", "0").executeTakeFirst())?.count ?? 0;
340
+ const sortField$1 = pagination.order.field === "balance" ? "b.balanceNum" : `a.${pagination.order.field}`;
341
+ return {
342
+ 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),
343
+ paging: (0, _ocap_indexdb.formatNextPagination)(total$1, pagination)
344
+ };
278
345
  }
279
- const total = accounts.length;
280
- accounts = accounts.slice(pagination.cursor, pagination.cursor + pagination.size);
346
+ let qb = this.db.selectFrom("account").selectAll();
347
+ const sortField = pagination.order.field === "balance" ? "balanceNum" : pagination.order.field;
348
+ qb = qb.orderBy(sortField, orderDir);
349
+ const total = (await this.db.selectFrom("account").select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
281
350
  return {
282
- accounts,
351
+ accounts: (await qb.limit(pagination.size).offset(pagination.cursor).execute()).map(parseAccount),
283
352
  paging: (0, _ocap_indexdb.formatNextPagination)(total, pagination)
284
353
  };
285
354
  }
@@ -461,7 +530,10 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
461
530
  if (parsedStart && parsedEnd) qb = qb.where("renaissanceTime", ">", parsedStart).where("renaissanceTime", "<=", parsedEnd);
462
531
  else if (parsedStart) qb = qb.where("renaissanceTime", ">", parsedStart);
463
532
  else if (parsedEnd) qb = qb.where("renaissanceTime", "<=", parsedEnd);
464
- if (assets.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("assets")]).as("je")).select("je.value").where("je.value", "in", assets)));
533
+ if (assets.length) qb = qb.where((eb) => {
534
+ const conditions = assets.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("assets")}, ${`"${v}"`})`, ">", 0));
535
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
536
+ });
465
537
  const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
466
538
  const orderDir = pagination.order.type === "desc" ? "desc" : "asc";
467
539
  qb = qb.orderBy(pagination.order.field, orderDir).offset(pagination.cursor).limit(pagination.size);
@@ -554,8 +626,14 @@ var SqliteBaseIndexDB = class extends _ocap_indexdb.BaseIndexDB {
554
626
  if (parsedStart && parsedEnd) qb = qb.where("genesisTime", ">", parsedStart).where("genesisTime", "<=", parsedEnd);
555
627
  else if (parsedStart) qb = qb.where("genesisTime", ">", parsedStart);
556
628
  else if (parsedEnd) qb = qb.where("genesisTime", "<=", parsedEnd);
557
- if (txs.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("txs")]).as("je")).select("je.value").where("je.value", "in", txs)));
558
- if (validators.length) qb = qb.where((eb) => eb.exists(eb.selectFrom(eb.fn("json_each", [eb.ref("validators")]).as("je")).select("je.value").where("je.value", "in", validators)));
629
+ if (txs.length) qb = qb.where((eb) => {
630
+ const conditions = txs.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("txs")}, ${`"${v}"`})`, ">", 0));
631
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
632
+ });
633
+ if (validators.length) qb = qb.where((eb) => {
634
+ const conditions = validators.map((v) => eb(kysely.sql`INSTR(${kysely.sql.ref("validators")}, ${`"${v}"`})`, ">", 0));
635
+ return conditions.length === 1 ? conditions[0] : eb.or(conditions);
636
+ });
559
637
  if (tokenAddress) qb = qb.where((eb) => eb(eb.fn("json_extract", [eb.ref("tokenInfo"), eb.val("$.address")]), "=", tokenAddress));
560
638
  const total = (await qb.clearSelect().select(this.db.fn.countAll().as("count")).executeTakeFirst())?.count ?? 0;
561
639
  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 require_table_base.default({
101
+ this.account = new require_table_account.default({
101
102
  name: "account",
102
103
  uniqIndex: "address",
103
104
  db: this.db,
@@ -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 };
@@ -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 { "001-genesis": require_migrations_001_genesis._001_genesis_exports };
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
- return super._insert(formatted);
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.9",
4
+ "version": "1.29.11",
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.9"
17
+ "@ocap/indexdb-test": "1.29.11"
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.9",
67
- "@ocap/indexdb": "1.29.9",
68
- "@ocap/types": "1.29.9",
69
- "@ocap/util": "1.29.9",
66
+ "@arcblock/did": "1.29.11",
67
+ "@ocap/indexdb": "1.29.11",
68
+ "@ocap/types": "1.29.11",
69
+ "@ocap/util": "1.29.11",
70
70
  "debug": "^4.4.3",
71
71
  "better-sqlite3": "^11.8.1",
72
72
  "kysely": "^0.27.0",