@kb-labs/adapters 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/CONTRIBUTING.md +90 -0
- package/IMPLEMENTATION_COMPLETE.md +416 -0
- package/LICENSE +186 -0
- package/README-TEMPLATE.md +179 -0
- package/README.md +306 -0
- package/docs/DOCUMENTATION.md +74 -0
- package/docs/adr/0000-template.md +49 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +33 -0
- package/docs/adr/0002-plugins-and-extensibility.md +46 -0
- package/docs/adr/0003-package-and-module-boundaries.md +37 -0
- package/docs/adr/0004-versioning-and-release-policy.md +38 -0
- package/docs/adr/0005-use-devkit-for-shared-tooling.md +48 -0
- package/docs/adr/0006-adopt-devkit-sync.md +47 -0
- package/docs/adr/0007-drift-kit-check.md +72 -0
- package/docs/adr/0008-devkit-sync-wrapper-strategy.md +67 -0
- package/docs/naming-convention.md +272 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +84 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/adapters-analytics-duckdb/package.json +54 -0
- package/packages/adapters-analytics-duckdb/scripts/migrate-from-jsonl.mjs +253 -0
- package/packages/adapters-analytics-duckdb/src/index.ts +380 -0
- package/packages/adapters-analytics-duckdb/src/manifest.ts +36 -0
- package/packages/adapters-analytics-duckdb/src/schema.ts +161 -0
- package/packages/adapters-analytics-duckdb/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-duckdb/tsconfig.json +9 -0
- package/packages/adapters-analytics-duckdb/tsup.config.ts +9 -0
- package/packages/adapters-analytics-file/README.md +32 -0
- package/packages/adapters-analytics-file/eslint.config.js +27 -0
- package/packages/adapters-analytics-file/package.json +50 -0
- package/packages/adapters-analytics-file/src/__tests__/daily-stats.spec.ts +287 -0
- package/packages/adapters-analytics-file/src/__tests__/scoped-analytics.test.ts +233 -0
- package/packages/adapters-analytics-file/src/index.test.ts +214 -0
- package/packages/adapters-analytics-file/src/index.ts +830 -0
- package/packages/adapters-analytics-file/src/manifest.ts +45 -0
- package/packages/adapters-analytics-file/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-file/tsconfig.json +9 -0
- package/packages/adapters-analytics-file/tsup.config.ts +9 -0
- package/packages/adapters-analytics-sqlite/package.json +55 -0
- package/packages/adapters-analytics-sqlite/scripts/migrate-from-jsonl.mjs +194 -0
- package/packages/adapters-analytics-sqlite/src/index.ts +460 -0
- package/packages/adapters-analytics-sqlite/src/manifest.ts +41 -0
- package/packages/adapters-analytics-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-analytics-sqlite/tsconfig.json +9 -0
- package/packages/adapters-analytics-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-environment-docker/README.md +28 -0
- package/packages/adapters-environment-docker/eslint.config.js +5 -0
- package/packages/adapters-environment-docker/package.json +49 -0
- package/packages/adapters-environment-docker/src/index.test.ts +138 -0
- package/packages/adapters-environment-docker/src/index.ts +439 -0
- package/packages/adapters-environment-docker/src/manifest.ts +65 -0
- package/packages/adapters-environment-docker/tsconfig.build.json +15 -0
- package/packages/adapters-environment-docker/tsconfig.json +16 -0
- package/packages/adapters-environment-docker/tsup.config.ts +9 -0
- package/packages/adapters-eventbus-cache/README.md +242 -0
- package/packages/adapters-eventbus-cache/eslint.config.js +27 -0
- package/packages/adapters-eventbus-cache/package.json +46 -0
- package/packages/adapters-eventbus-cache/src/index.test.ts +235 -0
- package/packages/adapters-eventbus-cache/src/index.ts +215 -0
- package/packages/adapters-eventbus-cache/src/manifest.ts +50 -0
- package/packages/adapters-eventbus-cache/src/types.ts +58 -0
- package/packages/adapters-eventbus-cache/tsconfig.build.json +15 -0
- package/packages/adapters-eventbus-cache/tsconfig.json +9 -0
- package/packages/adapters-eventbus-cache/tsup.config.ts +9 -0
- package/packages/adapters-fs/README.md +171 -0
- package/packages/adapters-fs/allowed.txt +1 -0
- package/packages/adapters-fs/conflict.txt +1 -0
- package/packages/adapters-fs/dest.txt +1 -0
- package/packages/adapters-fs/eslint.config.js +27 -0
- package/packages/adapters-fs/exists.txt +1 -0
- package/packages/adapters-fs/not-allowed.txt +1 -0
- package/packages/adapters-fs/other.txt +1 -0
- package/packages/adapters-fs/package.json +55 -0
- package/packages/adapters-fs/public/file1.txt +1 -0
- package/packages/adapters-fs/public/file2.txt +1 -0
- package/packages/adapters-fs/secret.txt +1 -0
- package/packages/adapters-fs/secrets/key.txt +1 -0
- package/packages/adapters-fs/src/index.test.ts +243 -0
- package/packages/adapters-fs/src/index.ts +258 -0
- package/packages/adapters-fs/src/manifest.ts +35 -0
- package/packages/adapters-fs/src/secure-storage.test.ts +380 -0
- package/packages/adapters-fs/src/secure-storage.ts +268 -0
- package/packages/adapters-fs/test.json +1 -0
- package/packages/adapters-fs/test.txt +1 -0
- package/packages/adapters-fs/test.xyz +1 -0
- package/packages/adapters-fs/test1.txt +1 -0
- package/packages/adapters-fs/test2.txt +1 -0
- package/packages/adapters-fs/tsconfig.build.json +15 -0
- package/packages/adapters-fs/tsconfig.json +9 -0
- package/packages/adapters-fs/tsup.config.ts +8 -0
- package/packages/adapters-fs/vitest.config.ts +19 -0
- package/packages/adapters-log-ringbuffer/README.md +228 -0
- package/packages/adapters-log-ringbuffer/eslint.config.js +27 -0
- package/packages/adapters-log-ringbuffer/package.json +47 -0
- package/packages/adapters-log-ringbuffer/src/__tests__/ring-buffer.test.ts +450 -0
- package/packages/adapters-log-ringbuffer/src/index.ts +212 -0
- package/packages/adapters-log-ringbuffer/src/manifest.ts +30 -0
- package/packages/adapters-log-ringbuffer/tsconfig.build.json +15 -0
- package/packages/adapters-log-ringbuffer/tsconfig.json +9 -0
- package/packages/adapters-log-ringbuffer/tsup.config.ts +9 -0
- package/packages/adapters-log-ringbuffer/vitest.config.ts +14 -0
- package/packages/adapters-log-sqlite/README.md +396 -0
- package/packages/adapters-log-sqlite/eslint.config.js +27 -0
- package/packages/adapters-log-sqlite/package.json +49 -0
- package/packages/adapters-log-sqlite/src/__tests__/log-persistence.test.ts +718 -0
- package/packages/adapters-log-sqlite/src/index.ts +1068 -0
- package/packages/adapters-log-sqlite/src/manifest.ts +36 -0
- package/packages/adapters-log-sqlite/src/schema.sql +46 -0
- package/packages/adapters-log-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-log-sqlite/tsconfig.json +9 -0
- package/packages/adapters-log-sqlite/tsup.config.ts +9 -0
- package/packages/adapters-log-sqlite/vitest.config.ts +15 -0
- package/packages/adapters-mongodb/README.md +147 -0
- package/packages/adapters-mongodb/eslint.config.js +27 -0
- package/packages/adapters-mongodb/package.json +53 -0
- package/packages/adapters-mongodb/src/index.ts +428 -0
- package/packages/adapters-mongodb/src/manifest.ts +45 -0
- package/packages/adapters-mongodb/src/secure-document.ts +231 -0
- package/packages/adapters-mongodb/tsconfig.build.json +15 -0
- package/packages/adapters-mongodb/tsconfig.json +9 -0
- package/packages/adapters-mongodb/tsup.config.ts +8 -0
- package/packages/adapters-openai/README.md +151 -0
- package/packages/adapters-openai/embeddings.ts +37 -0
- package/packages/adapters-openai/eslint.config.js +26 -0
- package/packages/adapters-openai/index.ts +22 -0
- package/packages/adapters-openai/package.json +57 -0
- package/packages/adapters-openai/src/embeddings-manifest.ts +45 -0
- package/packages/adapters-openai/src/embeddings.ts +104 -0
- package/packages/adapters-openai/src/index.ts +13 -0
- package/packages/adapters-openai/src/llm.ts +304 -0
- package/packages/adapters-openai/src/manifest.ts +47 -0
- package/packages/adapters-openai/tsconfig.build.json +15 -0
- package/packages/adapters-openai/tsconfig.json +9 -0
- package/packages/adapters-openai/tsup.config.ts +8 -0
- package/packages/adapters-pino/README.md +152 -0
- package/packages/adapters-pino/eslint.config.js +27 -0
- package/packages/adapters-pino/package.json +49 -0
- package/packages/adapters-pino/src/index.test.ts +44 -0
- package/packages/adapters-pino/src/index.ts +322 -0
- package/packages/adapters-pino/src/log-ring-buffer.ts +142 -0
- package/packages/adapters-pino/src/manifest.ts +49 -0
- package/packages/adapters-pino/tsconfig.build.json +15 -0
- package/packages/adapters-pino/tsconfig.json +9 -0
- package/packages/adapters-pino/tsup.config.ts +9 -0
- package/packages/adapters-pino-http/README.md +141 -0
- package/packages/adapters-pino-http/eslint.config.js +27 -0
- package/packages/adapters-pino-http/package.json +46 -0
- package/packages/adapters-pino-http/src/index.ts +229 -0
- package/packages/adapters-pino-http/tsconfig.build.json +15 -0
- package/packages/adapters-pino-http/tsconfig.json +9 -0
- package/packages/adapters-pino-http/tsup.config.ts +9 -0
- package/packages/adapters-qdrant/README.md +166 -0
- package/packages/adapters-qdrant/eslint.config.js +27 -0
- package/packages/adapters-qdrant/package.json +49 -0
- package/packages/adapters-qdrant/src/index.ts +490 -0
- package/packages/adapters-qdrant/src/manifest.ts +54 -0
- package/packages/adapters-qdrant/src/retry.ts +204 -0
- package/packages/adapters-qdrant/tsconfig.build.json +15 -0
- package/packages/adapters-qdrant/tsconfig.json +9 -0
- package/packages/adapters-qdrant/tsup.config.ts +9 -0
- package/packages/adapters-redis/README.md +159 -0
- package/packages/adapters-redis/eslint.config.js +27 -0
- package/packages/adapters-redis/package.json +49 -0
- package/packages/adapters-redis/src/index.ts +164 -0
- package/packages/adapters-redis/src/manifest.ts +49 -0
- package/packages/adapters-redis/tsconfig.build.json +15 -0
- package/packages/adapters-redis/tsconfig.json +9 -0
- package/packages/adapters-redis/tsup.config.ts +9 -0
- package/packages/adapters-snapshot-localfs/README.md +10 -0
- package/packages/adapters-snapshot-localfs/eslint.config.js +2 -0
- package/packages/adapters-snapshot-localfs/package.json +46 -0
- package/packages/adapters-snapshot-localfs/src/index.test.ts +40 -0
- package/packages/adapters-snapshot-localfs/src/index.ts +292 -0
- package/packages/adapters-snapshot-localfs/src/manifest.ts +32 -0
- package/packages/adapters-snapshot-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-snapshot-localfs/tsconfig.json +16 -0
- package/packages/adapters-snapshot-localfs/tsup.config.ts +11 -0
- package/packages/adapters-sqlite/README.md +163 -0
- package/packages/adapters-sqlite/eslint.config.js +27 -0
- package/packages/adapters-sqlite/package.json +54 -0
- package/packages/adapters-sqlite/src/index.test.ts +245 -0
- package/packages/adapters-sqlite/src/index.ts +382 -0
- package/packages/adapters-sqlite/src/manifest.ts +47 -0
- package/packages/adapters-sqlite/src/secure-sql.test.ts +290 -0
- package/packages/adapters-sqlite/src/secure-sql.ts +281 -0
- package/packages/adapters-sqlite/tsconfig.build.json +15 -0
- package/packages/adapters-sqlite/tsconfig.json +9 -0
- package/packages/adapters-sqlite/tsup.config.ts +8 -0
- package/packages/adapters-sqlite/vitest.config.ts +19 -0
- package/packages/adapters-transport/README.md +170 -0
- package/packages/adapters-transport/eslint.config.js +27 -0
- package/packages/adapters-transport/package.json +49 -0
- package/packages/adapters-transport/src/__tests__/unix-socket-server.test.ts +550 -0
- package/packages/adapters-transport/src/index.ts +101 -0
- package/packages/adapters-transport/src/ipc-transport.ts +228 -0
- package/packages/adapters-transport/src/transport.ts +224 -0
- package/packages/adapters-transport/src/types.ts +92 -0
- package/packages/adapters-transport/src/unix-socket-server.ts +193 -0
- package/packages/adapters-transport/src/unix-socket-transport.ts +280 -0
- package/packages/adapters-transport/tsconfig.build.json +15 -0
- package/packages/adapters-transport/tsconfig.json +9 -0
- package/packages/adapters-transport/tsup.config.ts +9 -0
- package/packages/adapters-vibeproxy/README.md +159 -0
- package/packages/adapters-vibeproxy/eslint.config.js +27 -0
- package/packages/adapters-vibeproxy/package.json +51 -0
- package/packages/adapters-vibeproxy/src/index.ts +13 -0
- package/packages/adapters-vibeproxy/src/llm.ts +437 -0
- package/packages/adapters-vibeproxy/src/manifest.ts +51 -0
- package/packages/adapters-vibeproxy/tsconfig.build.json +15 -0
- package/packages/adapters-vibeproxy/tsconfig.json +9 -0
- package/packages/adapters-vibeproxy/tsup.config.ts +8 -0
- package/packages/adapters-workspace-agent/package.json +46 -0
- package/packages/adapters-workspace-agent/src/__tests__/adapter.test.ts +212 -0
- package/packages/adapters-workspace-agent/src/index.ts +220 -0
- package/packages/adapters-workspace-agent/src/manifest.ts +36 -0
- package/packages/adapters-workspace-agent/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-agent/tsconfig.json +16 -0
- package/packages/adapters-workspace-agent/tsup.config.ts +11 -0
- package/packages/adapters-workspace-localfs/README.md +9 -0
- package/packages/adapters-workspace-localfs/eslint.config.js +2 -0
- package/packages/adapters-workspace-localfs/package.json +46 -0
- package/packages/adapters-workspace-localfs/src/index.test.ts +27 -0
- package/packages/adapters-workspace-localfs/src/index.ts +172 -0
- package/packages/adapters-workspace-localfs/src/manifest.ts +32 -0
- package/packages/adapters-workspace-localfs/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-localfs/tsconfig.json +16 -0
- package/packages/adapters-workspace-localfs/tsup.config.ts +11 -0
- package/packages/adapters-workspace-worktree/README.md +9 -0
- package/packages/adapters-workspace-worktree/eslint.config.js +2 -0
- package/packages/adapters-workspace-worktree/package.json +46 -0
- package/packages/adapters-workspace-worktree/src/index.test.ts +38 -0
- package/packages/adapters-workspace-worktree/src/index.ts +245 -0
- package/packages/adapters-workspace-worktree/src/manifest.ts +38 -0
- package/packages/adapters-workspace-worktree/tsconfig.build.json +15 -0
- package/packages/adapters-workspace-worktree/tsconfig.json +16 -0
- package/packages/adapters-workspace-worktree/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +2800 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/test-integration.ts +242 -0
- package/test.txt +1 -0
- package/tsconfig.base.json +6 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +26 -0
- package/tsconfig.tools.json +17 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +103 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @kb-labs/adapters-eventbus-cache
|
|
2
|
+
|
|
3
|
+
> Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem. Works exclusively within KB Labs platform.
|
|
4
|
+
|
|
5
|
+
EventBus adapter that uses platform cache (`ICache`) for persistent event storage with polling-based subscriptions.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
| Property | Value |
|
|
10
|
+
|----------|-------|
|
|
11
|
+
| **Implements** | `IEventBus` |
|
|
12
|
+
| **Type** | `core` |
|
|
13
|
+
| **Requires** | `cache` |
|
|
14
|
+
| **Category** | EventBus |
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Persistent events** - Events survive process restarts (if cache backend is persistent)
|
|
19
|
+
- **Distributed** - Works across multiple processes (if cache is Redis)
|
|
20
|
+
- **Automatic cleanup** - Old events removed via configurable TTL
|
|
21
|
+
- **Polling-based** - Configurable polling interval for subscribers
|
|
22
|
+
- **Ordered delivery** - Events delivered in timestamp order
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @kb-labs/adapters-eventbus-cache
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Add to your `kb.config.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"platform": {
|
|
37
|
+
"adapters": {
|
|
38
|
+
"cache": "@kb-labs/adapters-redis",
|
|
39
|
+
"eventBus": "@kb-labs/adapters-eventbus-cache"
|
|
40
|
+
},
|
|
41
|
+
"adapterOptions": {
|
|
42
|
+
"eventBus": {
|
|
43
|
+
"pollIntervalMs": 1000,
|
|
44
|
+
"eventTtlMs": 86400000,
|
|
45
|
+
"keyPrefix": "kb:eventbus:"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
| Option | Type | Default | Description |
|
|
55
|
+
|--------|------|---------|-------------|
|
|
56
|
+
| `pollIntervalMs` | `number` | `1000` | Polling interval in milliseconds |
|
|
57
|
+
| `eventTtlMs` | `number` | `86400000` | Event TTL (24 hours by default) |
|
|
58
|
+
| `keyPrefix` | `string` | `"eventbus:"` | Prefix for cache keys |
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Via Platform (Recommended)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { usePlatform } from '@kb-labs/sdk';
|
|
66
|
+
|
|
67
|
+
const platform = usePlatform();
|
|
68
|
+
|
|
69
|
+
// Subscribe to events
|
|
70
|
+
const unsubscribe = platform.eventBus.subscribe('user.created', async (event) => {
|
|
71
|
+
console.log('User created:', event);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Publish event
|
|
75
|
+
await platform.eventBus.publish('user.created', { id: '123', name: 'Alice' });
|
|
76
|
+
|
|
77
|
+
// Cleanup on shutdown
|
|
78
|
+
unsubscribe();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Standalone (Testing/Development)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createAdapter } from '@kb-labs/adapters-eventbus-cache';
|
|
85
|
+
import { MemoryCache } from '@kb-labs/core-platform/noop';
|
|
86
|
+
|
|
87
|
+
const cache = new MemoryCache();
|
|
88
|
+
const eventBus = createAdapter(
|
|
89
|
+
{ pollIntervalMs: 500, eventTtlMs: 3600000 },
|
|
90
|
+
{ cache },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Don't forget to disconnect on shutdown
|
|
94
|
+
eventBus.disconnect();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## How It Works
|
|
98
|
+
|
|
99
|
+
Events are stored in sorted sets using cache's `zadd`/`zrangebyscore` with timestamp as score:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
┌─────────────┐ ┌─────────────────────┐ ┌─────────────┐
|
|
103
|
+
│ Publisher │────▶│ CacheEventBusAdapter│────▶│ Cache │
|
|
104
|
+
└─────────────┘ └─────────────────────┘ └─────────────┘
|
|
105
|
+
│
|
|
106
|
+
│ poll (interval)
|
|
107
|
+
▼
|
|
108
|
+
┌─────────────────────┐
|
|
109
|
+
│ Subscribers │
|
|
110
|
+
└─────────────────────┘
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Storage structure:**
|
|
114
|
+
```
|
|
115
|
+
eventbus:user.created -> [
|
|
116
|
+
{ score: 1706745600000, member: '{"id":"evt-1","topic":"user.created","data":{...},"timestamp":1706745600000}' },
|
|
117
|
+
{ score: 1706745601000, member: '{"id":"evt-2","topic":"user.created","data":{...},"timestamp":1706745601000}' },
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Subscription flow:**
|
|
122
|
+
1. Subscriber registers with `lastTimestamp = Date.now()`
|
|
123
|
+
2. Polling timer fires every `pollIntervalMs`
|
|
124
|
+
3. Adapter queries `zrangebyscore(key, lastTimestamp + 1, now)`
|
|
125
|
+
4. Events processed sequentially, `lastTimestamp` updated
|
|
126
|
+
5. Old events (> TTL) cleaned up automatically
|
|
127
|
+
|
|
128
|
+
## Dependencies
|
|
129
|
+
|
|
130
|
+
This adapter requires the following adapters to be configured:
|
|
131
|
+
|
|
132
|
+
| Dependency | Adapter Key | Description |
|
|
133
|
+
|------------|-------------|-------------|
|
|
134
|
+
| `cache` | `cache` | Cache backend for event storage (Redis, Memory, etc.) |
|
|
135
|
+
|
|
136
|
+
> Dependencies are automatically resolved by the platform's AdapterLoader.
|
|
137
|
+
|
|
138
|
+
## Adapter Manifest
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{
|
|
142
|
+
id: 'eventbus-cache',
|
|
143
|
+
name: 'Cache-backed EventBus',
|
|
144
|
+
version: '1.0.0',
|
|
145
|
+
implements: 'IEventBus',
|
|
146
|
+
requires: {
|
|
147
|
+
adapters: [{ id: 'cache', alias: 'cache' }],
|
|
148
|
+
platform: '>= 1.0.0',
|
|
149
|
+
},
|
|
150
|
+
capabilities: {
|
|
151
|
+
custom: {
|
|
152
|
+
persistence: true,
|
|
153
|
+
distributed: true,
|
|
154
|
+
ttl: true,
|
|
155
|
+
polling: true,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Performance Considerations
|
|
162
|
+
|
|
163
|
+
- **Memory**: Depends on cache backend; events are JSON-serialized (~200-500 bytes per event)
|
|
164
|
+
- **Latency**: Polling-based, so delivery latency is up to `pollIntervalMs`
|
|
165
|
+
- **Throughput**: Limited by cache backend; Redis handles ~100K ops/sec
|
|
166
|
+
|
|
167
|
+
**Tuning tips:**
|
|
168
|
+
- Lower `pollIntervalMs` for faster delivery (more CPU/network)
|
|
169
|
+
- Shorter `eventTtlMs` for lower memory usage
|
|
170
|
+
- Use Redis cache for distributed deployments
|
|
171
|
+
|
|
172
|
+
## FAQ
|
|
173
|
+
|
|
174
|
+
<details>
|
|
175
|
+
<summary><strong>Q: Can I use this adapter outside KB Labs platform?</strong></summary>
|
|
176
|
+
|
|
177
|
+
No. This adapter is designed specifically for KB Labs ecosystem and depends on platform interfaces (`IEventBus`, `ICache`). Use `createAdapter()` with mock cache for standalone testing only.
|
|
178
|
+
</details>
|
|
179
|
+
|
|
180
|
+
<details>
|
|
181
|
+
<summary><strong>Q: Why polling instead of push notifications?</strong></summary>
|
|
182
|
+
|
|
183
|
+
Polling provides simpler implementation that works with any cache backend. For real-time requirements (< 100ms), consider using Redis pub/sub directly or a dedicated message broker.
|
|
184
|
+
</details>
|
|
185
|
+
|
|
186
|
+
<details>
|
|
187
|
+
<summary><strong>Q: What happens if a subscriber is slow?</strong></summary>
|
|
188
|
+
|
|
189
|
+
Events are processed sequentially per subscriber. If a handler is slow, that subscriber will lag behind. Other subscribers are not affected. Events are retained until TTL expires, so slow subscribers can catch up.
|
|
190
|
+
</details>
|
|
191
|
+
|
|
192
|
+
<details>
|
|
193
|
+
<summary><strong>Q: Are events guaranteed to be delivered exactly once?</strong></summary>
|
|
194
|
+
|
|
195
|
+
No. This is an at-least-once delivery system. If a process crashes mid-processing, events may be redelivered on restart. Design handlers to be idempotent.
|
|
196
|
+
</details>
|
|
197
|
+
|
|
198
|
+
<details>
|
|
199
|
+
<summary><strong>Q: Can I use MemoryCache in production?</strong></summary>
|
|
200
|
+
|
|
201
|
+
Not recommended. MemoryCache is single-process and loses data on restart. Use Redis for production deployments requiring persistence and distribution.
|
|
202
|
+
</details>
|
|
203
|
+
|
|
204
|
+
## Related Adapters
|
|
205
|
+
|
|
206
|
+
| Adapter | Use Case |
|
|
207
|
+
|---------|----------|
|
|
208
|
+
| `@kb-labs/adapters-redis` | Cache backend for distributed EventBus |
|
|
209
|
+
| `@kb-labs/core-platform/noop` | MemoryCache for testing/development |
|
|
210
|
+
|
|
211
|
+
## Troubleshooting
|
|
212
|
+
|
|
213
|
+
### Events not being received
|
|
214
|
+
|
|
215
|
+
**Cause**: Subscriber registered after events were published; `lastTimestamp` is newer than event timestamps.
|
|
216
|
+
|
|
217
|
+
**Solution**: Ensure subscribers are registered before publishers start, or adjust subscription logic.
|
|
218
|
+
|
|
219
|
+
### High memory usage
|
|
220
|
+
|
|
221
|
+
**Cause**: Long `eventTtlMs` with high event volume.
|
|
222
|
+
|
|
223
|
+
**Solution**: Reduce `eventTtlMs` or implement event archiving.
|
|
224
|
+
|
|
225
|
+
### Slow event delivery
|
|
226
|
+
|
|
227
|
+
**Cause**: `pollIntervalMs` too high.
|
|
228
|
+
|
|
229
|
+
**Solution**: Reduce `pollIntervalMs` (e.g., 100ms for near-real-time).
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Verify adapter is loaded
|
|
233
|
+
pnpm kb plugins list
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development guidelines.
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
[KB Public License v1.1](../../LICENSE) - KB Labs Team
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard ESLint configuration template
|
|
3
|
+
*
|
|
4
|
+
* This is the canonical template for all @kb-labs packages.
|
|
5
|
+
* DO NOT modify this file locally - it is synced from @kb-labs/devkit
|
|
6
|
+
*
|
|
7
|
+
* Customization guidelines:
|
|
8
|
+
* - DevKit preset already includes all standard ignores
|
|
9
|
+
* - Only add project-specific ignores if absolutely necessary
|
|
10
|
+
* - Document why custom ignores are needed
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/kb-labs/devkit#eslint-configuration
|
|
13
|
+
*/
|
|
14
|
+
import nodePreset from '@kb-labs/devkit/eslint/node.js';
|
|
15
|
+
|
|
16
|
+
export default [
|
|
17
|
+
...nodePreset,
|
|
18
|
+
|
|
19
|
+
// OPTIONAL: Add project-specific ignores only if needed
|
|
20
|
+
// DevKit preset already ignores: dist/, coverage/, node_modules/, *.d.ts, scripts/, etc.
|
|
21
|
+
// {
|
|
22
|
+
// ignores: [
|
|
23
|
+
// // Add ONLY project-specific patterns here
|
|
24
|
+
// // Example: '**/*.generated.ts',
|
|
25
|
+
// ]
|
|
26
|
+
// }
|
|
27
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/adapters-eventbus-cache",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "EventBus adapter using ICache for persistent event storage",
|
|
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
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rimraf dist",
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"type-check": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run --passWithNoTests",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "eslint src --ext .ts",
|
|
27
|
+
"lint:fix": "eslint . --fix"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@kb-labs/core-platform": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@kb-labs/core-platform": "link:../../../../platform/kb-labs-core/packages/core-platform",
|
|
34
|
+
"@types/node": "^24.3.3",
|
|
35
|
+
"eslint": "^9",
|
|
36
|
+
"tsup": "^8.5.0",
|
|
37
|
+
"typescript": "^5.6.3",
|
|
38
|
+
"vitest": "^3.2.4",
|
|
39
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
40
|
+
"rimraf": "^6.0.1"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0",
|
|
44
|
+
"pnpm": ">=9.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { CacheEventBusAdapter, createAdapter } from './index.js';
|
|
3
|
+
import type { ICache } from '@kb-labs/core-platform';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mock ICache implementation for testing.
|
|
7
|
+
*/
|
|
8
|
+
class MockCache implements ICache {
|
|
9
|
+
private store = new Map<string, unknown>();
|
|
10
|
+
private sortedSets = new Map<string, Array<{ score: number; member: string }>>();
|
|
11
|
+
|
|
12
|
+
async get<T>(key: string): Promise<T | null> {
|
|
13
|
+
return (this.store.get(key) as T) ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async set<T>(key: string, value: T, _ttl?: number): Promise<void> {
|
|
17
|
+
this.store.set(key, value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async delete(key: string): Promise<void> {
|
|
21
|
+
this.store.delete(key);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async clear(_pattern?: string): Promise<void> {
|
|
25
|
+
this.store.clear();
|
|
26
|
+
this.sortedSets.clear();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async zadd(key: string, score: number, member: string): Promise<void> {
|
|
30
|
+
let set = this.sortedSets.get(key);
|
|
31
|
+
if (!set) {
|
|
32
|
+
set = [];
|
|
33
|
+
this.sortedSets.set(key, set);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Remove existing member if present
|
|
37
|
+
const existingIndex = set.findIndex(m => m.member === member);
|
|
38
|
+
if (existingIndex !== -1) {
|
|
39
|
+
set.splice(existingIndex, 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add new member and sort by score
|
|
43
|
+
set.push({ score, member });
|
|
44
|
+
set.sort((a, b) => a.score - b.score);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async zrangebyscore(key: string, min: number, max: number): Promise<string[]> {
|
|
48
|
+
const set = this.sortedSets.get(key);
|
|
49
|
+
if (!set) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return set
|
|
54
|
+
.filter(m => m.score >= min && m.score <= max)
|
|
55
|
+
.map(m => m.member);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async zrem(key: string, member: string): Promise<void> {
|
|
59
|
+
const set = this.sortedSets.get(key);
|
|
60
|
+
if (!set) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const index = set.findIndex(m => m.member === member);
|
|
65
|
+
if (index !== -1) {
|
|
66
|
+
set.splice(index, 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async setIfNotExists<T>(key: string, value: T, _ttl?: number): Promise<boolean> {
|
|
71
|
+
if (this.store.has(key)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
this.store.set(key, value);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TEST_TOPIC = 'test.topic';
|
|
80
|
+
|
|
81
|
+
describe('CacheEventBusAdapter', () => {
|
|
82
|
+
let cache: MockCache;
|
|
83
|
+
let eventBus: CacheEventBusAdapter;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
vi.useFakeTimers();
|
|
87
|
+
cache = new MockCache();
|
|
88
|
+
eventBus = new CacheEventBusAdapter(cache, {
|
|
89
|
+
pollIntervalMs: 100,
|
|
90
|
+
eventTtlMs: 60000, // 1 minute
|
|
91
|
+
keyPrefix: 'test:eventbus:',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
eventBus.disconnect();
|
|
97
|
+
vi.useRealTimers();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('publish', () => {
|
|
101
|
+
it('should store event in cache', async () => {
|
|
102
|
+
await eventBus.publish(TEST_TOPIC, { message: 'hello' });
|
|
103
|
+
|
|
104
|
+
const events = await cache.zrangebyscore('test:eventbus:test.topic', 0, Date.now() + 1000);
|
|
105
|
+
expect(events).toHaveLength(1);
|
|
106
|
+
|
|
107
|
+
const storedEvent = JSON.parse(events[0]!);
|
|
108
|
+
expect(storedEvent.topic).toBe(TEST_TOPIC);
|
|
109
|
+
expect(storedEvent.data).toEqual({ message: 'hello' });
|
|
110
|
+
expect(storedEvent.id).toBeDefined();
|
|
111
|
+
expect(storedEvent.timestamp).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should store multiple events in order', async () => {
|
|
115
|
+
await eventBus.publish(TEST_TOPIC, { order: 1 });
|
|
116
|
+
vi.advanceTimersByTime(10);
|
|
117
|
+
await eventBus.publish(TEST_TOPIC, { order: 2 });
|
|
118
|
+
vi.advanceTimersByTime(10);
|
|
119
|
+
await eventBus.publish(TEST_TOPIC, { order: 3 });
|
|
120
|
+
|
|
121
|
+
const events = await cache.zrangebyscore('test:eventbus:test.topic', 0, Date.now() + 1000);
|
|
122
|
+
expect(events).toHaveLength(3);
|
|
123
|
+
|
|
124
|
+
const parsed = events.map(e => JSON.parse(e));
|
|
125
|
+
expect(parsed[0].data.order).toBe(1);
|
|
126
|
+
expect(parsed[1].data.order).toBe(2);
|
|
127
|
+
expect(parsed[2].data.order).toBe(3);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('subscribe', () => {
|
|
132
|
+
it('should receive published events via polling', async () => {
|
|
133
|
+
const received: unknown[] = [];
|
|
134
|
+
|
|
135
|
+
eventBus.subscribe(TEST_TOPIC, async (event) => {
|
|
136
|
+
received.push(event);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Advance time so event timestamp > subscription lastTimestamp
|
|
140
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
141
|
+
|
|
142
|
+
// Publish an event
|
|
143
|
+
await eventBus.publish(TEST_TOPIC, { message: 'hello' });
|
|
144
|
+
|
|
145
|
+
// Advance timer to trigger poll
|
|
146
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
147
|
+
|
|
148
|
+
expect(received).toHaveLength(1);
|
|
149
|
+
expect(received[0]).toEqual({ message: 'hello' });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should return unsubscribe function', () => {
|
|
153
|
+
const unsubscribe = eventBus.subscribe(TEST_TOPIC, async () => {});
|
|
154
|
+
|
|
155
|
+
expect(eventBus.subscriptionCount).toBe(1);
|
|
156
|
+
|
|
157
|
+
unsubscribe();
|
|
158
|
+
|
|
159
|
+
expect(eventBus.subscriptionCount).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should support multiple subscribers on same topic', async () => {
|
|
163
|
+
const received1: unknown[] = [];
|
|
164
|
+
const received2: unknown[] = [];
|
|
165
|
+
|
|
166
|
+
eventBus.subscribe(TEST_TOPIC, async (event) => {
|
|
167
|
+
received1.push(event);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
eventBus.subscribe(TEST_TOPIC, async (event) => {
|
|
171
|
+
received2.push(event);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Advance time so event timestamp > subscription lastTimestamp
|
|
175
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
176
|
+
|
|
177
|
+
await eventBus.publish(TEST_TOPIC, { message: 'hello' });
|
|
178
|
+
|
|
179
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
180
|
+
|
|
181
|
+
expect(received1).toHaveLength(1);
|
|
182
|
+
expect(received2).toHaveLength(1);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should not receive events from other topics', async () => {
|
|
186
|
+
const received: unknown[] = [];
|
|
187
|
+
|
|
188
|
+
eventBus.subscribe('topic.a', async (event) => {
|
|
189
|
+
received.push(event);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await eventBus.publish('topic.b', { message: 'wrong topic' });
|
|
193
|
+
|
|
194
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
195
|
+
|
|
196
|
+
expect(received).toHaveLength(0);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('disconnect', () => {
|
|
201
|
+
it('should stop all subscriptions', () => {
|
|
202
|
+
eventBus.subscribe('topic.a', async () => {});
|
|
203
|
+
eventBus.subscribe('topic.b', async () => {});
|
|
204
|
+
|
|
205
|
+
expect(eventBus.subscriptionCount).toBe(2);
|
|
206
|
+
|
|
207
|
+
eventBus.disconnect();
|
|
208
|
+
|
|
209
|
+
expect(eventBus.subscriptionCount).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('createAdapter', () => {
|
|
214
|
+
it('should create adapter with default config', () => {
|
|
215
|
+
const adapter = createAdapter({}, { cache });
|
|
216
|
+
|
|
217
|
+
expect(adapter).toBeInstanceOf(CacheEventBusAdapter);
|
|
218
|
+
adapter.disconnect();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should create adapter with custom config', () => {
|
|
222
|
+
const adapter = createAdapter(
|
|
223
|
+
{
|
|
224
|
+
pollIntervalMs: 500,
|
|
225
|
+
eventTtlMs: 3600000,
|
|
226
|
+
keyPrefix: 'custom:',
|
|
227
|
+
},
|
|
228
|
+
{ cache },
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
expect(adapter).toBeInstanceOf(CacheEventBusAdapter);
|
|
232
|
+
adapter.disconnect();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|