weifuwu 0.18.0 → 0.18.1

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 CHANGED
@@ -88,26 +88,49 @@ app.get('/admin', mw, handler) // route-level
88
88
 
89
89
  ## Module Patterns
90
90
 
91
- All modules follow one of 6 patterns. The pattern letter is marked in each module's heading.
91
+ All modules follow one of **2 patterns** learn these and you know every module.
92
92
 
93
93
  | Pattern | How to mount | Example |
94
94
  |---------|-------------|---------|
95
- | `[A]` | `app.use(mod())` | `compress()`, `preferences()` |
96
- | `[B]` | `app.use(mod())` + call `.stop()` / `.close()` etc. | `rateLimit({...})` |
97
- | `[C]` | `app.use(mod.middleware())` + `app.use('/', mod.router())` | `analytics()`, `user()` |
98
- | `[D]` | `app.use(mod().handler())` | `health()`, `seo()` |
99
- | `[E]` | `app.use('/', g.router().handler())` | `graphql(handler)` |
95
+ | `[α]` | `app.use(mod())` | `compress()`, `preferences()`, `postgres()` |
96
+ | `[β]` | `app.use('/path', mod())` | `health()`, `graphql(handler)`, `user()` |
97
+
98
+ ### Pattern α Middleware
99
+
100
+ ```ts
101
+ app.use(compress()) // basic
102
+ const pg = postgres() // with extras: .sql, .table, .migrate(), .close()
103
+ app.use(pg)
104
+ app.use(rateLimit({ max: 100 })) // with .stop()
105
+ ```
106
+
107
+ ### Pattern β — Router
108
+
109
+ ```ts
110
+ app.use('/health', health()) // no extras
111
+ app.use('/graphql', graphql(handler))
112
+ app.use('/logs', logdb({ pg })) // with .log(), .migrate()
113
+ app.use('/auth', user({ pg, jwtSecret })) // with .middleware(), .register()
114
+ app.ws('/ws', messager({ pg }).wsHandler())
115
+ ```
116
+
117
+ β modules that need **separate middleware** use `.middleware()`:
118
+ ```ts
119
+ const a = analytics()
120
+ app.use(a.middleware()) // tracking
121
+ app.use('/', a) // dashboard
122
+ ```
100
123
 
101
124
  ---
102
125
 
103
126
  ## Module Reference
104
127
 
105
- ### agent [C]
128
+ ### agent [β]
106
129
 
107
130
  ```ts
108
131
  const a = agent({ pg, model: openai('gpt-4o'), embeddingModel: openai.embedding('text-embedding-3-small') })
109
132
  await a.migrate()
110
- app.use('/api', a.router())
133
+ app.use('/api', a)
111
134
  await a.addKnowledge(agentId, 'Title', 'some knowledge content')
112
135
  a.run(agentId, { input: 'summarize the data', stream: true })
113
136
  ```
@@ -124,24 +147,30 @@ a.run(agentId, { input: 'summarize the data', stream: true })
124
147
  |--------|-------------|
125
148
  | `.run(agentId, { input, stream?, messages? })` | Execute agent with input |
126
149
  | `.addKnowledge(agentId, title, content)` | Add knowledge document |
127
- | `.router()` | REST + WS API |
150
+ | `.migrate()` | DB setup |
128
151
  | `.close()` | Cleanup |
129
152
 
130
- ### aiStream [E]
153
+ ### aiStream [β]
154
+
155
+ Creates an AI streaming chat endpoint using the Vercel AI SDK.
131
156
 
132
157
  ```ts
133
158
  const chat = await aiStream(async (req) => ({ model: openai('gpt-4o'), messages: (await req.json()).messages }))
134
- app.use('/chat', chat.router().handler())
159
+ app.use('/chat', chat)
135
160
  ```
136
161
 
137
- ### analytics [C]
162
+ | Param | Type | Description |
163
+ |-------|------|-------------|
164
+ | `handler` | `(req, ctx) => AIStreamOptions \| Promise<AIStreamOptions>` | Returns AI SDK options (model, messages, schema, etc.) |
165
+
166
+ ### analytics [β]
138
167
 
139
168
  In-memory or PostgreSQL page view tracking with built-in dashboard.
140
169
 
141
170
  ```ts
142
171
  const a = analytics()
143
172
  app.use(a.middleware())
144
- app.use('/', a.router()) // GET /__analytics (dashboard), GET /__analytics/data?days=7 (JSON)
173
+ app.use('/', a) // GET /__analytics (dashboard), GET /__analytics/data?days=7 (JSON)
145
174
  ```
146
175
 
147
176
  | Option | Type | Default | Description |
@@ -154,10 +183,10 @@ app.use('/', a.router()) // GET /__analytics (dashboard), GET /__analytics
154
183
  const a = analytics({ pg })
155
184
  await a.migrate()
156
185
  app.use(a.middleware())
157
- app.use('/', a.router())
186
+ app.use('/', a) // dashboard routes
158
187
  ```
159
188
 
160
- ### auth [A]
189
+ ### auth [α]
161
190
 
162
191
  ```ts
163
192
  app.use(auth({ token: 'sk-123' })) // static token
@@ -173,7 +202,7 @@ app.get('/protected', auth({ proxy: 'http://auth:3000/validate' }), handler)
173
202
  | `verify` | `(token, req) => object\|null` | — | Verify function, return value sets `ctx.user` |
174
203
  | `proxy` | `string` | — | Auth service URL to proxy requests to |
175
204
 
176
- ### compress [A]
205
+ ### compress [α]
177
206
 
178
207
  ```ts
179
208
  app.use(compress()) // brotli > gzip > deflate (min 1KB)
@@ -185,7 +214,7 @@ app.use(compress({ threshold: 2048, level: 4 })) // custom threshold and le
185
214
  | `threshold` | `number` | `1024` | Minimum byte size to compress |
186
215
  | `level` | `number` | `6` | Compression level (zlib) |
187
216
 
188
- ### cors [A]
217
+ ### cors [α]
189
218
 
190
219
  ```ts
191
220
  app.use(cors()) // allow all
@@ -203,7 +232,7 @@ app.use(cors({ credentials: true, maxAge: 3600 }))
203
232
  | `credentials` | `boolean` | `false` | Allow cookies/credentials |
204
233
  | `maxAge` | `number` | — | Preflight cache duration (seconds) |
205
234
 
206
- ### csrf [A]
235
+ ### csrf [α]
207
236
 
208
237
  ```ts
209
238
  app.use(csrf())
@@ -234,10 +263,10 @@ const server = await deploy(config)
234
263
  // server.apps.list(), server.apps.status(name), server.apps.deploy(name)
235
264
  ```
236
265
 
237
- ### health [D]
266
+ ### health [β]
238
267
 
239
268
  ```ts
240
- app.use(health({ path: '/health' }))
269
+ app.use('/health', health())
241
270
  // Returns 200 on success, 503 when check throws
242
271
  ```
