@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,450 @@
1
+ /**
2
+ * Tests for LogRingBufferAdapter
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from "vitest";
6
+ import { LogRingBufferAdapter } from "../index";
7
+ import type { LogRecord } from "@kb-labs/core-platform/adapters";
8
+
9
+ describe("LogRingBufferAdapter", () => {
10
+ let buffer: LogRingBufferAdapter;
11
+
12
+ beforeEach(() => {
13
+ buffer = new LogRingBufferAdapter({ maxSize: 5, ttl: 60000 }); // 60s TTL for testing
14
+ });
15
+
16
+ describe("append", () => {
17
+ it("should append logs to buffer", () => {
18
+ const log: LogRecord = {
19
+ id: "test-1",
20
+ timestamp: Date.now(),
21
+ level: "info",
22
+ message: "Test log",
23
+ fields: {},
24
+ source: "test",
25
+ };
26
+
27
+ buffer.append(log);
28
+
29
+ const stats = buffer.getStats();
30
+ expect(stats.size).toBe(1);
31
+ });
32
+
33
+ it("should evict oldest log when buffer is full", () => {
34
+ // Add 6 logs to a buffer with maxSize 5
35
+ for (let i = 0; i < 6; i++) {
36
+ buffer.append({
37
+ id: `test-${i}`,
38
+ timestamp: Date.now() + i,
39
+ level: "info",
40
+ message: `Log ${i}`,
41
+ fields: {},
42
+ source: "test",
43
+ });
44
+ }
45
+
46
+ const stats = buffer.getStats();
47
+ expect(stats.size).toBe(5); // Only 5 logs fit
48
+ expect(stats.evictions).toBe(1); // 1 log evicted
49
+
50
+ const logs = buffer.query();
51
+ expect(logs[logs.length - 1]!.message).toBe("Log 1"); // Log 0 was evicted
52
+ expect(logs[0]!.message).toBe("Log 5"); // Newest first
53
+ });
54
+
55
+ it("should notify subscribers when log is appended", () => {
56
+ const callback = vi.fn();
57
+ const unsubscribe = buffer.subscribe(callback);
58
+
59
+ const log: LogRecord = {
60
+ id: "test-2",
61
+ timestamp: Date.now(),
62
+ level: "info",
63
+ message: "Test log",
64
+ fields: {},
65
+ source: "test",
66
+ };
67
+
68
+ buffer.append(log);
69
+
70
+ expect(callback).toHaveBeenCalledTimes(1);
71
+ expect(callback).toHaveBeenCalledWith(log);
72
+
73
+ unsubscribe();
74
+ });
75
+
76
+ it("should handle subscriber errors gracefully", () => {
77
+ const errorCallback = vi.fn(() => {
78
+ throw new Error("Subscriber error");
79
+ });
80
+ const goodCallback = vi.fn();
81
+
82
+ buffer.subscribe(errorCallback);
83
+ buffer.subscribe(goodCallback);
84
+
85
+ const log: LogRecord = {
86
+ id: "test-3",
87
+ timestamp: Date.now(),
88
+ level: "info",
89
+ message: "Test log",
90
+ fields: {},
91
+ source: "test",
92
+ };
93
+
94
+ // Should not throw
95
+ expect(() => buffer.append(log)).not.toThrow();
96
+
97
+ // Good callback should still be called
98
+ expect(goodCallback).toHaveBeenCalledTimes(1);
99
+ });
100
+ });
101
+
102
+ describe("query", () => {
103
+ const now = Date.now();
104
+
105
+ beforeEach(() => {
106
+ // Add test logs (use current time to avoid TTL eviction)
107
+ buffer.append({
108
+ id: "test-query-1",
109
+ timestamp: now - 4000,
110
+ level: "debug",
111
+ message: "Debug log",
112
+ fields: {},
113
+ source: "test",
114
+ });
115
+ buffer.append({
116
+ id: "test-query-2",
117
+ timestamp: now - 3000,
118
+ level: "info",
119
+ message: "Info log",
120
+ fields: {},
121
+ source: "test",
122
+ });
123
+ buffer.append({
124
+ id: "test-query-3",
125
+ timestamp: now - 2000,
126
+ level: "warn",
127
+ message: "Warn log",
128
+ fields: {},
129
+ source: "test",
130
+ });
131
+ buffer.append({
132
+ id: "test-query-4",
133
+ timestamp: now - 1000,
134
+ level: "error",
135
+ message: "Error log",
136
+ fields: {},
137
+ source: "api",
138
+ });
139
+ });
140
+
141
+ it("should return all logs without filters", () => {
142
+ const logs = buffer.query();
143
+ expect(logs).toHaveLength(4);
144
+ expect(logs[0]!.timestamp).toBe(now - 1000); // Newest first
145
+ expect(logs[3]!.timestamp).toBe(now - 4000); // Oldest last
146
+ });
147
+
148
+ it("should filter by level", () => {
149
+ const logs = buffer.query({ level: "error" });
150
+ expect(logs).toHaveLength(1);
151
+ expect(logs[0]!.level).toBe("error");
152
+ });
153
+
154
+ it("should filter by source", () => {
155
+ const logs = buffer.query({ source: "api" });
156
+ expect(logs).toHaveLength(1);
157
+ expect(logs[0]!.source).toBe("api");
158
+ });
159
+
160
+ it("should filter by timestamp range", () => {
161
+ const logs = buffer.query({ from: now - 3000, to: now - 2000 });
162
+ expect(logs).toHaveLength(2);
163
+ expect(logs[0]!.timestamp).toBe(now - 2000);
164
+ expect(logs[1]!.timestamp).toBe(now - 3000);
165
+ });
166
+
167
+ it("should apply limit", () => {
168
+ const logs = buffer.query({ limit: 2 });
169
+ expect(logs).toHaveLength(2);
170
+ expect(logs[0]!.timestamp).toBe(now - 1000); // Newest
171
+ expect(logs[1]!.timestamp).toBe(now - 2000);
172
+ });
173
+
174
+ it("should combine multiple filters", () => {
175
+ const logs = buffer.query({
176
+ level: "info",
177
+ source: "test",
178
+ from: now - 3500,
179
+ });
180
+ expect(logs).toHaveLength(1);
181
+ expect(logs[0]!.level).toBe("info");
182
+ expect(logs[0]!.source).toBe("test");
183
+ expect(logs[0]!.timestamp).toBeGreaterThanOrEqual(now - 3500);
184
+ });
185
+ });
186
+
187
+ describe("subscribe", () => {
188
+ it("should allow multiple subscribers", () => {
189
+ const callback1 = vi.fn();
190
+ const callback2 = vi.fn();
191
+
192
+ buffer.subscribe(callback1);
193
+ buffer.subscribe(callback2);
194
+
195
+ const log: LogRecord = {
196
+ id: "test-4",
197
+ timestamp: Date.now(),
198
+ level: "info",
199
+ message: "Test log",
200
+ fields: {},
201
+ source: "test",
202
+ };
203
+
204
+ buffer.append(log);
205
+
206
+ expect(callback1).toHaveBeenCalledTimes(1);
207
+ expect(callback2).toHaveBeenCalledTimes(1);
208
+ });
209
+
210
+ it("should unsubscribe correctly", () => {
211
+ const callback = vi.fn();
212
+ const unsubscribe = buffer.subscribe(callback);
213
+
214
+ const log: LogRecord = {
215
+ id: "test-5",
216
+ timestamp: Date.now(),
217
+ level: "info",
218
+ message: "Test log 1",
219
+ fields: {},
220
+ source: "test",
221
+ };
222
+
223
+ buffer.append(log);
224
+ expect(callback).toHaveBeenCalledTimes(1);
225
+
226
+ // Unsubscribe
227
+ unsubscribe();
228
+
229
+ const log2: LogRecord = {
230
+ id: "test-6",
231
+ timestamp: Date.now(),
232
+ level: "info",
233
+ message: "Test log 2",
234
+ fields: {},
235
+ source: "test",
236
+ };
237
+
238
+ buffer.append(log2);
239
+ expect(callback).toHaveBeenCalledTimes(1); // Still 1, not called again
240
+ });
241
+ });
242
+
243
+ describe("getStats", () => {
244
+ it("should return correct stats", () => {
245
+ const now = Date.now();
246
+ const log1: LogRecord = {
247
+ id: "test-7",
248
+ timestamp: now - 1000,
249
+ level: "info",
250
+ message: "Log 1",
251
+ fields: {},
252
+ source: "test",
253
+ };
254
+ const log2: LogRecord = {
255
+ id: "test-8",
256
+ timestamp: now,
257
+ level: "info",
258
+ message: "Log 2",
259
+ fields: {},
260
+ source: "test",
261
+ };
262
+
263
+ buffer.append(log1);
264
+ buffer.append(log2);
265
+
266
+ const stats = buffer.getStats();
267
+ expect(stats.size).toBe(2);
268
+ expect(stats.maxSize).toBe(5);
269
+ expect(stats.oldestTimestamp).toBe(now - 1000);
270
+ expect(stats.newestTimestamp).toBe(now);
271
+ expect(stats.evictions).toBe(0);
272
+ });
273
+
274
+ it("should return zeros for empty buffer", () => {
275
+ const stats = buffer.getStats();
276
+ expect(stats.size).toBe(0);
277
+ expect(stats.maxSize).toBe(5);
278
+ expect(stats.oldestTimestamp).toBe(0);
279
+ expect(stats.newestTimestamp).toBe(0);
280
+ expect(stats.evictions).toBe(0);
281
+ });
282
+ });
283
+
284
+ describe("clear", () => {
285
+ it("should clear all logs", () => {
286
+ buffer.append({
287
+ id: "test-9",
288
+ timestamp: Date.now(),
289
+ level: "info",
290
+ message: "Test log",
291
+ fields: {},
292
+ source: "test",
293
+ });
294
+
295
+ expect(buffer.getStats().size).toBe(1);
296
+
297
+ buffer.clear();
298
+
299
+ const stats = buffer.getStats();
300
+ expect(stats.size).toBe(0);
301
+ expect(stats.evictions).toBe(0);
302
+ });
303
+ });
304
+
305
+ describe("TTL eviction", () => {
306
+ it("should evict expired logs based on TTL", async () => {
307
+ const shortTtlBuffer = new LogRingBufferAdapter({
308
+ maxSize: 10,
309
+ ttl: 100,
310
+ }); // 100ms TTL
311
+
312
+ // Add old log
313
+ shortTtlBuffer.append({
314
+ id: "test-ttl-1",
315
+ timestamp: Date.now() - 200, // Expired
316
+ level: "info",
317
+ message: "Old log",
318
+ fields: {},
319
+ source: "test",
320
+ });
321
+
322
+ // Add new log
323
+ shortTtlBuffer.append({
324
+ id: "test-ttl-2",
325
+ timestamp: Date.now(),
326
+ level: "info",
327
+ message: "New log",
328
+ fields: {},
329
+ source: "test",
330
+ });
331
+
332
+ // Query should trigger eviction
333
+ const logs = shortTtlBuffer.query();
334
+ expect(logs).toHaveLength(1);
335
+ expect(logs[0]!.message).toBe("New log");
336
+
337
+ const stats = shortTtlBuffer.getStats();
338
+ expect(stats.size).toBe(1);
339
+ expect(stats.evictions).toBe(1); // Old log evicted
340
+ });
341
+
342
+ it("should evict multiple expired logs", () => {
343
+ const now = Date.now();
344
+ const ttl = 1000;
345
+ const shortTtlBuffer = new LogRingBufferAdapter({ maxSize: 10, ttl });
346
+
347
+ // Add 3 expired logs
348
+ for (let i = 0; i < 3; i++) {
349
+ shortTtlBuffer.append({
350
+ id: `test-ttl-old-${i}`,
351
+ timestamp: now - ttl - 100, // Expired
352
+ level: "info",
353
+ message: `Old log ${i}`,
354
+ fields: {},
355
+ source: "test",
356
+ });
357
+ }
358
+
359
+ // Add 2 fresh logs
360
+ for (let i = 0; i < 2; i++) {
361
+ shortTtlBuffer.append({
362
+ id: `test-ttl-new-${i}`,
363
+ timestamp: now,
364
+ level: "info",
365
+ message: `New log ${i}`,
366
+ fields: {},
367
+ source: "test",
368
+ });
369
+ }
370
+
371
+ const logs = shortTtlBuffer.query();
372
+ expect(logs).toHaveLength(2);
373
+ expect(logs.every((l) => l.message.startsWith("New log"))).toBe(true);
374
+
375
+ const stats = shortTtlBuffer.getStats();
376
+ expect(stats.evictions).toBe(3);
377
+ });
378
+ });
379
+
380
+ describe("edge cases", () => {
381
+ it("should handle zero maxSize", () => {
382
+ const zeroBuffer = new LogRingBufferAdapter({ maxSize: 0 });
383
+
384
+ zeroBuffer.append({
385
+ id: "test-10",
386
+ timestamp: Date.now(),
387
+ level: "info",
388
+ message: "Test log",
389
+ fields: {},
390
+ source: "test",
391
+ });
392
+
393
+ const stats = zeroBuffer.getStats();
394
+ expect(stats.size).toBe(0); // Nothing stored
395
+ });
396
+
397
+ it("should handle negative timestamps", () => {
398
+ const now = Date.now();
399
+ // Use timestamp that won't be evicted by TTL (recent past)
400
+ const negativeTimestamp = now - 1000;
401
+
402
+ buffer.append({
403
+ id: "test-11",
404
+ timestamp: negativeTimestamp,
405
+ level: "info",
406
+ message: "Timestamp test",
407
+ fields: {},
408
+ source: "test",
409
+ });
410
+
411
+ const logs = buffer.query();
412
+ expect(logs).toHaveLength(1);
413
+ expect(logs[0]!.timestamp).toBe(negativeTimestamp);
414
+ });
415
+
416
+ it("should handle empty fields", () => {
417
+ buffer.append({
418
+ id: "test-12",
419
+ timestamp: Date.now(),
420
+ level: "info",
421
+ message: "No fields",
422
+ fields: {},
423
+ source: "test",
424
+ });
425
+
426
+ const logs = buffer.query();
427
+ expect(logs[0]!.fields).toEqual({});
428
+ });
429
+
430
+ it("should handle complex fields", () => {
431
+ const complexFields = {
432
+ user: { id: 123, name: "Alice" },
433
+ tags: ["api", "error"],
434
+ count: 42,
435
+ };
436
+
437
+ buffer.append({
438
+ id: "test-13",
439
+ timestamp: Date.now(),
440
+ level: "info",
441
+ message: "Complex fields",
442
+ fields: complexFields,
443
+ source: "test",
444
+ });
445
+
446
+ const logs = buffer.query();
447
+ expect(logs[0]!.fields).toEqual(complexFields);
448
+ });
449
+ });
450
+ });
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @module @kb-labs/adapters-log-ringbuffer
3
+ * In-memory ring buffer adapter for real-time log streaming.
4
+ *
5
+ * Features:
6
+ * - Fixed-size circular buffer (default 1000 logs)
7
+ * - Time-to-live expiration (default 1 hour)
8
+ * - Real-time subscription support
9
+ * - Automatic eviction of oldest logs
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createAdapter } from '@kb-labs/adapters-log-ringbuffer';
14
+ *
15
+ * const buffer = createAdapter({
16
+ * maxSize: 1000,
17
+ * ttl: 3600000, // 1 hour
18
+ * });
19
+ *
20
+ * // Append logs
21
+ * buffer.append({
22
+ * timestamp: Date.now(),
23
+ * level: 'info',
24
+ * message: 'Server started',
25
+ * fields: {},
26
+ * source: 'rest-api',
27
+ * });
28
+ *
29
+ * // Subscribe to real-time stream
30
+ * const unsubscribe = buffer.subscribe((log) => {
31
+ * console.log('New log:', log);
32
+ * });
33
+ *
34
+ * // Query logs
35
+ * const recentErrors = buffer.query({ level: 'error' });
36
+ * console.log(recentErrors);
37
+ *
38
+ * // Clean up
39
+ * unsubscribe();
40
+ * ```
41
+ */
42
+
43
+ import type {
44
+ ILogRingBuffer,
45
+ LogRingBufferConfig,
46
+ LogRecord,
47
+ LogQuery,
48
+ } from "@kb-labs/core-platform/adapters";
49
+
50
+ // Re-export manifest
51
+ export { manifest } from "./manifest.js";
52
+
53
+ /**
54
+ * In-memory ring buffer for log streaming.
55
+ *
56
+ * Implementation details:
57
+ * - Uses circular array for O(1) append
58
+ * - Lazy TTL eviction (on query/append)
59
+ * - No locks needed (single-threaded Node.js)
60
+ * - Memory-bounded by maxSize
61
+ */
62
+ export class LogRingBufferAdapter implements ILogRingBuffer {
63
+ private buffer: LogRecord[] = [];
64
+ private maxSize: number;
65
+ private ttl: number;
66
+ private subscribers: Set<(record: LogRecord) => void> = new Set();
67
+ private evictions = 0;
68
+
69
+ constructor(config: LogRingBufferConfig = {}) {
70
+ this.maxSize = config.maxSize ?? 1000;
71
+ this.ttl = config.ttl ?? 3600000; // 1 hour
72
+ }
73
+
74
+ /**
75
+ * Append log record to buffer.
76
+ * Evicts oldest log if buffer is full.
77
+ */
78
+ append(record: LogRecord): void {
79
+ // Remove expired logs before adding new one
80
+ this.evictExpired();
81
+
82
+ // Add new log
83
+ this.buffer.push(record);
84
+
85
+ // Evict oldest if buffer is full
86
+ if (this.buffer.length > this.maxSize) {
87
+ this.buffer.shift();
88
+ this.evictions++;
89
+ }
90
+
91
+ // Notify subscribers (real-time streaming)
92
+ this.notifySubscribers(record);
93
+ }
94
+
95
+ /**
96
+ * Query logs from buffer with optional filters.
97
+ * Returns logs in reverse chronological order (newest first).
98
+ */
99
+ query(query?: LogQuery): LogRecord[] {
100
+ // Remove expired logs before querying
101
+ this.evictExpired();
102
+
103
+ let results = [...this.buffer];
104
+
105
+ // Apply filters
106
+ if (query?.level) {
107
+ results = results.filter((r) => r.level === query.level);
108
+ }
109
+
110
+ if (query?.from !== undefined) {
111
+ results = results.filter((r) => r.timestamp >= query.from!);
112
+ }
113
+
114
+ if (query?.to !== undefined) {
115
+ results = results.filter((r) => r.timestamp <= query.to!);
116
+ }
117
+
118
+ if (query?.source) {
119
+ results = results.filter((r) => r.source === query.source);
120
+ }
121
+
122
+ // Apply limit
123
+ if (query?.limit !== undefined && query.limit > 0) {
124
+ results = results.slice(-query.limit);
125
+ }
126
+
127
+ // Return newest first
128
+ return results.reverse();
129
+ }
130
+
131
+ /**
132
+ * Subscribe to real-time log events.
133
+ * Callback is invoked synchronously for each new log.
134
+ */
135
+ subscribe(callback: (record: LogRecord) => void): () => void {
136
+ this.subscribers.add(callback);
137
+ return () => this.subscribers.delete(callback);
138
+ }
139
+
140
+ /**
141
+ * Get buffer statistics.
142
+ */
143
+ getStats() {
144
+ this.evictExpired();
145
+
146
+ return {
147
+ size: this.buffer.length,
148
+ maxSize: this.maxSize,
149
+ oldestTimestamp: this.buffer[0]?.timestamp ?? 0,
150
+ newestTimestamp: this.buffer[this.buffer.length - 1]?.timestamp ?? 0,
151
+ evictions: this.evictions,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Clear all logs from buffer.
157
+ * Useful for testing or manual cleanup.
158
+ */
159
+ clear(): void {
160
+ this.buffer = [];
161
+ this.evictions = 0;
162
+ }
163
+
164
+ /**
165
+ * Evict expired logs based on TTL.
166
+ * Called lazily on append/query operations.
167
+ */
168
+ private evictExpired(): void {
169
+ if (this.buffer.length === 0) {
170
+ return;
171
+ }
172
+
173
+ const now = Date.now();
174
+ const cutoff = now - this.ttl;
175
+
176
+ // Remove logs older than TTL from beginning of buffer
177
+ while (this.buffer.length > 0 && this.buffer[0]!.timestamp < cutoff) {
178
+ this.buffer.shift();
179
+ this.evictions++;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Notify all subscribers about new log.
185
+ */
186
+ private notifySubscribers(record: LogRecord): void {
187
+ this.subscribers.forEach((callback) => {
188
+ try {
189
+ callback(record);
190
+ } catch (error) {
191
+ // Don't let subscriber errors crash the buffer
192
+ console.error("Error in log buffer subscriber:", error);
193
+ }
194
+ });
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Factory function for creating ring buffer adapter.
200
+ * This is the function called by platform initialization.
201
+ *
202
+ * @param config - Ring buffer configuration
203
+ * @returns Ring buffer adapter instance
204
+ */
205
+ export function createAdapter(
206
+ config?: LogRingBufferConfig,
207
+ ): LogRingBufferAdapter {
208
+ return new LogRingBufferAdapter(config);
209
+ }
210
+
211
+ // Default export for convenience
212
+ export default createAdapter;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @module @kb-labs/adapters-log-ringbuffer/manifest
3
+ * Adapter manifest for log ring buffer extension.
4
+ */
5
+
6
+ import type { AdapterManifest } from "@kb-labs/core-platform";
7
+
8
+ /**
9
+ * Adapter manifest for log ring buffer extension.
10
+ */
11
+ export const manifest: AdapterManifest = {
12
+ manifestVersion: "1.0.0",
13
+ id: "log-ringbuffer",
14
+ name: "Log Ring Buffer",
15
+ version: "1.0.0",
16
+ description: "In-memory ring buffer for real-time log streaming",
17
+ author: "KB Labs Team",
18
+ license: "KBPL-1.1",
19
+ type: "extension",
20
+ implements: "ILogRingBuffer",
21
+ extends: {
22
+ adapter: "logger",
23
+ hook: "onLog",
24
+ method: "append",
25
+ priority: 10,
26
+ },
27
+ capabilities: {
28
+ streaming: true,
29
+ },
30
+ };
@@ -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
+ }