@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.
Files changed (276) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/CONTRIBUTING.md +90 -0
  21. package/IMPLEMENTATION_COMPLETE.md +416 -0
  22. package/LICENSE +186 -0
  23. package/README-TEMPLATE.md +179 -0
  24. package/README.md +306 -0
  25. package/docs/DOCUMENTATION.md +74 -0
  26. package/docs/adr/0000-template.md +49 -0
  27. package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
  28. package/docs/adr/0002-plugins-and-extensibility.md +46 -0
  29. package/docs/adr/0003-package-and-module-boundaries.md +37 -0
  30. package/docs/adr/0004-versioning-and-release-policy.md +38 -0
  31. package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
  32. package/docs/adr/0006-adopt-devkit-sync.md +47 -0
  33. package/docs/adr/0007-drift-kit-check.md +72 -0
  34. package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
  35. package/docs/naming-convention.md +272 -0
  36. package/eslint.config.js +27 -0
  37. package/kb-labs.config.json +5 -0
  38. package/package.json +84 -0
  39. package/package.json.bin +25 -0
  40. package/package.json.lib +30 -0
  41. package/packages/adapters-analytics-duckdb/package.json +54 -0
  42. package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
  43. package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
  44. package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
  45. package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
  46. package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
  47. package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
  48. package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
  49. package/packages/adapters-analytics-file/README.md +32 -0
  50. package/packages/adapters-analytics-file/eslint.config.js +27 -0
  51. package/packages/adapters-analytics-file/package.json +50 -0
  52. package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
  53. package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
  54. package/packages/adapters-analytics-file/src/index.test.ts +214 -0
  55. package/packages/adapters-analytics-file/src/index.ts +830 -0
  56. package/packages/adapters-analytics-file/src/manifest.ts +45 -0
  57. package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
  58. package/packages/adapters-analytics-file/tsconfig.json +9 -0
  59. package/packages/adapters-analytics-file/tsup.config.ts +9 -0
  60. package/packages/adapters-analytics-sqlite/package.json +55 -0
  61. package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
  62. package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
  63. package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
  64. package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
  65. package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
  66. package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
  67. package/packages/adapters-environment-docker/README.md +28 -0
  68. package/packages/adapters-environment-docker/eslint.config.js +5 -0
  69. package/packages/adapters-environment-docker/package.json +49 -0
  70. package/packages/adapters-environment-docker/src/index.test.ts +138 -0
  71. package/packages/adapters-environment-docker/src/index.ts +439 -0
  72. package/packages/adapters-environment-docker/src/manifest.ts +65 -0
  73. package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
  74. package/packages/adapters-environment-docker/tsconfig.json +16 -0
  75. package/packages/adapters-environment-docker/tsup.config.ts +9 -0
  76. package/packages/adapters-eventbus-cache/README.md +242 -0
  77. package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
  78. package/packages/adapters-eventbus-cache/package.json +46 -0
  79. package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
  80. package/packages/adapters-eventbus-cache/src/index.ts +215 -0
  81. package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
  82. package/packages/adapters-eventbus-cache/src/types.ts +58 -0
  83. package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
  84. package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
  85. package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
  86. package/packages/adapters-fs/README.md +171 -0
  87. package/packages/adapters-fs/allowed.txt +1 -0
  88. package/packages/adapters-fs/conflict.txt +1 -0
  89. package/packages/adapters-fs/dest.txt +1 -0
  90. package/packages/adapters-fs/eslint.config.js +27 -0
  91. package/packages/adapters-fs/exists.txt +1 -0
  92. package/packages/adapters-fs/not-allowed.txt +1 -0
  93. package/packages/adapters-fs/other.txt +1 -0
  94. package/packages/adapters-fs/package.json +55 -0
  95. package/packages/adapters-fs/public/file1.txt +1 -0
  96. package/packages/adapters-fs/public/file2.txt +1 -0
  97. package/packages/adapters-fs/secret.txt +1 -0
  98. package/packages/adapters-fs/secrets/key.txt +1 -0
  99. package/packages/adapters-fs/src/index.test.ts +243 -0
  100. package/packages/adapters-fs/src/index.ts +258 -0
  101. package/packages/adapters-fs/src/manifest.ts +35 -0
  102. package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
  103. package/packages/adapters-fs/src/secure-storage.ts +268 -0
  104. package/packages/adapters-fs/test.json +1 -0
  105. package/packages/adapters-fs/test.txt +1 -0
  106. package/packages/adapters-fs/test.xyz +1 -0
  107. package/packages/adapters-fs/test1.txt +1 -0
  108. package/packages/adapters-fs/test2.txt +1 -0
  109. package/packages/adapters-fs/tsconfig.build.json +15 -0
  110. package/packages/adapters-fs/tsconfig.json +9 -0
  111. package/packages/adapters-fs/tsup.config.ts +8 -0
  112. package/packages/adapters-fs/vitest.config.ts +19 -0
  113. package/packages/adapters-log-ringbuffer/README.md +228 -0
  114. package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
  115. package/packages/adapters-log-ringbuffer/package.json +47 -0
  116. package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
  117. package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
  118. package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
  119. package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
  120. package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
  121. package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
  122. package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
  123. package/packages/adapters-log-sqlite/README.md +396 -0
  124. package/packages/adapters-log-sqlite/eslint.config.js +27 -0
  125. package/packages/adapters-log-sqlite/package.json +49 -0
  126. package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
  127. package/packages/adapters-log-sqlite/src/index.ts +1068 -0
  128. package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
  129. package/packages/adapters-log-sqlite/src/schema.sql +46 -0
  130. package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
  131. package/packages/adapters-log-sqlite/tsconfig.json +9 -0
  132. package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
  133. package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
  134. package/packages/adapters-mongodb/README.md +147 -0
  135. package/packages/adapters-mongodb/eslint.config.js +27 -0
  136. package/packages/adapters-mongodb/package.json +53 -0
  137. package/packages/adapters-mongodb/src/index.ts +428 -0
  138. package/packages/adapters-mongodb/src/manifest.ts +45 -0
  139. package/packages/adapters-mongodb/src/secure-document.ts +231 -0
  140. package/packages/adapters-mongodb/tsconfig.build.json +15 -0
  141. package/packages/adapters-mongodb/tsconfig.json +9 -0
  142. package/packages/adapters-mongodb/tsup.config.ts +8 -0
  143. package/packages/adapters-openai/README.md +151 -0
  144. package/packages/adapters-openai/embeddings.ts +37 -0
  145. package/packages/adapters-openai/eslint.config.js +26 -0
  146. package/packages/adapters-openai/index.ts +22 -0
  147. package/packages/adapters-openai/package.json +57 -0
  148. package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
  149. package/packages/adapters-openai/src/embeddings.ts +104 -0
  150. package/packages/adapters-openai/src/index.ts +13 -0
  151. package/packages/adapters-openai/src/llm.ts +304 -0
  152. package/packages/adapters-openai/src/manifest.ts +47 -0
  153. package/packages/adapters-openai/tsconfig.build.json +15 -0
  154. package/packages/adapters-openai/tsconfig.json +9 -0
  155. package/packages/adapters-openai/tsup.config.ts +8 -0
  156. package/packages/adapters-pino/README.md +152 -0
  157. package/packages/adapters-pino/eslint.config.js +27 -0
  158. package/packages/adapters-pino/package.json +49 -0
  159. package/packages/adapters-pino/src/index.test.ts +44 -0
  160. package/packages/adapters-pino/src/index.ts +322 -0
  161. package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
  162. package/packages/adapters-pino/src/manifest.ts +49 -0
  163. package/packages/adapters-pino/tsconfig.build.json +15 -0
  164. package/packages/adapters-pino/tsconfig.json +9 -0
  165. package/packages/adapters-pino/tsup.config.ts +9 -0
  166. package/packages/adapters-pino-http/README.md +141 -0
  167. package/packages/adapters-pino-http/eslint.config.js +27 -0
  168. package/packages/adapters-pino-http/package.json +46 -0
  169. package/packages/adapters-pino-http/src/index.ts +229 -0
  170. package/packages/adapters-pino-http/tsconfig.build.json +15 -0
  171. package/packages/adapters-pino-http/tsconfig.json +9 -0
  172. package/packages/adapters-pino-http/tsup.config.ts +9 -0
  173. package/packages/adapters-qdrant/README.md +166 -0
  174. package/packages/adapters-qdrant/eslint.config.js +27 -0
  175. package/packages/adapters-qdrant/package.json +49 -0
  176. package/packages/adapters-qdrant/src/index.ts +490 -0
  177. package/packages/adapters-qdrant/src/manifest.ts +54 -0
  178. package/packages/adapters-qdrant/src/retry.ts +204 -0
  179. package/packages/adapters-qdrant/tsconfig.build.json +15 -0
  180. package/packages/adapters-qdrant/tsconfig.json +9 -0
  181. package/packages/adapters-qdrant/tsup.config.ts +9 -0
  182. package/packages/adapters-redis/README.md +159 -0
  183. package/packages/adapters-redis/eslint.config.js +27 -0
  184. package/packages/adapters-redis/package.json +49 -0
  185. package/packages/adapters-redis/src/index.ts +164 -0
  186. package/packages/adapters-redis/src/manifest.ts +49 -0
  187. package/packages/adapters-redis/tsconfig.build.json +15 -0
  188. package/packages/adapters-redis/tsconfig.json +9 -0
  189. package/packages/adapters-redis/tsup.config.ts +9 -0
  190. package/packages/adapters-snapshot-localfs/README.md +10 -0
  191. package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
  192. package/packages/adapters-snapshot-localfs/package.json +46 -0
  193. package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
  194. package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
  195. package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
  196. package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
  197. package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
  198. package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
  199. package/packages/adapters-sqlite/README.md +163 -0
  200. package/packages/adapters-sqlite/eslint.config.js +27 -0
  201. package/packages/adapters-sqlite/package.json +54 -0
  202. package/packages/adapters-sqlite/src/index.test.ts +245 -0
  203. package/packages/adapters-sqlite/src/index.ts +382 -0
  204. package/packages/adapters-sqlite/src/manifest.ts +47 -0
  205. package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
  206. package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
  207. package/packages/adapters-sqlite/tsconfig.build.json +15 -0
  208. package/packages/adapters-sqlite/tsconfig.json +9 -0
  209. package/packages/adapters-sqlite/tsup.config.ts +8 -0
  210. package/packages/adapters-sqlite/vitest.config.ts +19 -0
  211. package/packages/adapters-transport/README.md +170 -0
  212. package/packages/adapters-transport/eslint.config.js +27 -0
  213. package/packages/adapters-transport/package.json +49 -0
  214. package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
  215. package/packages/adapters-transport/src/index.ts +101 -0
  216. package/packages/adapters-transport/src/ipc-transport.ts +228 -0
  217. package/packages/adapters-transport/src/transport.ts +224 -0
  218. package/packages/adapters-transport/src/types.ts +92 -0
  219. package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
  220. package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
  221. package/packages/adapters-transport/tsconfig.build.json +15 -0
  222. package/packages/adapters-transport/tsconfig.json +9 -0
  223. package/packages/adapters-transport/tsup.config.ts +9 -0
  224. package/packages/adapters-vibeproxy/README.md +159 -0
  225. package/packages/adapters-vibeproxy/eslint.config.js +27 -0
  226. package/packages/adapters-vibeproxy/package.json +51 -0
  227. package/packages/adapters-vibeproxy/src/index.ts +13 -0
  228. package/packages/adapters-vibeproxy/src/llm.ts +437 -0
  229. package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
  230. package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
  231. package/packages/adapters-vibeproxy/tsconfig.json +9 -0
  232. package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
  233. package/packages/adapters-workspace-agent/package.json +46 -0
  234. package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
  235. package/packages/adapters-workspace-agent/src/index.ts +220 -0
  236. package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
  237. package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
  238. package/packages/adapters-workspace-agent/tsconfig.json +16 -0
  239. package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
  240. package/packages/adapters-workspace-localfs/README.md +9 -0
  241. package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
  242. package/packages/adapters-workspace-localfs/package.json +46 -0
  243. package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
  244. package/packages/adapters-workspace-localfs/src/index.ts +172 -0
  245. package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
  246. package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
  247. package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
  248. package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
  249. package/packages/adapters-workspace-worktree/README.md +9 -0
  250. package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
  251. package/packages/adapters-workspace-worktree/package.json +46 -0
  252. package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
  253. package/packages/adapters-workspace-worktree/src/index.ts +245 -0
  254. package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
  255. package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
  256. package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
  257. package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
  258. package/pnpm-workspace.yaml +2800 -0
  259. package/prettierrc.json +1 -0
  260. package/scripts/devkit-sync.mjs +37 -0
  261. package/scripts/hooks/post-push +9 -0
  262. package/scripts/hooks/pre-commit +9 -0
  263. package/scripts/hooks/pre-push +9 -0
  264. package/test-integration.ts +242 -0
  265. package/test.txt +1 -0
  266. package/tsconfig.base.json +6 -0
  267. package/tsconfig.build.json +15 -0
  268. package/tsconfig.json +9 -0
  269. package/tsconfig.paths.json +26 -0
  270. package/tsconfig.tools.json +17 -0
  271. package/tsup.config.bin.ts +34 -0
  272. package/tsup.config.cli.ts +41 -0
  273. package/tsup.config.dual.ts +46 -0
  274. package/tsup.config.ts +36 -0
  275. package/tsup.external.json +103 -0
  276. 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;