@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
|
@@ -0,0 +1,101 @@
|
|
|
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 _ocap_state_lib_states_account = require("@ocap/state/lib/states/account");
|
|
5
|
+
|
|
6
|
+
//#region src/table/account.ts
|
|
7
|
+
/**
|
|
8
|
+
* Account Table
|
|
9
|
+
* Standard account state with balance sync support
|
|
10
|
+
* Includes migration chain following and circular migration detection
|
|
11
|
+
*/
|
|
12
|
+
var AccountTable = class extends require_table_base.default {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
super({
|
|
15
|
+
...config,
|
|
16
|
+
name: "account",
|
|
17
|
+
uniqIndex: "address",
|
|
18
|
+
schema: { jsonFields: [
|
|
19
|
+
"migratedTo",
|
|
20
|
+
"migratedFrom",
|
|
21
|
+
"stake",
|
|
22
|
+
"tokens",
|
|
23
|
+
"data",
|
|
24
|
+
"context",
|
|
25
|
+
"poke"
|
|
26
|
+
] }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if adding migration targets would create a cycle
|
|
31
|
+
* @param sourceAddress - The address that will have migratedTo set
|
|
32
|
+
* @param targets - The migration targets to check
|
|
33
|
+
* @param context - Operation context with transaction
|
|
34
|
+
* @returns true if a cycle would be created
|
|
35
|
+
*/
|
|
36
|
+
async wouldCreateCycle(sourceAddress, targets, context) {
|
|
37
|
+
const visited = /* @__PURE__ */ new Set();
|
|
38
|
+
visited.add(sourceAddress);
|
|
39
|
+
const checkTarget = async (target) => {
|
|
40
|
+
const normalizedTarget = (0, _ocap_state_lib_states_account.ensureChecksumAddress)(target);
|
|
41
|
+
if (normalizedTarget === sourceAddress) return true;
|
|
42
|
+
if (visited.has(normalizedTarget)) return false;
|
|
43
|
+
visited.add(normalizedTarget);
|
|
44
|
+
const targetAccount = await super._get(normalizedTarget, context);
|
|
45
|
+
if (targetAccount?.migratedTo?.length) {
|
|
46
|
+
for (const nextTarget of targetAccount.migratedTo) if (await checkTarget(nextTarget)) return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
};
|
|
50
|
+
for (const target of targets) if (await checkTarget(target)) return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get account with migration chain following
|
|
55
|
+
* When an account has migratedTo set, follows the chain to get the final account
|
|
56
|
+
*/
|
|
57
|
+
async _get(address, context = {}) {
|
|
58
|
+
const { traceMigration = true, ...restContext } = context;
|
|
59
|
+
const normalizedAddress = (0, _ocap_state_lib_states_account.ensureChecksumAddress)(address);
|
|
60
|
+
const current = await super._get(normalizedAddress, restContext);
|
|
61
|
+
if (current && traceMigration && Array.isArray(current.migratedTo) && current.migratedTo.length) {
|
|
62
|
+
const visitedAddresses = context._visitedAddresses || /* @__PURE__ */ new Set();
|
|
63
|
+
if (visitedAddresses.has(normalizedAddress)) return null;
|
|
64
|
+
visitedAddresses.add(normalizedAddress);
|
|
65
|
+
return this._get(current.migratedTo[0], {
|
|
66
|
+
...context,
|
|
67
|
+
_visitedAddresses: visitedAddresses
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create account with address normalization and circular migration detection
|
|
74
|
+
*/
|
|
75
|
+
async _create(key, attrs = {}, context) {
|
|
76
|
+
const address = (0, _ocap_state_lib_states_account.ensureChecksumAddress)(key);
|
|
77
|
+
const attrsWithMigration = attrs;
|
|
78
|
+
if (attrsWithMigration.migratedTo?.length) {
|
|
79
|
+
if (await this.wouldCreateCycle(address, attrsWithMigration.migratedTo, context)) throw new Error(`Circular migration detected: ${address} -> ${attrsWithMigration.migratedTo.join(", ")}`);
|
|
80
|
+
}
|
|
81
|
+
return super._create(address, {
|
|
82
|
+
...attrs,
|
|
83
|
+
[this.uniqIndex]: address
|
|
84
|
+
}, context);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Update account with circular migration detection
|
|
88
|
+
*/
|
|
89
|
+
async _update(key, updates, context) {
|
|
90
|
+
const address = (0, _ocap_state_lib_states_account.ensureChecksumAddress)(key);
|
|
91
|
+
const updatesWithMigration = updates;
|
|
92
|
+
if (updatesWithMigration.migratedTo?.length) {
|
|
93
|
+
if (await this.wouldCreateCycle(address, updatesWithMigration.migratedTo, context)) throw new Error(`Circular migration detected: ${address} -> ${updatesWithMigration.migratedTo.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
return super._update(address, updates, context);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var account_default = AccountTable;
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
exports.default = account_default;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import SqliteTable, { SqliteTableConfig } from "./base.cjs";
|
|
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,71 @@
|
|
|
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 _ocap_state_lib_states_account = require("@ocap/state/lib/states/account");
|
|
5
|
+
|
|
6
|
+
//#region src/table/balance.ts
|
|
7
|
+
/**
|
|
8
|
+
* Balance Table
|
|
9
|
+
* Manages token balances with composite primary key (address, tokenAddress)
|
|
10
|
+
*/
|
|
11
|
+
var BalanceTable = class extends require_table_base.default {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super({
|
|
14
|
+
...config,
|
|
15
|
+
name: "balance",
|
|
16
|
+
uniqIndex: ["address", "tokenAddress"],
|
|
17
|
+
schema: { jsonFields: ["context"] }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get all token balances for an address
|
|
22
|
+
*
|
|
23
|
+
* @param address - Account address
|
|
24
|
+
* @param _context - Operation context
|
|
25
|
+
* @returns Object mapping tokenAddress to balance
|
|
26
|
+
*/
|
|
27
|
+
async getBalance(address, _context) {
|
|
28
|
+
const sqlContext = _context;
|
|
29
|
+
const executor = this.getExecutor(sqlContext);
|
|
30
|
+
const normalizedAddress = (0, _ocap_state_lib_states_account.ensureChecksumAddress)(address);
|
|
31
|
+
return (await executor.selectFrom("balance").selectAll().where("address", "=", normalizedAddress).execute() || []).reduce((acc, token) => {
|
|
32
|
+
acc[token.tokenAddress] = token.balance;
|
|
33
|
+
return acc;
|
|
34
|
+
}, {});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Update token balances for an address
|
|
38
|
+
* Creates new records for new tokens, updates existing ones
|
|
39
|
+
*
|
|
40
|
+
* @param params - Update parameters
|
|
41
|
+
* @param ctx - Operation context
|
|
42
|
+
* @returns Updated token balances
|
|
43
|
+
*/
|
|
44
|
+
async updateBalance({ address, tokens, context = {} }, ctx) {
|
|
45
|
+
if (!Object.keys(tokens).length) return {};
|
|
46
|
+
const tokenBalances = await this.getBalance(address, ctx);
|
|
47
|
+
const updatedTokens = Object.keys(tokens).filter((token) => tokenBalances[token] && tokenBalances[token] !== "0" || tokens[token] && tokens[token] !== "0").filter((token) => tokenBalances[token] !== tokens[token]);
|
|
48
|
+
await Promise.all(updatedTokens.map(async (tokenAddress) => {
|
|
49
|
+
const key = {
|
|
50
|
+
address,
|
|
51
|
+
tokenAddress
|
|
52
|
+
};
|
|
53
|
+
if (tokenBalances[tokenAddress]) await this.update(key, {
|
|
54
|
+
...key,
|
|
55
|
+
balance: tokens[tokenAddress],
|
|
56
|
+
context
|
|
57
|
+
}, ctx);
|
|
58
|
+
else await this.create(key, {
|
|
59
|
+
...key,
|
|
60
|
+
balance: tokens[tokenAddress],
|
|
61
|
+
context
|
|
62
|
+
}, ctx);
|
|
63
|
+
tokenBalances[tokenAddress] = tokens[tokenAddress];
|
|
64
|
+
}));
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var balance_default = BalanceTable;
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
exports.default = balance_default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import SqliteTable, { SqliteTableConfig } from "./base.cjs";
|
|
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,221 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
3
|
+
let _ocap_statedb = require("@ocap/statedb");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
node_crypto = require_rolldown_runtime.__toESM(node_crypto);
|
|
6
|
+
let lodash_omit = require("lodash/omit");
|
|
7
|
+
lodash_omit = require_rolldown_runtime.__toESM(lodash_omit);
|
|
8
|
+
|
|
9
|
+
//#region src/table/base.ts
|
|
10
|
+
/**
|
|
11
|
+
* SQLite State Table base class
|
|
12
|
+
* Extends StateDBTable to provide SQLite-specific implementation
|
|
13
|
+
*/
|
|
14
|
+
var SqliteTable = class SqliteTable extends _ocap_statedb.StateDBTable {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super(config.uniqIndex);
|
|
17
|
+
this.name = config.name;
|
|
18
|
+
this.db = config.db;
|
|
19
|
+
this.schema = config.schema || {};
|
|
20
|
+
this.balanceTable = config.balanceTable;
|
|
21
|
+
this.syncBalance = config.syncBalance ?? false;
|
|
22
|
+
if (this.syncBalance && !this.balanceTable) throw new Error("balanceTable is required when syncBalance is true");
|
|
23
|
+
this.markReady();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the Kysely executor (use transaction if available)
|
|
27
|
+
*/
|
|
28
|
+
getExecutor(context) {
|
|
29
|
+
return context?.txn || this.db;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate primary key from document
|
|
33
|
+
* Handles both simple string keys and composite keys
|
|
34
|
+
*/
|
|
35
|
+
generatePrimaryKey(doc) {
|
|
36
|
+
if (typeof doc === "string") return doc;
|
|
37
|
+
if (Array.isArray(this.uniqIndex)) {
|
|
38
|
+
const key = this.uniqIndex.map((id) => doc[id]).map((item) => typeof item === "object" ? JSON.stringify(item) : item).join("-");
|
|
39
|
+
return node_crypto.default.createHash("md5").update(key).digest("hex");
|
|
40
|
+
}
|
|
41
|
+
return doc[this.uniqIndex];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a key to a query object
|
|
45
|
+
*/
|
|
46
|
+
formatKeyToQuery(key) {
|
|
47
|
+
if (Array.isArray(this.uniqIndex) && typeof key === "object") {
|
|
48
|
+
const keyObj = key;
|
|
49
|
+
return Object.fromEntries(this.uniqIndex.map((k) => [this.mapColumnName(k), keyObj[k]]));
|
|
50
|
+
}
|
|
51
|
+
if (typeof key === "object") {
|
|
52
|
+
const keyObj = key;
|
|
53
|
+
return { [this.mapColumnName(this.primaryKey)]: keyObj[this.primaryKey] };
|
|
54
|
+
}
|
|
55
|
+
return { [this.mapColumnName(this.primaryKey)]: key };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Map column name for reserved keywords
|
|
59
|
+
*/
|
|
60
|
+
mapColumnName(name) {
|
|
61
|
+
return this.schema.columnMapping?.[name] ?? name;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reverse map column name
|
|
65
|
+
*/
|
|
66
|
+
unmapColumnName(name) {
|
|
67
|
+
const mapping = this.schema.columnMapping;
|
|
68
|
+
if (!mapping) return name;
|
|
69
|
+
for (const [original, mapped] of Object.entries(mapping)) if (mapped === name) return original;
|
|
70
|
+
return name;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Serialize data for database insert
|
|
74
|
+
* - Converts objects/arrays to JSON strings
|
|
75
|
+
* - Converts booleans to integers (0/1) for SQLite
|
|
76
|
+
* - Maps column names for reserved keywords
|
|
77
|
+
*/
|
|
78
|
+
serializeForDb(data) {
|
|
79
|
+
const result = {};
|
|
80
|
+
const jsonFields = new Set(this.schema.jsonFields || []);
|
|
81
|
+
const booleanFields = new Set(this.schema.booleanFields || []);
|
|
82
|
+
for (const [key, value] of Object.entries(data)) {
|
|
83
|
+
const mappedKey = this.mapColumnName(key);
|
|
84
|
+
if (value === void 0) result[mappedKey] = null;
|
|
85
|
+
else if (booleanFields.has(key) && typeof value === "boolean") result[mappedKey] = value ? 1 : 0;
|
|
86
|
+
else if (jsonFields.has(key) || value !== null && typeof value === "object") result[mappedKey] = JSON.stringify(value);
|
|
87
|
+
else result[mappedKey] = value;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Deserialize data from database read
|
|
93
|
+
* - Parses JSON strings back to objects
|
|
94
|
+
* - Converts integers (0/1) back to booleans
|
|
95
|
+
* - Unmaps column names
|
|
96
|
+
*/
|
|
97
|
+
deserializeFromDb(data) {
|
|
98
|
+
if (!data) return null;
|
|
99
|
+
const result = {};
|
|
100
|
+
const jsonFields = new Set(this.schema.jsonFields || []);
|
|
101
|
+
const booleanFields = new Set(this.schema.booleanFields || []);
|
|
102
|
+
for (const [key, value] of Object.entries(data)) {
|
|
103
|
+
const unmappedKey = this.unmapColumnName(key);
|
|
104
|
+
if (booleanFields.has(unmappedKey) && typeof value === "number") result[unmappedKey] = value === 1;
|
|
105
|
+
else if (jsonFields.has(unmappedKey) && typeof value === "string") try {
|
|
106
|
+
result[unmappedKey] = JSON.parse(value);
|
|
107
|
+
} catch {
|
|
108
|
+
result[unmappedKey] = value;
|
|
109
|
+
}
|
|
110
|
+
else result[unmappedKey] = value;
|
|
111
|
+
}
|
|
112
|
+
delete result.$loki;
|
|
113
|
+
delete result.meta;
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
async _create(key, attrs = {}, context) {
|
|
117
|
+
const sqlContext = context;
|
|
118
|
+
const executor = this.getExecutor(sqlContext);
|
|
119
|
+
const insertAttrs = { ...attrs };
|
|
120
|
+
if (Array.isArray(this.uniqIndex)) {
|
|
121
|
+
if (typeof key === "object") Object.assign(insertAttrs, key);
|
|
122
|
+
} else {
|
|
123
|
+
const id = this.generatePrimaryKey(key);
|
|
124
|
+
insertAttrs[this.primaryKey] = id;
|
|
125
|
+
}
|
|
126
|
+
const lookupKey = Array.isArray(this.uniqIndex) ? Object.fromEntries(this.uniqIndex.map((k) => [k, insertAttrs[k]])) : insertAttrs[this.primaryKey];
|
|
127
|
+
if (await SqliteTable.prototype._get.call(this, lookupKey, context)) throw new Error(`${this.name} already exists: ${JSON.stringify(key)}`);
|
|
128
|
+
if (this.syncBalance) delete insertAttrs.tokens;
|
|
129
|
+
const serialized = this.serializeForDb(insertAttrs);
|
|
130
|
+
await executor.insertInto(this.name).values(serialized).execute();
|
|
131
|
+
if (this.syncBalance && attrs.tokens) insertAttrs.tokens = await this.balanceTable.updateBalance({
|
|
132
|
+
address: insertAttrs.address,
|
|
133
|
+
tokens: attrs.tokens,
|
|
134
|
+
context: attrs.context
|
|
135
|
+
}, context);
|
|
136
|
+
return insertAttrs;
|
|
137
|
+
}
|
|
138
|
+
async _get(key, context) {
|
|
139
|
+
if (!key) return null;
|
|
140
|
+
const sqlContext = context;
|
|
141
|
+
const executor = this.getExecutor(sqlContext);
|
|
142
|
+
let query;
|
|
143
|
+
if (Array.isArray(this.uniqIndex)) if (typeof key === "object") {
|
|
144
|
+
const keyObj = key;
|
|
145
|
+
query = Object.fromEntries(this.uniqIndex.map((k) => [this.mapColumnName(k), keyObj[k]]));
|
|
146
|
+
} else return null;
|
|
147
|
+
else query = this.formatKeyToQuery(key);
|
|
148
|
+
let qb = executor.selectFrom(this.name).selectAll();
|
|
149
|
+
for (const [k, v] of Object.entries(query)) {
|
|
150
|
+
if (v === void 0) continue;
|
|
151
|
+
qb = qb.where(k, "=", v);
|
|
152
|
+
}
|
|
153
|
+
const result = await qb.executeTakeFirst();
|
|
154
|
+
const deserialized = this.deserializeFromDb(result);
|
|
155
|
+
if (deserialized && this.syncBalance) {
|
|
156
|
+
const balance = await this.balanceTable.getBalance(deserialized.address, context);
|
|
157
|
+
deserialized.tokens = {
|
|
158
|
+
...deserialized.tokens || {},
|
|
159
|
+
...balance
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return deserialized;
|
|
163
|
+
}
|
|
164
|
+
_history(_key, _context) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
async _update(key, updates, context) {
|
|
168
|
+
const sqlContext = context;
|
|
169
|
+
const executor = this.getExecutor(sqlContext);
|
|
170
|
+
let lookupKey = key;
|
|
171
|
+
if (Array.isArray(this.uniqIndex) && typeof key !== "object") {
|
|
172
|
+
const updatesObj = updates;
|
|
173
|
+
lookupKey = Object.fromEntries(this.uniqIndex.map((k) => [k, updatesObj[k]]));
|
|
174
|
+
}
|
|
175
|
+
const existing = await SqliteTable.prototype._get.call(this, lookupKey, context);
|
|
176
|
+
if (!existing) throw new Error(`${this.name} does not exist: ${JSON.stringify(key)}`);
|
|
177
|
+
let query;
|
|
178
|
+
if (Array.isArray(this.uniqIndex)) {
|
|
179
|
+
const keyObj = typeof lookupKey === "object" ? lookupKey : existing;
|
|
180
|
+
query = Object.fromEntries(this.uniqIndex.map((k) => [this.mapColumnName(k), keyObj[k]]));
|
|
181
|
+
} else query = this.formatKeyToQuery(key);
|
|
182
|
+
const updateAttrs = { ...updates };
|
|
183
|
+
if (Array.isArray(this.uniqIndex)) for (const k of this.uniqIndex) delete updateAttrs[k];
|
|
184
|
+
else delete updateAttrs[this.primaryKey];
|
|
185
|
+
if (this.syncBalance) delete updateAttrs.tokens;
|
|
186
|
+
if (Object.keys(updateAttrs).length > 0) {
|
|
187
|
+
const serialized = this.serializeForDb(updateAttrs);
|
|
188
|
+
let qb = executor.updateTable(this.name).set(serialized);
|
|
189
|
+
for (const [k, v] of Object.entries(query)) {
|
|
190
|
+
if (v === void 0) continue;
|
|
191
|
+
qb = qb.where(k, "=", v);
|
|
192
|
+
}
|
|
193
|
+
await qb.execute();
|
|
194
|
+
}
|
|
195
|
+
const merged = {
|
|
196
|
+
...existing,
|
|
197
|
+
...updateAttrs
|
|
198
|
+
};
|
|
199
|
+
if (this.syncBalance && updates.tokens) merged.tokens = await this.balanceTable.updateBalance({
|
|
200
|
+
address: merged.address,
|
|
201
|
+
tokens: updates.tokens,
|
|
202
|
+
context: updates.context
|
|
203
|
+
}, context);
|
|
204
|
+
return merged;
|
|
205
|
+
}
|
|
206
|
+
async _reset(_context) {
|
|
207
|
+
const sqlContext = _context;
|
|
208
|
+
await this.getExecutor(sqlContext).deleteFrom(this.name).execute();
|
|
209
|
+
}
|
|
210
|
+
async updateOrCreate(exist, state, ctx) {
|
|
211
|
+
const id = this.generatePrimaryKey(state);
|
|
212
|
+
const attrs = (0, lodash_omit.default)(state, Array.isArray(this.uniqIndex) ? this.uniqIndex : [this.uniqIndex]);
|
|
213
|
+
if (!id) throw new Error("Cannot update or create without uniq index");
|
|
214
|
+
if (exist) return this.update(id, attrs, ctx);
|
|
215
|
+
return this.create(id, attrs, ctx);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var base_default = SqliteTable;
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
exports.default = base_default;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Database, SqliteOperationContext } from "../interfaces.cjs";
|
|
2
|
+
import { StateDBTable } from "@ocap/statedb";
|
|
3
|
+
import { IBalanceTable, IOperationContext } from "@ocap/types";
|
|
4
|
+
import { Kysely, Transaction } from "kysely";
|
|
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 };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_table_base = require('./base.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/table/rollup.ts
|
|
5
|
+
/**
|
|
6
|
+
* Rollup Table
|
|
7
|
+
* Extends base table with tokenAddress-based existence check
|
|
8
|
+
*/
|
|
9
|
+
var RollupTable = class extends require_table_base.default {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
super({
|
|
12
|
+
...config,
|
|
13
|
+
name: "rollup",
|
|
14
|
+
uniqIndex: "address",
|
|
15
|
+
schema: {
|
|
16
|
+
jsonFields: [
|
|
17
|
+
"seedValidators",
|
|
18
|
+
"validators",
|
|
19
|
+
"migrateHistory",
|
|
20
|
+
"vaultHistory",
|
|
21
|
+
"data",
|
|
22
|
+
"context"
|
|
23
|
+
],
|
|
24
|
+
booleanFields: ["paused", "closed"]
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if an active (non-paused) rollup with the given tokenAddress exists
|
|
30
|
+
*
|
|
31
|
+
* @param tokenAddress - Token address to check
|
|
32
|
+
* @param context - Operation context with optional transaction
|
|
33
|
+
* @returns true if active rollup with tokenAddress exists, false if tokenAddress is empty/undefined or rollup is paused
|
|
34
|
+
*/
|
|
35
|
+
async existByToken(tokenAddress, context) {
|
|
36
|
+
if (!tokenAddress) return false;
|
|
37
|
+
const sqlContext = context;
|
|
38
|
+
const executor = this.getExecutor(sqlContext);
|
|
39
|
+
return ((await executor.selectFrom("rollup").select(executor.fn.count("address").as("count")).where("tokenAddress", "=", tokenAddress).where("paused", "=", 0).executeTakeFirst())?.count ?? 0) > 0;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var rollup_default = RollupTable;
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
exports.default = rollup_default;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import SqliteTable, { SqliteTableConfig } from "./base.cjs";
|
|
2
|
+
import { IOperationContext, IRollupState } from "@ocap/types";
|
|
3
|
+
|
|
4
|
+
//#region src/table/rollup.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rollup Table
|
|
8
|
+
* Extends base table with tokenAddress-based existence check
|
|
9
|
+
*/
|
|
10
|
+
declare class RollupTable extends SqliteTable<IRollupState> {
|
|
11
|
+
constructor(config: Omit<SqliteTableConfig, 'name' | 'uniqIndex' | 'schema'>);
|
|
12
|
+
/**
|
|
13
|
+
* Check if an active (non-paused) rollup with the given tokenAddress exists
|
|
14
|
+
*
|
|
15
|
+
* @param tokenAddress - Token address to check
|
|
16
|
+
* @param context - Operation context with optional transaction
|
|
17
|
+
* @returns true if active rollup with tokenAddress exists, false if tokenAddress is empty/undefined or rollup is paused
|
|
18
|
+
*/
|
|
19
|
+
existByToken(tokenAddress: string, context?: IOperationContext): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { RollupTable as default };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_table_base = require('./base.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/table/token.ts
|
|
5
|
+
/**
|
|
6
|
+
* Token Table
|
|
7
|
+
* Extends base table with symbol-based existence check
|
|
8
|
+
*/
|
|
9
|
+
var TokenTable = class extends require_table_base.default {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
super({
|
|
12
|
+
...config,
|
|
13
|
+
name: "token",
|
|
14
|
+
uniqIndex: "address",
|
|
15
|
+
schema: { jsonFields: [
|
|
16
|
+
"foreignToken",
|
|
17
|
+
"metadata",
|
|
18
|
+
"spenders",
|
|
19
|
+
"minters",
|
|
20
|
+
"data",
|
|
21
|
+
"context"
|
|
22
|
+
] }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if a token with the given symbol exists
|
|
27
|
+
*
|
|
28
|
+
* @param symbol - Token symbol to check
|
|
29
|
+
* @param context - Operation context with optional transaction
|
|
30
|
+
* @returns true if token with symbol exists
|
|
31
|
+
* @throws Error if symbol is empty
|
|
32
|
+
*/
|
|
33
|
+
async existBySymbol(symbol, context) {
|
|
34
|
+
if (!symbol) throw new Error("param symbol is required");
|
|
35
|
+
const sqlContext = context;
|
|
36
|
+
const executor = this.getExecutor(sqlContext);
|
|
37
|
+
return ((await executor.selectFrom("token").select(executor.fn.count("address").as("count")).where("symbol", "=", symbol).executeTakeFirst())?.count ?? 0) > 0;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var token_default = TokenTable;
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
exports.default = token_default;
|