wicked-bus 1.0.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/README.md +153 -0
- package/commands/cli.js +129 -0
- package/commands/cmd-ack.js +31 -0
- package/commands/cmd-cleanup.js +44 -0
- package/commands/cmd-deregister.js +26 -0
- package/commands/cmd-emit.js +52 -0
- package/commands/cmd-init.js +30 -0
- package/commands/cmd-list.js +39 -0
- package/commands/cmd-register.js +29 -0
- package/commands/cmd-replay.js +65 -0
- package/commands/cmd-status.js +70 -0
- package/commands/cmd-subscribe.js +102 -0
- package/install.mjs +83 -0
- package/lib/config.js +87 -0
- package/lib/db.js +72 -0
- package/lib/emit.js +111 -0
- package/lib/errors.js +45 -0
- package/lib/index.cjs +20 -0
- package/lib/index.js +13 -0
- package/lib/paths.js +69 -0
- package/lib/poll.js +212 -0
- package/lib/register.js +164 -0
- package/lib/schema.sql +68 -0
- package/lib/sweep.js +71 -0
- package/lib/validate.js +156 -0
- package/package.json +56 -0
- package/scripts/postinstall.js +14 -0
- package/skills/wicked-bus/emit/SKILL.md +147 -0
- package/skills/wicked-bus/init/SKILL.md +94 -0
- package/skills/wicked-bus/naming/SKILL.md +151 -0
- package/skills/wicked-bus/query/SKILL.md +177 -0
- package/skills/wicked-bus/subscribe/SKILL.md +164 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wicked-bus",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight, local-first SQLite event bus for AI agents and developer tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./lib/index.js",
|
|
9
|
+
"require": "./lib/index.cjs"
|
|
10
|
+
},
|
|
11
|
+
"./cli": "./commands/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"main": "./lib/index.cjs",
|
|
14
|
+
"module": "./lib/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"wicked-bus": "./commands/cli.js",
|
|
17
|
+
"wicked-bus-install": "./install.mjs"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18.0.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"postinstall": "node scripts/postinstall.js",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"uuid": "^9.0.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"better-sqlite3": ">=9.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
36
|
+
"better-sqlite3": "^11.0.0",
|
|
37
|
+
"vitest": "^4.1.4"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"lib/",
|
|
41
|
+
"commands/",
|
|
42
|
+
"scripts/",
|
|
43
|
+
"skills/",
|
|
44
|
+
"install.mjs"
|
|
45
|
+
],
|
|
46
|
+
"keywords": [
|
|
47
|
+
"event-bus",
|
|
48
|
+
"sqlite",
|
|
49
|
+
"local-first",
|
|
50
|
+
"ai-agents",
|
|
51
|
+
"developer-tools",
|
|
52
|
+
"cursor-poll",
|
|
53
|
+
"at-least-once"
|
|
54
|
+
],
|
|
55
|
+
"license": "MIT"
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script -- auto-create data directory on npm install.
|
|
5
|
+
* Must not fail -- all errors are swallowed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ensureDataDir } from '../lib/paths.js';
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
ensureDataDir();
|
|
12
|
+
} catch (_) {
|
|
13
|
+
// Swallow: postinstall must not fail npm install
|
|
14
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Emit events to the wicked-bus. Use when publishing events from a plugin, logging activity to the bus, or integrating a new system with the event bridge. Covers both programmatic (Node.js) and CLI usage.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# wicked-bus:emit
|
|
6
|
+
|
|
7
|
+
Guide for publishing events to the wicked-bus.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- User wants to emit an event from their code
|
|
12
|
+
- User asks "how do I publish to the bus"
|
|
13
|
+
- Integrating a plugin with wicked-bus for the first time
|
|
14
|
+
- User wants to fire-and-forget an event
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
Check that wicked-bus is initialized. If not, trigger `wicked-bus-init`.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx wicked-bus status 2>/dev/null
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Programmatic Usage (Node.js)
|
|
25
|
+
|
|
26
|
+
### Basic emit
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import { emit } from 'wicked-bus';
|
|
30
|
+
import { loadConfig } from 'wicked-bus/lib/config.js';
|
|
31
|
+
import { openDb } from 'wicked-bus/lib/db.js';
|
|
32
|
+
|
|
33
|
+
const config = loadConfig();
|
|
34
|
+
const db = openDb(config);
|
|
35
|
+
|
|
36
|
+
const result = emit(db, config, {
|
|
37
|
+
event_type: 'wicked.task.completed',
|
|
38
|
+
domain: 'my-plugin',
|
|
39
|
+
subdomain: 'workflow.task',
|
|
40
|
+
payload: { taskId: 'abc-123', status: 'done' },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(result);
|
|
44
|
+
// { event_id: 42, idempotency_key: '550e8400-...' }
|
|
45
|
+
|
|
46
|
+
db.close();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Fire-and-forget pattern (recommended for integrations)
|
|
50
|
+
|
|
51
|
+
Plugins should never block on the bus. Use this pattern:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
let _busEmit = null;
|
|
55
|
+
let _busChecked = false;
|
|
56
|
+
|
|
57
|
+
async function emitToBus(event) {
|
|
58
|
+
if (!_busChecked) {
|
|
59
|
+
_busChecked = true;
|
|
60
|
+
try {
|
|
61
|
+
const mod = await import('wicked-bus');
|
|
62
|
+
const { loadConfig } = await import('wicked-bus/lib/config.js');
|
|
63
|
+
const { openDb } = await import('wicked-bus/lib/db.js');
|
|
64
|
+
const config = loadConfig();
|
|
65
|
+
const db = openDb(config);
|
|
66
|
+
_busEmit = (evt) => mod.emit(db, config, evt);
|
|
67
|
+
} catch (_) {
|
|
68
|
+
_busEmit = null; // Bus not installed — degrade gracefully
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!_busEmit) return null;
|
|
72
|
+
try {
|
|
73
|
+
return _busEmit(event);
|
|
74
|
+
} catch (_) {
|
|
75
|
+
return null; // Never throw from fire-and-forget
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### With custom TTL
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
emit(db, config, {
|
|
84
|
+
event_type: 'wicked.cache.invalidated',
|
|
85
|
+
domain: 'my-plugin',
|
|
86
|
+
payload: { keys: ['user:123'] },
|
|
87
|
+
ttl_hours: 4, // Override default 72h TTL
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### With explicit idempotency key
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
emit(db, config, {
|
|
95
|
+
event_type: 'wicked.job.completed',
|
|
96
|
+
domain: 'my-plugin',
|
|
97
|
+
subdomain: 'jobs.batch',
|
|
98
|
+
payload: { jobId: 'job-42' },
|
|
99
|
+
idempotency_key: 'job-42-completed', // Prevents duplicate events
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## CLI Usage
|
|
104
|
+
|
|
105
|
+
### Basic emit
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx wicked-bus emit \
|
|
109
|
+
--type wicked.task.completed \
|
|
110
|
+
--domain my-plugin \
|
|
111
|
+
--subdomain workflow.task \
|
|
112
|
+
--payload '{"taskId": "abc-123", "status": "done"}'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Payload from file
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx wicked-bus emit \
|
|
119
|
+
--type wicked.report.generated \
|
|
120
|
+
--domain my-plugin \
|
|
121
|
+
--payload @./report-data.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### With metadata
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx wicked-bus emit \
|
|
128
|
+
--type wicked.deploy.completed \
|
|
129
|
+
--domain my-deploy \
|
|
130
|
+
--subdomain deploy.production \
|
|
131
|
+
--payload '{"version": "2.0.0"}' \
|
|
132
|
+
--metadata '{"host": "prod-01"}'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
| Error | Code | Meaning |
|
|
138
|
+
|-------|------|---------|
|
|
139
|
+
| WB-001 | INVALID_EVENT_SCHEMA | Event failed validation (bad type, missing fields, payload too large) |
|
|
140
|
+
| WB-002 | DUPLICATE_EVENT | Idempotency key already exists |
|
|
141
|
+
| WB-004 | DISK_FULL | SQLite database disk is full |
|
|
142
|
+
| WB-005 | SCHEMA_VERSION_UNSUPPORTED | schema_version > 1.x |
|
|
143
|
+
|
|
144
|
+
## Event Naming
|
|
145
|
+
|
|
146
|
+
For help choosing event_type, domain, and subdomain values, use the
|
|
147
|
+
`wicked-bus-naming` skill.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Initialize wicked-bus or connect to an existing instance. Use when setting up the bus for the first time, checking if it's running, or configuring a project to use it. Auto-triggered when any wicked-bus skill detects no config.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# wicked-bus:init
|
|
6
|
+
|
|
7
|
+
Set up wicked-bus for the current project. Detects an existing running instance
|
|
8
|
+
before creating a new one.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- First time using wicked-bus in a project
|
|
13
|
+
- Another wicked-bus skill detected no config and redirected here
|
|
14
|
+
- User asks to "set up the bus", "init wicked-bus", or "connect to the bus"
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
### Step 1: Check for existing instance
|
|
19
|
+
|
|
20
|
+
Before creating anything, check if wicked-bus is already initialized:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Check if the data directory exists
|
|
24
|
+
ls ~/.something-wicked/wicked-bus/bus.db 2>/dev/null
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If the data dir and DB exist, wicked-bus is already running. Skip to Step 4.
|
|
28
|
+
|
|
29
|
+
Also check if another agent or process already initialized it this session:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Check if the CLI is available
|
|
33
|
+
npx wicked-bus status 2>/dev/null
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If status returns valid JSON, the bus is live. Report to the user and skip init.
|
|
37
|
+
|
|
38
|
+
### Step 2: Check if wicked-bus is installed
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Check if the package is available
|
|
42
|
+
node -e "require.resolve('wicked-bus')" 2>/dev/null || \
|
|
43
|
+
node -e "import('wicked-bus').then(() => console.log('found'))" 2>/dev/null
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If not installed:
|
|
47
|
+
```
|
|
48
|
+
wicked-bus is not installed. Install it:
|
|
49
|
+
npm install wicked-bus
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Step 3: Initialize
|
|
53
|
+
|
|
54
|
+
Run the init command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx wicked-bus init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This creates:
|
|
61
|
+
- `~/.something-wicked/wicked-bus/` data directory
|
|
62
|
+
- `bus.db` SQLite database with WAL mode
|
|
63
|
+
- `config.json` with defaults
|
|
64
|
+
|
|
65
|
+
Verify success by checking the JSON output for `"initialized": true`.
|
|
66
|
+
|
|
67
|
+
### Step 4: Register the current project (optional)
|
|
68
|
+
|
|
69
|
+
If the user wants this project to emit events, register as a provider:
|
|
70
|
+
|
|
71
|
+
Ask: "What domain name should this project use?" (default: directory name)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx wicked-bus register \
|
|
75
|
+
--role provider \
|
|
76
|
+
--plugin {domain} \
|
|
77
|
+
--filter 'wicked.*'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Step 5: Confirm
|
|
81
|
+
|
|
82
|
+
Report:
|
|
83
|
+
```
|
|
84
|
+
wicked-bus is ready.
|
|
85
|
+
Data dir: ~/.something-wicked/wicked-bus/
|
|
86
|
+
DB: bus.db (WAL mode)
|
|
87
|
+
Status: {event count} events, {subscriber count} subscribers
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If the bus was already running, say so:
|
|
91
|
+
```
|
|
92
|
+
wicked-bus is already initialized and running.
|
|
93
|
+
{status output}
|
|
94
|
+
```
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Guide for naming wicked-bus events — helps choose event_type, domain, and subdomain when emitting events. Use when creating new events, integrating a plugin with the bus, or reviewing event naming for consistency.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# wicked-bus Event Naming
|
|
6
|
+
|
|
7
|
+
Interactive guide for naming events in the wicked-bus ecosystem. Helps users
|
|
8
|
+
choose correct event_type, domain, and subdomain values.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- User is adding wicked-bus integration to a plugin
|
|
13
|
+
- User asks "how do I name this event" or "what event_type should I use"
|
|
14
|
+
- User is emitting events and needs to pick domain/subdomain
|
|
15
|
+
- Reviewing event names for consistency with the catalog
|
|
16
|
+
- User asks about the event naming convention
|
|
17
|
+
|
|
18
|
+
## The Three Fields
|
|
19
|
+
|
|
20
|
+
Every event has three identity fields:
|
|
21
|
+
|
|
22
|
+
| Field | Purpose | Rule |
|
|
23
|
+
|-------|---------|------|
|
|
24
|
+
| `event_type` | **What happened** — semantic, shared across producers | `wicked.<noun>.<past-tense-verb>` |
|
|
25
|
+
| `domain` | **Who did it** — the publishing plugin's package name | Your npm package name (e.g., `wicked-testing`) |
|
|
26
|
+
| `subdomain` | **Where in the system** — functional area within the plugin | Dot-separated hierarchy (e.g., `crew.phase`, `test.run`) |
|
|
27
|
+
|
|
28
|
+
## event_type Rules
|
|
29
|
+
|
|
30
|
+
Pattern: `wicked.<noun>.<past-tense-verb>`
|
|
31
|
+
|
|
32
|
+
1. Always starts with `wicked.`
|
|
33
|
+
2. Second segment = **noun** (the thing that changed): `run`, `phase`, `memory`, `project`, `gate`
|
|
34
|
+
3. Third segment = **past-tense verb** (what happened): `completed`, `started`, `stored`, `failed`, `created`
|
|
35
|
+
4. Lowercase, `[a-z0-9_]` only, dot-separated
|
|
36
|
+
5. Max 128 characters
|
|
37
|
+
6. **Semantic, not source-specific** — two plugins emitting the same kind of event share the type
|
|
38
|
+
|
|
39
|
+
### Common mistakes to catch
|
|
40
|
+
|
|
41
|
+
| Wrong | Problem | Correct |
|
|
42
|
+
|-------|---------|---------|
|
|
43
|
+
| `wicked-testing.run.completed` | Domain leaked into type | `wicked.run.completed` + domain=`wicked-testing` |
|
|
44
|
+
| `wicked.test_run_completed` | Underscores instead of dots | `wicked.run.completed` |
|
|
45
|
+
| `wicked.run.complete` | Not past tense | `wicked.run.completed` |
|
|
46
|
+
| `wicked.crew.phase.started` | Subdomain leaked into type (4 segments) | `wicked.phase.started` + subdomain=`crew.phase` |
|
|
47
|
+
| `run.completed` | Missing `wicked.` prefix | `wicked.run.completed` |
|
|
48
|
+
|
|
49
|
+
## domain Rules
|
|
50
|
+
|
|
51
|
+
1. A unique identifier for the publishing system — package name, service name, or tool name
|
|
52
|
+
2. Max 64 characters
|
|
53
|
+
3. One domain per system — don't subdivide at this level
|
|
54
|
+
4. This is what subscribers use in `@domain` filters
|
|
55
|
+
|
|
56
|
+
## subdomain Rules
|
|
57
|
+
|
|
58
|
+
1. Dot-separated hierarchy: `<area>.<entity>` (e.g., `deploy.staging`)
|
|
59
|
+
2. First segment = top-level area within your plugin
|
|
60
|
+
3. Second segment = specific entity or concern
|
|
61
|
+
4. Defaults to `''` if not provided
|
|
62
|
+
5. Max 64 characters
|
|
63
|
+
6. Can be arbitrarily deep if needed
|
|
64
|
+
|
|
65
|
+
## Process
|
|
66
|
+
|
|
67
|
+
When a user needs to name an event:
|
|
68
|
+
|
|
69
|
+
### Step 1: Identify what happened
|
|
70
|
+
|
|
71
|
+
Ask: "What changed and what happened to it?"
|
|
72
|
+
|
|
73
|
+
Map to: `wicked.<noun>.<past-tense-verb>`
|
|
74
|
+
|
|
75
|
+
- Thing created → `wicked.<thing>.created`
|
|
76
|
+
- Thing completed → `wicked.<thing>.completed`
|
|
77
|
+
- Thing failed → `wicked.<thing>.failed`
|
|
78
|
+
- Thing updated → `wicked.<thing>.updated`
|
|
79
|
+
- Thing deleted/removed → `wicked.<thing>.deleted`
|
|
80
|
+
- Thing started → `wicked.<thing>.started`
|
|
81
|
+
|
|
82
|
+
### Step 2: Identify the publisher
|
|
83
|
+
|
|
84
|
+
Ask: "What plugin is emitting this?"
|
|
85
|
+
|
|
86
|
+
Map to `domain` = their package name.
|
|
87
|
+
|
|
88
|
+
### Step 3: Identify the functional area
|
|
89
|
+
|
|
90
|
+
Ask: "What part of the system does this come from?"
|
|
91
|
+
|
|
92
|
+
Map to `subdomain` using the pattern `<area>.<entity>`. Examples:
|
|
93
|
+
- A deployment subsystem → `deploy.staging`
|
|
94
|
+
- An auth module → `auth.session`
|
|
95
|
+
- A build pipeline → `build.artifact`
|
|
96
|
+
|
|
97
|
+
### Step 4: Validate
|
|
98
|
+
|
|
99
|
+
Check that your event follows the rules:
|
|
100
|
+
|
|
101
|
+
1. event_type starts with `wicked.` and has exactly 3 dot-separated segments
|
|
102
|
+
2. Third segment is past tense (`created`, not `create`)
|
|
103
|
+
3. Domain doesn't appear in the event_type
|
|
104
|
+
4. Subdomain doesn't appear in the event_type
|
|
105
|
+
5. If another plugin emits the same semantic event, you should share the event_type
|
|
106
|
+
|
|
107
|
+
**Example validation:**
|
|
108
|
+
|
|
109
|
+
| Proposed | Valid? | Issue |
|
|
110
|
+
|----------|--------|-------|
|
|
111
|
+
| `wicked.deployment.started` + domain=`my-deploy` | Yes | |
|
|
112
|
+
| `my-deploy.deployment.started` | No | Domain in type |
|
|
113
|
+
| `wicked.deploy.staging.started` | No | 4 segments — subdomain leaked in |
|
|
114
|
+
| `wicked.deployment.start` | No | Not past tense |
|
|
115
|
+
|
|
116
|
+
### Step 5: Generate the emit call
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import { emit } from 'wicked-bus';
|
|
120
|
+
|
|
121
|
+
emit(db, config, {
|
|
122
|
+
event_type: '{event_type}',
|
|
123
|
+
domain: '{domain}',
|
|
124
|
+
subdomain: '{subdomain}',
|
|
125
|
+
payload: { /* event-specific data */ },
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Step 6: Show the subscriber filter
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# All events of this type from any source
|
|
133
|
+
wicked-bus subscribe --filter '{event_type}'
|
|
134
|
+
|
|
135
|
+
# Only from this domain
|
|
136
|
+
wicked-bus subscribe --filter '{event_type}@{domain}'
|
|
137
|
+
|
|
138
|
+
# All events from this domain
|
|
139
|
+
wicked-bus subscribe --filter '*@{domain}'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Design Decisions
|
|
143
|
+
|
|
144
|
+
**Why event_type is semantic (not source-specific):**
|
|
145
|
+
Same `wicked.project.created` from `wicked-garden` and `wicked-testing`.
|
|
146
|
+
A subscriber wanting "all project creations" uses one filter. Baking domain
|
|
147
|
+
into event_type forces subscribers to enumerate every producer.
|
|
148
|
+
|
|
149
|
+
**Why subdomain is a column (not in event_type):**
|
|
150
|
+
`wicked.phase.started` is semantic. Whether it's `crew.phase` or `deploy.phase`
|
|
151
|
+
is identity, not semantics. Columns enable index-based filtering.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Query and debug the wicked-bus. Use when checking bus health, inspecting events, debugging delivery issues, tracing event flow, or investigating why a subscriber isn't receiving events. Covers status, replay, and direct SQLite queries.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# wicked-bus:query
|
|
6
|
+
|
|
7
|
+
Tools for inspecting, debugging, and querying the wicked-bus.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- User asks "what's in the bus" or "show me recent events"
|
|
12
|
+
- Debugging why a subscriber isn't receiving events
|
|
13
|
+
- Checking bus health or event counts
|
|
14
|
+
- Investigating delivery lag or missed events
|
|
15
|
+
- User asks about cursor positions or subscriber state
|
|
16
|
+
|
|
17
|
+
## Quick Health Check
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx wicked-bus status
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returns JSON with:
|
|
24
|
+
- Total event count
|
|
25
|
+
- Active subscriber count
|
|
26
|
+
- Provider list
|
|
27
|
+
- Cursor lag per subscriber
|
|
28
|
+
|
|
29
|
+
## Inspecting Events
|
|
30
|
+
|
|
31
|
+
### Recent events via SQLite
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Last 10 events
|
|
35
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
36
|
+
"SELECT event_id, event_type, domain, subdomain, datetime(emitted_at/1000, 'unixepoch') as time FROM events ORDER BY event_id DESC LIMIT 10;"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Events by type
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
43
|
+
"SELECT event_id, domain, subdomain, datetime(emitted_at/1000, 'unixepoch') as time FROM events WHERE event_type = 'wicked.phase.completed' ORDER BY event_id DESC LIMIT 10;"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Events by domain
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
50
|
+
"SELECT event_id, event_type, subdomain, datetime(emitted_at/1000, 'unixepoch') as time FROM events WHERE domain = 'wicked-garden' ORDER BY event_id DESC LIMIT 10;"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Event count by type
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
57
|
+
"SELECT event_type, COUNT(*) as count FROM events GROUP BY event_type ORDER BY count DESC;"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Full event payload
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
64
|
+
"SELECT event_id, event_type, domain, payload FROM events WHERE event_id = {id};"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Debugging Subscribers
|
|
68
|
+
|
|
69
|
+
### Check cursor positions
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
73
|
+
"SELECT c.cursor_id, s.plugin, s.event_type_filter, c.last_event_id, datetime(c.acked_at/1000, 'unixepoch') as last_ack FROM cursors c JOIN subscriptions s ON c.subscription_id = s.subscription_id WHERE c.deregistered_at IS NULL;"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Find subscriber lag
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Compare cursor position to latest event
|
|
80
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
81
|
+
"SELECT s.plugin, c.last_event_id, (SELECT MAX(event_id) FROM events) - c.last_event_id as lag FROM cursors c JOIN subscriptions s ON c.subscription_id = s.subscription_id WHERE c.deregistered_at IS NULL;"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Check if subscriber is registered
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx wicked-bus list --role subscriber --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Check active vs deregistered
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx wicked-bus list --include-deregistered --json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Common Issues
|
|
97
|
+
|
|
98
|
+
### "Subscriber isn't receiving events"
|
|
99
|
+
|
|
100
|
+
1. **Check registration**: `npx wicked-bus list --role subscriber`
|
|
101
|
+
2. **Check filter**: does the filter match the event_type?
|
|
102
|
+
- `wicked.run.*` matches `wicked.run.completed` but NOT `wicked.run.step.completed`
|
|
103
|
+
- `@domain` suffix must match the `domain` column exactly
|
|
104
|
+
3. **Check cursor position**: is the cursor ahead of the events?
|
|
105
|
+
4. **Check expiry**: events past `expires_at` (default 72h) are invisible
|
|
106
|
+
5. **Check deregistration**: was the subscription soft-deleted?
|
|
107
|
+
|
|
108
|
+
### "WB-003: Cursor behind oldest event"
|
|
109
|
+
|
|
110
|
+
The subscriber's cursor is behind the oldest event in the table. Events
|
|
111
|
+
between the cursor and the oldest event were swept (deleted after
|
|
112
|
+
`dedup_expires_at`). These events are permanently lost for this subscriber.
|
|
113
|
+
|
|
114
|
+
**Fix**: Reset the cursor to the current position:
|
|
115
|
+
```bash
|
|
116
|
+
npx wicked-bus replay --cursor-id {cursor_id} --event-id {latest_event_id}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### "Events seem to disappear"
|
|
120
|
+
|
|
121
|
+
Events are deleted by the sweep process after `dedup_expires_at` (default 24h).
|
|
122
|
+
Check your sweep configuration:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
sqlite3 ~/.something-wicked/wicked-bus/bus.db \
|
|
126
|
+
"SELECT * FROM schema_migrations;"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Check config:
|
|
130
|
+
```bash
|
|
131
|
+
cat ~/.something-wicked/wicked-bus/config.json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### "Duplicate events"
|
|
135
|
+
|
|
136
|
+
Events have a UNIQUE `idempotency_key`. If you're seeing duplicates, the
|
|
137
|
+
emitter is generating different keys for logically identical events. Fix
|
|
138
|
+
by using a deterministic key:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
emit(db, config, {
|
|
142
|
+
event_type: 'wicked.job.completed',
|
|
143
|
+
domain: 'my-plugin',
|
|
144
|
+
payload: { jobId: 'job-42' },
|
|
145
|
+
idempotency_key: `job-42-completed`, // Deterministic
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Cleanup and Maintenance
|
|
150
|
+
|
|
151
|
+
### Manual sweep
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Dry run — see what would be deleted
|
|
155
|
+
npx wicked-bus cleanup --dry-run
|
|
156
|
+
|
|
157
|
+
# Delete expired events
|
|
158
|
+
npx wicked-bus cleanup
|
|
159
|
+
|
|
160
|
+
# Delete and archive to events_archive table
|
|
161
|
+
npx wicked-bus cleanup --archive
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Check database size
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
ls -lh ~/.something-wicked/wicked-bus/bus.db
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Check WAL size
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
ls -lh ~/.something-wicked/wicked-bus/bus.db-wal
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Large WAL files indicate checkpointing isn't happening. This is normal
|
|
177
|
+
for busy periods — SQLite auto-checkpoints at 1000 pages.
|