@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,47 @@
1
+ /**
2
+ * @module @kb-labs/adapters-sqlite/manifest
3
+ * Adapter manifest for SQLite database.
4
+ */
5
+
6
+ import type { AdapterManifest } from "@kb-labs/core-platform";
7
+
8
+ /**
9
+ * Adapter manifest for SQLite database.
10
+ */
11
+ export const manifest: AdapterManifest = {
12
+ manifestVersion: "1.0.0",
13
+ id: "sqlite-database",
14
+ name: "SQLite Database",
15
+ version: "1.0.0",
16
+ description: "Lightweight embedded SQL database using better-sqlite3",
17
+ author: "KB Labs Team",
18
+ license: "KBPL-1.1",
19
+ type: "core",
20
+ implements: "ISQLDatabase",
21
+ contexts: ["workspace"],
22
+ capabilities: {
23
+ transactions: true,
24
+ search: true,
25
+ custom: {
26
+ prepared: true,
27
+ fts: true,
28
+ json: true,
29
+ },
30
+ },
31
+ configSchema: {
32
+ filename: {
33
+ type: "string",
34
+ description: "Database file path (use :memory: for in-memory database)",
35
+ },
36
+ readonly: {
37
+ type: "boolean",
38
+ default: false,
39
+ description: "Open database in readonly mode",
40
+ },
41
+ timeout: {
42
+ type: "number",
43
+ default: 5000,
44
+ description: "Busy timeout in milliseconds",
45
+ },
46
+ },
47
+ };
@@ -0,0 +1,290 @@
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
+ import { createSecureSQL, SQLPermissionError } from "./secure-sql.js";
7
+
8
+ describe("SecureSQLAdapter", () => {
9
+ let tmpDir: string;
10
+ let dbPath: string;
11
+
12
+ beforeEach(async () => {
13
+ tmpDir = await mkdtemp(join(tmpdir(), "kb-test-secure-sql-"));
14
+ dbPath = join(tmpDir, "test.db");
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await rm(tmpDir, { recursive: true, force: true });
19
+ });
20
+
21
+ describe("Permission Checks", () => {
22
+ it("should allow read when permission granted", async () => {
23
+ const base = createAdapter({ filename: dbPath });
24
+ await base.query(
25
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
26
+ [],
27
+ );
28
+ await base.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
29
+ const secure = createSecureSQL(base, { read: true });
30
+
31
+ const result = await secure.query<{ name: string }>(
32
+ "SELECT * FROM users",
33
+ [],
34
+ );
35
+
36
+ expect(result.rows).toHaveLength(1);
37
+ await base.close();
38
+ });
39
+
40
+ it("should deny read when permission not granted", async () => {
41
+ const base = createAdapter({ filename: dbPath });
42
+ await base.query(
43
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
44
+ [],
45
+ );
46
+ const secure = createSecureSQL(base, { read: false });
47
+
48
+ await expect(secure.query("SELECT * FROM users", [])).rejects.toThrow(
49
+ SQLPermissionError,
50
+ );
51
+ await base.close();
52
+ });
53
+
54
+ it("should allow write when permission granted", async () => {
55
+ const base = createAdapter({ filename: dbPath });
56
+ await base.query(
57
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
58
+ [],
59
+ );
60
+ const secure = createSecureSQL(base, { write: true });
61
+
62
+ await secure.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
63
+
64
+ const result = await base.query<{ name: string }>(
65
+ "SELECT * FROM users",
66
+ [],
67
+ );
68
+ expect(result.rows).toHaveLength(1);
69
+ await base.close();
70
+ });
71
+
72
+ it("should deny write when permission not granted", async () => {
73
+ const base = createAdapter({ filename: dbPath });
74
+ await base.query(
75
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
76
+ [],
77
+ );
78
+ const secure = createSecureSQL(base, { write: false });
79
+
80
+ await expect(
81
+ secure.query("INSERT INTO users (name) VALUES (?)", ["Alice"]),
82
+ ).rejects.toThrow(SQLPermissionError);
83
+ await base.close();
84
+ });
85
+
86
+ it("should allow delete when permission granted", async () => {
87
+ const base = createAdapter({ filename: dbPath });
88
+ await base.query(
89
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
90
+ [],
91
+ );
92
+ await base.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
93
+ const secure = createSecureSQL(base, { write: true });
94
+
95
+ await secure.query("DELETE FROM users WHERE name = ?", ["Alice"]);
96
+
97
+ const result = await base.query<{ name: string }>(
98
+ "SELECT * FROM users",
99
+ [],
100
+ );
101
+ expect(result.rows).toHaveLength(0);
102
+ await base.close();
103
+ });
104
+
105
+ it("should deny delete when permission not granted", async () => {
106
+ const base = createAdapter({ filename: dbPath });
107
+ await base.query(
108
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
109
+ [],
110
+ );
111
+ const secure = createSecureSQL(base, { write: false });
112
+
113
+ await expect(secure.query("DELETE FROM users", [])).rejects.toThrow(
114
+ SQLPermissionError,
115
+ );
116
+ await base.close();
117
+ });
118
+ });
119
+
120
+ describe("Table Allowlist", () => {
121
+ it("should allow access to allowlisted tables", async () => {
122
+ const base = createAdapter({ filename: dbPath });
123
+ await base.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
124
+ await base.query("CREATE TABLE posts (id INTEGER PRIMARY KEY)", []);
125
+ const secure = createSecureSQL(base, {
126
+ allowlist: ["users"],
127
+ read: true,
128
+ });
129
+
130
+ const result = await secure.query("SELECT * FROM users", []);
131
+
132
+ expect(result.rows).toBeDefined();
133
+ await base.close();
134
+ });
135
+
136
+ it("should deny access to non-allowlisted tables", async () => {
137
+ const base = createAdapter({ filename: dbPath });
138
+ await base.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
139
+ await base.query("CREATE TABLE restricted (id INTEGER PRIMARY KEY)", []);
140
+ const secure = createSecureSQL(base, {
141
+ allowlist: ["users"],
142
+ read: true,
143
+ });
144
+
145
+ await expect(
146
+ secure.query("SELECT * FROM restricted", []),
147
+ ).rejects.toThrow(SQLPermissionError);
148
+ await base.close();
149
+ });
150
+
151
+ it("should handle joins with allowlisted tables", async () => {
152
+ const base = createAdapter({ filename: dbPath });
153
+ await base.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
154
+ await base.query(
155
+ "CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER)",
156
+ [],
157
+ );
158
+ const secure = createSecureSQL(base, {
159
+ allowlist: ["users", "posts"],
160
+ read: true,
161
+ });
162
+
163
+ const result = await secure.query(
164
+ "SELECT * FROM users JOIN posts ON users.id = posts.user_id",
165
+ [],
166
+ );
167
+
168
+ expect(result.rows).toBeDefined();
169
+ await base.close();
170
+ });
171
+ });
172
+
173
+ describe("Table Denylist", () => {
174
+ it("should deny access to denylisted tables", async () => {
175
+ const base = createAdapter({ filename: dbPath });
176
+ await base.query("CREATE TABLE admin_users (id INTEGER PRIMARY KEY)", []);
177
+ const secure = createSecureSQL(base, {
178
+ denylist: ["admin_users"],
179
+ read: true,
180
+ });
181
+
182
+ await expect(
183
+ secure.query("SELECT * FROM admin_users", []),
184
+ ).rejects.toThrow(SQLPermissionError);
185
+ await base.close();
186
+ });
187
+
188
+ it("should denylist take precedence over allowlist", async () => {
189
+ const base = createAdapter({ filename: dbPath });
190
+ await base.query("CREATE TABLE conflict (id INTEGER PRIMARY KEY)", []);
191
+ const secure = createSecureSQL(base, {
192
+ allowlist: ["conflict"],
193
+ denylist: ["conflict"],
194
+ read: true,
195
+ });
196
+
197
+ await expect(secure.query("SELECT * FROM conflict", [])).rejects.toThrow(
198
+ SQLPermissionError,
199
+ );
200
+ await base.close();
201
+ });
202
+ });
203
+
204
+ describe("Schema Modification Prevention", () => {
205
+ it("should deny CREATE TABLE when schema=false", async () => {
206
+ const base = createAdapter({ filename: dbPath });
207
+ const secure = createSecureSQL(base, { schema: false });
208
+
209
+ await expect(
210
+ secure.query("CREATE TABLE bad (id INTEGER)", []),
211
+ ).rejects.toThrow(SQLPermissionError);
212
+ await base.close();
213
+ });
214
+
215
+ it("should deny ALTER TABLE when schema=false", async () => {
216
+ const base = createAdapter({ filename: dbPath });
217
+ await base.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
218
+ const secure = createSecureSQL(base, { schema: false });
219
+
220
+ await expect(
221
+ secure.query("ALTER TABLE users ADD COLUMN name TEXT", []),
222
+ ).rejects.toThrow(SQLPermissionError);
223
+ await base.close();
224
+ });
225
+
226
+ it("should deny DROP TABLE when schema=false", async () => {
227
+ const base = createAdapter({ filename: dbPath });
228
+ await base.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
229
+ const secure = createSecureSQL(base, { schema: false });
230
+
231
+ await expect(secure.query("DROP TABLE users", [])).rejects.toThrow(
232
+ SQLPermissionError,
233
+ );
234
+ await base.close();
235
+ });
236
+
237
+ it("should allow schema modifications when schema=true", async () => {
238
+ const base = createAdapter({ filename: dbPath });
239
+ const secure = createSecureSQL(base, { schema: true });
240
+
241
+ await secure.query("CREATE TABLE users (id INTEGER PRIMARY KEY)", []);
242
+
243
+ const result = await base.query(
244
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='users'",
245
+ [],
246
+ );
247
+ expect(result.rows).toHaveLength(1);
248
+ await base.close();
249
+ });
250
+ });
251
+
252
+ describe("Transactions with Permissions", () => {
253
+ it("should enforce permissions in transactions", async () => {
254
+ const base = createAdapter({ filename: dbPath });
255
+ await base.query(
256
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
257
+ [],
258
+ );
259
+ const secure = createSecureSQL(base, { write: false, read: true });
260
+
261
+ const tx = await secure.transaction();
262
+
263
+ await expect(
264
+ tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]),
265
+ ).rejects.toThrow(SQLPermissionError);
266
+ await tx.rollback();
267
+ await base.close();
268
+ });
269
+
270
+ it("should allow valid operations in transactions", async () => {
271
+ const base = createAdapter({ filename: dbPath });
272
+ await base.query(
273
+ "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
274
+ [],
275
+ );
276
+ const secure = createSecureSQL(base, { write: true });
277
+
278
+ const tx = await secure.transaction();
279
+ await tx.query("INSERT INTO users (name) VALUES (?)", ["Alice"]);
280
+ await tx.commit();
281
+
282
+ const result = await base.query<{ name: string }>(
283
+ "SELECT * FROM users",
284
+ [],
285
+ );
286
+ expect(result.rows).toHaveLength(1);
287
+ await base.close();
288
+ });
289
+ });
290
+ });
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @module @kb-labs/adapters-sqlite/secure-sql
3
+ * SecureSQLAdapter - ISQLDatabase wrapper with permission validation.
4
+ *
5
+ * Design Philosophy: Validation-only security (like fs-shim)
6
+ * - Validates table access against allowlists/denylists
7
+ * - Does NOT rewrite SQL queries
8
+ * - Fails fast with clear errors
9
+ * - Transparent pass-through when permitted
10
+ *
11
+ * Security Model:
12
+ * - Extracts table names from SQL using regex (basic, not perfect)
13
+ * - Checks table names against permissions
14
+ * - Does NOT parse SQL fully (use a SQL parser library for production)
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { createAdapter } from '@kb-labs/adapters-sqlite';
19
+ * import { SecureSQLAdapter } from '@kb-labs/adapters-sqlite/secure-sql';
20
+ *
21
+ * const base = createAdapter({ filename: 'app.db' });
22
+ * const secure = new SecureSQLAdapter(base, {
23
+ * allowlist: ['users', 'posts', 'comments'],
24
+ * denylist: ['admin_users', 'secrets'],
25
+ * });
26
+ *
27
+ * await secure.query('SELECT * FROM users WHERE id = ?', [123]); // ✅ Allowed
28
+ * await secure.query('SELECT * FROM admin_users LIMIT 1', []); // ❌ Denied
29
+ * ```
30
+ */
31
+
32
+ import type {
33
+ ISQLDatabase,
34
+ SQLQueryResult,
35
+ SQLTransaction,
36
+ } from "@kb-labs/core-platform/adapters";
37
+
38
+ /**
39
+ * Permission configuration for SQL database access.
40
+ */
41
+ export interface SQLPermissions {
42
+ /**
43
+ * Allowed table names (e.g., ['users', 'posts']).
44
+ * If empty or undefined, all tables are allowed (unless denied).
45
+ */
46
+ allowlist?: string[];
47
+
48
+ /**
49
+ * Denied table names (e.g., ['admin_users', 'secrets']).
50
+ * Takes precedence over allowlist.
51
+ */
52
+ denylist?: string[];
53
+
54
+ /**
55
+ * Allow read operations (SELECT, PRAGMA) (default: true)
56
+ */
57
+ read?: boolean;
58
+
59
+ /**
60
+ * Allow write operations (INSERT, UPDATE, DELETE) (default: true)
61
+ */
62
+ write?: boolean;
63
+
64
+ /**
65
+ * Allow schema modifications (CREATE, ALTER, DROP) (default: false - safer default)
66
+ */
67
+ schema?: boolean;
68
+ }
69
+
70
+ /**
71
+ * Error thrown when SQL access is denied.
72
+ */
73
+ export class SQLPermissionError extends Error {
74
+ constructor(
75
+ public readonly operation: string,
76
+ public readonly tables: string[],
77
+ public readonly reason: string,
78
+ ) {
79
+ super(
80
+ `SQL access denied: ${operation} on tables [${tables.join(", ")}] - ${reason}`,
81
+ );
82
+ this.name = "SQLPermissionError";
83
+ }
84
+ }
85
+
86
+ /**
87
+ * SecureSQLAdapter - validates permissions before delegating to base database.
88
+ *
89
+ * Design:
90
+ * - Validation-only (no SQL rewriting)
91
+ * - Fails fast with clear errors
92
+ * - Transparent pass-through when permitted
93
+ * - Basic table extraction via regex (not a full SQL parser)
94
+ *
95
+ * Limitations:
96
+ * - Table extraction is regex-based (may miss complex queries)
97
+ * - Does not handle subqueries, CTEs, or dynamic SQL
98
+ * - For production, consider using a proper SQL parser library
99
+ */
100
+ export class SecureSQLAdapter implements ISQLDatabase {
101
+ constructor(
102
+ private readonly baseDb: ISQLDatabase,
103
+ private readonly permissions: SQLPermissions,
104
+ ) {}
105
+
106
+ /**
107
+ * Extract table names from SQL query.
108
+ * Uses basic regex - not perfect, but good enough for validation.
109
+ */
110
+ private extractTableNames(sql: string): string[] {
111
+ const tables = new Set<string>();
112
+
113
+ // Normalize SQL: uppercase, remove extra spaces
114
+ const normalized = sql.toUpperCase().replace(/\s+/g, " ").trim();
115
+
116
+ // Pattern: FROM/JOIN/INTO/UPDATE <table_name>
117
+ const patterns = [
118
+ /FROM\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
119
+ /JOIN\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
120
+ /INTO\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
121
+ /UPDATE\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
122
+ /TABLE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?([a-zA-Z_][a-zA-Z0-9_]*)/g, // CREATE/DROP TABLE
123
+ ];
124
+
125
+ for (const pattern of patterns) {
126
+ let match: RegExpExecArray | null;
127
+ while ((match = pattern.exec(normalized)) !== null) {
128
+ if (match[1]) {
129
+ tables.add(match[1].toLowerCase());
130
+ }
131
+ }
132
+ }
133
+
134
+ return Array.from(tables);
135
+ }
136
+
137
+ /**
138
+ * Detect SQL operation type.
139
+ */
140
+ private detectOperation(sql: string): "read" | "write" | "schema" {
141
+ const normalized = sql.trim().toUpperCase();
142
+
143
+ // Schema operations
144
+ if (
145
+ normalized.startsWith("CREATE ") ||
146
+ normalized.startsWith("ALTER ") ||
147
+ normalized.startsWith("DROP ")
148
+ ) {
149
+ return "schema";
150
+ }
151
+
152
+ // Write operations
153
+ if (
154
+ normalized.startsWith("INSERT ") ||
155
+ normalized.startsWith("UPDATE ") ||
156
+ normalized.startsWith("DELETE ")
157
+ ) {
158
+ return "write";
159
+ }
160
+
161
+ // Read operations (SELECT, PRAGMA, etc.)
162
+ return "read";
163
+ }
164
+
165
+ /**
166
+ * Check if SQL query is allowed by permissions.
167
+ */
168
+ private checkPermissions(sql: string): void {
169
+ const operation = this.detectOperation(sql);
170
+
171
+ // Check operation-level permissions
172
+ const operationAllowed = this.permissions[operation] !== false;
173
+ if (!operationAllowed) {
174
+ throw new SQLPermissionError(
175
+ operation,
176
+ [],
177
+ `${operation} operations are disabled`,
178
+ );
179
+ }
180
+
181
+ // Extract table names
182
+ const tables = this.extractTableNames(sql);
183
+
184
+ // Check denylist first (takes precedence)
185
+ if (this.permissions.denylist) {
186
+ for (const table of tables) {
187
+ if (this.permissions.denylist.includes(table)) {
188
+ throw new SQLPermissionError(
189
+ operation,
190
+ tables,
191
+ `table '${table}' is in denylist`,
192
+ );
193
+ }
194
+ }
195
+ }
196
+
197
+ // Check allowlist (if defined)
198
+ if (this.permissions.allowlist && this.permissions.allowlist.length > 0) {
199
+ for (const table of tables) {
200
+ if (!this.permissions.allowlist.includes(table)) {
201
+ throw new SQLPermissionError(
202
+ operation,
203
+ tables,
204
+ `table '${table}' not in allowlist: [${this.permissions.allowlist.join(", ")}]`,
205
+ );
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Execute a SQL query with permission checks.
213
+ */
214
+ async query<T = unknown>(
215
+ sql: string,
216
+ params?: unknown[],
217
+ ): Promise<SQLQueryResult<T>> {
218
+ this.checkPermissions(sql);
219
+ return this.baseDb.query<T>(sql, params);
220
+ }
221
+
222
+ /**
223
+ * Begin a transaction with permission checks.
224
+ * Each query inside the transaction is also validated.
225
+ */
226
+ async transaction(): Promise<SQLTransaction> {
227
+ const baseTrx = await this.baseDb.transaction();
228
+
229
+ // Wrap transaction object to intercept queries
230
+ return {
231
+ query: async <T = unknown>(
232
+ sql: string,
233
+ params?: unknown[],
234
+ ): Promise<SQLQueryResult<T>> => {
235
+ this.checkPermissions(sql);
236
+ return baseTrx.query<T>(sql, params);
237
+ },
238
+
239
+ commit: async (): Promise<void> => {
240
+ await baseTrx.commit();
241
+ },
242
+
243
+ rollback: async (): Promise<void> => {
244
+ await baseTrx.rollback();
245
+ },
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Close the database connection.
251
+ */
252
+ async close(): Promise<void> {
253
+ await this.baseDb.close();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Create secure SQL adapter with permission validation.
259
+ *
260
+ * @param baseDb - Base SQL adapter (SQLite, PostgreSQL, etc.)
261
+ * @param permissions - Permission configuration
262
+ * @returns Wrapped SQL adapter with permission checks
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const secure = createSecureSQL(base, {
267
+ * allowlist: ['users', 'posts'],
268
+ * denylist: ['admin_users'],
269
+ * schema: false, // Prevent schema modifications
270
+ * });
271
+ * ```
272
+ */
273
+ export function createSecureSQL(
274
+ baseDb: ISQLDatabase,
275
+ permissions: SQLPermissions,
276
+ ): SecureSQLAdapter {
277
+ return new SecureSQLAdapter(baseDb, permissions);
278
+ }
279
+
280
+ // Default export for direct import
281
+ export default createSecureSQL;
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "baseUrl": ".",
6
+ "paths": {}
7
+ },
8
+ "include": [
9
+ "src/**/*"
10
+ ],
11
+ "exclude": [
12
+ "dist",
13
+ "node_modules"
14
+ ]
15
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@kb-labs/devkit/tsconfig/node.json",
4
+ "compilerOptions": {
5
+ "rootDir": "src",
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'tsup';
2
+ import nodePreset from '@kb-labs/devkit/tsup/node';
3
+
4
+ export default defineConfig({
5
+ ...nodePreset,
6
+ tsconfig: 'tsconfig.build.json',
7
+ dts: true,
8
+ });
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['src/**/*.{test,spec}.{ts,tsx}'],
8
+ coverage: {
9
+ provider: 'v8',
10
+ reporter: ['text', 'json', 'html'],
11
+ exclude: [
12
+ 'node_modules/',
13
+ 'dist/',
14
+ '**/*.d.ts',
15
+ '**/*.config.*',
16
+ ],
17
+ },
18
+ },
19
+ });