@ocap/statedb-sqlite 1.29.5

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.
Files changed (50) hide show
  1. package/README.md +66 -0
  2. package/esm/_virtual/rolldown_runtime.mjs +21 -0
  3. package/esm/db.d.mts +92 -0
  4. package/esm/db.mjs +257 -0
  5. package/esm/index.d.mts +4 -0
  6. package/esm/index.mjs +7 -0
  7. package/esm/interfaces.d.mts +201 -0
  8. package/esm/interfaces.mjs +1 -0
  9. package/esm/kysely.d.mts +43 -0
  10. package/esm/kysely.mjs +62 -0
  11. package/esm/migrations/001-genesis.d.mts +14 -0
  12. package/esm/migrations/001-genesis.mjs +52 -0
  13. package/esm/migrations/index.d.mts +24 -0
  14. package/esm/migrations/index.mjs +60 -0
  15. package/esm/package.mjs +70 -0
  16. package/esm/table/account.d.mts +40 -0
  17. package/esm/table/account.mjs +99 -0
  18. package/esm/table/balance.d.mts +39 -0
  19. package/esm/table/balance.mjs +69 -0
  20. package/esm/table/base.d.mts +84 -0
  21. package/esm/table/base.mjs +217 -0
  22. package/esm/table/rollup.d.mts +22 -0
  23. package/esm/table/rollup.mjs +44 -0
  24. package/esm/table/token.d.mts +23 -0
  25. package/esm/table/token.mjs +42 -0
  26. package/lib/_virtual/rolldown_runtime.cjs +43 -0
  27. package/lib/db.cjs +259 -0
  28. package/lib/db.d.cts +92 -0
  29. package/lib/index.cjs +9 -0
  30. package/lib/index.d.cts +4 -0
  31. package/lib/interfaces.cjs +0 -0
  32. package/lib/interfaces.d.cts +201 -0
  33. package/lib/kysely.cjs +63 -0
  34. package/lib/kysely.d.cts +43 -0
  35. package/lib/migrations/001-genesis.cjs +59 -0
  36. package/lib/migrations/001-genesis.d.cts +14 -0
  37. package/lib/migrations/index.cjs +62 -0
  38. package/lib/migrations/index.d.cts +24 -0
  39. package/lib/package.cjs +76 -0
  40. package/lib/table/account.cjs +101 -0
  41. package/lib/table/account.d.cts +40 -0
  42. package/lib/table/balance.cjs +71 -0
  43. package/lib/table/balance.d.cts +39 -0
  44. package/lib/table/base.cjs +221 -0
  45. package/lib/table/base.d.cts +84 -0
  46. package/lib/table/rollup.cjs +45 -0
  47. package/lib/table/rollup.d.cts +22 -0
  48. package/lib/table/token.cjs +43 -0
  49. package/lib/table/token.d.cts +23 -0
  50. package/package.json +75 -0
