@loom-framework/core 0.1.0-alpha.150 → 0.1.0-alpha.152
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/builtin-skills/app-skill/SKILL.md +16 -5
- package/builtin-skills/app-skill/references/auth.md +23 -19
- package/builtin-skills/app-skill/references/events.md +119 -0
- package/builtin-skills/app-skill/references/evolution.md +90 -0
- package/builtin-skills/app-skill/references/process-builder.md +140 -0
- package/builtin-skills/app-skill/references/process-metrics.md +93 -0
- package/builtin-skills/app-skill/references/process.md +222 -0
- package/builtin-skills/loom/SKILL.md +9 -7
- package/dist/backend/auth/request-context.d.ts +15 -0
- package/dist/backend/auth/request-context.d.ts.map +1 -0
- package/dist/backend/auth/request-context.js +17 -0
- package/dist/backend/auth/request-context.js.map +1 -0
- package/dist/backend/events/event-bus.d.ts +39 -0
- package/dist/backend/events/event-bus.d.ts.map +1 -0
- package/dist/backend/events/event-bus.js +182 -0
- package/dist/backend/events/event-bus.js.map +1 -0
- package/dist/backend/events/event-emitting-adapter.d.ts +31 -0
- package/dist/backend/events/event-emitting-adapter.d.ts.map +1 -0
- package/dist/backend/events/event-emitting-adapter.js +70 -0
- package/dist/backend/events/event-emitting-adapter.js.map +1 -0
- package/dist/backend/events/event-registry.d.ts +24 -0
- package/dist/backend/events/event-registry.d.ts.map +1 -0
- package/dist/backend/events/event-registry.js +22 -0
- package/dist/backend/events/event-registry.js.map +1 -0
- package/dist/backend/events/index.d.ts +7 -0
- package/dist/backend/events/index.d.ts.map +1 -0
- package/dist/backend/events/index.js +7 -0
- package/dist/backend/events/index.js.map +1 -0
- package/dist/backend/index.d.ts +12 -0
- package/dist/backend/index.d.ts.map +1 -1
- package/dist/backend/index.js +143 -3
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/process/engine.d.ts +84 -0
- package/dist/backend/process/engine.d.ts.map +1 -0
- package/dist/backend/process/engine.js +511 -0
- package/dist/backend/process/engine.js.map +1 -0
- package/dist/backend/process/index.d.ts +7 -0
- package/dist/backend/process/index.d.ts.map +1 -0
- package/dist/backend/process/index.js +6 -0
- package/dist/backend/process/index.js.map +1 -0
- package/dist/backend/process/logger.d.ts +30 -0
- package/dist/backend/process/logger.d.ts.map +1 -0
- package/dist/backend/process/logger.js +132 -0
- package/dist/backend/process/logger.js.map +1 -0
- package/dist/backend/process/queue.d.ts +31 -0
- package/dist/backend/process/queue.d.ts.map +1 -0
- package/dist/backend/process/queue.js +80 -0
- package/dist/backend/process/queue.js.map +1 -0
- package/dist/backend/process/registry.d.ts +25 -0
- package/dist/backend/process/registry.d.ts.map +1 -0
- package/dist/backend/process/registry.js +98 -0
- package/dist/backend/process/registry.js.map +1 -0
- package/dist/backend/process/trigger.d.ts +29 -0
- package/dist/backend/process/trigger.d.ts.map +1 -0
- package/dist/backend/process/trigger.js +108 -0
- package/dist/backend/process/trigger.js.map +1 -0
- package/dist/backend/routes/ai-config.d.ts +2 -0
- package/dist/backend/routes/ai-config.d.ts.map +1 -1
- package/dist/backend/routes/ai-config.js +2 -1
- package/dist/backend/routes/ai-config.js.map +1 -1
- package/dist/backend/routes/auth-routes.d.ts +7 -0
- package/dist/backend/routes/auth-routes.d.ts.map +1 -1
- package/dist/backend/routes/auth-routes.js +228 -1
- package/dist/backend/routes/auth-routes.js.map +1 -1
- package/dist/backend/routes/chat.d.ts +2 -0
- package/dist/backend/routes/chat.d.ts.map +1 -1
- package/dist/backend/routes/chat.js +3 -2
- package/dist/backend/routes/chat.js.map +1 -1
- package/dist/backend/routes/data.d.ts +2 -1
- package/dist/backend/routes/data.d.ts.map +1 -1
- package/dist/backend/routes/data.js +7 -1
- package/dist/backend/routes/data.js.map +1 -1
- package/dist/backend/routes/event-routes.d.ts +15 -0
- package/dist/backend/routes/event-routes.d.ts.map +1 -0
- package/dist/backend/routes/event-routes.js +35 -0
- package/dist/backend/routes/event-routes.js.map +1 -0
- package/dist/backend/routes/index.d.ts +4 -0
- package/dist/backend/routes/index.d.ts.map +1 -1
- package/dist/backend/routes/index.js +2 -0
- package/dist/backend/routes/index.js.map +1 -1
- package/dist/backend/routes/process-routes.d.ts +15 -0
- package/dist/backend/routes/process-routes.d.ts.map +1 -0
- package/dist/backend/routes/process-routes.js +237 -0
- package/dist/backend/routes/process-routes.js.map +1 -0
- package/dist/backend/routes/session-routes.d.ts +2 -0
- package/dist/backend/routes/session-routes.d.ts.map +1 -1
- package/dist/backend/routes/session-routes.js +4 -1
- package/dist/backend/routes/session-routes.js.map +1 -1
- package/dist/backend/routes/skills.d.ts +2 -0
- package/dist/backend/routes/skills.d.ts.map +1 -1
- package/dist/backend/routes/skills.js +4 -0
- package/dist/backend/routes/skills.js.map +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +30 -22
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/data.d.ts.map +1 -1
- package/dist/cli/commands/data.js +47 -44
- package/dist/cli/commands/data.js.map +1 -1
- package/dist/cli/commands/generate-system-settings.d.ts +3 -2
- package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
- package/dist/cli/commands/generate-system-settings.js +50 -7
- package/dist/cli/commands/generate-system-settings.js.map +1 -1
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/process.d.ts +8 -0
- package/dist/cli/commands/process.d.ts.map +1 -0
- package/dist/cli/commands/process.js +444 -0
- package/dist/cli/commands/process.js.map +1 -0
- package/dist/cli/commands/role.d.ts +5 -2
- package/dist/cli/commands/role.d.ts.map +1 -1
- package/dist/cli/commands/role.js +145 -18
- package/dist/cli/commands/role.js.map +1 -1
- package/dist/cli/commands/user-cmd.d.ts.map +1 -1
- package/dist/cli/commands/user-cmd.js +41 -50
- package/dist/cli/commands/user-cmd.js.map +1 -1
- package/dist/cli/generators/capability-generator.d.ts.map +1 -1
- package/dist/cli/generators/capability-generator.js +131 -5
- package/dist/cli/generators/capability-generator.js.map +1 -1
- package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
- package/dist/cli/helpers/app-tsx-wiring.js +21 -14
- package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
- package/dist/cli/helpers/auth-client.d.ts +19 -0
- package/dist/cli/helpers/auth-client.d.ts.map +1 -0
- package/dist/cli/helpers/auth-client.js +42 -0
- package/dist/cli/helpers/auth-client.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/templates/index.d.ts +1 -0
- package/dist/cli/templates/index.d.ts.map +1 -1
- package/dist/cli/templates/index.js +1 -0
- package/dist/cli/templates/index.js.map +1 -1
- package/dist/cli/templates/process-management-page.d.ts +8 -0
- package/dist/cli/templates/process-management-page.d.ts.map +1 -0
- package/dist/cli/templates/process-management-page.js +824 -0
- package/dist/cli/templates/process-management-page.js.map +1 -0
- package/dist/cli/templates/user-management-page.d.ts +2 -1
- package/dist/cli/templates/user-management-page.d.ts.map +1 -1
- package/dist/cli/templates/user-management-page.js +321 -62
- package/dist/cli/templates/user-management-page.js.map +1 -1
- package/dist/config.d.ts +371 -35
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/types/auth.d.ts +0 -2
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/event.d.ts +87 -0
- package/dist/types/event.d.ts.map +1 -0
- package/dist/types/event.js +38 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/process.d.ts +106 -0
- package/dist/types/process.d.ts.map +1 -0
- package/dist/types/process.js +5 -0
- package/dist/types/process.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Process CLI Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Process is Loom's automation engine for scheduling and event-driven AI tasks. Each process has triggers (cron, event, or manual) and executes AI prompts via the Claude Code engine.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
### `loom process add`
|
|
10
|
+
|
|
11
|
+
Create a new process definition.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Lightweight process (inline prompt)
|
|
15
|
+
loom process add --name "daily-summary" \
|
|
16
|
+
--cron "0 9 * * 1-5" \
|
|
17
|
+
--prompt "Summarize yesterday's data and email the team" \
|
|
18
|
+
--owner admin
|
|
19
|
+
|
|
20
|
+
# Full process (with PROCESS.md)
|
|
21
|
+
loom process add --name "weekly-report" \
|
|
22
|
+
--cron "0 9 * * 1" \
|
|
23
|
+
--process "weekly-report" \
|
|
24
|
+
--owner admin \
|
|
25
|
+
--description "Weekly analytics report"
|
|
26
|
+
|
|
27
|
+
# Event-triggered process
|
|
28
|
+
loom process add --name "on-task-create" \
|
|
29
|
+
--on-event "data:create:tasks" \
|
|
30
|
+
--prompt "Analyze the new task and suggest priorities" \
|
|
31
|
+
--owner admin \
|
|
32
|
+
--event-debounce 2000
|
|
33
|
+
|
|
34
|
+
# With metrics config
|
|
35
|
+
loom process add --name "health-check" \
|
|
36
|
+
--cron "*/30 * * * *" \
|
|
37
|
+
--prompt "Check system health" \
|
|
38
|
+
--metrics-config ./metrics-config.json \
|
|
39
|
+
--owner bot
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Options:**
|
|
43
|
+
| Flag | Required | Description |
|
|
44
|
+
|------|----------|-------------|
|
|
45
|
+
| `--name` | Yes | Process name (^[a-zA-Z][a-zA-Z0-9_-]*$) |
|
|
46
|
+
| `--prompt` | One of | Inline prompt for lightweight process |
|
|
47
|
+
| `--process` | One of | Full process name (expects .claude/processes/{name}/PROCESS.md) |
|
|
48
|
+
| `--cron` | No | Cron schedule expression |
|
|
49
|
+
| `--cron-timezone` | No | Timezone for cron (e.g., "America/New_York") |
|
|
50
|
+
| `--on-event` | No | EventBus pattern (e.g., "data:create:tasks") |
|
|
51
|
+
| `--event-filter` | No | JSON filter on event payload fields |
|
|
52
|
+
| `--event-debounce` | No | Debounce window in ms (default: 1000) |
|
|
53
|
+
| `--description` | No | Process description |
|
|
54
|
+
| `--owner` | No | Owner username (default: from processes config) |
|
|
55
|
+
| `--owner-token` | No | Direct JWT token (bypasses runtime resolution) |
|
|
56
|
+
| `--metrics-config` | No | Path to metrics DashboardWidget config JSON |
|
|
57
|
+
| `--token` | No | Auth token (or set LOOM_TOKEN) |
|
|
58
|
+
| `--server` | No | Server URL (or set LOOM_SERVER, default: http://localhost:3000) |
|
|
59
|
+
|
|
60
|
+
### `loom process list`
|
|
61
|
+
|
|
62
|
+
List all processes.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
loom process list
|
|
66
|
+
loom process list --status active
|
|
67
|
+
loom process list --status paused
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `loom process status <name>`
|
|
71
|
+
|
|
72
|
+
Show detailed process status.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
loom process status daily-summary
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `loom process run <name>`
|
|
79
|
+
|
|
80
|
+
Manually trigger a process execution.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
loom process run daily-summary
|
|
84
|
+
loom process run on-task-create --payload '{"model":"tasks","id":"task_123"}'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `loom process pause <name>`
|
|
88
|
+
|
|
89
|
+
Pause a process (stops triggers, manual run still works).
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
loom process pause daily-summary
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `loom process resume <name>`
|
|
96
|
+
|
|
97
|
+
Resume a paused process.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
loom process resume daily-summary
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `loom process update <name>`
|
|
104
|
+
|
|
105
|
+
Update an existing process definition.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
loom process update daily-summary --cron "0 10 * * 1-5"
|
|
109
|
+
loom process update on-task-create --prompt "Updated prompt text"
|
|
110
|
+
loom process update daily-summary --description "Updated description" --cron "0 8 * * 1-5"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Options:**
|
|
114
|
+
| Flag | Required | Description |
|
|
115
|
+
|------|----------|-------------|
|
|
116
|
+
| `--cron` | No | Update cron schedule expression |
|
|
117
|
+
| `--cron-timezone` | No | Update cron timezone |
|
|
118
|
+
| `--on-event` | No | Update event trigger pattern |
|
|
119
|
+
| `--event-filter` | No | Update event payload filter as JSON |
|
|
120
|
+
| `--event-debounce` | No | Update debounce window in ms |
|
|
121
|
+
| `--prompt` | No | Update inline prompt |
|
|
122
|
+
| `--description` | No | Update description |
|
|
123
|
+
| `--token` | No | Auth token (or set LOOM_TOKEN) |
|
|
124
|
+
| `--server` | No | Server URL (or set LOOM_SERVER, default: http://localhost:3000) |
|
|
125
|
+
|
|
126
|
+
### `loom process remove <name>`
|
|
127
|
+
|
|
128
|
+
Remove a process definition. Logs are preserved.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
loom process remove daily-summary
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `loom process logs <name> [runId]`
|
|
135
|
+
|
|
136
|
+
View execution logs. When `runId` is provided, shows details for a single run.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# List recent logs
|
|
140
|
+
loom process logs daily-summary
|
|
141
|
+
loom process logs daily-summary --limit 50
|
|
142
|
+
loom process logs daily-summary --offset 10
|
|
143
|
+
|
|
144
|
+
# View a specific run
|
|
145
|
+
loom process logs daily-summary run_abc123
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `loom process generate <name>`
|
|
149
|
+
|
|
150
|
+
Create PROCESS.md scaffold directory (for full process mode). Called by AI, not directly by users.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
loom process generate daily-report --description "Daily report generator"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `loom process export <name>`
|
|
157
|
+
|
|
158
|
+
Export process definition as JSON.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
loom process export daily-summary
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Process Modes
|
|
165
|
+
|
|
166
|
+
### Lightweight Mode (`--prompt`)
|
|
167
|
+
|
|
168
|
+
- Inline prompt, no PROCESS.md file
|
|
169
|
+
- Best for simple, single-task automations
|
|
170
|
+
- Quick to set up and modify
|
|
171
|
+
|
|
172
|
+
### Full Mode (`--process`)
|
|
173
|
+
|
|
174
|
+
- Has PROCESS.md at `.claude/processes/{name}/PROCESS.md`
|
|
175
|
+
- Can include reference files in `.claude/processes/{name}/references/`
|
|
176
|
+
- Best for complex, multi-step workflows
|
|
177
|
+
- Supports Metrics and Evolution sections
|
|
178
|
+
|
|
179
|
+
## Trigger Types
|
|
180
|
+
|
|
181
|
+
### Cron
|
|
182
|
+
Standard 5-field cron expression: `minute hour day month weekday`
|
|
183
|
+
```
|
|
184
|
+
*/5 * * * * — Every 5 minutes
|
|
185
|
+
0 9 * * 1-5 — Weekdays at 9 AM
|
|
186
|
+
0 0 1 * * — First of every month
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Event
|
|
190
|
+
EventBus pattern matching: `{category}:{action}:{target}`
|
|
191
|
+
```
|
|
192
|
+
data:create:tasks — When a task is created
|
|
193
|
+
data:update:* — Any data update
|
|
194
|
+
user:created — When a user is created
|
|
195
|
+
process:completed:xxx — When process xxx completes
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Manual
|
|
199
|
+
`loom process run <name>` — immediate execution, works even when paused.
|
|
200
|
+
|
|
201
|
+
## Service Token Authentication
|
|
202
|
+
|
|
203
|
+
When auth is enabled, processes need a service token to operate:
|
|
204
|
+
- `--owner <username>`: Runtime resolution via UserStore (recommended)
|
|
205
|
+
- `--owner-token <jwt>`: Direct JWT token (less secure, stored in processes.json)
|
|
206
|
+
|
|
207
|
+
## Execution Model
|
|
208
|
+
|
|
209
|
+
1. Trigger fires → entry enqueued
|
|
210
|
+
2. Queue slot available → process starts
|
|
211
|
+
3. AI prompt built (PROCESS.md + references + event context)
|
|
212
|
+
4. Claude Code engine executes with timeout
|
|
213
|
+
5. Result logged to `.loom/process-logs/{name}/{runId}.json`
|
|
214
|
+
6. Event emitted: `process:started`, `process:completed`, or `process:failed`
|
|
215
|
+
|
|
216
|
+
## Concurrency
|
|
217
|
+
|
|
218
|
+
Controlled by `processes.maxConcurrent` in loom.config.ts (default: 5). When max is reached, new triggers queue until a slot opens.
|
|
219
|
+
|
|
220
|
+
## Debounce
|
|
221
|
+
|
|
222
|
+
Event triggers have `eventDebounceMs` (default: 1000ms). Rapid events within the window are collapsed — only one execution fires with the last event's payload.
|
|
@@ -103,11 +103,14 @@ loom generate dashboard <name>
|
|
|
103
103
|
1. **询问需求**:是否需要登录功能?有哪些角色?每个角色能做什么?
|
|
104
104
|
2. **编辑配置**:在 `loom.config.ts` 中添加 `auth` 字段(Schema 见上方)。至少定义一个 `admin` 角色
|
|
105
105
|
3. **设置密钥**:`secret` 推荐 `env:JWT_SECRET`,不要硬编码。提醒用户设置环境变量 `export JWT_SECRET=your-secret`
|
|
106
|
-
4.
|
|
107
|
-
5.
|
|
108
|
-
6.
|
|
106
|
+
4. **生成认证页面**:`loom generate system-settings user` — 自动生成用户管理页、登录页,并接入 AuthProvider 路由守卫
|
|
107
|
+
5. **重启后端**:auth 配置需要重启后端生效(**必须先询问用户**)
|
|
108
|
+
6. **首次登录**:默认创建 admin 用户(密码 `admin123`),**提醒用户立即修改密码**
|
|
109
|
+
7. **生成 app-skill**:`loom generate capabilities` 更新 Skill 中的 auth.md
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
启用后获得:登录页 + 路由守卫 + RBAC 权限控制 + 用户管理页 + CLI `--token`/`LOOM_TOKEN` 鉴权。
|
|
112
|
+
|
|
113
|
+
> 如果 `loom init` 时配置中已包含 `auth` 字段,上述页面会在初始化时自动生成。后续添加 auth 配置时,手动执行 `loom generate system-settings user` 即可补全。
|
|
111
114
|
|
|
112
115
|
### 重启服务前必须询问用户
|
|
113
116
|
|
|
@@ -163,8 +166,8 @@ loom generate page <Name> --model <model-name> --force
|
|
|
163
166
|
| `loom generate capabilities` | 生成应用 Skill(`references/models.md` + `references/data-semantics.md`,`SKILL.md` 仅首次创建不覆盖) |
|
|
164
167
|
| `loom generate dashboard <name>` | 从 loom.config.ts dashboards 生成 Dashboard 页面 |
|
|
165
168
|
| `loom generate dashboard <name> --force` | 重建 Dashboard 页面(自动备份旧文件到 `.loom/backup/`) |
|
|
166
|
-
| `loom generate system-settings <model\|skill\|user>` |
|
|
167
|
-
| `loom generate system-settings <model\|skill\|user> --force` | 重建系统管理页面(自动备份旧文件到 `.loom/backup/`) |
|
|
169
|
+
| `loom generate system-settings <model\|skill\|user\|process>` | 生成系统管理页面(模型管理、技能管理、用户管理或过程管理),首次调用创建子菜单,后续调用追加子页面;`user` 类型同时生成登录页和 AuthProvider 接线 |
|
|
170
|
+
| `loom generate system-settings <model\|skill\|user\|process> --force` | 重建系统管理页面(自动备份旧文件到 `.loom/backup/`) |
|
|
168
171
|
| `loom generate skill <name>` | 生成自定义 Skill 脚架 |
|
|
169
172
|
|
|
170
173
|
## loom.config.ts Schema(核心)
|
|
@@ -228,7 +231,6 @@ export default defineConfig({
|
|
|
228
231
|
defaults: {
|
|
229
232
|
read: 'read', // 未显式配置的模型默认读权限
|
|
230
233
|
write: 'write', // 未显式配置的模型默认写权限
|
|
231
|
-
readIncludesAiButtons: false, // read 权限是否可见 AI 按钮
|
|
232
234
|
writeExcludesDelete: false, // write 权限是否排除删除操作
|
|
233
235
|
},
|
|
234
236
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-scoped context via AsyncLocalStorage
|
|
3
|
+
*
|
|
4
|
+
* Allows EventEmittingAdapter to access userId from the current
|
|
5
|
+
* HTTP request without coupling to the Fastify request object.
|
|
6
|
+
*/
|
|
7
|
+
interface RequestContext {
|
|
8
|
+
userId?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Get the current request context (undefined if outside a request) */
|
|
11
|
+
export declare function getRequestContext(): RequestContext | undefined;
|
|
12
|
+
/** Set context for the current async execution path */
|
|
13
|
+
export declare function setRequestContext(ctx: RequestContext): void;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../src/backend/auth/request-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAE3D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-scoped context via AsyncLocalStorage
|
|
3
|
+
*
|
|
4
|
+
* Allows EventEmittingAdapter to access userId from the current
|
|
5
|
+
* HTTP request without coupling to the Fastify request object.
|
|
6
|
+
*/
|
|
7
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
8
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
9
|
+
/** Get the current request context (undefined if outside a request) */
|
|
10
|
+
export function getRequestContext() {
|
|
11
|
+
return asyncLocalStorage.getStore();
|
|
12
|
+
}
|
|
13
|
+
/** Set context for the current async execution path */
|
|
14
|
+
export function setRequestContext(ctx) {
|
|
15
|
+
asyncLocalStorage.enterWith(ctx);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../../src/backend/auth/request-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMhD,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAElE,uEAAuE;AACvE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,iBAAiB,CAAC,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,GAAmB;IACnD,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus - Universal event bus with pattern matching and batch support
|
|
3
|
+
*
|
|
4
|
+
* - Pattern matching: `data:create:tasks`, `data:*:tasks`, `data:**`, `*:create:*`
|
|
5
|
+
* - Wildcards: `*` = single segment (`[^:]+`), `**` = any depth (`.*`)
|
|
6
|
+
* - Fire-and-forget emit: handler errors are caught and logged, never propagated
|
|
7
|
+
* - beginBatch/endBatch: suppress fine-grained events, emit aggregate only
|
|
8
|
+
* - Circular detection: warn when emit depth exceeds 10
|
|
9
|
+
*/
|
|
10
|
+
import type { LoomEvent, EventMeta } from '../../types/event.js';
|
|
11
|
+
type EventHandler = (event: LoomEvent) => void | Promise<void>;
|
|
12
|
+
export declare class EventBus {
|
|
13
|
+
private handlers;
|
|
14
|
+
private wildcards;
|
|
15
|
+
private patternCache;
|
|
16
|
+
private batchStack;
|
|
17
|
+
private suppressedEvents;
|
|
18
|
+
private emitDepth;
|
|
19
|
+
/** Subscribe to an event pattern. Returns unsubscribe function. */
|
|
20
|
+
on(pattern: string, handler: EventHandler): () => void;
|
|
21
|
+
/** Unsubscribe a handler from a pattern. */
|
|
22
|
+
off(pattern: string, handler: EventHandler): void;
|
|
23
|
+
/** Emit an event — fire-and-forget. Handler errors are caught and logged. */
|
|
24
|
+
emit(pattern: string, payload: Record<string, unknown>, meta?: EventMeta): void;
|
|
25
|
+
/** Check if a pattern has any subscribers (for previousData optimization). */
|
|
26
|
+
hasSubscribers(pattern: string): boolean;
|
|
27
|
+
/** Begin a batch operation. Returns batchId. */
|
|
28
|
+
beginBatch(batchPattern: string): string;
|
|
29
|
+
/** End a batch operation. Emits aggregate event with merged payload. */
|
|
30
|
+
endBatch(batchId: string, payload: Record<string, unknown>, meta?: EventMeta): void;
|
|
31
|
+
/** Remove all handlers (for server shutdown). */
|
|
32
|
+
removeAllHandlers(): void;
|
|
33
|
+
/** Dispatch event to all matching handlers. Fire-and-forget: errors are caught, never propagated. */
|
|
34
|
+
private dispatch;
|
|
35
|
+
/** Compile a pattern string to a RegExp. Caches compiled patterns. */
|
|
36
|
+
private compilePattern;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=event-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../../src/backend/events/event-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjE,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAQ/D,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,UAAU,CAAmD;IACrE,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,SAAS,CAAK;IAEtB,mEAAmE;IACnE,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI;IAuBtD,4CAA4C;IAC5C,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAejD,6EAA6E;IAC7E,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI;IA4B/E,8EAA8E;IAC9E,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOxC,gDAAgD;IAChD,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAMxC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI;IAkBnF,iDAAiD;IACjD,iBAAiB,IAAI,IAAI;IAQzB,qGAAqG;IACrG,OAAO,CAAC,QAAQ;IAkChB,sEAAsE;IACtE,OAAO,CAAC,cAAc;CAwBvB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus - Universal event bus with pattern matching and batch support
|
|
3
|
+
*
|
|
4
|
+
* - Pattern matching: `data:create:tasks`, `data:*:tasks`, `data:**`, `*:create:*`
|
|
5
|
+
* - Wildcards: `*` = single segment (`[^:]+`), `**` = any depth (`.*`)
|
|
6
|
+
* - Fire-and-forget emit: handler errors are caught and logged, never propagated
|
|
7
|
+
* - beginBatch/endBatch: suppress fine-grained events, emit aggregate only
|
|
8
|
+
* - Circular detection: warn when emit depth exceeds 10
|
|
9
|
+
*/
|
|
10
|
+
export class EventBus {
|
|
11
|
+
handlers = new Map();
|
|
12
|
+
wildcards = [];
|
|
13
|
+
patternCache = new Map();
|
|
14
|
+
batchStack = [];
|
|
15
|
+
suppressedEvents = [];
|
|
16
|
+
emitDepth = 0;
|
|
17
|
+
/** Subscribe to an event pattern. Returns unsubscribe function. */
|
|
18
|
+
on(pattern, handler) {
|
|
19
|
+
if (pattern.includes('*')) {
|
|
20
|
+
const regex = this.compilePattern(pattern);
|
|
21
|
+
this.wildcards.push({ pattern, regex, handler });
|
|
22
|
+
return () => {
|
|
23
|
+
const idx = this.wildcards.findIndex(w => w.pattern === pattern && w.handler === handler);
|
|
24
|
+
if (idx !== -1)
|
|
25
|
+
this.wildcards.splice(idx, 1);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const list = this.handlers.get(pattern) ?? [];
|
|
29
|
+
list.push(handler);
|
|
30
|
+
this.handlers.set(pattern, list);
|
|
31
|
+
return () => {
|
|
32
|
+
const arr = this.handlers.get(pattern);
|
|
33
|
+
if (arr) {
|
|
34
|
+
const idx = arr.indexOf(handler);
|
|
35
|
+
if (idx !== -1)
|
|
36
|
+
arr.splice(idx, 1);
|
|
37
|
+
if (arr.length === 0)
|
|
38
|
+
this.handlers.delete(pattern);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Unsubscribe a handler from a pattern. */
|
|
43
|
+
off(pattern, handler) {
|
|
44
|
+
if (pattern.includes('*')) {
|
|
45
|
+
const idx = this.wildcards.findIndex(w => w.pattern === pattern && w.handler === handler);
|
|
46
|
+
if (idx !== -1)
|
|
47
|
+
this.wildcards.splice(idx, 1);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const arr = this.handlers.get(pattern);
|
|
51
|
+
if (arr) {
|
|
52
|
+
const idx = arr.indexOf(handler);
|
|
53
|
+
if (idx !== -1)
|
|
54
|
+
arr.splice(idx, 1);
|
|
55
|
+
if (arr.length === 0)
|
|
56
|
+
this.handlers.delete(pattern);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Emit an event — fire-and-forget. Handler errors are caught and logged. */
|
|
60
|
+
emit(pattern, payload, meta) {
|
|
61
|
+
this.emitDepth++;
|
|
62
|
+
if (this.emitDepth > 10) {
|
|
63
|
+
console.warn(`[EventBus] Circular emit detected (depth=${this.emitDepth}) for pattern: ${pattern}`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const event = {
|
|
67
|
+
id: `evt_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`,
|
|
68
|
+
pattern,
|
|
69
|
+
payload,
|
|
70
|
+
userId: meta?.userId,
|
|
71
|
+
source: meta?.source ?? 'server',
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
// If inside a batch, suppress the event
|
|
75
|
+
if (this.batchStack.length > 0) {
|
|
76
|
+
this.suppressedEvents.push(event);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.dispatch(event);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.emitDepth--;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Check if a pattern has any subscribers (for previousData optimization). */
|
|
86
|
+
hasSubscribers(pattern) {
|
|
87
|
+
if (this.handlers.has(pattern) && this.handlers.get(pattern).length > 0) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return this.wildcards.some(w => w.regex.test(pattern));
|
|
91
|
+
}
|
|
92
|
+
/** Begin a batch operation. Returns batchId. */
|
|
93
|
+
beginBatch(batchPattern) {
|
|
94
|
+
const batchId = `batch_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
95
|
+
this.batchStack.push({ batchId, pattern: batchPattern });
|
|
96
|
+
return batchId;
|
|
97
|
+
}
|
|
98
|
+
/** End a batch operation. Emits aggregate event with merged payload. */
|
|
99
|
+
endBatch(batchId, payload, meta) {
|
|
100
|
+
const idx = this.batchStack.findIndex(b => b.batchId === batchId);
|
|
101
|
+
if (idx === -1)
|
|
102
|
+
return;
|
|
103
|
+
const batch = this.batchStack[idx];
|
|
104
|
+
// Remove from stack
|
|
105
|
+
this.batchStack.splice(idx, 1);
|
|
106
|
+
// Clear suppressed events when batch stack is empty
|
|
107
|
+
if (this.batchStack.length === 0) {
|
|
108
|
+
this.suppressedEvents = [];
|
|
109
|
+
}
|
|
110
|
+
// Emit aggregate event using the stored pattern
|
|
111
|
+
this.emit(batch.pattern, payload, meta);
|
|
112
|
+
}
|
|
113
|
+
/** Remove all handlers (for server shutdown). */
|
|
114
|
+
removeAllHandlers() {
|
|
115
|
+
this.handlers.clear();
|
|
116
|
+
this.wildcards = [];
|
|
117
|
+
this.patternCache.clear();
|
|
118
|
+
this.batchStack = [];
|
|
119
|
+
this.suppressedEvents = [];
|
|
120
|
+
}
|
|
121
|
+
/** Dispatch event to all matching handlers. Fire-and-forget: errors are caught, never propagated. */
|
|
122
|
+
dispatch(event) {
|
|
123
|
+
// Exact match handlers
|
|
124
|
+
const exactHandlers = this.handlers.get(event.pattern) ?? [];
|
|
125
|
+
for (const handler of exactHandlers) {
|
|
126
|
+
try {
|
|
127
|
+
const result = handler(event);
|
|
128
|
+
// If handler returns a promise, catch its rejection silently
|
|
129
|
+
if (result instanceof Promise) {
|
|
130
|
+
result.catch(err => {
|
|
131
|
+
console.error(`[EventBus] Async handler error for ${event.pattern}:`, err);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error(`[EventBus] Handler error for ${event.pattern}:`, err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Wildcard handlers
|
|
140
|
+
for (const entry of this.wildcards) {
|
|
141
|
+
if (entry.regex.test(event.pattern)) {
|
|
142
|
+
try {
|
|
143
|
+
const result = entry.handler(event);
|
|
144
|
+
if (result instanceof Promise) {
|
|
145
|
+
result.catch(err => {
|
|
146
|
+
console.error(`[EventBus] Async handler error for wildcard ${entry.pattern}:`, err);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error(`[EventBus] Handler error for wildcard ${entry.pattern}:`, err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Compile a pattern string to a RegExp. Caches compiled patterns. */
|
|
157
|
+
compilePattern(pattern) {
|
|
158
|
+
const cached = this.patternCache.get(pattern);
|
|
159
|
+
if (cached)
|
|
160
|
+
return cached;
|
|
161
|
+
// Lone `*` matches everything
|
|
162
|
+
if (pattern === '*') {
|
|
163
|
+
const regex = /^.*$/;
|
|
164
|
+
this.patternCache.set(pattern, regex);
|
|
165
|
+
return regex;
|
|
166
|
+
}
|
|
167
|
+
const regexStr = pattern
|
|
168
|
+
.split(':')
|
|
169
|
+
.map(segment => {
|
|
170
|
+
if (segment === '**')
|
|
171
|
+
return '.*';
|
|
172
|
+
if (segment.includes('*'))
|
|
173
|
+
return segment.replace(/\*/g, '[^:]+');
|
|
174
|
+
return segment;
|
|
175
|
+
})
|
|
176
|
+
.join(':');
|
|
177
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
178
|
+
this.patternCache.set(pattern, regex);
|
|
179
|
+
return regex;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=event-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../../src/backend/events/event-bus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventEmittingAdapter - Decorator that wraps DataAdapter to auto-emit events
|
|
3
|
+
*
|
|
4
|
+
* - write() → emits data:create:<model> with { model, id, data }
|
|
5
|
+
* - update() → emits data:update:<model> with { model, id, data, previousData, changedFields }
|
|
6
|
+
* - delete() → emits data:delete:<model> with { model, id, previousData }
|
|
7
|
+
* - hasSubscribers() optimization: skips previousData read when no subscribers
|
|
8
|
+
* - Events only emitted after inner operation succeeds
|
|
9
|
+
* - getUserId callback for injecting userId (auth-aware)
|
|
10
|
+
*/
|
|
11
|
+
import type { DataAdapter, DataRecord, ReadOptions } from '../../types/adapter.js';
|
|
12
|
+
import type { ModelSchema } from '../../types/model.js';
|
|
13
|
+
import type { EventBus } from './event-bus.js';
|
|
14
|
+
export declare class EventEmittingAdapter implements DataAdapter {
|
|
15
|
+
private inner;
|
|
16
|
+
private eventBus;
|
|
17
|
+
private source;
|
|
18
|
+
private getUserId?;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
constructor(inner: DataAdapter, eventBus: EventBus, source: 'server' | 'cli', getUserId?: (() => string | undefined) | undefined);
|
|
21
|
+
write(model: string, data: DataRecord): Promise<DataRecord>;
|
|
22
|
+
update(model: string, id: string, data: DataRecord): Promise<DataRecord>;
|
|
23
|
+
delete(model: string, id: string): Promise<void>;
|
|
24
|
+
read(model: string, options?: ReadOptions): Promise<DataRecord | DataRecord[] | null>;
|
|
25
|
+
count(model: string, options?: Omit<ReadOptions, 'limit' | 'offset' | 'sort'>): Promise<number>;
|
|
26
|
+
getSchema(model: string): Promise<ModelSchema | undefined>;
|
|
27
|
+
listModels(): Promise<string[]>;
|
|
28
|
+
initialize(): Promise<void>;
|
|
29
|
+
healthCheck(): Promise<boolean>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=event-emitting-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitting-adapter.d.ts","sourceRoot":"","sources":["../../../src/backend/events/event-emitting-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,qBAAa,oBAAqB,YAAW,WAAW;IAIpD,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,SAAS,CAAC;IANpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAGZ,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,QAAQ,GAAG,KAAK,EACxB,SAAS,CAAC,GAAE,MAAM,MAAM,GAAG,SAAS,aAAA;IAKxC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAU3D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBxE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAehD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC;IAIrF,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/F,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI1D,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI/B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventEmittingAdapter - Decorator that wraps DataAdapter to auto-emit events
|
|
3
|
+
*
|
|
4
|
+
* - write() → emits data:create:<model> with { model, id, data }
|
|
5
|
+
* - update() → emits data:update:<model> with { model, id, data, previousData, changedFields }
|
|
6
|
+
* - delete() → emits data:delete:<model> with { model, id, previousData }
|
|
7
|
+
* - hasSubscribers() optimization: skips previousData read when no subscribers
|
|
8
|
+
* - Events only emitted after inner operation succeeds
|
|
9
|
+
* - getUserId callback for injecting userId (auth-aware)
|
|
10
|
+
*/
|
|
11
|
+
import { EventPatterns } from '../../types/event.js';
|
|
12
|
+
export class EventEmittingAdapter {
|
|
13
|
+
inner;
|
|
14
|
+
eventBus;
|
|
15
|
+
source;
|
|
16
|
+
getUserId;
|
|
17
|
+
name;
|
|
18
|
+
constructor(inner, eventBus, source, getUserId) {
|
|
19
|
+
this.inner = inner;
|
|
20
|
+
this.eventBus = eventBus;
|
|
21
|
+
this.source = source;
|
|
22
|
+
this.getUserId = getUserId;
|
|
23
|
+
this.name = `event-emitting:${inner.name}`;
|
|
24
|
+
}
|
|
25
|
+
async write(model, data) {
|
|
26
|
+
const record = await this.inner.write(model, data);
|
|
27
|
+
void this.eventBus.emit(EventPatterns.DATA_CREATE(model), { model, id: record.id, data: record }, { userId: this.getUserId?.(), source: this.source });
|
|
28
|
+
return record;
|
|
29
|
+
}
|
|
30
|
+
async update(model, id, data) {
|
|
31
|
+
const pattern = EventPatterns.DATA_UPDATE(model);
|
|
32
|
+
const previous = this.eventBus.hasSubscribers(pattern)
|
|
33
|
+
? await this.inner.read(model, { id }).catch(() => null)
|
|
34
|
+
: null;
|
|
35
|
+
const record = await this.inner.update(model, id, data);
|
|
36
|
+
const changedFields = previous
|
|
37
|
+
? Object.keys(data).filter(k => data[k] !== previous?.[k])
|
|
38
|
+
: undefined;
|
|
39
|
+
void this.eventBus.emit(pattern, { model, id, data: record, previousData: previous ?? undefined, changedFields }, { userId: this.getUserId?.(), source: this.source });
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
async delete(model, id) {
|
|
43
|
+
const pattern = EventPatterns.DATA_DELETE(model);
|
|
44
|
+
const previous = this.eventBus.hasSubscribers(pattern)
|
|
45
|
+
? await this.inner.read(model, { id }).catch(() => null)
|
|
46
|
+
: null;
|
|
47
|
+
await this.inner.delete(model, id);
|
|
48
|
+
void this.eventBus.emit(pattern, { model, id, previousData: previous ?? undefined }, { userId: this.getUserId?.(), source: this.source });
|
|
49
|
+
}
|
|
50
|
+
// ── Pure delegation (no events for read operations) ──
|
|
51
|
+
async read(model, options) {
|
|
52
|
+
return this.inner.read(model, options);
|
|
53
|
+
}
|
|
54
|
+
async count(model, options) {
|
|
55
|
+
return this.inner.count(model, options);
|
|
56
|
+
}
|
|
57
|
+
async getSchema(model) {
|
|
58
|
+
return this.inner.getSchema(model);
|
|
59
|
+
}
|
|
60
|
+
async listModels() {
|
|
61
|
+
return this.inner.listModels();
|
|
62
|
+
}
|
|
63
|
+
async initialize() {
|
|
64
|
+
return this.inner.initialize();
|
|
65
|
+
}
|
|
66
|
+
async healthCheck() {
|
|
67
|
+
return this.inner.healthCheck();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=event-emitting-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitting-adapter.js","sourceRoot":"","sources":["../../../src/backend/events/event-emitting-adapter.ts"],"names":[],"mappings":"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"}
|