@ocap/indexdb-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 (42) hide show
  1. package/README.md +84 -0
  2. package/esm/_virtual/rolldown_runtime.mjs +21 -0
  3. package/esm/db/base.d.mts +76 -0
  4. package/esm/db/base.mjs +666 -0
  5. package/esm/db/index.d.mts +80 -0
  6. package/esm/db/index.mjs +551 -0
  7. package/esm/index.d.mts +3 -0
  8. package/esm/index.mjs +8 -0
  9. package/esm/interfaces.d.mts +328 -0
  10. package/esm/interfaces.mjs +1 -0
  11. package/esm/kysely.d.mts +43 -0
  12. package/esm/kysely.mjs +62 -0
  13. package/esm/migrations/001-genesis.d.mts +14 -0
  14. package/esm/migrations/001-genesis.mjs +107 -0
  15. package/esm/migrations/index.d.mts +16 -0
  16. package/esm/migrations/index.mjs +44 -0
  17. package/esm/package.mjs +70 -0
  18. package/esm/table/base.d.mts +107 -0
  19. package/esm/table/base.mjs +262 -0
  20. package/esm/table/transaction.d.mts +18 -0
  21. package/esm/table/transaction.mjs +22 -0
  22. package/lib/_virtual/rolldown_runtime.cjs +43 -0
  23. package/lib/db/base.cjs +670 -0
  24. package/lib/db/base.d.cts +76 -0
  25. package/lib/db/index.cjs +552 -0
  26. package/lib/db/index.d.cts +80 -0
  27. package/lib/index.cjs +10 -0
  28. package/lib/index.d.cts +3 -0
  29. package/lib/interfaces.cjs +0 -0
  30. package/lib/interfaces.d.cts +328 -0
  31. package/lib/kysely.cjs +63 -0
  32. package/lib/kysely.d.cts +43 -0
  33. package/lib/migrations/001-genesis.cjs +114 -0
  34. package/lib/migrations/001-genesis.d.cts +14 -0
  35. package/lib/migrations/index.cjs +46 -0
  36. package/lib/migrations/index.d.cts +16 -0
  37. package/lib/package.cjs +76 -0
  38. package/lib/table/base.cjs +265 -0
  39. package/lib/table/base.d.cts +107 -0
  40. package/lib/table/transaction.cjs +24 -0
  41. package/lib/table/transaction.d.cts +18 -0
  42. package/package.json +75 -0
