@dexto/storage 1.6.0
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/LICENSE +44 -0
- package/README.md +80 -0
- package/dist/blob/factories/index.cjs +31 -0
- package/dist/blob/factories/index.d.cts +6 -0
- package/dist/blob/factories/index.d.ts +6 -0
- package/dist/blob/factories/index.d.ts.map +1 -0
- package/dist/blob/factories/index.js +6 -0
- package/dist/blob/factories/local.cjs +38 -0
- package/dist/blob/factories/local.d.cts +21 -0
- package/dist/blob/factories/local.d.ts +17 -0
- package/dist/blob/factories/local.d.ts.map +1 -0
- package/dist/blob/factories/local.js +14 -0
- package/dist/blob/factories/memory.cjs +44 -0
- package/dist/blob/factories/memory.d.cts +21 -0
- package/dist/blob/factories/memory.d.ts +17 -0
- package/dist/blob/factories/memory.d.ts.map +1 -0
- package/dist/blob/factories/memory.js +20 -0
- package/dist/blob/factory.cjs +16 -0
- package/dist/blob/factory.d.cts +36 -0
- package/dist/blob/factory.d.ts +35 -0
- package/dist/blob/factory.d.ts.map +1 -0
- package/dist/blob/factory.js +0 -0
- package/dist/blob/index.cjs +45 -0
- package/dist/blob/index.d.cts +8 -0
- package/dist/blob/index.d.ts +26 -0
- package/dist/blob/index.d.ts.map +1 -0
- package/dist/blob/index.js +19 -0
- package/dist/blob/local-blob-store.cjs +532 -0
- package/dist/blob/local-blob-store.d.cts +56 -0
- package/dist/blob/local-blob-store.d.ts +54 -0
- package/dist/blob/local-blob-store.d.ts.map +1 -0
- package/dist/blob/local-blob-store.js +498 -0
- package/dist/blob/memory-blob-store.cjs +327 -0
- package/dist/blob/memory-blob-store.d.cts +69 -0
- package/dist/blob/memory-blob-store.d.ts +67 -0
- package/dist/blob/memory-blob-store.d.ts.map +1 -0
- package/dist/blob/memory-blob-store.js +303 -0
- package/dist/blob/schemas.cjs +52 -0
- package/dist/blob/schemas.d.cts +87 -0
- package/dist/blob/schemas.d.ts +86 -0
- package/dist/blob/schemas.d.ts.map +1 -0
- package/dist/blob/schemas.js +25 -0
- package/dist/blob/types.cjs +16 -0
- package/dist/blob/types.d.cts +1 -0
- package/dist/blob/types.d.ts +2 -0
- package/dist/blob/types.d.ts.map +1 -0
- package/dist/blob/types.js +0 -0
- package/dist/cache/factories/index.cjs +31 -0
- package/dist/cache/factories/index.d.cts +6 -0
- package/dist/cache/factories/index.d.ts +6 -0
- package/dist/cache/factories/index.d.ts.map +1 -0
- package/dist/cache/factories/index.js +6 -0
- package/dist/cache/factories/memory.cjs +39 -0
- package/dist/cache/factories/memory.d.cts +21 -0
- package/dist/cache/factories/memory.d.ts +17 -0
- package/dist/cache/factories/memory.d.ts.map +1 -0
- package/dist/cache/factories/memory.js +15 -0
- package/dist/cache/factories/redis.cjs +65 -0
- package/dist/cache/factories/redis.d.cts +24 -0
- package/dist/cache/factories/redis.d.ts +20 -0
- package/dist/cache/factories/redis.d.ts.map +1 -0
- package/dist/cache/factories/redis.js +31 -0
- package/dist/cache/factory.cjs +16 -0
- package/dist/cache/factory.d.cts +42 -0
- package/dist/cache/factory.d.ts +41 -0
- package/dist/cache/factory.d.ts.map +1 -0
- package/dist/cache/factory.js +0 -0
- package/dist/cache/index.cjs +42 -0
- package/dist/cache/index.d.cts +7 -0
- package/dist/cache/index.d.ts +25 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +17 -0
- package/dist/cache/memory-cache-store.cjs +106 -0
- package/dist/cache/memory-cache-store.d.cts +27 -0
- package/dist/cache/memory-cache-store.d.ts +25 -0
- package/dist/cache/memory-cache-store.d.ts.map +1 -0
- package/dist/cache/memory-cache-store.js +82 -0
- package/dist/cache/redis-store.cjs +176 -0
- package/dist/cache/redis-store.d.cts +34 -0
- package/dist/cache/redis-store.d.ts +32 -0
- package/dist/cache/redis-store.d.ts.map +1 -0
- package/dist/cache/redis-store.js +152 -0
- package/dist/cache/schemas.cjs +70 -0
- package/dist/cache/schemas.d.cts +93 -0
- package/dist/cache/schemas.d.ts +91 -0
- package/dist/cache/schemas.d.ts.map +1 -0
- package/dist/cache/schemas.js +43 -0
- package/dist/cache/types.cjs +16 -0
- package/dist/cache/types.d.cts +1 -0
- package/dist/cache/types.d.ts +2 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +0 -0
- package/dist/database/factories/index.cjs +34 -0
- package/dist/database/factories/index.d.cts +7 -0
- package/dist/database/factories/index.d.ts +7 -0
- package/dist/database/factories/index.d.ts.map +1 -0
- package/dist/database/factories/index.js +8 -0
- package/dist/database/factories/memory.cjs +39 -0
- package/dist/database/factories/memory.d.cts +20 -0
- package/dist/database/factories/memory.d.ts +16 -0
- package/dist/database/factories/memory.d.ts.map +1 -0
- package/dist/database/factories/memory.js +15 -0
- package/dist/database/factories/postgres.cjs +61 -0
- package/dist/database/factories/postgres.d.cts +23 -0
- package/dist/database/factories/postgres.d.ts +19 -0
- package/dist/database/factories/postgres.d.ts.map +1 -0
- package/dist/database/factories/postgres.js +27 -0
- package/dist/database/factories/sqlite.cjs +65 -0
- package/dist/database/factories/sqlite.d.cts +24 -0
- package/dist/database/factories/sqlite.d.ts +20 -0
- package/dist/database/factories/sqlite.d.ts.map +1 -0
- package/dist/database/factories/sqlite.js +31 -0
- package/dist/database/factory.cjs +16 -0
- package/dist/database/factory.d.cts +42 -0
- package/dist/database/factory.d.ts +41 -0
- package/dist/database/factory.d.ts.map +1 -0
- package/dist/database/factory.js +0 -0
- package/dist/database/index.cjs +46 -0
- package/dist/database/index.d.cts +8 -0
- package/dist/database/index.d.ts +26 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +24 -0
- package/dist/database/memory-database-store.cjs +121 -0
- package/dist/database/memory-database-store.d.cts +30 -0
- package/dist/database/memory-database-store.d.ts +28 -0
- package/dist/database/memory-database-store.d.ts.map +1 -0
- package/dist/database/memory-database-store.js +97 -0
- package/dist/database/postgres-store.cjs +342 -0
- package/dist/database/postgres-store.d.cts +57 -0
- package/dist/database/postgres-store.d.ts +55 -0
- package/dist/database/postgres-store.d.ts.map +1 -0
- package/dist/database/postgres-store.js +318 -0
- package/dist/database/schemas.cjs +82 -0
- package/dist/database/schemas.d.cts +127 -0
- package/dist/database/schemas.d.ts +125 -0
- package/dist/database/schemas.d.ts.map +1 -0
- package/dist/database/schemas.js +54 -0
- package/dist/database/sqlite-store.cjs +270 -0
- package/dist/database/sqlite-store.d.cts +35 -0
- package/dist/database/sqlite-store.d.ts +33 -0
- package/dist/database/sqlite-store.d.ts.map +1 -0
- package/dist/database/sqlite-store.js +236 -0
- package/dist/database/types.cjs +16 -0
- package/dist/database/types.d.cts +1 -0
- package/dist/database/types.d.ts +2 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +0 -0
- package/dist/index.cjs +82 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/schemas.cjs +67 -0
- package/dist/schemas.d.cts +72 -0
- package/dist/schemas.d.ts +70 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +46 -0
- package/package.json +55 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EnvExpandedString, ErrorScope, ErrorType, StorageErrorCode } from "@dexto/core";
|
|
3
|
+
const DATABASE_TYPES = ["in-memory", "sqlite", "postgres"];
|
|
4
|
+
const BaseDatabaseSchema = z.object({
|
|
5
|
+
maxConnections: z.number().int().positive().optional().describe("Maximum connections"),
|
|
6
|
+
idleTimeoutMillis: z.number().int().positive().optional().describe("Idle timeout in milliseconds"),
|
|
7
|
+
connectionTimeoutMillis: z.number().int().positive().optional().describe("Connection timeout in milliseconds"),
|
|
8
|
+
options: z.record(z.unknown()).optional().describe("Backend-specific options")
|
|
9
|
+
});
|
|
10
|
+
const InMemoryDatabaseSchema = BaseDatabaseSchema.extend({
|
|
11
|
+
type: z.literal("in-memory")
|
|
12
|
+
// In-memory database doesn't need connection options, but inherits pool options for consistency
|
|
13
|
+
}).strict();
|
|
14
|
+
const SqliteDatabaseSchema = BaseDatabaseSchema.extend({
|
|
15
|
+
type: z.literal("sqlite"),
|
|
16
|
+
path: z.string().describe(
|
|
17
|
+
"SQLite database file path (required for SQLite - CLI enrichment provides per-agent path)"
|
|
18
|
+
)
|
|
19
|
+
}).strict();
|
|
20
|
+
const PostgresDatabaseSchema = BaseDatabaseSchema.extend({
|
|
21
|
+
type: z.literal("postgres"),
|
|
22
|
+
url: EnvExpandedString().optional().describe("PostgreSQL connection URL (postgresql://...)"),
|
|
23
|
+
connectionString: EnvExpandedString().optional().describe("PostgreSQL connection string"),
|
|
24
|
+
host: z.string().optional().describe("PostgreSQL host"),
|
|
25
|
+
port: z.number().int().positive().optional().describe("PostgreSQL port"),
|
|
26
|
+
database: z.string().optional().describe("PostgreSQL database name"),
|
|
27
|
+
password: z.string().optional().describe("PostgreSQL password"),
|
|
28
|
+
// TODO: keyPrefix is reserved for future use - allows namespacing keys when multiple
|
|
29
|
+
// agents or environments share the same database (e.g., "dev:agent1:" vs "prod:agent2:")
|
|
30
|
+
keyPrefix: z.string().optional().describe('Optional key prefix for namespacing (e.g., "dev:myagent:")')
|
|
31
|
+
}).strict().superRefine((data, ctx) => {
|
|
32
|
+
if (!data.url && !data.connectionString && !data.host) {
|
|
33
|
+
ctx.addIssue({
|
|
34
|
+
code: z.ZodIssueCode.custom,
|
|
35
|
+
message: "PostgreSQL database requires one of 'url', 'connectionString', or 'host' to be specified",
|
|
36
|
+
path: ["url"],
|
|
37
|
+
params: {
|
|
38
|
+
code: StorageErrorCode.CONNECTION_CONFIG_MISSING,
|
|
39
|
+
scope: ErrorScope.STORAGE,
|
|
40
|
+
type: ErrorType.USER
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const DatabaseConfigSchema = z.object({
|
|
46
|
+
type: z.string().describe("Database backend type identifier")
|
|
47
|
+
}).passthrough().describe("Database configuration (validated by image factory)");
|
|
48
|
+
export {
|
|
49
|
+
DATABASE_TYPES,
|
|
50
|
+
DatabaseConfigSchema,
|
|
51
|
+
InMemoryDatabaseSchema,
|
|
52
|
+
PostgresDatabaseSchema,
|
|
53
|
+
SqliteDatabaseSchema
|
|
54
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
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 __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var sqlite_store_exports = {};
|
|
30
|
+
__export(sqlite_store_exports, {
|
|
31
|
+
SQLiteStore: () => SQLiteStore
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(sqlite_store_exports);
|
|
34
|
+
var import_path = require("path");
|
|
35
|
+
var import_fs = require("fs");
|
|
36
|
+
var import_core = require("@dexto/core");
|
|
37
|
+
let BetterSqlite3Database;
|
|
38
|
+
class SQLiteStore {
|
|
39
|
+
db = null;
|
|
40
|
+
// Database.Database
|
|
41
|
+
dbPath;
|
|
42
|
+
config;
|
|
43
|
+
logger;
|
|
44
|
+
constructor(config, logger) {
|
|
45
|
+
this.config = config;
|
|
46
|
+
this.dbPath = "";
|
|
47
|
+
this.logger = logger.createChild(import_core.DextoLogComponent.STORAGE);
|
|
48
|
+
}
|
|
49
|
+
initializeTables() {
|
|
50
|
+
this.logger.debug("SQLite initializing database schema...");
|
|
51
|
+
try {
|
|
52
|
+
this.db.exec(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
54
|
+
key TEXT PRIMARY KEY,
|
|
55
|
+
value TEXT NOT NULL,
|
|
56
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
57
|
+
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
58
|
+
)
|
|
59
|
+
`);
|
|
60
|
+
this.db.exec(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS list_store (
|
|
62
|
+
key TEXT NOT NULL,
|
|
63
|
+
value TEXT NOT NULL,
|
|
64
|
+
sequence INTEGER,
|
|
65
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
66
|
+
PRIMARY KEY (key, sequence)
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
this.db.exec(`
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_kv_store_key ON kv_store(key);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_list_store_key ON list_store(key);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_list_store_sequence ON list_store(key, sequence);
|
|
73
|
+
`);
|
|
74
|
+
this.logger.debug(
|
|
75
|
+
"SQLite database schema initialized: kv_store, list_store tables with indexes"
|
|
76
|
+
);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw import_core.StorageError.migrationFailed(
|
|
79
|
+
error instanceof Error ? error.message : String(error),
|
|
80
|
+
{
|
|
81
|
+
operation: "table_initialization",
|
|
82
|
+
backend: "sqlite"
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async connect() {
|
|
88
|
+
if (this.db) return;
|
|
89
|
+
if (!BetterSqlite3Database) {
|
|
90
|
+
try {
|
|
91
|
+
const module2 = await import("better-sqlite3");
|
|
92
|
+
BetterSqlite3Database = module2.default || module2;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const err = error;
|
|
95
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
96
|
+
throw import_core.StorageError.dependencyNotInstalled(
|
|
97
|
+
"SQLite",
|
|
98
|
+
"better-sqlite3",
|
|
99
|
+
"npm install better-sqlite3"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
throw import_core.StorageError.connectionFailed(
|
|
103
|
+
`Failed to import better-sqlite3: ${error instanceof Error ? error.message : String(error)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.dbPath = this.config.path;
|
|
108
|
+
this.logger.info(`SQLite using database file: ${this.dbPath}`);
|
|
109
|
+
const dir = (0, import_path.dirname)(this.dbPath);
|
|
110
|
+
this.logger.debug(`SQLite ensuring directory exists: ${dir}`);
|
|
111
|
+
try {
|
|
112
|
+
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.logger.debug(`Directory creation result: ${error ? "exists" : "created"}`);
|
|
115
|
+
}
|
|
116
|
+
const sqliteOptions = this.config.options || {};
|
|
117
|
+
this.logger.debug(`SQLite initializing database with config:`, {
|
|
118
|
+
readonly: sqliteOptions.readonly || false,
|
|
119
|
+
fileMustExist: sqliteOptions.fileMustExist || false,
|
|
120
|
+
timeout: sqliteOptions.timeout || 5e3
|
|
121
|
+
});
|
|
122
|
+
this.db = new BetterSqlite3Database(this.dbPath, {
|
|
123
|
+
readonly: sqliteOptions.readonly || false,
|
|
124
|
+
fileMustExist: sqliteOptions.fileMustExist || false,
|
|
125
|
+
timeout: sqliteOptions.timeout || 5e3,
|
|
126
|
+
verbose: sqliteOptions.verbose ? (message, ...additionalArgs) => {
|
|
127
|
+
const messageStr = typeof message === "string" ? message : typeof message === "object" && message !== null ? JSON.stringify(message) : String(message);
|
|
128
|
+
this.logger.debug(
|
|
129
|
+
messageStr,
|
|
130
|
+
additionalArgs.length > 0 ? { args: additionalArgs } : void 0
|
|
131
|
+
);
|
|
132
|
+
} : void 0
|
|
133
|
+
});
|
|
134
|
+
this.db.pragma("journal_mode = WAL");
|
|
135
|
+
this.logger.debug("SQLite enabled WAL mode for better concurrency");
|
|
136
|
+
this.initializeTables();
|
|
137
|
+
this.logger.info(`\u2705 SQLite store successfully connected to: ${this.dbPath}`);
|
|
138
|
+
}
|
|
139
|
+
async disconnect() {
|
|
140
|
+
if (this.db) {
|
|
141
|
+
this.db.close();
|
|
142
|
+
this.db = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
isConnected() {
|
|
146
|
+
return this.db !== null;
|
|
147
|
+
}
|
|
148
|
+
getStoreType() {
|
|
149
|
+
return "sqlite";
|
|
150
|
+
}
|
|
151
|
+
// Core operations
|
|
152
|
+
async get(key) {
|
|
153
|
+
this.checkConnection();
|
|
154
|
+
try {
|
|
155
|
+
const row = this.db.prepare("SELECT value FROM kv_store WHERE key = ?").get(key);
|
|
156
|
+
return row ? JSON.parse(row.value) : void 0;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw import_core.StorageError.readFailed(
|
|
159
|
+
"get",
|
|
160
|
+
error instanceof Error ? error.message : String(error),
|
|
161
|
+
{ key }
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async set(key, value) {
|
|
166
|
+
this.checkConnection();
|
|
167
|
+
try {
|
|
168
|
+
const serialized = JSON.stringify(value);
|
|
169
|
+
this.db.prepare(
|
|
170
|
+
"INSERT OR REPLACE INTO kv_store (key, value, updated_at) VALUES (?, ?, ?)"
|
|
171
|
+
).run(key, serialized, Date.now());
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw import_core.StorageError.writeFailed(
|
|
174
|
+
"set",
|
|
175
|
+
error instanceof Error ? error.message : String(error),
|
|
176
|
+
{ key }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async delete(key) {
|
|
181
|
+
this.checkConnection();
|
|
182
|
+
try {
|
|
183
|
+
this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(key);
|
|
184
|
+
this.db.prepare("DELETE FROM list_store WHERE key = ?").run(key);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw import_core.StorageError.deleteFailed(
|
|
187
|
+
"delete",
|
|
188
|
+
error instanceof Error ? error.message : String(error),
|
|
189
|
+
{ key }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// List operations
|
|
194
|
+
async list(prefix) {
|
|
195
|
+
this.checkConnection();
|
|
196
|
+
try {
|
|
197
|
+
const kvKeys = this.db.prepare("SELECT key FROM kv_store WHERE key LIKE ?").all(`${prefix}%`);
|
|
198
|
+
const listKeys = this.db.prepare("SELECT DISTINCT key FROM list_store WHERE key LIKE ?").all(`${prefix}%`);
|
|
199
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
200
|
+
...kvKeys.map((row) => row.key),
|
|
201
|
+
...listKeys.map((row) => row.key)
|
|
202
|
+
]);
|
|
203
|
+
return Array.from(allKeys).sort();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw import_core.StorageError.readFailed(
|
|
206
|
+
"list",
|
|
207
|
+
error instanceof Error ? error.message : String(error),
|
|
208
|
+
{ prefix }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async append(key, item) {
|
|
213
|
+
this.checkConnection();
|
|
214
|
+
try {
|
|
215
|
+
const serialized = JSON.stringify(item);
|
|
216
|
+
this.db.prepare(
|
|
217
|
+
"INSERT INTO list_store (key, value, sequence) VALUES (?, ?, (SELECT COALESCE(MAX(sequence), 0) + 1 FROM list_store WHERE key = ?))"
|
|
218
|
+
).run(key, serialized, key);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
throw import_core.StorageError.writeFailed(
|
|
221
|
+
"append",
|
|
222
|
+
error instanceof Error ? error.message : String(error),
|
|
223
|
+
{ key }
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async getRange(key, start, count) {
|
|
228
|
+
this.checkConnection();
|
|
229
|
+
try {
|
|
230
|
+
const rows = this.db.prepare(
|
|
231
|
+
"SELECT value FROM list_store WHERE key = ? ORDER BY sequence ASC LIMIT ? OFFSET ?"
|
|
232
|
+
).all(key, count, start);
|
|
233
|
+
return rows.map((row) => JSON.parse(row.value));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw import_core.StorageError.readFailed(
|
|
236
|
+
"getRange",
|
|
237
|
+
error instanceof Error ? error.message : String(error),
|
|
238
|
+
{ key, start, count }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Schema management
|
|
243
|
+
checkConnection() {
|
|
244
|
+
if (!this.db) {
|
|
245
|
+
throw import_core.StorageError.notConnected("SQLiteStore");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Maintenance operations
|
|
249
|
+
async vacuum() {
|
|
250
|
+
this.checkConnection();
|
|
251
|
+
this.db.exec("VACUUM");
|
|
252
|
+
}
|
|
253
|
+
async getStats() {
|
|
254
|
+
this.checkConnection();
|
|
255
|
+
const kvCount = this.db.prepare("SELECT COUNT(*) as count FROM kv_store").get();
|
|
256
|
+
const listCount = this.db.prepare("SELECT COUNT(*) as count FROM list_store").get();
|
|
257
|
+
const dbSize = this.db.prepare(
|
|
258
|
+
"SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"
|
|
259
|
+
).get();
|
|
260
|
+
return {
|
|
261
|
+
kvCount: kvCount.count,
|
|
262
|
+
listCount: listCount.count,
|
|
263
|
+
dbSize: dbSize.size
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
268
|
+
0 && (module.exports = {
|
|
269
|
+
SQLiteStore
|
|
270
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Database, Logger } from '@dexto/core';
|
|
2
|
+
import { SqliteDatabaseConfig } from './schemas.cjs';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SQLite database store for local development and production.
|
|
7
|
+
* Implements the Database interface with proper schema and connection handling.
|
|
8
|
+
*/
|
|
9
|
+
declare class SQLiteStore implements Database {
|
|
10
|
+
private db;
|
|
11
|
+
private dbPath;
|
|
12
|
+
private config;
|
|
13
|
+
private logger;
|
|
14
|
+
constructor(config: SqliteDatabaseConfig, logger: Logger);
|
|
15
|
+
private initializeTables;
|
|
16
|
+
connect(): Promise<void>;
|
|
17
|
+
disconnect(): Promise<void>;
|
|
18
|
+
isConnected(): boolean;
|
|
19
|
+
getStoreType(): string;
|
|
20
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
21
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
22
|
+
delete(key: string): Promise<void>;
|
|
23
|
+
list(prefix: string): Promise<string[]>;
|
|
24
|
+
append<T>(key: string, item: T): Promise<void>;
|
|
25
|
+
getRange<T>(key: string, start: number, count: number): Promise<T[]>;
|
|
26
|
+
private checkConnection;
|
|
27
|
+
vacuum(): Promise<void>;
|
|
28
|
+
getStats(): Promise<{
|
|
29
|
+
kvCount: number;
|
|
30
|
+
listCount: number;
|
|
31
|
+
dbSize: number;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { SQLiteStore };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Database } from './types.js';
|
|
2
|
+
import type { Logger } from '@dexto/core';
|
|
3
|
+
import type { SqliteDatabaseConfig } from './schemas.js';
|
|
4
|
+
/**
|
|
5
|
+
* SQLite database store for local development and production.
|
|
6
|
+
* Implements the Database interface with proper schema and connection handling.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SQLiteStore implements Database {
|
|
9
|
+
private db;
|
|
10
|
+
private dbPath;
|
|
11
|
+
private config;
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(config: SqliteDatabaseConfig, logger: Logger);
|
|
14
|
+
private initializeTables;
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
disconnect(): Promise<void>;
|
|
17
|
+
isConnected(): boolean;
|
|
18
|
+
getStoreType(): string;
|
|
19
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
20
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
21
|
+
delete(key: string): Promise<void>;
|
|
22
|
+
list(prefix: string): Promise<string[]>;
|
|
23
|
+
append<T>(key: string, item: T): Promise<void>;
|
|
24
|
+
getRange<T>(key: string, start: number, count: number): Promise<T[]>;
|
|
25
|
+
private checkConnection;
|
|
26
|
+
vacuum(): Promise<void>;
|
|
27
|
+
getStats(): Promise<{
|
|
28
|
+
kvCount: number;
|
|
29
|
+
listCount: number;
|
|
30
|
+
dbSize: number;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=sqlite-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/database/sqlite-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAKzD;;;GAGG;AACH,qBAAa,WAAY,YAAW,QAAQ;IACxC,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM;IAOxD,OAAO,CAAC,gBAAgB;IA8ClB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2ExB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,MAAM;IAKhB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAgB3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAelC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA0BvC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9C,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAqB1E,OAAO,CAAC,eAAe;IAOjB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,QAAQ,IAAI,OAAO,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAClB,CAAC;CAqBL"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { mkdirSync } from "fs";
|
|
3
|
+
import { DextoLogComponent, StorageError } from "@dexto/core";
|
|
4
|
+
let BetterSqlite3Database;
|
|
5
|
+
class SQLiteStore {
|
|
6
|
+
db = null;
|
|
7
|
+
// Database.Database
|
|
8
|
+
dbPath;
|
|
9
|
+
config;
|
|
10
|
+
logger;
|
|
11
|
+
constructor(config, logger) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.dbPath = "";
|
|
14
|
+
this.logger = logger.createChild(DextoLogComponent.STORAGE);
|
|
15
|
+
}
|
|
16
|
+
initializeTables() {
|
|
17
|
+
this.logger.debug("SQLite initializing database schema...");
|
|
18
|
+
try {
|
|
19
|
+
this.db.exec(`
|
|
20
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
21
|
+
key TEXT PRIMARY KEY,
|
|
22
|
+
value TEXT NOT NULL,
|
|
23
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
24
|
+
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
this.db.exec(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS list_store (
|
|
29
|
+
key TEXT NOT NULL,
|
|
30
|
+
value TEXT NOT NULL,
|
|
31
|
+
sequence INTEGER,
|
|
32
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
33
|
+
PRIMARY KEY (key, sequence)
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
this.db.exec(`
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_kv_store_key ON kv_store(key);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_list_store_key ON list_store(key);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_list_store_sequence ON list_store(key, sequence);
|
|
40
|
+
`);
|
|
41
|
+
this.logger.debug(
|
|
42
|
+
"SQLite database schema initialized: kv_store, list_store tables with indexes"
|
|
43
|
+
);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw StorageError.migrationFailed(
|
|
46
|
+
error instanceof Error ? error.message : String(error),
|
|
47
|
+
{
|
|
48
|
+
operation: "table_initialization",
|
|
49
|
+
backend: "sqlite"
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async connect() {
|
|
55
|
+
if (this.db) return;
|
|
56
|
+
if (!BetterSqlite3Database) {
|
|
57
|
+
try {
|
|
58
|
+
const module = await import("better-sqlite3");
|
|
59
|
+
BetterSqlite3Database = module.default || module;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const err = error;
|
|
62
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
63
|
+
throw StorageError.dependencyNotInstalled(
|
|
64
|
+
"SQLite",
|
|
65
|
+
"better-sqlite3",
|
|
66
|
+
"npm install better-sqlite3"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
throw StorageError.connectionFailed(
|
|
70
|
+
`Failed to import better-sqlite3: ${error instanceof Error ? error.message : String(error)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.dbPath = this.config.path;
|
|
75
|
+
this.logger.info(`SQLite using database file: ${this.dbPath}`);
|
|
76
|
+
const dir = dirname(this.dbPath);
|
|
77
|
+
this.logger.debug(`SQLite ensuring directory exists: ${dir}`);
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(dir, { recursive: true });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.logger.debug(`Directory creation result: ${error ? "exists" : "created"}`);
|
|
82
|
+
}
|
|
83
|
+
const sqliteOptions = this.config.options || {};
|
|
84
|
+
this.logger.debug(`SQLite initializing database with config:`, {
|
|
85
|
+
readonly: sqliteOptions.readonly || false,
|
|
86
|
+
fileMustExist: sqliteOptions.fileMustExist || false,
|
|
87
|
+
timeout: sqliteOptions.timeout || 5e3
|
|
88
|
+
});
|
|
89
|
+
this.db = new BetterSqlite3Database(this.dbPath, {
|
|
90
|
+
readonly: sqliteOptions.readonly || false,
|
|
91
|
+
fileMustExist: sqliteOptions.fileMustExist || false,
|
|
92
|
+
timeout: sqliteOptions.timeout || 5e3,
|
|
93
|
+
verbose: sqliteOptions.verbose ? (message, ...additionalArgs) => {
|
|
94
|
+
const messageStr = typeof message === "string" ? message : typeof message === "object" && message !== null ? JSON.stringify(message) : String(message);
|
|
95
|
+
this.logger.debug(
|
|
96
|
+
messageStr,
|
|
97
|
+
additionalArgs.length > 0 ? { args: additionalArgs } : void 0
|
|
98
|
+
);
|
|
99
|
+
} : void 0
|
|
100
|
+
});
|
|
101
|
+
this.db.pragma("journal_mode = WAL");
|
|
102
|
+
this.logger.debug("SQLite enabled WAL mode for better concurrency");
|
|
103
|
+
this.initializeTables();
|
|
104
|
+
this.logger.info(`\u2705 SQLite store successfully connected to: ${this.dbPath}`);
|
|
105
|
+
}
|
|
106
|
+
async disconnect() {
|
|
107
|
+
if (this.db) {
|
|
108
|
+
this.db.close();
|
|
109
|
+
this.db = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
isConnected() {
|
|
113
|
+
return this.db !== null;
|
|
114
|
+
}
|
|
115
|
+
getStoreType() {
|
|
116
|
+
return "sqlite";
|
|
117
|
+
}
|
|
118
|
+
// Core operations
|
|
119
|
+
async get(key) {
|
|
120
|
+
this.checkConnection();
|
|
121
|
+
try {
|
|
122
|
+
const row = this.db.prepare("SELECT value FROM kv_store WHERE key = ?").get(key);
|
|
123
|
+
return row ? JSON.parse(row.value) : void 0;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw StorageError.readFailed(
|
|
126
|
+
"get",
|
|
127
|
+
error instanceof Error ? error.message : String(error),
|
|
128
|
+
{ key }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async set(key, value) {
|
|
133
|
+
this.checkConnection();
|
|
134
|
+
try {
|
|
135
|
+
const serialized = JSON.stringify(value);
|
|
136
|
+
this.db.prepare(
|
|
137
|
+
"INSERT OR REPLACE INTO kv_store (key, value, updated_at) VALUES (?, ?, ?)"
|
|
138
|
+
).run(key, serialized, Date.now());
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw StorageError.writeFailed(
|
|
141
|
+
"set",
|
|
142
|
+
error instanceof Error ? error.message : String(error),
|
|
143
|
+
{ key }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async delete(key) {
|
|
148
|
+
this.checkConnection();
|
|
149
|
+
try {
|
|
150
|
+
this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(key);
|
|
151
|
+
this.db.prepare("DELETE FROM list_store WHERE key = ?").run(key);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw StorageError.deleteFailed(
|
|
154
|
+
"delete",
|
|
155
|
+
error instanceof Error ? error.message : String(error),
|
|
156
|
+
{ key }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// List operations
|
|
161
|
+
async list(prefix) {
|
|
162
|
+
this.checkConnection();
|
|
163
|
+
try {
|
|
164
|
+
const kvKeys = this.db.prepare("SELECT key FROM kv_store WHERE key LIKE ?").all(`${prefix}%`);
|
|
165
|
+
const listKeys = this.db.prepare("SELECT DISTINCT key FROM list_store WHERE key LIKE ?").all(`${prefix}%`);
|
|
166
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
167
|
+
...kvKeys.map((row) => row.key),
|
|
168
|
+
...listKeys.map((row) => row.key)
|
|
169
|
+
]);
|
|
170
|
+
return Array.from(allKeys).sort();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw StorageError.readFailed(
|
|
173
|
+
"list",
|
|
174
|
+
error instanceof Error ? error.message : String(error),
|
|
175
|
+
{ prefix }
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async append(key, item) {
|
|
180
|
+
this.checkConnection();
|
|
181
|
+
try {
|
|
182
|
+
const serialized = JSON.stringify(item);
|
|
183
|
+
this.db.prepare(
|
|
184
|
+
"INSERT INTO list_store (key, value, sequence) VALUES (?, ?, (SELECT COALESCE(MAX(sequence), 0) + 1 FROM list_store WHERE key = ?))"
|
|
185
|
+
).run(key, serialized, key);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw StorageError.writeFailed(
|
|
188
|
+
"append",
|
|
189
|
+
error instanceof Error ? error.message : String(error),
|
|
190
|
+
{ key }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async getRange(key, start, count) {
|
|
195
|
+
this.checkConnection();
|
|
196
|
+
try {
|
|
197
|
+
const rows = this.db.prepare(
|
|
198
|
+
"SELECT value FROM list_store WHERE key = ? ORDER BY sequence ASC LIMIT ? OFFSET ?"
|
|
199
|
+
).all(key, count, start);
|
|
200
|
+
return rows.map((row) => JSON.parse(row.value));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw StorageError.readFailed(
|
|
203
|
+
"getRange",
|
|
204
|
+
error instanceof Error ? error.message : String(error),
|
|
205
|
+
{ key, start, count }
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Schema management
|
|
210
|
+
checkConnection() {
|
|
211
|
+
if (!this.db) {
|
|
212
|
+
throw StorageError.notConnected("SQLiteStore");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Maintenance operations
|
|
216
|
+
async vacuum() {
|
|
217
|
+
this.checkConnection();
|
|
218
|
+
this.db.exec("VACUUM");
|
|
219
|
+
}
|
|
220
|
+
async getStats() {
|
|
221
|
+
this.checkConnection();
|
|
222
|
+
const kvCount = this.db.prepare("SELECT COUNT(*) as count FROM kv_store").get();
|
|
223
|
+
const listCount = this.db.prepare("SELECT COUNT(*) as count FROM list_store").get();
|
|
224
|
+
const dbSize = this.db.prepare(
|
|
225
|
+
"SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"
|
|
226
|
+
).get();
|
|
227
|
+
return {
|
|
228
|
+
kvCount: kvCount.count,
|
|
229
|
+
listCount: listCount.count,
|
|
230
|
+
dbSize: dbSize.size
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export {
|
|
235
|
+
SQLiteStore
|
|
236
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var types_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(types_exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Database } from '@dexto/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/database/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
|
File without changes
|