moost 0.6.5 → 0.6.7

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/dist/index.cjs CHANGED
@@ -248,14 +248,16 @@ const scopeVarsMap = /* @__PURE__ */ new Map();
248
248
  const controllerInstanceKey = (0, __wooksjs_event_core.key)("controller.instance");
249
249
  const controllerMethodKey = (0, __wooksjs_event_core.key)("controller.method");
250
250
  const controllerRouteKey = (0, __wooksjs_event_core.key)("controller.route");
251
+ const controllerPrefixKey = (0, __wooksjs_event_core.key)("controller.prefix");
251
252
  /**
252
253
  * Sets the controller context for the current event scope.
253
254
  * Called internally by adapters when dispatching events to handlers.
254
- */ function setControllerContext(controller, method, route, ctx) {
255
- const _ctx = ctx || (0, __wooksjs_event_core.current)();
255
+ */ function setControllerContext(controller, method, route, opts) {
256
+ const _ctx = opts?.ctx || (0, __wooksjs_event_core.current)();
256
257
  _ctx.set(controllerInstanceKey, controller);
257
258
  _ctx.set(controllerMethodKey, method);
258
259
  _ctx.set(controllerRouteKey, route);
260
+ if (opts?.prefix !== void 0) _ctx.set(controllerPrefixKey, opts.prefix);
259
261
  }
260
262
  /**
261
263
  * Provides access to the current controller context within an event handler.
@@ -265,6 +267,7 @@ const controllerRouteKey = (0, __wooksjs_event_core.key)("controller.route");
265
267
  const getController = () => _ctx.get(controllerInstanceKey);
266
268
  const getMethod = () => _ctx.get(controllerMethodKey);
267
269
  const getRoute = () => _ctx.get(controllerRouteKey);
270
+ const getPrefix = () => _ctx.get(controllerPrefixKey);
268
271
  const getControllerMeta = () => getMoostMate().read(getController());
269
272
  const getMethodMeta = (name) => getMoostMate().read(getController(), name || getMethod());
270
273
  function instantiate(c) {
@@ -273,6 +276,7 @@ const controllerRouteKey = (0, __wooksjs_event_core.key)("controller.route");
273
276
  return {
274
277
  instantiate,
275
278
  getRoute,
279
+ getPrefix,
276
280
  getController,
277
281
  getMethod,
278
282
  getControllerMeta,
@@ -370,7 +374,7 @@ function nextScopeId() {
370
374
  }
371
375
  function afterInstance(instance) {
372
376
  if (instance) {
373
- setControllerContext(instance, options.controllerMethod || "", options.targetPath);
377
+ setControllerContext(instance, options.controllerMethod || "", options.targetPath, { prefix: options.controllerPrefix });
374
378
  ci?.hook(options.handlerType, "Controller:registered");
375
379
  }
376
380
  interceptorHandler = options.getIterceptorHandler();
@@ -1512,6 +1516,8 @@ function _define_property(obj, key$3, value) {
1512
1516
  const classMeta = mate$1.read(controller);
1513
1517
  const infact = getMoostInfact();
1514
1518
  const isControllerConsructor = (0, __prostojs_mate.isConstructor)(controller);
1519
+ const ownPrefix = typeof replaceOwnPrefix === "string" ? replaceOwnPrefix : classMeta?.controller?.prefix || "";
1520
+ const computedPrefix = `${globalPrefix}/${ownPrefix}`;
1515
1521
  const pipes = mergeSorted(this.pipes, classMeta?.pipes);
1516
1522
  let instance;
1517
1523
  const infactOpts = {
@@ -1520,7 +1526,7 @@ function _define_property(obj, key$3, value) {
1520
1526
  customData: { pipes }
1521
1527
  };
1522
1528
  if (isControllerConsructor && (classMeta?.injectable === "SINGLETON" || classMeta?.injectable === true)) await (0, __wooksjs_event_core.createEventContext)({ logger: this.logger }, async () => {
1523
- setControllerContext(this, "bindController", "");
1529
+ setControllerContext(this, "bindController", "", { prefix: computedPrefix });
1524
1530
  instance = await infact.get(controller, infactOpts);
1525
1531
  });
1526
1532
  else if (!isControllerConsructor) {
package/dist/index.d.ts CHANGED
@@ -577,6 +577,7 @@ interface TMoostEventHandlerOptions<T> {
577
577
  end?: (opts: TMoostEventHandlerHookOptions<T>) => unknown;
578
578
  };
579
579
  targetPath: string;
580
+ controllerPrefix?: string;
580
581
  handlerType: string;
581
582
  }
582
583
  /** Composable returning the moost scope ID for the current event. Generates on first call, caches for subsequent calls. */
@@ -774,7 +775,10 @@ interface TMoostAdapter<H> {
774
775
  * Sets the controller context for the current event scope.
775
776
  * Called internally by adapters when dispatching events to handlers.
776
777
  */
777
- declare function setControllerContext<T>(controller: T, method: keyof T, route: string, ctx?: EventContext): void;
778
+ declare function setControllerContext<T>(controller: T, method: keyof T, route: string, opts?: {
779
+ prefix?: string;
780
+ ctx?: EventContext;
781
+ }): void;
778
782
  /**
779
783
  * Provides access to the current controller context within an event handler.
780
784
  * Returns utilities for accessing the controller instance, method metadata, and DI.
@@ -782,6 +786,7 @@ declare function setControllerContext<T>(controller: T, method: keyof T, route:
782
786
  declare function useControllerContext<T extends object>(ctx?: EventContext): {
783
787
  instantiate: <TT>(c: TClassConstructor<TT>) => Promise<TT>;
784
788
  getRoute: () => string;
789
+ getPrefix: () => string;
785
790
  getController: () => T;
786
791
  getMethod: () => string | undefined;
787
792
  getControllerMeta: <TT extends object>() => (TMoostMetadata<TEmpty> & TT & {
package/dist/index.mjs CHANGED
@@ -225,14 +225,16 @@ const scopeVarsMap = /* @__PURE__ */ new Map();
225
225
  const controllerInstanceKey = key$1("controller.instance");
226
226
  const controllerMethodKey = key$1("controller.method");
227
227
  const controllerRouteKey = key$1("controller.route");
228
+ const controllerPrefixKey = key$1("controller.prefix");
228
229
  /**
229
230
  * Sets the controller context for the current event scope.
230
231
  * Called internally by adapters when dispatching events to handlers.
231
- */ function setControllerContext(controller, method, route, ctx) {
232
- const _ctx = ctx || current$1();
232
+ */ function setControllerContext(controller, method, route, opts) {
233
+ const _ctx = opts?.ctx || current$1();
233
234
  _ctx.set(controllerInstanceKey, controller);
234
235
  _ctx.set(controllerMethodKey, method);
235
236
  _ctx.set(controllerRouteKey, route);
237
+ if (opts?.prefix !== void 0) _ctx.set(controllerPrefixKey, opts.prefix);
236
238
  }
237
239
  /**
238
240
  * Provides access to the current controller context within an event handler.
@@ -242,6 +244,7 @@ const controllerRouteKey = key$1("controller.route");
242
244
  const getController = () => _ctx.get(controllerInstanceKey);
243
245
  const getMethod = () => _ctx.get(controllerMethodKey);
244
246
  const getRoute = () => _ctx.get(controllerRouteKey);
247
+ const getPrefix = () => _ctx.get(controllerPrefixKey);
245
248
  const getControllerMeta = () => getMoostMate().read(getController());
246
249
  const getMethodMeta = (name) => getMoostMate().read(getController(), name || getMethod());
247
250
  function instantiate(c) {
@@ -250,6 +253,7 @@ const controllerRouteKey = key$1("controller.route");
250
253
  return {
251
254
  instantiate,
252
255
  getRoute,
256
+ getPrefix,
253
257
  getController,
254
258
  getMethod,
255
259
  getControllerMeta,
@@ -347,7 +351,7 @@ function nextScopeId() {
347
351
  }
348
352
  function afterInstance(instance) {
349
353
  if (instance) {
350
- setControllerContext(instance, options.controllerMethod || "", options.targetPath);
354
+ setControllerContext(instance, options.controllerMethod || "", options.targetPath, { prefix: options.controllerPrefix });
351
355
  ci?.hook(options.handlerType, "Controller:registered");
352
356
  }
353
357
  interceptorHandler = options.getIterceptorHandler();
@@ -1489,6 +1493,8 @@ function _define_property(obj, key$2, value) {
1489
1493
  const classMeta = mate$1.read(controller);
1490
1494
  const infact = getMoostInfact();
1491
1495
  const isControllerConsructor = isConstructor$1(controller);
1496
+ const ownPrefix = typeof replaceOwnPrefix === "string" ? replaceOwnPrefix : classMeta?.controller?.prefix || "";
1497
+ const computedPrefix = `${globalPrefix}/${ownPrefix}`;
1492
1498
  const pipes = mergeSorted(this.pipes, classMeta?.pipes);
1493
1499
  let instance;
1494
1500
  const infactOpts = {
@@ -1497,7 +1503,7 @@ function _define_property(obj, key$2, value) {
1497
1503
  customData: { pipes }
1498
1504
  };
1499
1505
  if (isControllerConsructor && (classMeta?.injectable === "SINGLETON" || classMeta?.injectable === true)) await createEventContext$1({ logger: this.logger }, async () => {
1500
- setControllerContext(this, "bindController", "");
1506
+ setControllerContext(this, "bindController", "", { prefix: computedPrefix });
1501
1507
  instance = await infact.get(controller, infactOpts);
1502
1508
  });
1503
1509
  else if (!isControllerConsructor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moost",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "moost",
5
5
  "keywords": [
6
6
  "composables",
@@ -21,13 +21,8 @@
21
21
  "url": "git+https://github.com/moostjs/moostjs.git",
22
22
  "directory": "packages/moost"
23
23
  },
24
- "bin": {
25
- "moost-skill": "./scripts/setup-skills.js"
26
- },
27
24
  "files": [
28
- "dist",
29
- "skills",
30
- "scripts/setup-skills.js"
25
+ "dist"
31
26
  ],
32
27
  "type": "module",
33
28
  "sideEffects": false,
@@ -46,19 +41,18 @@
46
41
  "@prostojs/infact": "^0.4.1",
47
42
  "@prostojs/logger": "^0.4.3",
48
43
  "@prostojs/mate": "^0.4.0",
49
- "@wooksjs/event-core": "^0.7.8",
44
+ "@wooksjs/event-core": "^0.7.9",
50
45
  "hookable": "^5.5.3",
51
- "wooks": "^0.7.8"
46
+ "wooks": "^0.7.9"
52
47
  },
53
48
  "devDependencies": {
54
- "@wooksjs/event-http": "^0.7.8",
55
- "@wooksjs/http-body": "^0.7.8",
49
+ "@wooksjs/event-http": "^0.7.9",
50
+ "@wooksjs/http-body": "^0.7.9",
56
51
  "vitest": "3.2.4",
57
- "@moostjs/event-http": "^0.6.5"
52
+ "@moostjs/event-http": "^0.6.7"
58
53
  },
59
54
  "scripts": {
60
55
  "pub": "pnpm publish --access public",
61
- "test": "vitest",
62
- "setup-skills": "node ./scripts/setup-skills.js"
56
+ "test": "vitest"
63
57
  }
64
58
  }
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env node
2
- /* prettier-ignore */
3
- import fs from 'fs'
4
- import path from 'path'
5
- import os from 'os'
6
- import { fileURLToPath } from 'url'
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
-
10
- const SKILL_NAME = 'moost'
11
- const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
12
-
13
- if (!fs.existsSync(SKILL_SRC)) {
14
- console.error(`No skills found at ${SKILL_SRC}`)
15
- console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
16
- process.exit(1)
17
- }
18
-
19
- const AGENTS = {
20
- 'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
21
- 'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
22
- 'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
23
- 'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
24
- 'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
25
- }
26
-
27
- const args = process.argv.slice(2)
28
- const isGlobal = args.includes('--global') || args.includes('-g')
29
- const isPostinstall = args.includes('--postinstall')
30
- let installed = 0, skipped = 0
31
- const installedDirs = []
32
-
33
- for (const [agentName, cfg] of Object.entries(AGENTS)) {
34
- const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
35
- const agentRootDir = path.dirname(cfg.global)
36
-
37
- if (isPostinstall || isGlobal) {
38
- if (!fs.existsSync(agentRootDir)) { skipped++; continue }
39
- }
40
-
41
- const dest = path.join(targetBase, SKILL_NAME)
42
- try {
43
- fs.mkdirSync(dest, { recursive: true })
44
- fs.cpSync(SKILL_SRC, dest, { recursive: true })
45
- console.log(`[${SKILL_NAME}] installed to ${dest}`)
46
- installed++
47
- if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
48
- } catch (err) {
49
- console.warn(`[${SKILL_NAME}] failed — ${err.message}`)
50
- }
51
- }
52
-
53
- if (!isGlobal && installedDirs.length > 0) {
54
- const gitignorePath = path.join(process.cwd(), '.gitignore')
55
- let gitignoreContent = ''
56
- try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
57
- const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
58
- if (linesToAdd.length > 0) {
59
- const hasHeader = gitignoreContent.includes('# AI agent skills')
60
- const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
61
- + (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
62
- + linesToAdd.join('\n') + '\n'
63
- fs.appendFileSync(gitignorePath, block)
64
- console.log(`[${SKILL_NAME}] added entries to .gitignore`)
65
- }
66
- }
67
-
68
- if (installed === 0 && isPostinstall) {
69
- // Silence is fine — no agents present, nothing to do
70
- } else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
71
- console.log('No agent directories detected. Try --global or run without it for project-local install.')
72
- } else if (installed === 0) {
73
- console.log('Nothing installed. Run without --global to install project-locally.')
74
- } else {
75
- console.log(`Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
76
- }
@@ -1,34 +0,0 @@
1
- ---
2
- name: moost
3
- description: Use this skill when working with moost — to create a Moost application, register controllers with @Controller(), build custom event adapters implementing TMoostAdapter, use defineMoostEventHandler() to bridge event sources, define routes with decorators, configure dependency injection with @Injectable()/@Inject()/@Provide(), create interceptors with @Intercept() and InterceptorHandler, build pipe pipelines with @Pipe()/@Resolve(), access metadata with getMoostMate(), or understand the adapter pattern used by MoostHttp, MoostWs, and MoostWf.
4
- ---
5
-
6
- # moost
7
-
8
- Moost is a metadata-driven event processing framework for TypeScript. It uses decorators and a composable architecture (powered by Wooks) to handle HTTP, CLI, WebSocket, and Workflow events — or any custom event type via the adapter pattern.
9
-
10
- ## How to use this skill
11
-
12
- Read the domain file that matches the task. Do not load all files — only what you need.
13
-
14
- | Domain | File | Load when... |
15
- |--------|------|------------|
16
- | Core concepts & setup | [core.md](core.md) | Starting a new project, understanding the Moost class, registering controllers, configuring adapters |
17
- | Custom adapters | [custom-adapters.md](custom-adapters.md) | Building a new event adapter implementing TMoostAdapter, using defineMoostEventHandler, understanding bindHandler lifecycle |
18
- | Decorators & metadata | [decorators.md](decorators.md) | Creating or using decorators, reading/writing metadata with getMoostMate(), defining handler types |
19
- | Dependency injection | [di.md](di.md) | Configuring DI scopes, @Injectable, @Inject, @Provide, @Circular, createProvideRegistry |
20
- | Interceptors | [interceptors.md](interceptors.md) | Creating interceptors, understanding priority levels, @Intercept, InterceptorHandler lifecycle |
21
- | Pipes & resolvers | [pipes.md](pipes.md) | Building pipes, @Pipe, @Resolve, validation/transform stages, argument resolution |
22
-
23
- ## Quick reference
24
-
25
- ```ts
26
- import { Moost, Controller, Param, Intercept, Injectable, Resolve } from 'moost'
27
- import { defineMoostEventHandler, getMoostMate, createProvideRegistry } from 'moost'
28
- import type { TMoostAdapter, TMoostAdapterOptions } from 'moost'
29
-
30
- const app = new Moost()
31
- app.adapter(myAdapter) // attach event adapter
32
- app.registerControllers(MyCtrl) // register controllers
33
- await app.init() // bind all handlers, call adapter.onInit()
34
- ```
@@ -1,174 +0,0 @@
1
- # Core Concepts & Setup — moost
2
-
3
- > Moost application lifecycle, controller registration, adapter attachment, and the mental model for event-driven TypeScript applications.
4
-
5
- ## Concepts
6
-
7
- Moost is a **metadata-driven event processing framework**. The core mental model:
8
-
9
- 1. **Controllers** — Classes decorated with `@Controller()` that contain handler methods
10
- 2. **Adapters** — Implementations of `TMoostAdapter<H>` that bridge external event sources (HTTP, CLI, WS, etc.) to Moost's handler system
11
- 3. **Handlers** — Methods on controllers decorated with adapter-specific decorators (e.g., `@Get()`, `@Cli()`, `@Message()`) that process events
12
- 4. **Pipes** — Transform/resolve/validate method arguments before handler execution
13
- 5. **Interceptors** — Cross-cutting logic (auth, logging, error handling) wrapping handler execution
14
-
15
- Unlike NestJS, Moost has **no module abstraction** — controllers are registered directly on the Moost instance.
16
-
17
- ### Lifecycle
18
-
19
- ```
20
- app.adapter(adapter) // 1. Attach adapters
21
- app.registerControllers(Ctrl) // 2. Register controller classes
22
- await app.init() // 3. Bind all handlers + call adapter.onInit()
23
- adapter.listen(3000) // 4. Start listening (adapter-specific)
24
- ```
25
-
26
- During `init()`:
27
- 1. Adapter provide registries are merged into DI
28
- 2. All controller methods are scanned for handler metadata
29
- 3. For each handler, `adapter.bindHandler()` is called with full context
30
- 4. Each adapter's `onInit(moost)` hook fires last
31
-
32
- ## Installation
33
-
34
- ```bash
35
- npm install moost
36
- # or
37
- pnpm add moost
38
- ```
39
-
40
- ## API Reference
41
-
42
- ### `Moost` (class)
43
-
44
- The main application class. Can be extended or used directly.
45
-
46
- ```ts
47
- import { Moost } from 'moost'
48
-
49
- const app = new Moost()
50
- ```
51
-
52
- #### `app.adapter<T>(a: T): T`
53
-
54
- Attaches an event adapter. Returns the adapter for chaining.
55
-
56
- ```ts
57
- const http = app.adapter(new MoostHttp())
58
- http.listen(3000)
59
- ```
60
-
61
- #### `app.registerControllers(...controllers)`
62
-
63
- Registers one or more controller classes or instances.
64
-
65
- ```ts
66
- app.registerControllers(UserController, OrderController)
67
- ```
68
-
69
- #### `app.init(): Promise<void>`
70
-
71
- Initializes the application: binds all controllers to all adapters, then calls each adapter's `onInit()`.
72
-
73
- #### `app.interceptor(...interceptors)`
74
-
75
- Registers global interceptors that apply to all handlers.
76
-
77
- ```ts
78
- app.interceptor(myLoggingInterceptor)
79
- ```
80
-
81
- #### `app.getLogger(topic?: string)`
82
-
83
- Returns a logger instance, optionally scoped to a topic.
84
-
85
- #### `app.getGlobalInterceptorHandler()`
86
-
87
- Returns an `InterceptorHandler` for global interceptors. Used by adapters for 404/not-found handlers.
88
-
89
- ### `@Controller(prefix?: string)`
90
-
91
- Class decorator marking a class as a controller. The optional prefix is prepended to all handler paths.
92
-
93
- ```ts
94
- @Controller('users')
95
- class UserController {
96
- @Get(':id') // resolves to /users/:id
97
- getUser(@Param('id') id: string) { return { id } }
98
- }
99
- ```
100
-
101
- ### `@ImportController(ctrl, opts?)`
102
-
103
- Registers a nested controller within another controller, inheriting its prefix.
104
-
105
- ```ts
106
- @Controller('api')
107
- @ImportController(UserController)
108
- @ImportController(OrderController)
109
- class ApiController {}
110
- ```
111
-
112
- ## Common Patterns
113
-
114
- ### Pattern: Extending Moost
115
-
116
- Create an application class extending Moost. Handler methods can live directly on it.
117
-
118
- ```ts
119
- class MyApp extends Moost {
120
- @Get('health')
121
- health() { return { ok: true } }
122
- }
123
-
124
- const app = new MyApp()
125
- const http = app.adapter(new MoostHttp())
126
- http.listen(3000)
127
- app.init()
128
- ```
129
-
130
- ### Pattern: Multi-adapter application
131
-
132
- Attach multiple adapters to handle different event types simultaneously.
133
-
134
- ```ts
135
- const app = new Moost()
136
- const http = app.adapter(new MoostHttp())
137
- const ws = app.adapter(new MoostWs({ httpApp: http.getHttpApp() }))
138
- const cli = app.adapter(new MoostCli())
139
-
140
- app.registerControllers(HttpController, WsController, CliController)
141
- await app.init()
142
- http.listen(3000)
143
- ```
144
-
145
- Each adapter only processes handlers matching its own type (e.g., HTTP adapter ignores `@Cli()` handlers).
146
-
147
- ### Pattern: Controller-only registration
148
-
149
- Controllers are not tied to a specific adapter. The same controller class can contain handlers for multiple adapters.
150
-
151
- ```ts
152
- @Controller('app')
153
- class AppController {
154
- @Get('status') // handled by HTTP adapter
155
- httpStatus() { return { up: true } }
156
-
157
- @Cli('status') // handled by CLI adapter
158
- cliStatus() { console.log('up') }
159
- }
160
- ```
161
-
162
- ## Best Practices
163
-
164
- - Call `app.init()` **after** registering all controllers and adapters
165
- - Start listening (`adapter.listen()`) before or after `init()` — the adapter buffers requests until handlers are bound
166
- - Use `@Controller(prefix)` to namespace related handlers
167
- - Keep controllers focused — one per feature area
168
- - Extend `Moost` for simple apps; use `registerControllers()` for larger projects
169
-
170
- ## Gotchas
171
-
172
- - `app.init()` must be `await`ed — it's async because singleton controllers may need async DI resolution
173
- - Handler methods are bound to **all** attached adapters, but each adapter filters by handler `type` — a `@Get()` decorator is ignored by the CLI adapter
174
- - The order of `adapter()` calls doesn't matter for functionality but affects log output order