@@ -0,0 +1,70 @@
1
+ //#region package.json
2
+ var package_default = {
3
+ name: "@ocap/indexdb-sqlite",
4
+ description: "OCAP indexdb 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/indexdb-test": "workspace:*" },
14
+ homepage: "https://github.com/ArcBlock/blockchain/tree/master/indexdb/sqlite",
15
+ keywords: [
16
+ "ocap",
17
+ "indexdb",
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/indexdb/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
+ "@arcblock/did": "workspace:*",
58
+ "@ocap/indexdb": "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,107 @@
1
+ import { Database, SqliteOperationContext } from "../interfaces.mjs";
2
+ import { Kysely, Transaction } from "kysely";
3
+ import { BaseIndex } from "@ocap/indexdb";
4
+ import { IIndexTable } 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
+ /** Numeric column mapping (field name -> numeric field name for sorting) */
19
+ numericFields?: Record<string, string>;
20
+ /** Known column names for this table (used to filter unknown fields) */
21
+ knownColumns?: string[];
22
+ }
23
+ /**
24
+ * Table configuration
25
+ */
26
+ interface SqliteTableConfig {
27
+ name: string;
28
+ uniqIndex: string | string[];
29
+ db: Kysely<Database>;
30
+ schema?: TableSchema;
31
+ }
32
+ /**
33
+ * SQLite Index Table base class
34
+ * Extends IndexDBTable to provide SQLite-specific implementation
35
+ */
36
+ declare class SqliteTable<T = unknown> extends BaseIndex<T> implements IIndexTable<T> {
37
+ name: string;
38
+ db: Kysely<Database>;
39
+ schema: TableSchema;
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
+ * Map column name for reserved keywords
52
+ */
53
+ protected mapColumnName(name: string): string;
54
+ /**
55
+ * Reverse map column name
56
+ */
57
+ protected unmapColumnName(name: string): string;
58
+ /**
59
+ * Serialize data for database insert
60
+ * - Converts objects/arrays to JSON strings
61
+ * - Converts booleans to integers (0/1) for SQLite
62
+ * - Maps column names for reserved keywords
63
+ * - Adds numeric fields for sorting
64
+ * - Stores unknown fields in the 'data' column as JSON
65
+ */
66
+ protected serializeForDb(data: Record<string, unknown>): Record<string, unknown>;
67
+ /**
68
+ * Serialize data for database update
69
+ * Preserves unknown fields from existing document's data column
70
+ */
71
+ protected serializeForDbUpdate(updates: Record<string, unknown>, existing: Record<string, unknown>): Record<string, unknown>;
72
+ /**
73
+ * Convert a big number string to a sortable numeric string
74
+ * Pads with leading zeros to ensure lexicographic sorting works
75
+ */
76
+ protected toSortableNumeric(value: string): string;
77
+ /**
78
+ * Deserialize data from database read
79
+ * - Parses JSON strings back to objects
80
+ * - Converts integers (0/1) back to booleans
81
+ * - Unmaps column names
82
+ * - Extracts unknown fields from 'data' column back to top level
83
+ */
84
+ protected deserializeFromDb(data: Record<string, unknown> | undefined | null): Record<string, unknown> | null;
85
+ /**
86
+ * Internal get implementation - called by BaseIndex.get() via hooks
87
+ */
88
+ _get(key: string | Record<string, unknown>): Promise<T | null>;
89
+ /**
90
+ * Internal insert implementation - called by BaseIndex.insert() via hooks
91
+ */
92
+ _insert(doc: T | Record<string, unknown>): Promise<T>;
93
+ /**
94
+ * Internal update implementation - called by BaseIndex.update() via hooks
95
+ */
96
+ _update(key: string | Record<string, unknown>, updates: Partial<T>): Promise<T>;
97
+ /**
98
+ * Count documents matching a query
99
+ */
100
+ count(query?: unknown): Promise<number>;
101
+ /**
102
+ * Internal reset implementation - called by BaseIndex.reset() via hooks
103
+ */
104
+ _reset(): Promise<void>;
105
+ }
106
+ //#endregion
107
+ export { SqliteTableConfig, TableSchema, SqliteTable as default };
@@ -0,0 +1,262 @@
1
+ import crypto from "node:crypto";
2
+ import { BaseIndex } from "@ocap/indexdb";
3
+
4
+ //#region src/table/base.ts
5
+ /**
6
+ * SQLite Index Table base class
7
+ * Extends IndexDBTable to provide SQLite-specific implementation
8
+ */
9
+ var SqliteTable = class extends BaseIndex {
10
+ constructor(config) {
11
+ super(config.name, config.uniqIndex);
12
+ this.name = config.name;
13
+ this.db = config.db;
14
+ this.schema = config.schema || {};
15
+ this.markReady();
16
+ }
17
+ /**
18
+ * Get the Kysely executor (use transaction if available)
19
+ */
20
+ getExecutor(context) {
21
+ return context?.txn || this.db;
22
+ }
23
+ /**
24
+ * Generate primary key from document
25
+ * Handles both simple string keys and composite keys
26
+ */
27
+ generatePrimaryKey(doc) {
28
+ if (typeof doc === "string") return doc;
29
+ if (Array.isArray(this.uniqIndex)) {
30
+ const key = this.uniqIndex.map((id) => doc[id]).map((item) => typeof item === "object" ? JSON.stringify(item) : item).join("-");
31
+ return crypto.createHash("md5").update(key).digest("hex");
32
+ }
33
+ return doc[this.uniqIndex];
34
+ }
35
+ /**
36
+ * Map column name for reserved keywords
37
+ */
38
+ mapColumnName(name) {
39
+ return this.schema.columnMapping?.[name] ?? name;
40
+ }
41
+ /**
42
+ * Reverse map column name
43
+ */
44
+ unmapColumnName(name) {
45
+ const mapping = this.schema.columnMapping;
46
+ if (!mapping) return name;
47
+ for (const [original, mapped] of Object.entries(mapping)) if (mapped === name) return original;
48
+ return name;
49
+ }
50
+ /**
51
+ * Serialize data for database insert
52
+ * - Converts objects/arrays to JSON strings
53
+ * - Converts booleans to integers (0/1) for SQLite
54
+ * - Maps column names for reserved keywords
55
+ * - Adds numeric fields for sorting
56
+ * - Stores unknown fields in the 'data' column as JSON
57
+ */
58
+ serializeForDb(data) {
59
+ const result = {};
60
+ const jsonFields = new Set(this.schema.jsonFields || []);
61
+ const booleanFields = new Set(this.schema.booleanFields || []);
62
+ const numericFields = this.schema.numericFields || {};
63
+ const knownColumns = this.schema.knownColumns ? new Set(this.schema.knownColumns) : null;
64
+ const unknownFields = {};
65
+ for (const [key, value] of Object.entries(data)) {
66
+ const mappedKey = this.mapColumnName(key);
67
+ if (knownColumns && !knownColumns.has(key) && key !== "data") {
68
+ unknownFields[key] = value;
69
+ continue;
70
+ }
71
+ if (value === void 0) result[mappedKey] = null;
72
+ else if (booleanFields.has(key) && typeof value === "boolean") result[mappedKey] = value ? 1 : 0;
73
+ else if (jsonFields.has(key) || value !== null && typeof value === "object") result[mappedKey] = JSON.stringify(value);
74
+ else result[mappedKey] = value;
75
+ if (numericFields[key] && value) result[numericFields[key]] = this.toSortableNumeric(String(value));
76
+ }
77
+ if (Object.keys(unknownFields).length > 0) {
78
+ let existingData = {};
79
+ if (result.data && typeof result.data === "string") try {
80
+ existingData = JSON.parse(result.data);
81
+ } catch {
82
+ existingData = {};
83
+ }
84
+ else if (data.data && typeof data.data === "object") existingData = data.data;
85
+ result.data = JSON.stringify({
86
+ ...existingData,
87
+ ...unknownFields
88
+ });
89
+ }
90
+ return result;
91
+ }
92
+ /**
93
+ * Serialize data for database update
94
+ * Preserves unknown fields from existing document's data column
95
+ */
96
+ serializeForDbUpdate(updates, existing) {
97
+ const result = {};
98
+ const jsonFields = new Set(this.schema.jsonFields || []);
99
+ const booleanFields = new Set(this.schema.booleanFields || []);
100
+ const numericFields = this.schema.numericFields || {};
101
+ const knownColumns = this.schema.knownColumns ? new Set(this.schema.knownColumns) : null;
102
+ const unknownFields = {};
103
+ for (const [key, value] of Object.entries(updates)) {
104
+ const mappedKey = this.mapColumnName(key);
105
+ if (knownColumns && !knownColumns.has(key) && key !== "data") {
106
+ unknownFields[key] = value;
107
+ continue;
108
+ }
109
+ if (value === void 0) result[mappedKey] = null;
110
+ else if (booleanFields.has(key) && typeof value === "boolean") result[mappedKey] = value ? 1 : 0;
111
+ else if (jsonFields.has(key) || value !== null && typeof value === "object") result[mappedKey] = JSON.stringify(value);
112
+ else result[mappedKey] = value;
113
+ if (numericFields[key] && value) result[numericFields[key]] = this.toSortableNumeric(String(value));
114
+ }
115
+ if (knownColumns) {
116
+ const existingUnknownFields = {};
117
+ for (const [key, value] of Object.entries(existing)) if (!knownColumns.has(key) && key !== "data") existingUnknownFields[key] = value;
118
+ const mergedUnknownFields = {
119
+ ...existingUnknownFields,
120
+ ...unknownFields
121
+ };
122
+ if (Object.keys(mergedUnknownFields).length > 0) {
123
+ let baseData = {};
124
+ if (result.data && typeof result.data === "string") try {
125
+ baseData = JSON.parse(result.data);
126
+ } catch {
127
+ baseData = {};
128
+ }
129
+ else if (updates.data && typeof updates.data === "object") baseData = updates.data;
130
+ result.data = JSON.stringify({
131
+ ...baseData,
132
+ ...mergedUnknownFields
133
+ });
134
+ }
135
+ }
136
+ return result;
137
+ }
138
+ /**
139
+ * Convert a big number string to a sortable numeric string
140
+ * Pads with leading zeros to ensure lexicographic sorting works
141
+ */
142
+ toSortableNumeric(value) {
143
+ return (value.replace(/^0+/, "") || "0").padStart(40, "0");
144
+ }
145
+ /**
146
+ * Deserialize data from database read
147
+ * - Parses JSON strings back to objects
148
+ * - Converts integers (0/1) back to booleans
149
+ * - Unmaps column names
150
+ * - Extracts unknown fields from 'data' column back to top level
151
+ */
152
+ deserializeFromDb(data) {
153
+ if (!data) return null;
154
+ const result = {};
155
+ const jsonFields = new Set(this.schema.jsonFields || []);
156
+ const booleanFields = new Set(this.schema.booleanFields || []);
157
+ const numericFieldValues = new Set(Object.values(this.schema.numericFields || {}));
158
+ const knownColumns = this.schema.knownColumns ? new Set(this.schema.knownColumns) : null;
159
+ for (const [key, value] of Object.entries(data)) {
160
+ if (numericFieldValues.has(key)) continue;
161
+ const unmappedKey = this.unmapColumnName(key);
162
+ if (booleanFields.has(unmappedKey) && typeof value === "number") result[unmappedKey] = value === 1;
163
+ else if (jsonFields.has(unmappedKey) && typeof value === "string") try {
164
+ result[unmappedKey] = JSON.parse(value);
165
+ } catch {
166
+ result[unmappedKey] = value;
167
+ }
168
+ else result[unmappedKey] = value;
169
+ }
170
+ if (knownColumns && result.data && typeof result.data === "object") {
171
+ const dataObj = result.data;
172
+ for (const [key, value] of Object.entries(dataObj)) if (!knownColumns.has(key)) result[key] = value;
173
+ }
174
+ delete result.$loki;
175
+ delete result.meta;
176
+ return result;
177
+ }
178
+ /**
179
+ * Internal get implementation - called by BaseIndex.get() via hooks
180
+ */
181
+ async _get(key) {
182
+ if (!key) return null;
183
+ let query;
184
+ if (Array.isArray(this.uniqIndex)) if (typeof key === "object") {
185
+ const keyObj = key;
186
+ query = Object.fromEntries(this.uniqIndex.map((k) => [this.mapColumnName(k), keyObj[k]]));
187
+ } else return null;
188
+ else {
189
+ const id = typeof key === "string" ? key : key[this.primaryKey];
190
+ query = { [this.mapColumnName(this.primaryKey)]: id };
191
+ }
192
+ let qb = this.db.selectFrom(this.name).selectAll();
193
+ for (const [k, v] of Object.entries(query)) {
194
+ if (v === void 0) continue;
195
+ qb = qb.where(k, "=", v);
196
+ }
197
+ const result = await qb.executeTakeFirst();
198
+ return this.deserializeFromDb(result);
199
+ }
200
+ /**
201
+ * Internal insert implementation - called by BaseIndex.insert() via hooks
202
+ */
203
+ async _insert(doc) {
204
+ const data = doc;
205
+ const serialized = this.serializeForDb(data);
206
+ await this.db.insertInto(this.name).values(serialized).execute();
207
+ return data;
208
+ }
209
+ /**
210
+ * Internal update implementation - called by BaseIndex.update() via hooks
211
+ */
212
+ async _update(key, updates) {
213
+ const existing = await this._get(key);
214
+ if (!existing) throw new Error(`${this.name} does not exist: ${JSON.stringify(key)}`);
215
+ let query;
216
+ if (Array.isArray(this.uniqIndex)) {
217
+ const keyObj = typeof key === "object" ? key : existing;
218
+ query = Object.fromEntries(this.uniqIndex.map((k) => [this.mapColumnName(k), keyObj[k]]));
219
+ } else {
220
+ const id = typeof key === "string" ? key : key[this.primaryKey];
221
+ query = { [this.mapColumnName(this.primaryKey)]: id };
222
+ }
223
+ const updateAttrs = { ...updates };
224
+ if (Array.isArray(this.uniqIndex)) for (const k of this.uniqIndex) delete updateAttrs[k];
225
+ else delete updateAttrs[this.primaryKey];
226
+ if (Object.keys(updateAttrs).length > 0) {
227
+ const serialized = this.serializeForDbUpdate(updateAttrs, existing);
228
+ let qb = this.db.updateTable(this.name).set(serialized);
229
+ for (const [k, v] of Object.entries(query)) {
230
+ if (v === void 0) continue;
231
+ qb = qb.where(k, "=", v);
232
+ }
233
+ await qb.execute();
234
+ }
235
+ return {
236
+ ...existing,
237
+ ...updateAttrs
238
+ };
239
+ }
240
+ /**
241
+ * Count documents matching a query
242
+ */
243
+ async count(query) {
244
+ let qb = this.db.selectFrom(this.name).select(this.db.fn.countAll().as("count"));
245
+ if (query && typeof query === "object") for (const [k, v] of Object.entries(query)) {
246
+ const mappedKey = this.mapColumnName(k);
247
+ if (v && typeof v === "object" && "$in" in v) qb = qb.where(mappedKey, "in", v.$in);
248
+ else qb = qb.where(mappedKey, "=", v);
249
+ }
250
+ return (await qb.executeTakeFirst())?.count ?? 0;
251
+ }
252
+ /**
253
+ * Internal reset implementation - called by BaseIndex.reset() via hooks
254
+ */
255
+ async _reset() {
256
+ await this.db.deleteFrom(this.name).execute();
257
+ }
258
+ };
259
+ var base_default = SqliteTable;
260
+
261
+ //#endregion
262
+ export { base_default as default };
@@ -0,0 +1,18 @@
1
+ import SqliteTable from "./base.mjs";
2
+ import { TIndexedTransaction } from "@ocap/types";
3
+
4
+ //#region src/table/transaction.d.ts
5
+
6
+ /**
7
+ * Transaction table for SQLite IndexDB
8
+ * Extends base table to call formatTxBeforeInsert on insert
9
+ */
10
+ declare class TransactionTable extends SqliteTable<TIndexedTransaction> {
11
+ /**
12
+ * Override _insert to format transaction before inserting
13
+ * This populates assets, tokens, factories, etc. from itxJson
14
+ */
15
+ _insert(row: TIndexedTransaction | Record<string, unknown>): Promise<TIndexedTransaction>;
16
+ }
17
+ //#endregion
18
+ export { TransactionTable as default };
@@ -0,0 +1,22 @@
1
+ import base_default from "./base.mjs";
2
+ import { formatTxBeforeInsert } from "@ocap/indexdb";
3
+
4
+ //#region src/table/transaction.ts
5
+ /**
6
+ * Transaction table for SQLite IndexDB
7
+ * Extends base table to call formatTxBeforeInsert on insert
8
+ */
9
+ var TransactionTable = class extends base_default {
10
+ /**
11
+ * Override _insert to format transaction before inserting
12
+ * This populates assets, tokens, factories, etc. from itxJson
13
+ */
14
+ async _insert(row) {
15
+ const formatted = formatTxBeforeInsert(row);
16
+ return super._insert(formatted);
17
+ }
18
+ };
19
+ var transaction_default = TransactionTable;
20
+
21
+ //#endregion
22
+ export { transaction_default as default };
@@ -0,0 +1,43 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __exportAll = (all, symbols) => {
9
+ let target = {};
10
+ for (var name in all) {
11
+ __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ }
16
+ if (symbols) {
17
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
18
+ }
19
+ return target;
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
24
+ key = keys[i];
25
+ if (!__hasOwnProp.call(to, key) && key !== except) {
26
+ __defProp(to, key, {
27
+ get: ((k) => from[k]).bind(null, key),
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ });
30
+ }
31
+ }
32
+ }
33
+ return to;
34
+ };
35
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
36
+ value: mod,
37
+ enumerable: true
38
+ }) : target, mod));
39
+
40
+ //#endregion
41
+
42
+ exports.__exportAll = __exportAll;
43
+ exports.__toESM = __toESM;