package/esm/kysely.mjs ADDED
@@ -0,0 +1,62 @@
1
+ import { __require } from "./_virtual/rolldown_runtime.mjs";
2
+ import { Kysely, SqliteDialect } from "kysely";
3
+
4
+ //#region src/kysely.ts
5
+ function isBunRuntime() {
6
+ return typeof globalThis.Bun !== "undefined";
7
+ }
8
+ /**
9
+ * Create a Kysely instance with SQLite dialect
10
+ * Automatically uses kysely-bun-sqlite in Bun or better-sqlite3 in Node.js
11
+ *
12
+ * @param config - SQLite configuration
13
+ * @returns Kysely instance configured for the database
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In-memory database for testing
18
+ * const db = createKysely({ filename: ':memory:' });
19
+ *
20
+ * // File-based database for production
21
+ * const db = createKysely({ filename: './data/state.sqlite' });
22
+ * ```
23
+ */
24
+ function createKysely(config) {
25
+ if (isBunRuntime()) {
26
+ const { BunSqliteDialect } = import.meta.require("kysely-bun-sqlite");
27
+ const { Database: BunDatabase } = import.meta.require("bun:sqlite");
28
+ const sqlite$1 = new BunDatabase(config.filename, {
29
+ create: config.create !== false,
30
+ readonly: config.readonly === true
31
+ });
32
+ if (config.walMode !== false && !config.readonly && config.filename !== ":memory:") {
33
+ sqlite$1.exec("PRAGMA journal_mode = WAL");
34
+ sqlite$1.exec("PRAGMA synchronous = NORMAL");
35
+ }
36
+ sqlite$1.exec("PRAGMA foreign_keys = ON");
37
+ sqlite$1.exec("PRAGMA busy_timeout = 5000");
38
+ return new Kysely({ dialect: new BunSqliteDialect({ database: sqlite$1 }) });
39
+ }
40
+ const sqlite = new (__require("better-sqlite3"))(config.filename, {
41
+ readonly: config.readonly === true,
42
+ fileMustExist: config.create === false
43
+ });
44
+ if (config.walMode !== false && !config.readonly && config.filename !== ":memory:") {
45
+ sqlite.pragma("journal_mode = WAL");
46
+ sqlite.pragma("synchronous = NORMAL");
47
+ }
48
+ sqlite.pragma("foreign_keys = ON");
49
+ sqlite.pragma("busy_timeout = 5000");
50
+ return new Kysely({ dialect: new SqliteDialect({ database: sqlite }) });
51
+ }
52
+ /**
53
+ * Get the underlying SQLite database instance from Kysely
54
+ * Useful for executing raw SQL or pragmas
55
+ * Returns either bun:sqlite Database or better-sqlite3 Database depending on runtime
56
+ */
57
+ function getRawDatabase(db) {
58
+ return db.getExecutor().adapter.db;
59
+ }
60
+
61
+ //#endregion
62
+ export { createKysely, getRawDatabase };
@@ -0,0 +1,14 @@
1
+ import { Kysely } from "kysely";
2
+
3
+ //#region src/migrations/001-genesis.d.ts
4
+
5
+ /**
6
+ * Genesis migration - creates all StateDB tables
7
+ */
8
+ declare function up(db: Kysely<unknown>): Promise<void>;
9
+ /**
10
+ * Rollback migration - drops all tables
11
+ */
12
+ declare function down(db: Kysely<unknown>): Promise<void>;
13
+ //#endregion
14
+ export { down, up };
@@ -0,0 +1,52 @@
1
+ import { __exportAll } from "../_virtual/rolldown_runtime.mjs";
2
+ import { sql } from "kysely";
3
+
4
+ //#region src/migrations/001-genesis.ts
5
+ var _001_genesis_exports = /* @__PURE__ */ __exportAll({
6
+ down: () => down,
7
+ up: () => up
8
+ });
9
+ /**
10
+ * Genesis migration - creates all StateDB tables
11
+ */
12
+ async function up(db) {
13
+ await db.schema.createTable("account").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("balance", "text", (col) => col.notNull().defaultTo("0")).addColumn("gasBalance", "text", (col) => col.defaultTo("0")).addColumn("moniker", "text").addColumn("pk", "text").addColumn("nonce", "integer", (col) => col.defaultTo(0)).addColumn("numTxs", "integer", (col) => col.defaultTo(0)).addColumn("numAssets", "integer", (col) => col.defaultTo(0)).addColumn("issuer", "text").addColumn("migratedTo", "text").addColumn("migratedFrom", "text").addColumn("stake", "text").addColumn("tokens", "text").addColumn("data", "text").addColumn("context", "text").addColumn("poke", "text").addColumn("_retrievedAt", "text").execute();
14
+ await db.schema.createTable("balance").ifNotExists().addColumn("address", "text", (col) => col.notNull()).addColumn("tokenAddress", "text", (col) => col.notNull()).addColumn("balance", "text", (col) => col.notNull().defaultTo("0")).addColumn("context", "text").addPrimaryKeyConstraint("balance_pk", ["address", "tokenAddress"]).execute();
15
+ await db.schema.createTable("chain").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("chainId", "text").addColumn("version", "text").addColumn("accounts", "text").addColumn("moderator", "text").addColumn("token", "text").addColumn("transaction", "text").addColumn("vaults", "text").addColumn("context", "text").execute();
16
+ await db.schema.createTable("tx").ifNotExists().addColumn("hash", "text", (col) => col.primaryKey()).addColumn("code", "text").addColumn("height", "integer").addColumn("index_", "integer").addColumn("receipts", "text").addColumn("receiptsVerified", "integer").addColumn("receiver", "text").addColumn("sender", "text").addColumn("time", "text").addColumn("tx", "text").addColumn("type", "text").addColumn("finalized", "integer").addColumn("valid", "integer").execute();
17
+ await db.schema.createTable("asset").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("owner", "text").addColumn("moniker", "text").addColumn("readonly", "integer").addColumn("transferrable", "integer").addColumn("issuer", "text").addColumn("parent", "text").addColumn("ttl", "integer").addColumn("consumedTime", "text").addColumn("endpoint", "text").addColumn("display", "text").addColumn("tags", "text").addColumn("stake", "text").addColumn("data", "text").addColumn("context", "text").execute();
18
+ await db.schema.createTable("token").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("symbol", "text").addColumn("name", "text").addColumn("decimal", "integer").addColumn("unit", "text").addColumn("description", "text").addColumn("icon", "text").addColumn("totalSupply", "text").addColumn("initialSupply", "text").addColumn("maxTotalSupply", "text").addColumn("foreignToken", "text").addColumn("issuer", "text").addColumn("website", "text").addColumn("metadata", "text").addColumn("tokenFactoryAddress", "text").addColumn("spenders", "text").addColumn("minters", "text").addColumn("type", "text").addColumn("data", "text").addColumn("context", "text").execute();
19
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_token_symbol ON token(symbol) WHERE symbol IS NOT NULL`.execute(db);
20
+ await db.schema.createTable("factory").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("owner", "text").addColumn("name", "text").addColumn("description", "text").addColumn("settlement", "text").addColumn("limit", "integer").addColumn("trustedIssuers", "text").addColumn("tokens", "text").addColumn("numMinted", "integer").addColumn("lastSettlement", "text").addColumn("input", "text").addColumn("output", "text").addColumn("display", "text").addColumn("hooks", "text").addColumn("data", "text").addColumn("context", "text").addColumn("balance", "text").addColumn("stake", "text").execute();
21
+ await db.schema.createTable("tokenFactory").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("owner", "text").addColumn("name", "text").addColumn("description", "text").addColumn("moniker", "text").addColumn("tokenAddress", "text").addColumn("reserveAddress", "text").addColumn("currentSupply", "text").addColumn("reserveBalance", "text").addColumn("feeRate", "integer").addColumn("status", "text").addColumn("curve", "text").addColumn("input", "text").addColumn("output", "text").addColumn("data", "text").addColumn("context", "text").execute();
22
+ await db.schema.createTable("stake").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("sender", "text").addColumn("receiver", "text").addColumn("tokens", "text").addColumn("assets", "text").addColumn("slashers", "text").addColumn("revocable", "integer").addColumn("message", "text").addColumn("revokeWaitingPeriod", "integer").addColumn("revokedTokens", "text").addColumn("revokedAssets", "text").addColumn("nonce", "text").addColumn("data", "text").addColumn("context", "text").execute();
23
+ await db.schema.createTable("delegation").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("from_", "text").addColumn("to_", "text").addColumn("ops", "text").addColumn("data", "text").addColumn("context", "text").execute();
24
+ await db.schema.createTable("rollup").ifNotExists().addColumn("address", "text", (col) => col.primaryKey()).addColumn("tokenAddress", "text").addColumn("vaultAddress", "text").addColumn("contractAddress", "text").addColumn("paused", "integer").addColumn("closed", "integer").addColumn("seedValidators", "text").addColumn("validators", "text").addColumn("minStakeAmount", "text").addColumn("maxStakeAmount", "text").addColumn("minSignerCount", "integer").addColumn("maxSignerCount", "integer").addColumn("minBlockSize", "integer").addColumn("maxBlockSize", "integer").addColumn("minBlockInterval", "integer").addColumn("minBlockConfirmation", "integer").addColumn("minDepositAmount", "text").addColumn("maxDepositAmount", "text").addColumn("minWithdrawAmount", "text").addColumn("maxWithdrawAmount", "text").addColumn("depositFeeRate", "integer").addColumn("withdrawFeeRate", "integer").addColumn("proposerFeeShare", "integer").addColumn("publisherFeeShare", "integer").addColumn("minDepositFee", "text").addColumn("maxDepositFee", "text").addColumn("minWithdrawFee", "text").addColumn("maxWithdrawFee", "text").addColumn("issuer", "text").addColumn("blockHeight", "integer").addColumn("blockHash", "text").addColumn("leaveWaitingPeriod", "integer").addColumn("publishWaitingPeriod", "integer").addColumn("publishSlashRate", "integer").addColumn("revokeWaitingPeriod", "integer").addColumn("erc20TokenAddress", "text").addColumn("foreignChainId", "text").addColumn("foreignChainType", "text").addColumn("migrateHistory", "text").addColumn("vaultHistory", "text").addColumn("data", "text").addColumn("context", "text").execute();
25
+ await sql`DROP INDEX IF EXISTS idx_rollup_token`.execute(db);
26
+ await sql`CREATE INDEX IF NOT EXISTS idx_rollup_token ON rollup(tokenAddress) WHERE tokenAddress IS NOT NULL`.execute(db);
27
+ await db.schema.createTable("rollupBlock").ifNotExists().addColumn("hash", "text", (col) => col.primaryKey()).addColumn("rollup", "text").addColumn("height", "integer").addColumn("merkleRoot", "text").addColumn("previousHash", "text").addColumn("txsHash", "text").addColumn("txs", "text").addColumn("proposer", "text").addColumn("mintedAmount", "text").addColumn("burnedAmount", "text").addColumn("rewardAmount", "text").addColumn("minReward", "text").addColumn("blockReward", "text").addColumn("governance", "integer").addColumn("checkpoint", "text").addColumn("txHash", "text").addColumn("signatures", "text").addColumn("data", "text").addColumn("context", "text").execute();
28
+ await db.schema.createTable("evidence").ifNotExists().addColumn("hash", "text", (col) => col.primaryKey()).addColumn("type", "text").addColumn("data", "text").addColumn("context", "text").execute();
29
+ }
30
+ /**
31
+ * Rollback migration - drops all tables
32
+ */
33
+ async function down(db) {
34
+ for (const table of [
35
+ "evidence",
36
+ "rollupBlock",
37
+ "rollup",
38
+ "delegation",
39
+ "stake",
40
+ "tokenFactory",
41
+ "factory",
42
+ "token",
43
+ "asset",
44
+ "tx",
45
+ "chain",
46
+ "balance",
47
+ "account"
48
+ ]) await db.schema.dropTable(table).ifExists().execute();
49
+ }
50
+
51
+ //#endregion
52
+ export { _001_genesis_exports, down, up };
@@ -0,0 +1,24 @@
1
+ import { Kysely } from "kysely";
2
+
3
+ //#region src/migrations/index.d.ts
4
+
5
+ /**
6
+ * Run all pending migrations
7
+ *
8
+ * @param db - Kysely database instance
9
+ * @throws Error if any migration fails
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const db = createKysely({ filename: ':memory:' });
14
+ * await runMigrations(db);
15
+ * ```
16
+ */
17
+ declare function runMigrations(db: Kysely<unknown>): Promise<void>;
18
+ /**
19
+ * Roll back all migrations
20
+ * WARNING: This will drop all tables and data
21
+ */
22
+ declare function rollbackMigrations(db: Kysely<unknown>): Promise<void>;
23
+ //#endregion
24
+ export { rollbackMigrations, runMigrations };
@@ -0,0 +1,60 @@
1
+ import { _001_genesis_exports } from "./001-genesis.mjs";
2
+ import { Migrator } from "kysely";
3
+
4
+ //#region src/migrations/index.ts
5
+ /**
6
+ * In-memory migration provider
7
+ * Provides migrations directly without filesystem access
8
+ */
9
+ var InMemoryMigrationProvider = class {
10
+ constructor(migrations) {
11
+ this.migrations = migrations;
12
+ }
13
+ async getMigrations() {
14
+ return this.migrations;
15
+ }
16
+ };
17
+ /**
18
+ * Run all pending migrations
19
+ *
20
+ * @param db - Kysely database instance
21
+ * @throws Error if any migration fails
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const db = createKysely({ filename: ':memory:' });
26
+ * await runMigrations(db);
27
+ * ```
28
+ */
29
+ async function runMigrations(db) {
30
+ const { error, results } = await new Migrator({
31
+ db,
32
+ provider: new InMemoryMigrationProvider({ "001-genesis": _001_genesis_exports })
33
+ }).migrateToLatest();
34
+ results?.forEach((it) => {
35
+ if (it.status === "Success") {} else if (it.status === "Error") console.error(`Migration "${it.migrationName}" failed`);
36
+ });
37
+ if (error) {
38
+ console.error("Migration failed:", error);
39
+ throw error;
40
+ }
41
+ }
42
+ /**
43
+ * Roll back all migrations
44
+ * WARNING: This will drop all tables and data
45
+ */
46
+ async function rollbackMigrations(db) {
47
+ const migrator = new Migrator({
48
+ db,
49
+ provider: new InMemoryMigrationProvider({ "001-genesis": _001_genesis_exports })
50
+ });
51
+ let hasMore = true;
52
+ while (hasMore) {
53
+ const { error, results } = await migrator.migrateDown();
54
+ if (error) throw error;
55
+ hasMore = results !== void 0 && results.length > 0;
56
+ }
57
+ }
58
+
59
+ //#endregion
60
+ export { rollbackMigrations, runMigrations };
@@ -0,0 +1,70 @@
1
+ //#region package.json
2
+ var package_default = {
3
+ name: "@ocap/statedb-sqlite",
4
+ description: "OCAP statedb adapter that uses SQLite as backend",
5
+ version: "1.0.0",
6
+ author: "wangshijun <shijun@arcblock.io> (https://www.arcblock.io)",
7
+ bugs: {
8
+ "url": "https://github.com/ArcBlock/blockchain/issues",
9
+ "email": "shijun@arcblock.io"
10
+ },
11
+ publishConfig: { "access": "public" },
12
+ contributors: ["wangshijun <shijun@arcblock.io> (https://www.arcblock.io)"],
13
+ devDependencies: { "@ocap/statedb-test": "workspace:*" },
14
+ homepage: "https://github.com/ArcBlock/blockchain/tree/master/statedb/sqlite",
15
+ keywords: [
16
+ "ocap",
17
+ "statedb",
18
+ "sqlite",
19
+ "kysely"
20
+ ],
21
+ license: "Apache-2.0",
22
+ type: "module",
23
+ main: "./lib/index.cjs",
24
+ module: "./esm/index.mjs",
25
+ types: "./esm/index.d.mts",
26
+ exports: {
27
+ ".": {
28
+ "types": "./esm/index.d.mts",
29
+ "import": "./esm/index.mjs",
30
+ "default": "./lib/index.cjs"
31
+ },
32
+ "./lib/*.js": {
33
+ "types": "./esm/*.d.mts",
34
+ "import": "./esm/*.mjs",
35
+ "default": "./lib/*.cjs"
36
+ },
37
+ "./lib/*": {
38
+ "types": "./esm/*.d.mts",
39
+ "import": "./esm/*.mjs",
40
+ "default": "./lib/*.cjs"
41
+ }
42
+ },
43
+ files: ["lib", "esm"],
44
+ repository: {
45
+ "type": "git",
46
+ "url": "https://github.com/ArcBlock/blockchain/tree/master/statedb/sqlite"
47
+ },
48
+ scripts: {
49
+ "build": "tsdown",
50
+ "prebuild": "rm -rf lib esm",
51
+ "lint": "biome check",
52
+ "lint:fix": "biome check --write",
53
+ "test": "bun test",
54
+ "coverage": "npm run test -- --coverage"
55
+ },
56
+ dependencies: {
57
+ "@ocap/state": "workspace:*",
58
+ "@ocap/statedb": "workspace:*",
59
+ "@ocap/types": "workspace:*",
60
+ "@ocap/util": "workspace:*",
61
+ "debug": "^4.4.3",
62
+ "better-sqlite3": "^11.8.1",
63
+ "kysely": "^0.27.0",
64
+ "kysely-bun-sqlite": "^0.4.0",
65
+ "lodash": "^4.17.23"
66
+ }
67
+ };
68
+
69
+ //#endregion
70
+ export { package_default as default };
@@ -0,0 +1,40 @@
1
+ import SqliteTable, { SqliteTableConfig } from "./base.mjs";
2
+ import { IAccountState, IOperationContext } from "@ocap/types";
3
+
4
+ //#region src/table/account.d.ts
5
+ interface AccountOperationContext extends IOperationContext {
6
+ traceMigration?: boolean;
7
+ /** Internal: tracks visited addresses to detect circular migrations */
8
+ _visitedAddresses?: Set<string>;
9
+ }
10
+ /**
11
+ * Account Table
12
+ * Standard account state with balance sync support
13
+ * Includes migration chain following and circular migration detection
14
+ */
15
+ declare class AccountTable extends SqliteTable<IAccountState> {
16
+ constructor(config: Omit<SqliteTableConfig, 'name' | 'uniqIndex' | 'schema'>);
17
+ /**
18
+ * Check if adding migration targets would create a cycle
19
+ * @param sourceAddress - The address that will have migratedTo set
20
+ * @param targets - The migration targets to check
21
+ * @param context - Operation context with transaction
22
+ * @returns true if a cycle would be created
23
+ */
24
+ private wouldCreateCycle;
25
+ /**
26
+ * Get account with migration chain following
27
+ * When an account has migratedTo set, follows the chain to get the final account
28
+ */
29
+ _get(address: string, context?: AccountOperationContext): Promise<IAccountState | null>;
30
+ /**
31
+ * Create account with address normalization and circular migration detection
32
+ */
33
+ _create(key: string, attrs?: Partial<IAccountState>, context?: IOperationContext): Promise<IAccountState>;
34
+ /**
35
+ * Update account with circular migration detection
36
+ */
37
+ _update(key: string, updates: Partial<IAccountState>, context?: IOperationContext): Promise<IAccountState>;
38
+ }
39
+ //#endregion
40
+ export { AccountTable as default };
@@ -0,0 +1,99 @@
1
+ import base_default from "./base.mjs";
2
+ import { ensureChecksumAddress } from "@ocap/state/lib/states/account";
3
+
4
+ //#region src/table/account.ts
5
+ /**
6
+ * Account Table
7
+ * Standard account state with balance sync support
8
+ * Includes migration chain following and circular migration detection
9
+ */
10
+ var AccountTable = class extends base_default {
11
+ constructor(config) {
12
+ super({
13
+ ...config,
14
+ name: "account",
15
+ uniqIndex: "address",
16
+ schema: { jsonFields: [
17
+ "migratedTo",
18
+ "migratedFrom",
19
+ "stake",
20
+ "tokens",
21
+ "data",
22
+ "context",
23
+ "poke"
24
+ ] }
25
+ });
26
+ }
27
+ /**
28
+ * Check if adding migration targets would create a cycle
29
+ * @param sourceAddress - The address that will have migratedTo set
30
+ * @param targets - The migration targets to check
31
+ * @param context - Operation context with transaction
32
+ * @returns true if a cycle would be created
33
+ */
34
+ async wouldCreateCycle(sourceAddress, targets, context) {
35
+ const visited = /* @__PURE__ */ new Set();
36
+ visited.add(sourceAddress);
37
+ const checkTarget = async (target) => {
38
+ const normalizedTarget = ensureChecksumAddress(target);
39
+ if (normalizedTarget === sourceAddress) return true;
40
+ if (visited.has(normalizedTarget)) return false;
41
+ visited.add(normalizedTarget);
42
+ const targetAccount = await super._get(normalizedTarget, context);
43
+ if (targetAccount?.migratedTo?.length) {
44
+ for (const nextTarget of targetAccount.migratedTo) if (await checkTarget(nextTarget)) return true;
45
+ }
46
+ return false;
47
+ };
48
+ for (const target of targets) if (await checkTarget(target)) return true;
49
+ return false;
50
+ }
51
+ /**
52
+ * Get account with migration chain following
53
+ * When an account has migratedTo set, follows the chain to get the final account
54
+ */
55
+ async _get(address, context = {}) {
56
+ const { traceMigration = true, ...restContext } = context;
57
+ const normalizedAddress = ensureChecksumAddress(address);
58
+ const current = await super._get(normalizedAddress, restContext);
59
+ if (current && traceMigration && Array.isArray(current.migratedTo) && current.migratedTo.length) {
60
+ const visitedAddresses = context._visitedAddresses || /* @__PURE__ */ new Set();
61
+ if (visitedAddresses.has(normalizedAddress)) return null;
62
+ visitedAddresses.add(normalizedAddress);
63
+ return this._get(current.migratedTo[0], {
64
+ ...context,
65
+ _visitedAddresses: visitedAddresses
66
+ });
67
+ }
68
+ return current;
69
+ }
70
+ /**
71
+ * Create account with address normalization and circular migration detection
72
+ */
73
+ async _create(key, attrs = {}, context) {
74
+ const address = ensureChecksumAddress(key);
75
+ const attrsWithMigration = attrs;
76
+ if (attrsWithMigration.migratedTo?.length) {
77
+ if (await this.wouldCreateCycle(address, attrsWithMigration.migratedTo, context)) throw new Error(`Circular migration detected: ${address} -> ${attrsWithMigration.migratedTo.join(", ")}`);
78
+ }
79
+ return super._create(address, {
80
+ ...attrs,
81
+ [this.uniqIndex]: address
82
+ }, context);
83
+ }
84
+ /**
85
+ * Update account with circular migration detection
86
+ */
87
+ async _update(key, updates, context) {
88
+ const address = ensureChecksumAddress(key);
89
+ const updatesWithMigration = updates;
90
+ if (updatesWithMigration.migratedTo?.length) {
91
+ if (await this.wouldCreateCycle(address, updatesWithMigration.migratedTo, context)) throw new Error(`Circular migration detected: ${address} -> ${updatesWithMigration.migratedTo.join(", ")}`);
92
+ }
93
+ return super._update(address, updates, context);
94
+ }
95
+ };
96
+ var account_default = AccountTable;
97
+
98
+ //#endregion
99
+ export { account_default as default };
@@ -0,0 +1,39 @@
1
+ import SqliteTable, { SqliteTableConfig } from "./base.mjs";
2
+ import { IBalanceState, IOperationContext } from "@ocap/types";
3
+
4
+ //#region src/table/balance.d.ts
5
+
6
+ /**
7
+ * Balance Table
8
+ * Manages token balances with composite primary key (address, tokenAddress)
9
+ */
10
+ declare class BalanceTable extends SqliteTable<IBalanceState> {
11
+ constructor(config: Omit<SqliteTableConfig, 'name' | 'uniqIndex' | 'schema'>);
12
+ /**
13
+ * Get all token balances for an address
14
+ *
15
+ * @param address - Account address
16
+ * @param _context - Operation context
17
+ * @returns Object mapping tokenAddress to balance
18
+ */
19
+ getBalance(address: string, _context?: IOperationContext): Promise<Record<string, string>>;
20
+ /**
21
+ * Update token balances for an address
22
+ * Creates new records for new tokens, updates existing ones
23
+ *
24
+ * @param params - Update parameters
25
+ * @param ctx - Operation context
26
+ * @returns Updated token balances
27
+ */
28
+ updateBalance({
29
+ address,
30
+ tokens,
31
+ context
32
+ }: {
33
+ address: string;
34
+ tokens: Record<string, string>;
35
+ context?: unknown;
36
+ }, ctx?: IOperationContext): Promise<Record<string, string>>;
37
+ }
38
+ //#endregion
39
+ export { BalanceTable as default };
@@ -0,0 +1,69 @@
1
+ import base_default from "./base.mjs";
2
+ import { ensureChecksumAddress } from "@ocap/state/lib/states/account";
3
+
4
+ //#region src/table/balance.ts
5
+ /**
6
+ * Balance Table
7
+ * Manages token balances with composite primary key (address, tokenAddress)
8
+ */
9
+ var BalanceTable = class extends base_default {
10
+ constructor(config) {
11
+ super({
12
+ ...config,
13
+ name: "balance",
14
+ uniqIndex: ["address", "tokenAddress"],
15
+ schema: { jsonFields: ["context"] }
16
+ });
17
+ }
18
+ /**
19
+ * Get all token balances for an address
20
+ *
21
+ * @param address - Account address
22
+ * @param _context - Operation context
23
+ * @returns Object mapping tokenAddress to balance
24
+ */
25
+ async getBalance(address, _context) {
26
+ const sqlContext = _context;
27
+ const executor = this.getExecutor(sqlContext);
28
+ const normalizedAddress = ensureChecksumAddress(address);
29
+ return (await executor.selectFrom("balance").selectAll().where("address", "=", normalizedAddress).execute() || []).reduce((acc, token) => {
30
+ acc[token.tokenAddress] = token.balance;
31
+ return acc;
32
+ }, {});
33
+ }
34
+ /**
35
+ * Update token balances for an address
36
+ * Creates new records for new tokens, updates existing ones
37
+ *
38
+ * @param params - Update parameters
39
+ * @param ctx - Operation context
40
+ * @returns Updated token balances
41
+ */
42
+ async updateBalance({ address, tokens, context = {} }, ctx) {
43
+ if (!Object.keys(tokens).length) return {};
44
+ const tokenBalances = await this.getBalance(address, ctx);
45
+ const updatedTokens = Object.keys(tokens).filter((token) => tokenBalances[token] && tokenBalances[token] !== "0" || tokens[token] && tokens[token] !== "0").filter((token) => tokenBalances[token] !== tokens[token]);
46
+ await Promise.all(updatedTokens.map(async (tokenAddress) => {
47
+ const key = {
48
+ address,
49
+ tokenAddress
50
+ };
51
+ if (tokenBalances[tokenAddress]) await this.update(key, {
52
+ ...key,
53
+ balance: tokens[tokenAddress],
54
+ context
55
+ }, ctx);
56
+ else await this.create(key, {
57
+ ...key,
58
+ balance: tokens[tokenAddress],
59
+ context
60
+ }, ctx);
61
+ tokenBalances[tokenAddress] = tokens[tokenAddress];
62
+ }));
63
+ return tokens;
64
+ }
65
+ };
66
+ var balance_default = BalanceTable;
67
+
68
+ //#endregion
69
+ export { balance_default as default };
@@ -0,0 +1,84 @@
1
+ import { Database, SqliteOperationContext } from "../interfaces.mjs";
2
+ import { StateDBTable } from "@ocap/statedb";
3
+ import { Kysely, Transaction } from "kysely";
4
+ import { IBalanceTable, IOperationContext } from "@ocap/types";
5
+
6
+ //#region src/table/base.d.ts
7
+
8
+ /**
9
+ * Table schema configuration
10
+ */
11
+ interface TableSchema {
12
+ /** Column names that contain JSON data */
13
+ jsonFields?: string[];
14
+ /** Column names that contain boolean data (stored as integer 0/1 in SQLite) */
15
+ booleanFields?: string[];
16
+ /** Column name mapping (e.g., 'from' -> 'from_' for reserved keywords) */
17
+ columnMapping?: Record<string, string>;
18
+ }
19
+ /**
20
+ * Table configuration
21
+ */
22
+ interface SqliteTableConfig {
23
+ name: string;
24
+ uniqIndex: string | string[];
25
+ db: Kysely<Database>;
26
+ schema?: TableSchema;
27
+ syncBalance?: boolean;
28
+ balanceTable?: IBalanceTable;
29
+ }
30
+ /**
31
+ * SQLite State Table base class
32
+ * Extends StateDBTable to provide SQLite-specific implementation
33
+ */
34
+ declare class SqliteTable<T = unknown> extends StateDBTable<T> {
35
+ name: string;
36
+ db: Kysely<Database>;
37
+ schema: TableSchema;
38
+ balanceTable?: IBalanceTable;
39
+ syncBalance: boolean;
40
+ constructor(config: SqliteTableConfig);
41
+ /**
42
+ * Get the Kysely executor (use transaction if available)
43
+ */
44
+ protected getExecutor(context?: SqliteOperationContext): Kysely<Database> | Transaction<Database>;
45
+ /**
46
+ * Generate primary key from document
47
+ * Handles both simple string keys and composite keys
48
+ */
49
+ generatePrimaryKey(doc: string | Record<string, unknown>): string;
50
+ /**
51
+ * Convert a key to a query object
52
+ */
53
+ protected formatKeyToQuery(key: unknown): Record<string, unknown>;
54
+ /**
55
+ * Map column name for reserved keywords
56
+ */
57
+ protected mapColumnName(name: string): string;
58
+ /**
59
+ * Reverse map column name
60
+ */
61
+ protected unmapColumnName(name: string): string;
62
+ /**
63
+ * Serialize data for database insert
64
+ * - Converts objects/arrays to JSON strings
65
+ * - Converts booleans to integers (0/1) for SQLite
66
+ * - Maps column names for reserved keywords
67
+ */
68
+ protected serializeForDb(data: Record<string, unknown>): Record<string, unknown>;
69
+ /**
70
+ * Deserialize data from database read
71
+ * - Parses JSON strings back to objects
72
+ * - Converts integers (0/1) back to booleans
73
+ * - Unmaps column names
74
+ */
75
+ protected deserializeFromDb(data: Record<string, unknown> | undefined | null): Record<string, unknown> | null;
76
+ _create(key: string | Record<string, unknown>, attrs?: Partial<T>, context?: IOperationContext): Promise<T>;
77
+ _get(key: string | Record<string, unknown>, context?: IOperationContext): Promise<T | null>;
78
+ _history(_key?: string, _context?: IOperationContext): T[];
79
+ _update(key: string | Record<string, unknown>, updates: Partial<T>, context?: IOperationContext): Promise<T>;
80
+ _reset(_context?: IOperationContext): Promise<void>;
81
+ updateOrCreate(exist: unknown, state: Partial<T>, ctx?: IOperationContext): Promise<T>;
82
+ }
83
+ //#endregion
84
+ export { SqliteTableConfig, TableSchema, SqliteTable as default };