@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.
- package/README.md +66 -0
- package/esm/_virtual/rolldown_runtime.mjs +21 -0
- package/esm/db.d.mts +92 -0
- package/esm/db.mjs +257 -0
- package/esm/index.d.mts +4 -0
- package/esm/index.mjs +7 -0
- package/esm/interfaces.d.mts +201 -0
- package/esm/interfaces.mjs +1 -0
- package/esm/kysely.d.mts +43 -0
- package/esm/kysely.mjs +62 -0
- package/esm/migrations/001-genesis.d.mts +14 -0
- package/esm/migrations/001-genesis.mjs +52 -0
- package/esm/migrations/index.d.mts +24 -0
- package/esm/migrations/index.mjs +60 -0
- package/esm/package.mjs +70 -0
- package/esm/table/account.d.mts +40 -0
- package/esm/table/account.mjs +99 -0
- package/esm/table/balance.d.mts +39 -0
- package/esm/table/balance.mjs +69 -0
- package/esm/table/base.d.mts +84 -0
- package/esm/table/base.mjs +217 -0
- package/esm/table/rollup.d.mts +22 -0
- package/esm/table/rollup.mjs +44 -0
- package/esm/table/token.d.mts +23 -0
- package/esm/table/token.mjs +42 -0
- package/lib/_virtual/rolldown_runtime.cjs +43 -0
- package/lib/db.cjs +259 -0
- package/lib/db.d.cts +92 -0
- package/lib/index.cjs +9 -0
- package/lib/index.d.cts +4 -0
- package/lib/interfaces.cjs +0 -0
- package/lib/interfaces.d.cts +201 -0
- package/lib/kysely.cjs +63 -0
- package/lib/kysely.d.cts +43 -0
- package/lib/migrations/001-genesis.cjs +59 -0
- package/lib/migrations/001-genesis.d.cts +14 -0
- package/lib/migrations/index.cjs +62 -0
- package/lib/migrations/index.d.cts +24 -0
- package/lib/package.cjs +76 -0
- package/lib/table/account.cjs +101 -0
- package/lib/table/account.d.cts +40 -0
- package/lib/table/balance.cjs +71 -0
- package/lib/table/balance.d.cts +39 -0
- package/lib/table/base.cjs +221 -0
- package/lib/table/base.d.cts +84 -0
- package/lib/table/rollup.cjs +45 -0
- package/lib/table/rollup.d.cts +22 -0
- package/lib/table/token.cjs +43 -0
- package/lib/table/token.d.cts +23 -0
- 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 };
|
package/esm/package.mjs
ADDED
|
@@ -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 };
|