@kb-labs/adapters 0.5.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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/adapters-sqlite",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "SQLite adapter implementing ISQLDatabase interface",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./secure-sql": {
|
|
14
|
+
"import": "./dist/secure-sql.js",
|
|
15
|
+
"types": "./dist/secure-sql.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"type-check": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run --passWithNoTests",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"lint": "eslint src --ext .ts",
|
|
31
|
+
"lint:fix": "eslint . --fix"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"better-sqlite3": "^11.8.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@kb-labs/core-platform": "*"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@kb-labs/core-platform": "link:../../../../platform/kb-labs-core/packages/core-platform",
|
|
41
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
42
|
+
"@types/node": "^24.3.3",
|
|
43
|
+
"eslint": "^9",
|
|
44
|
+
"tsup": "^8.5.0",
|
|
45
|
+
"typescript": "^5.6.3",
|
|
46
|
+
"vitest": "^3.2.4",
|
|
47
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
48
|
+
"rimraf": "^6.0.1"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0",
|
|
52
|
+
"pnpm": ">=9.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
5
|
+
import { createAdapter } from "./index.js";
|
|
6
|
+
|
|
7
|
+
describe("SQLiteAdapter", () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
let dbPath: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
tmpDir = await mkdtemp(join(tmpdir(), "kb-test-sqlite-"));
|
|
13
|
+
dbPath = join(tmpDir, "test.db");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("Basic Operations", () => {
|
|
21
|
+
it("should create a table", async () => {
|
|
22
|
+
const db = createAdapter({ filename: dbPath });
|
|
23
|
+
|
|
24
|
+
await db.query(
|
|
25
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
26
|
+
[],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const result = await db.query<{ name: string }>(
|
|
30
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='users'",
|
|
31
|
+
[],
|
|
32
|
+
);
|
|
33
|
+
expect(result.rows).toHaveLength(1);
|
|
34
|
+
await db.close();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should insert and select data", async () => {
|
|
38
|
+
const db = createAdapter({ filename: dbPath });
|
|
39
|
+
await db.query(
|
|
40
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
|
|
41
|
+
[],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await db.query("INSERT INTO users (name, age) VALUES (?, ?)", [
|
|
45
|
+
"Alice",
|
|
46
|
+
25,
|
|
47
|
+
]);
|
|
48
|
+
await db.query("INSERT INTO users (name, age) VALUES (?, ?)", [
|
|
49
|
+
"Bob",
|
|
50
|
+
30,
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const result = await db.query<{ id: number; name: string; age: number }>(
|
|
54
|
+
"SELECT * FROM users WHERE age > ?",
|
|
55
|
+
[20],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(result.rows).toHaveLength(2);
|
|
59
|
+
expect(result.rowCount).toBe(2);
|
|
60
|
+
expect(result.rows[0]?.name).toBe("Alice");
|
|
61
|
+
expect(result.rows[1]?.name).toBe("Bob");
|
|
62
|
+
await db.close();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should update data", async () => {
|
|
66
|
+
const db = createAdapter({ filename: dbPath });
|
|
67
|
+
await db.query(
|
|
68
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
71
|
+
await db.query("INSERT INTO users (name, age) VALUES (?, ?)", [
|
|
72
|
+
"Alice",
|
|
73
|
+
25,
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
await db.query("UPDATE users SET age = ? WHERE name = ?", [26, "Alice"]);
|
|
77
|
+
|
|
78
|
+
const result = await db.query<{ age: number }>(
|
|
79
|
+
"SELECT age FROM users WHERE name = ?",
|
|
80
|
+
["Alice"],
|
|
81
|
+
);
|
|
82
|
+
expect(result.rows[0]?.age).toBe(26);
|
|
83
|
+
await db.close();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should delete data", async () => {
|
|
87
|
+
const db = createAdapter({ filename: dbPath });
|
|
88
|
+
await db.query(
|
|
89
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
90
|
+
[],
|
|
91
|
+
);
|
|
92
|
+
await db.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
93
|
+
await db.query("INSERT INTO users (name) VALUES (?)", ["Bob"]);
|
|
94
|
+
|
|
95
|
+
await db.query("DELETE FROM users WHERE name = ?", ["Alice"]);
|
|
96
|
+
|
|
97
|
+
const result = await db.query<{ name: string }>(
|
|
98
|
+
"SELECT * FROM users",
|
|
99
|
+
[],
|
|
100
|
+
);
|
|
101
|
+
expect(result.rows).toHaveLength(1);
|
|
102
|
+
expect(result.rows[0]?.name).toBe("Bob");
|
|
103
|
+
await db.close();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("Transactions", () => {
|
|
108
|
+
it("should commit a transaction", async () => {
|
|
109
|
+
const db = createAdapter({ filename: dbPath });
|
|
110
|
+
await db.query(
|
|
111
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
112
|
+
[],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const tx = await db.transaction();
|
|
116
|
+
await tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
117
|
+
await tx.query("INSERT INTO users (name) VALUES (?)", ["Bob"]);
|
|
118
|
+
await tx.commit();
|
|
119
|
+
|
|
120
|
+
const result = await db.query<{ name: string }>(
|
|
121
|
+
"SELECT * FROM users",
|
|
122
|
+
[],
|
|
123
|
+
);
|
|
124
|
+
expect(result.rows).toHaveLength(2);
|
|
125
|
+
await db.close();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should rollback a transaction", async () => {
|
|
129
|
+
const db = createAdapter({ filename: dbPath });
|
|
130
|
+
await db.query(
|
|
131
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
132
|
+
[],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const tx = await db.transaction();
|
|
136
|
+
await tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
137
|
+
await tx.rollback();
|
|
138
|
+
|
|
139
|
+
const result = await db.query<{ name: string }>(
|
|
140
|
+
"SELECT * FROM users",
|
|
141
|
+
[],
|
|
142
|
+
);
|
|
143
|
+
expect(result.rows).toHaveLength(0);
|
|
144
|
+
await db.close();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should not allow operations after commit", async () => {
|
|
148
|
+
const db = createAdapter({ filename: dbPath });
|
|
149
|
+
await db.query(
|
|
150
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
151
|
+
[],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const tx = await db.transaction();
|
|
155
|
+
await tx.commit();
|
|
156
|
+
|
|
157
|
+
await expect(
|
|
158
|
+
tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]),
|
|
159
|
+
).rejects.toThrow("Transaction already completed");
|
|
160
|
+
await db.close();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should not allow operations after rollback", async () => {
|
|
164
|
+
const db = createAdapter({ filename: dbPath });
|
|
165
|
+
await db.query(
|
|
166
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
167
|
+
[],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const tx = await db.transaction();
|
|
171
|
+
await tx.rollback();
|
|
172
|
+
|
|
173
|
+
await expect(
|
|
174
|
+
tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]),
|
|
175
|
+
).rejects.toThrow("Transaction already completed");
|
|
176
|
+
await db.close();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("Query Results", () => {
|
|
181
|
+
it("should return field metadata", async () => {
|
|
182
|
+
const db = createAdapter({ filename: dbPath });
|
|
183
|
+
await db.query(
|
|
184
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
|
|
185
|
+
[],
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const result = await db.query("SELECT id, name, age FROM users", []);
|
|
189
|
+
|
|
190
|
+
expect(result.fields).toHaveLength(3);
|
|
191
|
+
expect(result.fields![0]).toEqual({ name: "id", type: "INTEGER" });
|
|
192
|
+
expect(result.fields![1]).toEqual({ name: "name", type: "TEXT" });
|
|
193
|
+
expect(result.fields![2]).toEqual({ name: "age", type: "INTEGER" });
|
|
194
|
+
await db.close();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should return rowCount", async () => {
|
|
198
|
+
const db = createAdapter({ filename: dbPath });
|
|
199
|
+
await db.query(
|
|
200
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
|
|
201
|
+
[],
|
|
202
|
+
);
|
|
203
|
+
await db.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
204
|
+
await db.query("INSERT INTO users (name) VALUES (?)", ["Bob"]);
|
|
205
|
+
|
|
206
|
+
const result = await db.query("SELECT * FROM users", []);
|
|
207
|
+
|
|
208
|
+
expect(result.rowCount).toBe(2);
|
|
209
|
+
await db.close();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("Error Handling", () => {
|
|
214
|
+
it("should throw on invalid SQL", async () => {
|
|
215
|
+
const db = createAdapter({ filename: dbPath });
|
|
216
|
+
|
|
217
|
+
await expect(db.query("INVALID SQL", [])).rejects.toThrow();
|
|
218
|
+
await db.close();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should throw when using after close", async () => {
|
|
222
|
+
const db = createAdapter({ filename: dbPath });
|
|
223
|
+
await db.close();
|
|
224
|
+
|
|
225
|
+
await expect(db.query("SELECT 1", [])).rejects.toThrow(
|
|
226
|
+
"Database is closed",
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("In-Memory Database", () => {
|
|
232
|
+
it("should support in-memory database", async () => {
|
|
233
|
+
const db = createAdapter({ filename: ":memory:" });
|
|
234
|
+
await db.query("CREATE TABLE temp (id INTEGER PRIMARY KEY)", []);
|
|
235
|
+
|
|
236
|
+
const result = await db.query(
|
|
237
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='temp'",
|
|
238
|
+
[],
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(result.rows).toHaveLength(1);
|
|
242
|
+
await db.close();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/adapters-sqlite
|
|
3
|
+
* SQLite adapter implementing ISQLDatabase interface.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Based on better-sqlite3 (synchronous API)
|
|
7
|
+
* - Connection pooling (single connection, thread-safe)
|
|
8
|
+
* - Transaction support with rollback
|
|
9
|
+
* - Prepared statement caching
|
|
10
|
+
* - Type-safe query results
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createAdapter } from '@kb-labs/adapters-sqlite';
|
|
15
|
+
*
|
|
16
|
+
* const db = createAdapter({
|
|
17
|
+
* filename: '/var/data/app.db',
|
|
18
|
+
* readonly: false,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Execute query
|
|
22
|
+
* const result = await db.query('SELECT * FROM users WHERE id = ?', [123]);
|
|
23
|
+
* console.log(result.rows); // [{ id: 123, name: 'Alice' }]
|
|
24
|
+
*
|
|
25
|
+
* // Transaction
|
|
26
|
+
* await db.transaction(async (trx) => {
|
|
27
|
+
* await trx.query('INSERT INTO users (name) VALUES (?)', ['Bob']);
|
|
28
|
+
* await trx.query('INSERT INTO logs (action) VALUES (?)', ['user_created']);
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Close connection
|
|
32
|
+
* await db.close();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import Database from "better-sqlite3";
|
|
37
|
+
import { mkdirSync } from "node:fs";
|
|
38
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
39
|
+
import type {
|
|
40
|
+
ISQLDatabase,
|
|
41
|
+
IDisposable,
|
|
42
|
+
SQLQueryResult,
|
|
43
|
+
SQLTransaction,
|
|
44
|
+
} from "@kb-labs/core-platform/adapters";
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// Re-export manifest
|
|
48
|
+
export { manifest } from "./manifest.js";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Configuration for SQLite database adapter.
|
|
52
|
+
*/
|
|
53
|
+
export interface SQLiteConfig {
|
|
54
|
+
/**
|
|
55
|
+
* Database file path.
|
|
56
|
+
* Use ':memory:' for in-memory database (useful for testing).
|
|
57
|
+
* Relative paths are resolved against workspace.cwd (injected by core-runtime).
|
|
58
|
+
*/
|
|
59
|
+
filename: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Workspace context injected by core-runtime.
|
|
63
|
+
* Provides cwd for resolving relative filename paths.
|
|
64
|
+
*/
|
|
65
|
+
workspace?: { cwd: string };
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Open in readonly mode (default: false)
|
|
69
|
+
*/
|
|
70
|
+
readonly?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Enable WAL mode for better concurrency (default: true)
|
|
74
|
+
* https://www.sqlite.org/wal.html
|
|
75
|
+
*/
|
|
76
|
+
wal?: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Enable foreign keys (default: true)
|
|
80
|
+
*/
|
|
81
|
+
foreignKeys?: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Busy timeout in milliseconds (default: 5000)
|
|
85
|
+
*/
|
|
86
|
+
busyTimeout?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* SQLite implementation of ISQLDatabase interface.
|
|
91
|
+
*
|
|
92
|
+
* Design:
|
|
93
|
+
* - Uses better-sqlite3 (synchronous, but wrapped in async for interface compatibility)
|
|
94
|
+
* - Single connection (thread-safe via better-sqlite3)
|
|
95
|
+
* - Prepared statement caching (automatic via better-sqlite3)
|
|
96
|
+
* - Transaction support with savepoints
|
|
97
|
+
*/
|
|
98
|
+
export class SQLiteAdapter implements ISQLDatabase, IDisposable {
|
|
99
|
+
private db: Database.Database;
|
|
100
|
+
private closed = false;
|
|
101
|
+
/** Stored so we can deregister it in dispose() — prevents listener accumulation. */
|
|
102
|
+
private _onExit: (() => void) | null = null;
|
|
103
|
+
/** True for ':memory:' databases — skip WAL ops and exit-handler registration. */
|
|
104
|
+
private _isMemory = false;
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
constructor(config: SQLiteConfig) {
|
|
108
|
+
// Resolve relative filename against workspace root (injected by core-runtime)
|
|
109
|
+
const cwd = config.workspace?.cwd ?? process.cwd();
|
|
110
|
+
const resolvedFilename =
|
|
111
|
+
config.filename === ":memory:" || isAbsolute(config.filename)
|
|
112
|
+
? config.filename
|
|
113
|
+
: join(cwd, config.filename);
|
|
114
|
+
|
|
115
|
+
// Create parent directory if it doesn't exist (unless :memory:)
|
|
116
|
+
if (resolvedFilename !== ":memory:" && !config.readonly) {
|
|
117
|
+
const dir = dirname(resolvedFilename);
|
|
118
|
+
mkdirSync(dir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.db = new Database(resolvedFilename, {
|
|
122
|
+
readonly: config.readonly ?? false,
|
|
123
|
+
fileMustExist: false, // Create if not exists
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Set busy timeout
|
|
127
|
+
this.db.pragma(`busy_timeout = ${config.busyTimeout ?? 5000}`);
|
|
128
|
+
|
|
129
|
+
// Enable WAL mode for better concurrency
|
|
130
|
+
if (config.wal !== false) {
|
|
131
|
+
this.db.pragma("journal_mode = WAL");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Enable foreign keys
|
|
135
|
+
if (config.foreignKeys !== false) {
|
|
136
|
+
this.db.pragma("foreign_keys = ON");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Exit handler registration ───────────────────────────────────────────
|
|
140
|
+
// Register a synchronous exit handler only for file-backed, writable, WAL-mode databases.
|
|
141
|
+
//
|
|
142
|
+
// WHY: If the process exits abruptly (SIGTERM, SIGKILL, unhandled rejection) without
|
|
143
|
+
// explicitly calling dispose() or close(), the WAL file may contain unflushed frames.
|
|
144
|
+
// The next process opening the database will perform WAL recovery, which is safe but
|
|
145
|
+
// slow. An explicit TRUNCATE checkpoint moves all WAL frames into the main database
|
|
146
|
+
// file and empties the WAL, making re-open instantaneous.
|
|
147
|
+
//
|
|
148
|
+
// WHY SYNC: process.on('exit') callbacks must be synchronous — async code scheduled
|
|
149
|
+
// after the event loop drains is silently dropped by Node.js. better-sqlite3 is
|
|
150
|
+
// natively synchronous so this is correct and efficient.
|
|
151
|
+
//
|
|
152
|
+
// WHY NOT :memory:: In-memory databases have no WAL file on disk; there is nothing
|
|
153
|
+
// to checkpoint and nothing to persist. Registering a process listener for them would
|
|
154
|
+
// be a no-op at best and a source of listener-count warnings at worst.
|
|
155
|
+
this._isMemory = resolvedFilename === ":memory:";
|
|
156
|
+
if (!this._isMemory && !config.readonly && config.wal !== false) {
|
|
157
|
+
this._onExit = () => { this.dispose(); };
|
|
158
|
+
process.on("exit", this._onExit);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Execute a SQL query.
|
|
165
|
+
*
|
|
166
|
+
* @param sql - SQL query string (supports ? placeholders)
|
|
167
|
+
* @param params - Query parameters
|
|
168
|
+
* @returns Query result with rows and metadata
|
|
169
|
+
*/
|
|
170
|
+
async query<T = unknown>(
|
|
171
|
+
sql: string,
|
|
172
|
+
params?: unknown[],
|
|
173
|
+
): Promise<SQLQueryResult<T>> {
|
|
174
|
+
this.checkClosed();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const trimmedSql = sql.trim().toUpperCase();
|
|
178
|
+
|
|
179
|
+
// SELECT queries
|
|
180
|
+
if (trimmedSql.startsWith("SELECT") || trimmedSql.startsWith("PRAGMA")) {
|
|
181
|
+
const stmt = this.db.prepare(sql);
|
|
182
|
+
const rows = params ? stmt.all(...params) : stmt.all();
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
rows: rows as T[],
|
|
186
|
+
rowCount: rows.length,
|
|
187
|
+
fields: this.getFieldMetadata(stmt),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// INSERT/UPDATE/DELETE queries
|
|
192
|
+
const stmt = this.db.prepare(sql);
|
|
193
|
+
const info = params ? stmt.run(...params) : stmt.run();
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
rows: [] as T[],
|
|
197
|
+
rowCount: info.changes,
|
|
198
|
+
fields: [],
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`SQLite query failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Begin a SQL transaction.
|
|
209
|
+
*
|
|
210
|
+
* @returns Transaction object with query, commit, rollback methods
|
|
211
|
+
*/
|
|
212
|
+
async transaction(): Promise<SQLTransaction> {
|
|
213
|
+
this.checkClosed();
|
|
214
|
+
|
|
215
|
+
// SQLite doesn't have true async transactions, but we use savepoint
|
|
216
|
+
await this.query("BEGIN TRANSACTION", []);
|
|
217
|
+
|
|
218
|
+
let committed = false;
|
|
219
|
+
let rolledBack = false;
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
query: async <T = unknown>(
|
|
223
|
+
sql: string,
|
|
224
|
+
params?: unknown[],
|
|
225
|
+
): Promise<SQLQueryResult<T>> => {
|
|
226
|
+
if (committed || rolledBack) {
|
|
227
|
+
throw new Error("Transaction already completed");
|
|
228
|
+
}
|
|
229
|
+
return this.query<T>(sql, params);
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
commit: async (): Promise<void> => {
|
|
233
|
+
if (committed || rolledBack) {
|
|
234
|
+
throw new Error("Transaction already completed");
|
|
235
|
+
}
|
|
236
|
+
await this.query("COMMIT", []);
|
|
237
|
+
committed = true;
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
rollback: async (): Promise<void> => {
|
|
241
|
+
if (committed || rolledBack) {
|
|
242
|
+
throw new Error("Transaction already completed");
|
|
243
|
+
}
|
|
244
|
+
await this.query("ROLLBACK", []);
|
|
245
|
+
rolledBack = true;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Checkpoint WAL and close the database connection. Implements IDisposable.
|
|
252
|
+
*
|
|
253
|
+
* Synchronous by design — process.on('exit') callbacks that are async are
|
|
254
|
+
* silently dropped by Node.js after the event loop drains. better-sqlite3
|
|
255
|
+
* is natively synchronous so sync disposal is both correct and sufficient.
|
|
256
|
+
*
|
|
257
|
+
* Idempotent: subsequent calls after the first are no-ops (guarded by this.closed).
|
|
258
|
+
*
|
|
259
|
+
* Sequence:
|
|
260
|
+
* 1. Set this.closed = true immediately to block new queries.
|
|
261
|
+
* 2. Deregister the 'exit' listener (prevents double-dispose on graceful shutdown
|
|
262
|
+
* followed by normal process exit — both paths are safe but one is enough).
|
|
263
|
+
* 3. Checkpoint WAL into the main DB file and truncate the WAL to zero bytes
|
|
264
|
+
* (TRUNCATE mode — fastest and most space-efficient).
|
|
265
|
+
* 4. Close the better-sqlite3 connection.
|
|
266
|
+
*/
|
|
267
|
+
dispose(): void {
|
|
268
|
+
if (this.closed) {return;}
|
|
269
|
+
this.closed = true;
|
|
270
|
+
|
|
271
|
+
// Deregister exit listener — prevents a second dispose() call when graceful
|
|
272
|
+
// shutdown (SIGTERM → platform.shutdown() → dispose()) is followed by process.exit(0).
|
|
273
|
+
if (this._onExit !== null) {
|
|
274
|
+
process.removeListener("exit", this._onExit);
|
|
275
|
+
this._onExit = null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// Checkpoint WAL: flush all WAL frames into the main database file and
|
|
280
|
+
// truncate the WAL to 0 bytes. Skipped for :memory: (no WAL file) and
|
|
281
|
+
// readonly connections (cannot write checkpoint).
|
|
282
|
+
if (!this._isMemory && !this.db.readonly) {
|
|
283
|
+
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
284
|
+
}
|
|
285
|
+
this.db.close();
|
|
286
|
+
} catch {
|
|
287
|
+
// Already closed or I/O error at exit time — nothing we can do.
|
|
288
|
+
// Swallow silently: throwing from a dispose() called during process.exit
|
|
289
|
+
// would print an uncaught exception without actually halting anything useful.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Implements ISQLDatabase.close() — delegates to dispose() for WAL checkpoint.
|
|
295
|
+
*
|
|
296
|
+
* PlatformContainer.shutdown() checks for close() before dispose() (container.ts line 830),
|
|
297
|
+
* so this path is taken when the container disposes this adapter. Delegating to dispose()
|
|
298
|
+
* ensures WAL checkpoint happens regardless of which method is called.
|
|
299
|
+
*/
|
|
300
|
+
async close(): Promise<void> {
|
|
301
|
+
this.dispose();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if database is closed.
|
|
307
|
+
*/
|
|
308
|
+
private checkClosed(): void {
|
|
309
|
+
if (this.closed) {
|
|
310
|
+
throw new Error("Database is closed");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract field metadata from prepared statement.
|
|
316
|
+
*/
|
|
317
|
+
private getFieldMetadata(
|
|
318
|
+
stmt: Database.Statement,
|
|
319
|
+
): Array<{ name: string; type: string }> {
|
|
320
|
+
try {
|
|
321
|
+
// better-sqlite3 provides column info
|
|
322
|
+
return stmt.columns().map((col) => ({
|
|
323
|
+
name: col.name,
|
|
324
|
+
type: col.type ?? "unknown",
|
|
325
|
+
}));
|
|
326
|
+
} catch {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
332
|
+
// Utility methods (not part of ISQLDatabase interface)
|
|
333
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Execute raw SQL (useful for schema migrations).
|
|
337
|
+
* Does not return rows - use query() for SELECT.
|
|
338
|
+
*/
|
|
339
|
+
async exec(sql: string): Promise<void> {
|
|
340
|
+
this.checkClosed();
|
|
341
|
+
this.db.exec(sql);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Check if database is open.
|
|
346
|
+
*/
|
|
347
|
+
isOpen(): boolean {
|
|
348
|
+
return !this.closed;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get underlying better-sqlite3 instance (for advanced usage).
|
|
353
|
+
* Use with caution - bypasses adapter interface.
|
|
354
|
+
*/
|
|
355
|
+
getRawDatabase(): Database.Database {
|
|
356
|
+
this.checkClosed();
|
|
357
|
+
return this.db;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Create SQLite database adapter.
|
|
363
|
+
* This is the factory function called by initPlatform() when loading adapters.
|
|
364
|
+
*
|
|
365
|
+
* @param config - SQLite configuration
|
|
366
|
+
* @returns SQLite adapter instance
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```typescript
|
|
370
|
+
* const db = createAdapter({
|
|
371
|
+
* filename: '/var/data/app.db',
|
|
372
|
+
* wal: true,
|
|
373
|
+
* foreignKeys: true,
|
|
374
|
+
* });
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
export function createAdapter(config: SQLiteConfig): SQLiteAdapter {
|
|
378
|
+
return new SQLiteAdapter(config);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Default export for direct import
|
|
382
|
+
export default createAdapter;
|