243
272
 
@@ -246,7 +275,7 @@ app.use(health({ path: '/health' }))
246
275
  | `path` | `string` | `'/health'` | Health check endpoint |
247
276
  | `check` | `() => Promise<void>` | — | Async function; throws → 503 |
248
277
 
249
- ### helmet [A]
278
+ ### helmet [α]
250
279
 
251
280
  15 security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.
252
281
 
@@ -267,12 +296,14 @@ app.use(helmet({ contentSecurityPolicy: "default-src 'self'", xFrameOptions: 'DE
267
296
  | `crossOriginOpenerPolicy` | — | COOP header |
268
297
  | `crossOriginResourcePolicy` | — | CORP header |
269
298
 
270
- ### iii [C] — Worker / Function / Trigger
299
+ ### iii [β] — Worker / Function / Trigger
300
+
301
+ Distributed function execution with WebSocket workers, triggers, and Redis streams.
271
302
 
272
303
  ```ts
273
304
  import { createWorker } from 'weifuwu'
274
305
  const engine = iii({ pg, redis })
275
- app.use('/iii', engine.router())
306
+ app.use('/iii', engine)
276
307
  app.ws('/iii', engine.wsHandler())
277
308
 
278
309
  const w = createWorker('orders')
@@ -281,6 +312,12 @@ engine.addWorker(w)
281
312
  await engine.trigger({ function_id: 'orders::create', payload: { items: ['apple'] } })
282
313
  ```
283
314
 
315
+ | Option | Type | Default | Description |
316
+ |--------|------|---------|-------------|
317
+ | `pg` | `object` | — | PostgreSQL client for persistent triggers |
318
+ | `redis` | `object` | — | Redis client for streams |
319
+ | `streamTTL` | `number` | `3600` | Redis stream key TTL (seconds, 0 = no expiry) |
320
+
284
321
  | Method | Description |
285
322
  |--------|-------------|
286
323
  | `.addWorker(w)` | Register a worker |
@@ -289,19 +326,18 @@ await engine.trigger({ function_id: 'orders::create', payload: { items: ['apple'
289
326
  | `.listWorkers()` | List registered workers |
290
327
  | `.listFunctions()` | List registered functions |
291
328
  | `.listTriggers()` | List registered triggers |
292
- | `.router()` | REST + WS API |
293
329
  | `.wsHandler()` | WebSocket handler |
294
330
  | `.migrate()` | DB setup |
295
331
  | `.shutdown()` | Clean shutdown |
296
332
 
297
- ### logdb [C]
333
+ ### logdb [β]
298
334
 
299
335
  PostgreSQL structured event logging with monthly partitioning.
300
336
 
301
337
  ```ts
302
338
  const logger = logdb({ pg })
303
339
  await logger.migrate()
304
- app.use('/logs', logger.router())
340
+ app.use('/logs', logger)
305
341
  await logger.clean(12) // drop partitions older than 12 months
306
342
  await logger.log({ level: 'info', source: 'app', message: 'hello', metadata: { userId: 1 } })
307
343
  ```
@@ -317,13 +353,17 @@ await logger.log({ level: 'info', source: 'app', message: 'hello', metadata: { u
317
353
  | GET | `/` | Query (`?level=`, `?source=`, `?after=`, `?before=`, `?meta.*=`) |
318
354
  | GET | `/:id` | Get single entry |
319
355
 
320
- ### logger [A]
356
+ ### logger [α]
321
357
 
322
358
  ```ts
323
359
  app.use(logger()) // GET /hello 200 5ms
324
360
  app.use(logger({ format: 'combined' })) // with query params
325
361
  ```
326
362
 
363
+ | Option | Type | Default | Description |
364
+ |--------|------|---------|-------------|
365
+ | `format` | `'short' \| 'combined'` | `'short'` | Log format: path only, or path + query params |
366
+
327
367
  ### mailer
328
368
 
329
369
  ```ts
@@ -337,25 +377,32 @@ await mail.send({ to: 'user@test.com', subject: 'Hello', text: 'Body', html: '<p
337
377
  | `from` | `string` | — | Default sender address |
338
378
  | `send` | `function` | — | Custom send function (alternative to transport) |
339
379
 
340
- ### messager [C]
380
+ ### messager [β]
341
381
 
342
382
  Real-time chat with channels, WebSocket, agent routing.
343
383
 
344
384
  ```ts
345
385
  const msg = messager({ pg, agents, redis: redis() })
346
386
  await msg.migrate()
387
+ app.use('/api', msg)
347
388
  app.ws('/ws', msg.wsHandler())
348
389
  await msg.send(channelId, 'System message', { sender_type: 'system', sender_id: 'bot' })
349
390
  ```
350
391
 
392
+ | Option | Type | Default | Description |
393
+ |--------|------|---------|-------------|
394
+ | `pg` | `object` | — | PostgreSQL client |
395
+ | `agents` | `AgentModule` | — | Agent module for routing |
396
+ | `webhookTimeout` | `number` | — | Webhook timeout |
397
+ | `redis` | `object` | — | Redis client |
398
+
351
399
  | Method | Description |
352
400
  |--------|-------------|
353
401
  | `.wsHandler()` | WebSocket handler (channels, typing, read receipts) |
354
402
  | `.send(channel, content, opts?)` | Send message to channel |
355
- | `.router()` | REST API |
356
403
  | `.close()` | Cleanup |
357
404
 
358
- ### opencode [C]
405
+ ### opencode [β]
359
406
 
360
407
  AI programming assistant.
361
408
 
@@ -367,14 +414,14 @@ const oc = await opencode({
367
414
  permissions: { bash: { allow: true }, write: { allow: false } },
368
415
  })
369
416
  await oc.migrate()
370
- app.use('/opencode', await oc.router())
417
+ app.use('/opencode', oc)
371
418
  app.ws('/opencode', oc.wsHandler())
372
419
  ```
373
420
 
374
421
  | Option | Type | Default | Description |
375
422
  |--------|------|---------|-------------|
376
423
  | `pg` | `object` | — | PostgreSQL client |
377
- | `model` | `object` | — | AI model |
424
+ | `model` | `string` | — | AI model name (e.g. `'gpt-4o'`, `'deepseek-v4-flash'`) |
378
425
  | `baseURL` | `string` | — | OpenAI-compatible API base URL |
379
426
  | `apiKey` | `string` | — | API key for the model |
380
427
  | `workspace` | `string` | — | Project directory |
@@ -382,7 +429,7 @@ app.ws('/opencode', oc.wsHandler())
382
429
  | `skills` | `object[]` | — | Custom skill definitions |
383
430
  | `permissions` | `object` | — | Tool permission rules |
384
431
 
385
- ### postgres [B]
432
+ ### postgres [α]
386
433
 
387
434
  ```ts
388
435
  const pg = postgres() // reads DATABASE_URL
@@ -413,7 +460,7 @@ await pg.transaction(async (tx) => { const users = pg.table('_users', { ... }).w
413
460
 
414
461
  Where helpers: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `isNull`, `isNotNull`, `like`, `contains`, `in_`, `and`, `or`, `not`.
415
462
 
416
- ### preferences [A]
463
+ ### preferences [α]
417
464
 
418
465
  Locale detection + theme + translations. `/__lang/:locale` and `/__theme/:theme` auto-routed.
419
466
 
@@ -444,7 +491,7 @@ const { theme, resolvedTheme, setTheme } = useTheme()
444
491
  // resolvedTheme resolves 'system' → 'dark'|'light' based on prefers-color-scheme
445
492
  ```
446
493
 
447
- ### queue [B]
494
+ ### queue [α]
448
495
 
449
496
  ```ts
450
497
  const q = queue({ redis })
@@ -467,7 +514,7 @@ await q.add('send-email', { to: 'user@test.com' }, { cron: '0 8 * * *' })
467
514
  | `.stop()` | Stop processing |
468
515
  | `.close()` | Cleanup |
469
516
 
470
- ### rateLimit [B]
517
+ ### rateLimit [α]
471
518
 
472
519
  ```ts
473
520
  app.use(rateLimit({ max: 100, window: 60_000 })) // 100 req/min
@@ -484,7 +531,7 @@ app.use(rateLimit({ key: (req) => req.headers.get('x-api-key') ?? 'anonymous' })
484
531
  | `key` | `(req) => string` | IP-based | Key function |
485
532
  | `message` | `string` | `'Too Many Requests'` | 429 response body |
486
533
 
487
- ### redis [B]
534
+ ### redis [α]
488
535
 
489
536
  ```ts
490
537
  const r = redis() // reads REDIS_URL
@@ -498,7 +545,7 @@ await ctx.redis.set('key', 'value')
498
545
  | `url` | `string` | `REDIS_URL` env | Redis connection string |
499
546
  | (all ioredis options) | — | — | Passed directly to ioredis |
500
547
 
501
- ### requestId [A]
548
+ ### requestId [α]
502
549
 
503
550
  ```ts
504
551
  app.use(requestId())
@@ -511,10 +558,10 @@ app.use(requestId({ header: 'X-Request-Id', generator: () => crypto.randomUUID()
511
558
  | `header` | `string` | `'X-Request-ID'` | Header name to read/write |
512
559
  | `generator` | `() => string` | `crypto.randomUUID()` | ID generator |
513
560
 
514
- ### seo [D] + seoMiddleware [A]
561
+ ### seo [β] + seoMiddleware [α]
515
562
 
516
563
  ```ts
517
- app.use(seo({ baseUrl: 'https://example.com', robots: [{ userAgent: '*', allow: '/' }], sitemap: { urls: [{ loc: '/' }] } }))
564
+ app.use('/', seo({ baseUrl: 'https://example.com', robots: [{ userAgent: '*', allow: '/' }], sitemap: { urls: [{ loc: '/' }] } }))
518
565
  // GET /robots.txt, GET /sitemap.xml
519
566
 
520
567
  app.use(seoMiddleware({ headers: { 'X-Robots-Tag': (path) => path.startsWith('/admin') ? 'noindex' : undefined } }))
@@ -522,7 +569,14 @@ app.use(seoMiddleware({ headers: { 'X-Robots-Tag': (path) => path.startsWith('/a
522
569
 
523
570
  Also exports `seoTags(config)` for generating meta/og/twitter tags as an HTML string.
524
571
 
525
- ### tenant [C]
572
+ | Option | Type | Default | Description |
573
+ |--------|------|---------|-------------|
574
+ | `baseUrl` | `string` | — | Base URL for sitemap URLs |
575
+ | `robots` | `RobotsRule[]` | `[{ userAgent: '*', allow: '/' }]` | Robots.txt rules |
576
+ | `sitemap` | `SitemapConfig` | — | Sitemap configuration (urls, resolve, cacheTTL) |
577
+ | `headers` | `SeoHeadersConfig` | — | Response headers (e.g. `X-Robots-Tag`) |
578
+
579
+ ### tenant [β]
526
580
 
527
581
  Multi-tenant BaaS with dynamic table API and GraphQL.
528
582
 
@@ -530,11 +584,16 @@ Multi-tenant BaaS with dynamic table API and GraphQL.
530
584
  const t = tenant({ pg, usersTable: '_users' })
531
585
  await t.migrate()
532
586
  app.use('/api', t.middleware()) // → ctx.tenant
533
- app.use('/api', t.router()) // dynamic CRUD
587
+ app.use('/api', t) // dynamic CRUD
534
588
  app.use('/graphql', t.graphql()) // dynamic GraphQL
535
589
  ```
536
590
 
537
- ### upload [A]
591
+ | Option | Type | Default | Description |
592
+ |--------|------|---------|-------------|
593
+ | `pg` | `object` | — | PostgreSQL client |
594
+ | `usersTable` | `string` | — | Users table name for tenant membership lookup |
595
+
596
+ ### upload [α]
538
597
 
539
598
  ```ts
540
599
  app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedTypes: ['image/jpeg', 'image/png'] }), (req, ctx) => {
@@ -550,14 +609,14 @@ app.post('/upload', upload({ dir: './uploads', maxFileSize: 10_485_760, allowedT
550
609
  | `maxFileSize` | `number` | — | Max bytes per file |
551
610
  | `allowedTypes` | `string[]` | — | Allowed MIME types |
552
611
 
553
- ### user [C]
612
+ ### user [β]
554
613
 
555
614
  Authentication: register, login, JWT, OAuth2.
556
615
 
557
616
  ```ts
558
617
  const auth = user({ pg, jwtSecret: process.env.JWT_SECRET! })
559
618
  await auth.migrate()
560
- app.use('/auth', auth.router()) // POST /register, POST /login, OAuth2 routes
619
+ app.use('/auth', auth) // POST /register, POST /login, OAuth2 routes
561
620
  app.get('/me', auth.middleware(), (req, ctx) => Response.json(ctx.user))
562
621
  ```
563
622
 
@@ -574,10 +633,9 @@ app.get('/me', auth.middleware(), (req, ctx) => Response.json(ctx.user))
574
633
  | `.register(data)` | Register a new user programmatically |
575
634
  | `.login(data)` | Log in programmatically |
576
635
  | `.verify(token)` | Verify JWT token |
577
- | `.router()` | REST routes (register, login, OAuth2) |
578
636
  | `.middleware()` | JWT verify middleware — sets `ctx.user` |
579
637
 
580
- ### validate [A]
638
+ ### validate [α]
581
639
 
582
640
  ```ts
583
641
  import { z } from 'zod'
@@ -789,21 +847,7 @@ import { openai, streamText, generateText, streamObject, generateObject, tool, e
789
847
  import { runWorkflow } from 'weifuwu'
790
848
  ```
791
849
 
792
- ### Streaming
793
-
794
- ```ts
795
- const chat = await aiStream(async (req) => ({ model: openai('gpt-4o'), messages: (await req.json()).messages }))
796
- app.use('/chat', chat.router().handler())
797
- ```
798
-
799
- ### Agents
800
-
801
- ```ts
802
- const agents = agent({ pg })
803
- await agents.migrate()
804
- app.use('/api', agents.router())
805
- await agents.addKnowledge(agentId, 'Title', 'content')
806
- ```
850
+ For AI streaming endpoints see [`aiStream`](#aistream-β). For AI agent APIs see [`agent`](#agent-β).
807
851
 
808
852
  ### DAG Workflow
809
853
 
@@ -1,3 +1,4 @@
1
+ import type { Router } from '../router.ts';
1
2
  import type { LanguageModel, EmbeddingModel, Tool } from 'ai';
2
3
  export interface AgentConfig {
3
4
  id: number;
@@ -42,9 +43,8 @@ export interface AgentOptions {
42
43
  embeddingDimension?: number;
43
44
  tools?: Record<string, Tool>;
44
45
  }
45
- export interface AgentModule {
46
+ export interface AgentModule extends Router {
46
47
  migrate: () => Promise<void>;
47
- router: () => any;
48
48
  run: (agentId: number, params: RunParams) => Promise<RunResult>;
49
49
  addKnowledge: (agentId: number, title: string, content: string) => Promise<KnowledgeDoc>;
50
50
  close: () => Promise<void>;
package/dist/ai.d.ts CHANGED
@@ -2,6 +2,4 @@ import type { Context } from './types.ts';
2
2
  import { Router } from './router.ts';
3
3
  export type AIHandler = (req: Request, ctx: Context) => Record<string, unknown> | Promise<Record<string, unknown>>;
4
4
  export declare const _ai: Record<string, any>;
5
- export declare function aiStream(handler: AIHandler): Promise<{
6
- router(): Router;
7
- }>;
5
+ export declare function aiStream(handler: AIHandler): Promise<Router>;
@@ -7,9 +7,8 @@ export interface AnalyticsOptions {
7
7
  table: (name: string, cols: any) => any;
8
8
  };
9
9
  }
10
- export interface AnalyticsModule {
10
+ export interface AnalyticsModule extends Router {
11
11
  middleware: () => Middleware;
12
- router: () => Router;
13
12
  migrate: () => Promise<void>;
14
13
  close: () => Promise<void>;
15
14
  }
package/dist/graphql.d.ts CHANGED
@@ -9,6 +9,4 @@ export interface GraphQLOptions {
9
9
  graphiql?: boolean;
10
10
  }
11
11
  export type GraphQLHandler = (req: Request, ctx: Context) => GraphQLOptions | Promise<GraphQLOptions>;
12
- export declare function graphql(handler: GraphQLHandler): {
13
- router(): Router;
14
- };
12
+ export declare function graphql(handler: GraphQLHandler): Router;
@@ -30,8 +30,7 @@ export interface TriggerOptions {
30
30
  action?: 'sync' | 'void';
31
31
  timeout_ms?: number;
32
32
  }
33
- export interface IIIModule {
34
- router: () => Router;
33
+ export interface IIIModule extends Router {
35
34
  wsHandler: () => any;
36
35
  addWorker: (worker: Worker) => void;
37
36
  trigger: (request: TriggerRequest) => Promise<unknown>;
package/dist/index.js CHANGED
@@ -2292,7 +2292,7 @@ function graphql(handler) {
2292
2292
  }
2293
2293
  return executeQuery(schema, params, options, req, ctx);
2294
2294
  });
2295
- return { router: () => r };
2295
+ return r;
2296
2296
  }
2297
2297
 
2298
2298
  // ai.ts
@@ -2319,7 +2319,7 @@ async function aiStream(handler) {
2319
2319
  const result = streamText4(options);
2320
2320
  return result.toTextStreamResponse();
2321
2321
  });
2322
- return { router: () => r };
2322
+ return r;
2323
2323
  }
2324
2324
 
2325
2325
  // ai/workflow.ts
@@ -3592,8 +3592,8 @@ function user(options) {
3592
3592
  };
3593
3593
  }
3594
3594
  function router() {
3595
- const r = new Router();
3596
- r.post("/register", async (req) => {
3595
+ const r2 = new Router();
3596
+ r2.post("/register", async (req) => {
3597
3597
  try {
3598
3598
  const body = await req.json();
3599
3599
  const result = await register(body);
@@ -3606,7 +3606,7 @@ function user(options) {
3606
3606
  return Response.json({ error: err.message }, { status });
3607
3607
  }
3608
3608
  });
3609
- r.post("/login", async (req) => {
3609
+ r2.post("/login", async (req) => {
3610
3610
  try {
3611
3611
  const body = await req.json();
3612
3612
  const result = await login(body);
@@ -3622,30 +3622,29 @@ function user(options) {
3622
3622
  }
3623
3623
  });
3624
3624
  if (oauth2) {
3625
- r.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
3626
- r.post("/oauth/consent", (req) => oauth2.consentHandler(req));
3627
- r.post("/oauth/token", (req) => oauth2.tokenHandler(req));
3628
- }
3629
- return r;
3630
- }
3631
- const mod = {
3632
- router,
3633
- middleware,
3634
- migrate,
3635
- register,
3636
- login,
3637
- verify,
3638
- registerClient: oauth2 ? (data) => oauth2.registerClient(data) : async () => {
3639
- throw new Error("OAuth2 server is not enabled");
3640
- },
3641
- getClient: oauth2 ? (clientId) => oauth2.getClient(clientId) : async () => {
3642
- throw new Error("OAuth2 server is not enabled");
3643
- },
3644
- revokeClient: oauth2 ? (clientId) => oauth2.revokeClient(clientId) : async () => {
3645
- throw new Error("OAuth2 server is not enabled");
3646
- },
3647
- close: () => base.close()
3625
+ r2.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
3626
+ r2.post("/oauth/consent", (req) => oauth2.consentHandler(req));
3627
+ r2.post("/oauth/token", (req) => oauth2.tokenHandler(req));
3628
+ }
3629
+ return r2;
3630
+ }
3631
+ const r = router();
3632
+ const mod = r;
3633
+ mod.middleware = middleware;
3634
+ mod.migrate = migrate;
3635
+ mod.register = register;
3636
+ mod.login = login;
3637
+ mod.verify = verify;
3638
+ mod.registerClient = oauth2 ? (data) => oauth2.registerClient(data) : async () => {
3639
+ throw new Error("OAuth2 server is not enabled");
3640
+ };
3641
+ mod.getClient = oauth2 ? (clientId) => oauth2.getClient(clientId) : async () => {
3642
+ throw new Error("OAuth2 server is not enabled");
3648
3643
  };
3644
+ mod.revokeClient = oauth2 ? (clientId) => oauth2.revokeClient(clientId) : async () => {
3645
+ throw new Error("OAuth2 server is not enabled");
3646
+ };
3647
+ mod.close = () => base.close();
3649
3648
  return mod;
3650
3649
  }
3651
3650
 
@@ -4724,76 +4723,78 @@ function tenant(options) {
4724
4723
  const sql2 = pg.sql;
4725
4724
  const usersTable = options.usersTable;
4726
4725
  const base = new PgModule(pg);
4727
- return {
4728
- migrate: async () => {
4729
- await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "vector"`);
4730
- const tenants = pgTable("_tenants", {
4731
- id: text("id").primaryKey().default(sql`gen_random_uuid()`),
4732
- name: text("name").notNull(),
4733
- created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4734
- });
4735
- await tenants.create(sql2);
4736
- const members = pgTable("_tenant_members", {
4737
- id: serial("id").primaryKey(),
4738
- tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
4739
- user_id: integer("user_id").notNull(),
4740
- role: text("role").notNull().default("member"),
4741
- created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4742
- });
4743
- await members.create(sql2);
4744
- await members.createIndex(sql2, "user_id");
4745
- await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_tenant_members_unique_idx" ON "_tenant_members" ("tenant_id", "user_id")`);
4746
- const tables = pgTable("_user_tables", {
4747
- id: serial("id").primaryKey(),
4748
- tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
4749
- slug: text("slug").notNull(),
4750
- label: text("label").notNull().default(""),
4751
- fields: jsonb("fields").notNull().default(sql`'[]'::jsonb`),
4752
- created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4753
- });
4754
- await tables.create(sql2);
4755
- await tables.createIndex(sql2, "tenant_id");
4756
- await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_user_tables_unique_idx" ON "_user_tables" ("tenant_id", "slug")`);
4757
- },
4758
- middleware() {
4759
- return async (req, ctx, next) => {
4760
- const user2 = ctx.user;
4761
- if (!user2) {
4762
- return new Response("Unauthorized", { status: 401 });
4763
- }
4764
- const members = await sql2`
4765
- SELECT tm.role, t.id, t.name
4766
- FROM "_tenant_members" tm
4767
- JOIN "_tenants" t ON t.id = tm.tenant_id
4768
- WHERE tm.user_id = ${user2.id}
4769
- `;
4770
- if (members.length === 0) {
4771
- return new Response("No tenant found. Create one via POST /sys/tenants.", { status: 403 });
4772
- }
4773
- if (members.length === 1) {
4774
- const m = members[0];
4775
- ctx.tenant = { id: m.id, name: m.name, role: m.role };
4776
- return next(req, ctx);
4777
- }
4778
- const headerId = req.headers.get("X-Tenant-ID");
4779
- if (!headerId) {
4780
- return Response.json({
4781
- error: "Multiple tenants. Set X-Tenant-ID header.",
4782
- tenants: members.map((m) => ({ id: m.id, name: m.name, role: m.role }))
4783
- }, { status: 300 });
4784
- }
4785
- const member = members.find((m) => m.id === headerId);
4786
- if (!member) {
4787
- return new Response("Tenant not found", { status: 403 });
4788
- }
4789
- ctx.tenant = { id: member.id, name: member.name, role: member.role };
4726
+ async function migrate() {
4727
+ await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "vector"`);
4728
+ const tenants = pgTable("_tenants", {
4729
+ id: text("id").primaryKey().default(sql`gen_random_uuid()`),
4730
+ name: text("name").notNull(),
4731
+ created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4732
+ });
4733
+ await tenants.create(sql2);
4734
+ const members = pgTable("_tenant_members", {
4735
+ id: serial("id").primaryKey(),
4736
+ tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
4737
+ user_id: integer("user_id").notNull(),
4738
+ role: text("role").notNull().default("member"),
4739
+ created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4740
+ });
4741
+ await members.create(sql2);
4742
+ await members.createIndex(sql2, "user_id");
4743
+ await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_tenant_members_unique_idx" ON "_tenant_members" ("tenant_id", "user_id")`);
4744
+ const tables = pgTable("_user_tables", {
4745
+ id: serial("id").primaryKey(),
4746
+ tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
4747
+ slug: text("slug").notNull(),
4748
+ label: text("label").notNull().default(""),
4749
+ fields: jsonb("fields").notNull().default(sql`'[]'::jsonb`),
4750
+ created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
4751
+ });
4752
+ await tables.create(sql2);
4753
+ await tables.createIndex(sql2, "tenant_id");
4754
+ await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_user_tables_unique_idx" ON "_user_tables" ("tenant_id", "slug")`);
4755
+ }
4756
+ function middleware() {
4757
+ return async (req, ctx, next) => {
4758
+ const user2 = ctx.user;
4759
+ if (!user2) {
4760
+ return new Response("Unauthorized", { status: 401 });
4761
+ }
4762
+ const members = await sql2`
4763
+ SELECT tm.role, t.id, t.name
4764
+ FROM "_tenant_members" tm
4765
+ JOIN "_tenants" t ON t.id = tm.tenant_id
4766
+ WHERE tm.user_id = ${user2.id}
4767
+ `;
4768
+ if (members.length === 0) {
4769
+ return new Response("No tenant found. Create one via POST /sys/tenants.", { status: 403 });
4770
+ }
4771
+ if (members.length === 1) {
4772
+ const m = members[0];
4773
+ ctx.tenant = { id: m.id, name: m.name, role: m.role };
4790
4774
  return next(req, ctx);
4791
- };
4792
- },
4793
- router: () => buildRouter(sql2, usersTable),
4794
- graphql: () => buildGraphQLHandler(sql2),
4795
- close: () => base.close()
4796
- };
4775
+ }
4776
+ const headerId = req.headers.get("X-Tenant-ID");
4777
+ if (!headerId) {
4778
+ return Response.json({
4779
+ error: "Multiple tenants. Set X-Tenant-ID header.",
4780
+ tenants: members.map((m) => ({ id: m.id, name: m.name, role: m.role }))
4781
+ }, { status: 300 });
4782
+ }
4783
+ const member = members.find((m) => m.id === headerId);
4784
+ if (!member) {
4785
+ return new Response("Tenant not found", { status: 403 });
4786
+ }
4787
+ ctx.tenant = { id: member.id, name: member.name, role: member.role };
4788
+ return next(req, ctx);
4789
+ };
4790
+ }
4791
+ const r = buildRouter(sql2, usersTable);
4792
+ const mod = r;
4793
+ mod.migrate = migrate;
4794
+ mod.middleware = middleware;
4795
+ mod.graphql = () => buildGraphQLHandler(sql2);
4796
+ mod.close = () => base.close();
4797
+ return mod;
4797
4798
  }
4798
4799
 
4799
4800
  // agent/client.ts
@@ -5070,18 +5071,18 @@ function agent(options) {
5070
5071
  });
5071
5072
  const runner = createRunner({ sql: sql2, agents: agentsTable, knowledge: knowledgeTable, getModel, getEmbeddingModel, userTools: options.tools });
5072
5073
  const base = new PgModule(pg);
5073
- return {
5074
- migrate: async () => {
5075
- await agentsTable.create();
5076
- await agentsTable.createIndex("tenant_id");
5077
- await knowledgeTable.create();
5078
- await knowledgeTable.createIndex("agent_id");
5079
- },
5080
- router: () => buildRouter2({ agents: agentsTable, knowledge: knowledgeTable, runner }),
5081
- run: (agentId, params) => runner.run(agentId, params),
5082
- addKnowledge: (agentId, title, content) => runner.addKnowledge(agentId, title, content),
5083
- close: () => base.close()
5074
+ const r = buildRouter2({ agents: agentsTable, knowledge: knowledgeTable, runner });
5075
+ const mod = r;
5076
+ mod.migrate = async () => {
5077
+ await agentsTable.create();
5078
+ await agentsTable.createIndex("tenant_id");
5079
+ await knowledgeTable.create();
5080
+ await knowledgeTable.createIndex("agent_id");
5084
5081
  };
5082
+ mod.run = (agentId, params) => runner.run(agentId, params);
5083
+ mod.addKnowledge = (agentId, title, content) => runner.addKnowledge(agentId, title, content);
5084
+ mod.close = () => base.close();
5085
+ return mod;
5085
5086
  }
5086
5087
 
5087
5088
  // messager/agent.ts
@@ -5390,31 +5391,32 @@ function messager(options) {
5390
5391
  mime_type: text("mime_type"),
5391
5392
  created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
5392
5393
  });
5393
- return {
5394
- migrate: async () => {
5395
- await channels.create();
5396
- await channels.createIndex("tenant_id");
5397
- await members.create();
5398
- await members.createIndex("member_id");
5399
- await members.createIndex(["channel_id", "member_id", "member_type"], { unique: true });
5400
- await messages2.create();
5401
- await messages2.createIndex(["channel_id", "created_at"], { desc: true });
5402
- },
5403
- router: () => buildRouter3({ sql: sql2, channels, members, messages: messages2, agents, hub }),
5404
- wsHandler: () => wsResult.handler,
5405
- async send(channelId, content, opts) {
5406
- const msg = await messages2.insert({
5407
- channel_id: channelId,
5408
- sender_id: opts?.sender_id ?? 0,
5409
- sender_type: opts?.sender_type ?? "system",
5410
- type: opts?.type ?? "text",
5411
- content
5412
- });
5413
- broadcastToChannel(hub, channelId, { type: "message", data: msg });
5414
- return msg;
5415
- },
5416
- close: () => base.close()
5394
+ const r = buildRouter3({ sql: sql2, channels, members, messages: messages2, agents, hub });
5395
+ async function send(channelId, content, opts) {
5396
+ const msg = await messages2.insert({
5397
+ channel_id: channelId,
5398
+ sender_id: opts?.sender_id ?? 0,
5399
+ sender_type: opts?.sender_type ?? "system",
5400
+ type: opts?.type ?? "text",
5401
+ content
5402
+ });
5403
+ broadcastToChannel(hub, channelId, { type: "message", data: msg });
5404
+ return msg;
5405
+ }
5406
+ const mod = r;
5407
+ mod.migrate = async () => {
5408
+ await channels.create();
5409
+ await channels.createIndex("tenant_id");
5410
+ await members.create();
5411
+ await members.createIndex("member_id");
5412
+ await members.createIndex(["channel_id", "member_id", "member_type"], { unique: true });
5413
+ await messages2.create();
5414
+ await messages2.createIndex(["channel_id", "created_at"], { desc: true });
5417
5415
  };
5416
+ mod.wsHandler = () => wsResult.handler;
5417
+ mod.send = send;
5418
+ mod.close = () => base.close();
5419
+ return mod;
5418
5420
  }
5419
5421
 
5420
5422
  // deploy/index.ts
@@ -6838,47 +6840,47 @@ async function opencode(options) {
6838
6840
  const model = provider.chat(modelName);
6839
6841
  const pendingQuestions = /* @__PURE__ */ new Map();
6840
6842
  const base = new PgModule(pg);
6841
- return {
6842
- migrate: async () => {
6843
- const sessions2 = pgTable("_opencode_sessions", {
6844
- id: uuid("id").default(sql`gen_random_uuid()`).primaryKey(),
6845
- tenant_id: text("tenant_id"),
6846
- user_id: integer("user_id").default(0),
6847
- title: text("title"),
6848
- agent_type: text("agent_type").default("build"),
6849
- model: text("model").default("deepseek-v4-flash"),
6850
- system_prompt: text("system_prompt"),
6851
- workspace: text("workspace"),
6852
- metadata: jsonb("metadata").default(sql`'{}'::jsonb`),
6853
- active: boolean_("active").default(true),
6854
- created_at: timestamptz("created_at").default(sql`NOW()`),
6855
- updated_at: timestamptz("updated_at").default(sql`NOW()`)
6856
- });
6857
- await sessions2.create(sql2);
6858
- await sessions2.createIndex(sql2, "user_id");
6859
- const messages2 = pgTable("_opencode_messages", {
6860
- id: serial("id").primaryKey(),
6861
- session_id: uuid("session_id").notNull().references("_opencode_sessions", "id", "cascade"),
6862
- role: text("role").notNull(),
6863
- content: text("content"),
6864
- tool_calls: jsonb("tool_calls"),
6865
- tool_results: jsonb("tool_results"),
6866
- tokens_in: integer("tokens_in").default(0),
6867
- tokens_out: integer("tokens_out").default(0),
6868
- created_at: timestamptz("created_at").default(sql`NOW()`)
6869
- });
6870
- await messages2.create(sql2);
6871
- await messages2.createIndex(sql2, ["session_id", "created_at"]);
6872
- },
6873
- router: () => buildRouter4({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions }),
6874
- wsHandler: () => createWSHandler2({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions }),
6875
- close: () => base.close()
6843
+ const r = await buildRouter4({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions });
6844
+ const mod = r;
6845
+ mod.migrate = async () => {
6846
+ const sessions2 = pgTable("_opencode_sessions", {
6847
+ id: uuid("id").default(sql`gen_random_uuid()`).primaryKey(),
6848
+ tenant_id: text("tenant_id"),
6849
+ user_id: integer("user_id").default(0),
6850
+ title: text("title"),
6851
+ agent_type: text("agent_type").default("build"),
6852
+ model: text("model").default("deepseek-v4-flash"),
6853
+ system_prompt: text("system_prompt"),
6854
+ workspace: text("workspace"),
6855
+ metadata: jsonb("metadata").default(sql`'{}'::jsonb`),
6856
+ active: boolean_("active").default(true),
6857
+ created_at: timestamptz("created_at").default(sql`NOW()`),
6858
+ updated_at: timestamptz("updated_at").default(sql`NOW()`)
6859
+ });
6860
+ await sessions2.create(sql2);
6861
+ await sessions2.createIndex(sql2, "user_id");
6862
+ const messages2 = pgTable("_opencode_messages", {
6863
+ id: serial("id").primaryKey(),
6864
+ session_id: uuid("session_id").notNull().references("_opencode_sessions", "id", "cascade"),
6865
+ role: text("role").notNull(),
6866
+ content: text("content"),
6867
+ tool_calls: jsonb("tool_calls"),
6868
+ tool_results: jsonb("tool_results"),
6869
+ tokens_in: integer("tokens_in").default(0),
6870
+ tokens_out: integer("tokens_out").default(0),
6871
+ created_at: timestamptz("created_at").default(sql`NOW()`)
6872
+ });
6873
+ await messages2.create(sql2);
6874
+ await messages2.createIndex(sql2, ["session_id", "created_at"]);
6876
6875
  };
6876
+ mod.wsHandler = () => createWSHandler2({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions });
6877
+ mod.close = () => base.close();
6878
+ return mod;
6877
6879
  }
6878
6880
 
6879
6881
  // health.ts
6880
6882
  function health(options) {
6881
- const path2 = options?.path ?? "/health";
6883
+ const path2 = options?.path ?? "/";
6882
6884
  const r = new Router();
6883
6885
  const handler = async () => {
6884
6886
  try {
@@ -7099,18 +7101,19 @@ function analytics(options) {
7099
7101
  headers: { "content-type": "text/html; charset=utf-8" }
7100
7102
  });
7101
7103
  };
7102
- const router = () => {
7103
- const r = new Router();
7104
- r.get("/__analytics/data", handler);
7105
- r.get("/__analytics", handler);
7106
- return r;
7107
- };
7104
+ const r = new Router();
7105
+ r.get("/__analytics/data", handler);
7106
+ r.get("/__analytics", handler);
7108
7107
  const migrate = async () => {
7109
7108
  if (pg) await migratePg(pg.sql, pg.table);
7110
7109
  };
7111
7110
  const close = async () => {
7112
7111
  };
7113
- return { middleware, router, migrate, close };
7112
+ const mod = r;
7113
+ mod.middleware = middleware;
7114
+ mod.migrate = migrate;
7115
+ mod.close = close;
7116
+ return mod;
7114
7117
  }
7115
7118
 
7116
7119
  // preferences.ts
@@ -7585,11 +7588,11 @@ function logdb(options) {
7585
7588
  return row;
7586
7589
  }
7587
7590
  function router() {
7588
- const r = new Router();
7589
- r.post("/", createHandler(entries));
7590
- r.get("/", listHandler(entries));
7591
- r.get("/:id", getHandler(entries));
7592
- return r;
7591
+ const r2 = new Router();
7592
+ r2.post("/", createHandler(entries));
7593
+ r2.get("/", listHandler(entries));
7594
+ r2.get("/:id", getHandler(entries));
7595
+ return r2;
7593
7596
  }
7594
7597
  async function clean(retentionMonths) {
7595
7598
  const cutoff = /* @__PURE__ */ new Date();
@@ -7611,21 +7614,22 @@ function logdb(options) {
7611
7614
  }
7612
7615
  return dropped;
7613
7616
  }
7614
- return {
7615
- log,
7616
- router,
7617
- migrate: async () => {
7618
- await entries.create({ partitionBy: partitionBy("range", "created_at") });
7619
- await entries.createIndex(["created_at", "id"]);
7620
- await entries.createIndex(["level"]);
7621
- await entries.createIndex(["source"]);
7622
- await entries.createIndex(["level", "created_at"]);
7623
- await ensurePartitions(sql2, tableName);
7624
- },
7625
- clean,
7626
- close: async () => {
7627
- }
7617
+ async function migrate() {
7618
+ await entries.create({ partitionBy: partitionBy("range", "created_at") });
7619
+ await entries.createIndex(["created_at", "id"]);
7620
+ await entries.createIndex(["level"]);
7621
+ await entries.createIndex(["source"]);
7622
+ await entries.createIndex(["level", "created_at"]);
7623
+ await ensurePartitions(sql2, tableName);
7624
+ }
7625
+ const r = router();
7626
+ const mod = r;
7627
+ mod.log = log;
7628
+ mod.migrate = migrate;
7629
+ mod.clean = clean;
7630
+ mod.close = async () => {
7628
7631
  };
7632
+ return mod;
7629
7633
  }
7630
7634
 
7631
7635
  // iii/client.ts
@@ -8234,6 +8238,7 @@ function iii(opts = {}) {
8234
8238
  for (const t of reg.triggers) triggers.delete(t.id);
8235
8239
  workers.delete(workerId);
8236
8240
  }
8241
+ let engineRef = null;
8237
8242
  const wsHandler = createWsHandler({
8238
8243
  registerRemoteWorker(ws, name) {
8239
8244
  const id2 = crypto6.randomUUID();
@@ -8304,7 +8309,7 @@ function iii(opts = {}) {
8304
8309
  }));
8305
8310
  return;
8306
8311
  }
8307
- const ctx = { engine: module, functionId, workerName: fn.workerName };
8312
+ const ctx = { engine: engineRef, functionId, workerName: fn.workerName };
8308
8313
  Promise.resolve(fn.handler(payload, ctx)).then((result) => {
8309
8314
  ws.send(JSON.stringify({ type: "invoke_result", invocation_id: invocationId, result }));
8310
8315
  }).catch((err) => {
@@ -8312,70 +8317,78 @@ function iii(opts = {}) {
8312
8317
  });
8313
8318
  }
8314
8319
  });
8315
- const module = {
8316
- router: () => {
8317
- const r = buildRouter5(module, wsHandler);
8318
- module.router = () => r;
8319
- return r;
8320
- },
8321
- wsHandler: () => wsHandler,
8322
- addWorker: addLocalWorker,
8323
- removeWorker: (worker) => {
8324
- for (const [wid, reg] of workers) {
8325
- if (reg.name === worker.name) {
8326
- removeWorker(wid);
8327
- return;
8328
- }
8320
+ function removeWorkerByName(worker) {
8321
+ for (const [wid, reg] of workers) {
8322
+ if (reg.name === worker.name) {
8323
+ removeWorker(wid);
8324
+ return;
8329
8325
  }
8330
- },
8331
- trigger(request) {
8332
- const fn = functions.get(request.function_id);
8333
- if (!fn) throw new Error(`Function "${request.function_id}" not found`);
8334
- const ctx = { engine: module, functionId: request.function_id, workerName: fn.workerName };
8335
- if (request.action === "void") {
8336
- queueMicrotask(() => fn.handler(request.payload, ctx));
8337
- return Promise.resolve(void 0);
8338
- }
8339
- return Promise.resolve(fn.handler(request.payload, ctx));
8340
- },
8341
- listWorkers: () => Array.from(workers.values()).map((w) => ({
8326
+ }
8327
+ }
8328
+ function trigger(request) {
8329
+ const fn = functions.get(request.function_id);
8330
+ if (!fn) throw new Error(`Function "${request.function_id}" not found`);
8331
+ const ctx = { engine: engineRef, functionId: request.function_id, workerName: fn.workerName };
8332
+ if (request.action === "void") {
8333
+ queueMicrotask(() => fn.handler(request.payload, ctx));
8334
+ return Promise.resolve(void 0);
8335
+ }
8336
+ return Promise.resolve(fn.handler(request.payload, ctx));
8337
+ }
8338
+ function listWorkers() {
8339
+ return Array.from(workers.values()).map((w) => ({
8342
8340
  id: w.id,
8343
8341
  name: w.name,
8344
8342
  status: "connected",
8345
8343
  connectedAt: Date.now(),
8346
8344
  functionCount: w.functions.length,
8347
8345
  triggerCount: w.triggers.length
8348
- })),
8349
- listFunctions: () => Array.from(functions.values()).map((f) => ({
8346
+ }));
8347
+ }
8348
+ function listFunctions() {
8349
+ return Array.from(functions.values()).map((f) => ({
8350
8350
  id: f.id,
8351
8351
  workerId: f.workerId,
8352
8352
  workerName: f.workerName,
8353
8353
  triggers: f.triggers
8354
- })),
8355
- listTriggers: () => Array.from(triggers.values()).map((t) => ({
8354
+ }));
8355
+ }
8356
+ function listTriggers() {
8357
+ return Array.from(triggers.values()).map((t) => ({
8356
8358
  id: t.id,
8357
8359
  type: t.type,
8358
8360
  function_id: t.function_id,
8359
8361
  config: t.config,
8360
8362
  workerId: t.workerId
8361
- })),
8362
- migrate: async () => {
8363
- await stream.migrate();
8364
- },
8365
- shutdown: async () => {
8366
- for (const [, p] of pending) {
8367
- clearTimeout(p.timer);
8368
- p.reject(new Error("Engine shutting down"));
8369
- }
8370
- pending.clear();
8371
- for (const [, reg] of workers) reg.ws?.close();
8372
- workers.clear();
8373
- functions.clear();
8374
- triggers.clear();
8375
- await stream.close();
8376
- }
8363
+ }));
8364
+ }
8365
+ const routerMethods = { listWorkers, listFunctions, listTriggers, trigger };
8366
+ const r = buildRouter5(routerMethods, wsHandler);
8367
+ engineRef = r;
8368
+ const mod = r;
8369
+ mod.wsHandler = () => wsHandler;
8370
+ mod.addWorker = addLocalWorker;
8371
+ mod.removeWorker = removeWorkerByName;
8372
+ mod.trigger = trigger;
8373
+ mod.listWorkers = listWorkers;
8374
+ mod.listFunctions = listFunctions;
8375
+ mod.listTriggers = listTriggers;
8376
+ mod.migrate = async () => {
8377
+ await stream.migrate();
8377
8378
  };
8378
- return module;
8379
+ mod.shutdown = async () => {
8380
+ for (const [, p] of pending) {
8381
+ clearTimeout(p.timer);
8382
+ p.reject(new Error("Engine shutting down"));
8383
+ }
8384
+ pending.clear();
8385
+ for (const [, reg] of workers) reg.ws?.close();
8386
+ workers.clear();
8387
+ functions.clear();
8388
+ triggers.clear();
8389
+ await stream.close();
8390
+ };
8391
+ return mod;
8379
8392
  }
8380
8393
 
8381
8394
  // iii/worker.ts
@@ -18,9 +18,8 @@ export interface LogEntryInput {
18
18
  message: string;
19
19
  metadata?: Record<string, unknown>;
20
20
  }
21
- export interface LogdbModule {
21
+ export interface LogdbModule extends Router {
22
22
  log(input: LogEntryInput): Promise<LogEntry>;
23
- router(): Router;
24
23
  migrate(): Promise<void>;
25
24
  clean(retentionMonths: number): Promise<number>;
26
25
  close(): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import type { Router } from '../router.ts';
1
2
  import type { AgentModule } from '../agent/types.ts';
2
3
  import type { PostgresClient } from '../postgres/types.ts';
3
4
  import type { Redis } from '../vendor.ts';
@@ -36,9 +37,8 @@ export interface Message {
36
37
  mime_type: string | null;
37
38
  created_at: string;
38
39
  }
39
- export interface MessagerModule {
40
+ export interface MessagerModule extends Router {
40
41
  migrate: () => Promise<void>;
41
- router: () => any;
42
42
  wsHandler: () => any;
43
43
  send: (channelId: number, content: string, opts?: {
44
44
  sender_type?: string;
@@ -74,9 +74,8 @@ export interface OpencodeOptions {
74
74
  skills?: SkillDef[];
75
75
  permissions?: OpencodePermissions;
76
76
  }
77
- export interface OpencodeModule {
77
+ export interface OpencodeModule extends Router {
78
78
  migrate: () => Promise<void>;
79
- router: () => Router | Promise<Router>;
80
79
  wsHandler: () => any;
81
80
  close: () => Promise<void>;
82
81
  }
@@ -1,4 +1,5 @@
1
1
  import type { Context } from '../types.ts';
2
+ import type { Router } from '../router.ts';
2
3
  import type { PostgresClient } from '../postgres/types.ts';
3
4
  declare module '../types.ts' {
4
5
  interface Context {
@@ -39,10 +40,9 @@ export interface TenantOptions {
39
40
  pg: PostgresClient;
40
41
  usersTable: string;
41
42
  }
42
- export interface TenantModule {
43
+ export interface TenantModule extends Router {
43
44
  migrate: () => Promise<void>;
44
45
  middleware: () => (req: Request, ctx: Context, next: any) => Promise<Response>;
45
- router: () => any;
46
46
  graphql: () => any;
47
47
  close: () => Promise<void>;
48
48
  }
@@ -31,8 +31,7 @@ export interface UserOptions {
31
31
  expiresIn?: string | number;
32
32
  oauth2?: OAuth2ServerOptions;
33
33
  }
34
- export interface UserModule {
35
- router: () => Router;
34
+ export interface UserModule extends Router {
36
35
  middleware: () => Middleware;
37
36
  migrate: () => Promise<void>;
38
37
  register: (data: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",