{"_id":"@canlooks/roost","name":"@canlooks/roost","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@canlooks/roost","version":"0.0.1","author":{"name":"C.CanLiang","email":"canlooks@gmail.com"},"description":"A backend micro service framework","keywords":["micro service"],"main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"index.d.ts","exports":{".":{"types":"./index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+https://github.com/canlooks/roost.git"},"homepage":"https://github.com/canlooks/roost","bugs":{"url":"https://github.com/canlooks/roost/issues","email":"canlooks@gmail.com"},"license":"MIT","scripts":{"clean":"npx shx rm -rf dist","build":"tsc -m esnext --outDir dist/esm & tsc -m commonjs --outDir dist/cjs","build:alias":"tsc-alias --outDir dist/esm","rebuild":"npm run clean && npm run build && npm run build:alias","test":"vitest run"},"dependencies":{"ajv":"^8.20.0","tslib":"^2.8.1"},"devDependencies":{"@types/node":"^25.9.1","tsc-alias":"^1.8.17","typescript":"^6.0.3","vitest":"^4.1.7"},"_id":"@canlooks/roost@0.0.1","gitHead":"505c5898b5d648b7d7d7fd033290357f58536119","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-FNogZ+ilgWrTGcL5uwODfuwM7q2HHLsQ4KMPL9v7GlIM5V8S18kZE+KykYWK5xx2eJCO24CEfvfsvEP2IFQgWw==","shasum":"12bf8a7a655f9478b1bf8c10d0d4fd2db5e6b696","tarball":"https://registry.npmjs.org/@canlooks/roost/-/roost-0.0.1.tgz","fileCount":34,"unpackedSize":101827,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDg/w0+TcQzP2VyDh2G/M+bh/A0ewGh06po4EzhCfk7SwIgEwPwCRbUwk2hvTSWNEYZRz65QjFuZkgFNEpjnZW3nMM="}]},"_npmUser":{"name":"canlooks","email":"364021661@qq.com"},"directories":{},"maintainers":[{"name":"canlooks","email":"364021661@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/roost_0.0.1_1781147748006_0.30751558473723195"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-11T03:15:47.811Z","0.0.1":"2026-06-11T03:15:48.191Z","modified":"2026-06-11T03:15:48.437Z"},"maintainers":[{"name":"canlooks","email":"364021661@qq.com"}],"description":"A backend micro service framework","homepage":"https://github.com/canlooks/roost","keywords":["micro service"],"repository":{"type":"git","url":"git+https://github.com/canlooks/roost.git"},"author":{"name":"C.CanLiang","email":"canlooks@gmail.com"},"bugs":{"url":"https://github.com/canlooks/roost/issues","email":"canlooks@gmail.com"},"license":"MIT","readme":"# @canlooks/roost\n\nA lightweight, decorator-driven microservice framework for Node.js. Built on dependency injection, declarative routing,\nand schema-based validation — designed to minimize boilerplate and maximize clarity.\n\n## Installation\n\n```bash\nnpm install @canlooks/roost\n```\n\nRequires TypeScript with `experimentalDecorators` and `emitDecoratorMetadata` enabled in `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true\n  }\n}\n```\n\n## Quick Start\n\n```typescript\nimport {Roost, Controller, Action} from '@canlooks/roost'\n\n@Controller('api')\nclass ApiController {\n    @Action('hello')\n    hello(name: string) {\n        return `Hello, ${name}!`\n    }\n}\n\nconst app = await Roost.create({\n    anonymous: [ApiController]\n})\n\nconst [result] = await app.invoke('api/hello', 'World')\nconsole.log(result) // \"Hello, World!\"\n```\n\n---\n\n## Core Concepts\n\n### Application Bootstrap\n\n`Roost.create()` is the sole entry point. It wires the IoC container, plugin pipeline, and route registry.\n\n```typescript\nconst app = await Roost.create({\n    named: {cache: CacheService},     // named components\n    anonymous: [UserController],         // auto-registered components\n    plugins: [loggingPlugin],            // lifecycle plugins\n    dtoOptions: {allErrors: true}      // ajv options for validation\n})\n```\n\n### Components\n\nA **component** is any class registered with the framework. Components go through a lifecycle pipeline during\nregistration:\n\n```\ninstantiate → @Config → @Expect → @Controller/@Action → @Module → @Inject → @Initialize\n```\n\n---\n\n## Routing\n\nRoutes are declared with `@Controller` and `@Action` decorators. Three routing modes are supported.\n\n### Path Routing\n\n```typescript\n\n@Controller('users')\nclass UserController {\n    @Action('list')\n    list() {\n        return ['alice', 'bob']\n    }\n\n    @Action(':id')\n    getById(@Params() params: Params) {\n        return {id: params.id}\n    }\n}\n\nconst app = await Roost.create({anonymous: [UserController]})\nawait app.invoke('users/list')     // → ['alice', 'bob']\nawait app.invoke('users/42')       // → { id: '42' }\n```\n\n**Path wildcards:**\n\n| Pattern | Matches                                    |\n|---------|--------------------------------------------|\n| `*`     | Exactly one segment                        |\n| `**`    | Zero or more segments                      |\n| `:name` | One segment, captured as a named parameter |\n\n### Pattern Routing\n\n```typescript\n\n@Controller({type: 'rpc'})\nclass RpcController {\n    @Action({method: 'add'})\n    add(a: number, b: number) {\n        return a + b\n    }\n}\n\nawait app.invoke({type: 'rpc', method: 'add'}, 3, 4) // → [7]\n```\n\n### Regular Expression Routing\n\n```typescript\n\n@Controller()\nclass ApiController {\n    @Action(/^\\/api\\/v\\d+\\/health$/)\n    health() {\n        return {status: 'ok'}\n    }\n}\n```\n\n### Route Invocation\n\n```typescript\n// By path (string)\nconst results = await app.invoke('users/42', ...args)\n\n// By pattern (object)\nconst results = await app.invoke({type: 'rpc', method: 'add'}, ...args)\n```\n\n---\n\n## Dependency Injection\n\n`@Inject` supports four injection strategies:\n\n```typescript\nclass UserService {\n    // Inject the app instance itself\n    @Inject(app)\n    app!: Roost\n\n    // Inject by component class (auto-registered + singleton)\n    @Inject(CacheService)\n    cache!: CacheService\n\n    // Inject by registered name\n    @Inject('logger')\n    logger!: Logger\n\n    // Lazy-load via dynamic import\n    @Inject(() => import('./heavy-module'))\n    heavy!: HeavyModule\n}\n```\n\n---\n\n## Modules\n\n`@Module` declares sub-component dependencies. The framework recursively registers all declared components.\n\n```typescript\nclass AuthService {\n}\n\nclass Logger {\n}\n\n// Anonymous array form\n@Module([AuthService, Logger])\nclass AppModule {\n}\n\n// Named object form\n@Module({auth: AuthService, log: Logger})\nclass AppModule {\n}\n```\n\n---\n\n## Configuration\n\n`@Config` injects default property values at registration time.\n\n```typescript\n\n@Config({timeout: 3000, retries: 3})\nclass ApiClient {\n    timeout!: number\n    retries!: number\n}\n\n// Or as a higher-order function:\nConfig(ApiClient, {timeout: 5000})\n```\n\nValues are deeply cloned per instance via `structuredClone`, so object configs are safe from cross-instance mutation.\n\n---\n\n## Lifecycle Hooks\n\n`@Initialize` marks methods that run after all dependencies are injected.\n\n```typescript\nclass DatabaseService {\n    @Inject('config')\n    config!: Config\n\n    @Initialize()\n    async connect() {\n        await this.driver.connect(this.config.url)\n    }\n}\n```\n\nMultiple `@Initialize` methods run in parallel via `Promise.all`.\n\n---\n\n## DTO Validation\n\nBuilt on [ajv](https://ajv.js.org/). Define schemas with decorators and validate data at registration time or invocation\ntime.\n\n### Schema Definition\n\n```typescript\n\n@DTO()\nclass CreateUserDTO {\n    @Str({minLength: 2, maxLength: 50})\n    name!: string\n\n    @Num({minimum: 0, maximum: 150})\n    age!: number\n\n    @Bool()\n    active!: boolean\n\n    @Required()\n    email!: string\n\n    @Nullable()\n    nickname!: string | null\n\n    @Enum('admin', 'user', 'guest')\n    role!: string\n}\n```\n\n### Nested Objects\n\n```typescript\n\n@DTO()\nclass AddressDTO {\n    @Str() street!: string\n    @Str() city!: string\n}\n\n@DTO()\nclass UserDTO {\n    @Obj(AddressDTO)\n    address!: AddressDTO\n}\n```\n\n### Arrays\n\n```typescript\n\n@DTO()\nclass OrderDTO {\n    @Arr({type: 'string'})\n    tags!: string[]\n\n    @Arr({items: {type: 'number'}, minItems: 1, uniqueItems: true})\n    itemIds!: number[]\n}\n```\n\n### Property Validation (`@Expect`)\n\nValidates component properties at registration time.\n\n```typescript\n\n@Controller('users')\nclass UserController {\n    @Expect(CreateUserDTO)\n    body!: any\n\n    @Expect(CreateUserDTO, {required: false})\n    optionalBody!: any\n\n    @Expect(CreateUserDTO, {nullable: true})\n    nullableBody!: any | null\n}\n```\n\nShorthand:\n\n```typescript\n@Expect.Optional(CreateUserDTO)   // → { required: false }\n@Expect.Nullable(CreateUserDTO)   // → { nullable: true }\n```\n\n### Parameter Validation (`@Verify`)\n\nValidates method arguments at invocation time.\n\n```typescript\n\n@Controller('math')\nclass MathController {\n    @Action('square')\n    square(@Verify({type: 'number', minimum: 0}) n: number) {\n        return n * n\n    }\n}\n```\n\n### Composable Schemas\n\nReuse schema definitions across `@Expect`, `@Verify`, and `@Obj`:\n\n```typescript\nconst NumberSchema = {type: 'number', minimum: 0} as const\nconst StringSchema = {type: 'string', minLength: 1} as const\n\n@DTO()\nclass ItemDTO {\n    @Obj(NumberSchema)\n    price!: number\n\n    @Obj(StringSchema)\n    label!: string\n}\n```\n\n---\n\n## Plugins\n\nPlugins hook into the application lifecycle.\n\n```typescript\nconst loggingPlugin: Plugin = {\n    name: 'logger',\n    onCreate(app) {\n        console.log('[roost] app created')\n    },\n    onStaticInjected(app) {\n        console.log('[roost] static injection done')\n    },\n    onReady(app) {\n        console.log('[roost] app ready')\n    },\n    onError(app, err) {\n        console.error('[roost] error:', err.message)\n    }\n}\n\nconst app = await Roost.create({\n    plugins: [loggingPlugin]\n})\n```\n\nLifecycle order:\n\n```\nonCreate → register components → onStaticInjected → await registration → onReady\n```\n\nErrors during registration trigger `onError` before propagating.\n\n---\n\n## Container (IoC)\n\nThe container stores singleton component instances. Components are lazily instantiated on first access.\n\n```typescript\n// Register\nawait app.register.registerComponent(MyService)\nawait app.register.registerComponent('cache', RedisCache)\n\n// Retrieve\nconst service = await app.container.get(MyService)\nconst cache = await app.container.get('cache')\n```\n\n---\n\n## Complete Example\n\n```typescript\nimport {\n    Roost,\n    Controller,\n    Action,\n    Module,\n    Inject,\n    Config,\n    Initialize,\n    Params,\n    DTO,\n    Str,\n    Required,\n    Verify\n} from '@canlooks/roost'\n\n// ── DTO ──────────────────────────────────────────\n@DTO()\nclass LoginDTO {\n    @Str({minLength: 3})\n    username!: string\n\n    @Str({minLength: 6})\n    @Required()\n    password!: string\n}\n\n// ── Services ─────────────────────────────────────\nclass AuthService {\n    async login(username: string, password: string) {\n        return {token: 'jwt-token', user: username}\n    }\n}\n\n// ── Controller ───────────────────────────────────\n@Controller('auth')\nclass AuthController {\n    @Inject(AuthService)\n    auth!: AuthService\n\n    @Action('login')\n    async login(\n        @Verify(LoginDTO) credentials: any,\n        @Params() params: Params,\n    ) {\n        return this.auth.login(credentials.username, credentials.password)\n    }\n}\n\n// ── Plugin ───────────────────────────────────────\nconst perfPlugin: Plugin = {\n    name: 'perf',\n    onReady() {\n        console.log('ready in', performance.now().toFixed(0), 'ms')\n    }\n}\n\n// ── Bootstrap ────────────────────────────────────\nconst app = await Roost.create({\n    anonymous: [AuthService, AuthController],\n    plugins: [perfPlugin]\n})\n\nconst [result] = await app.invoke('/auth/login', {\n    username: 'alice',\n    password: 'secret123'\n})\nconsole.log(result) // { token: 'jwt-token', user: 'alice' }\n```\n\n---\n\n## API Reference\n\n### `Roost`\n\n| Member                         | Description               |\n|--------------------------------|---------------------------|\n| `static create(options)`       | Bootstrap the application |\n| `container: Container`         | IoC container             |\n| `register: Register`           | Component registration    |\n| `invoker: Invoker`             | Route invocation          |\n| `invoke: Invoker.invoke`       | Route invocation          |\n| `pluginManager: PluginManager` | Plugin event emitter      |\n| `dto: DTOManager`              | DTO validation manager    |\n| `pathMap`                      | Registered path routes    |\n| `patternMap`                   | Registered pattern routes |\n| `regularMap`                   | Registered regex routes   |\n\n### `CreateOptions`\n\n| Property     | Type                            | Description                |\n|--------------|---------------------------------|----------------------------|\n| `named`      | `Record<string, ComponentType>` | Named components           |\n| `anonymous`  | `ComponentType[]`               | Auto-registered components |\n| `plugins`    | `Plugin[]`                      | Lifecycle plugins          |\n| `dtoOptions` | `AjvOptions`                    | Options passed to ajv      |\n\n### Decorators\n\n| Decorator                           | Target    | Description                        |\n|-------------------------------------|-----------|------------------------------------|\n| `@Controller(path \\| pattern)`      | Class     | Declare a route controller         |\n| `@Action(path \\| pattern \\| regex)` | Method    | Declare an action handler          |\n| `@Module(components)`               | Class     | Declare sub-component dependencies |\n| `@Inject(target)`                   | Property  | Inject a dependency                |\n| `@Config(config)`                   | Class     | Inject default property values     |\n| `@Initialize()`                     | Method    | Run after dependencies injected    |\n| `@Params()`                         | Parameter | Inject path parameters             |\n| `@Option()`                         | Parameter | Inject action option               |\n| `@DTO(options?)`                    | Class     | Declare a DTO schema class         |\n| `@Obj(schema)`                      | Property  | Object/nested schema type          |\n| `@Num(options?)`                    | Property  | Number type                        |\n| `@Int(options?)`                    | Property  | Integer type                       |\n| `@Str(options?)`                    | Property  | String type                        |\n| `@Bool()`                           | Property  | Boolean type                       |\n| `@Arr(items \\| options)`            | Property  | Array type                         |\n| `@Required()`                       | Property  | Mark field as required             |\n| `@Nullable()`                       | Property  | Allow null values                  |\n| `@Enum(...values)`                  | Property  | Enum constraint                    |\n| `@Const(value)`                     | Property  | Constant value constraint          |\n| `@Default(value)`                   | Property  | Default value                      |\n| `@Expect(dto, options?)`            | Property  | Validate property against DTO      |\n| `@Verify(dto, options?)`            | Parameter | Validate argument against DTO      |\n\n### `Plugin`\n\n```typescript\ninterface Plugin {\n    name: string\n    onCreate?(app: Roost): any\n    onStaticInjected?(app: Roost): any\n    onReady?(app: Roost): any\n    onError?(app: Roost, err: any): any\n}\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-a4c78d4010000c710f23ee444ad2a2e7"}