> ## Documentation Index
> Fetch the complete documentation index at: https://pulian.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# @hile/model

> Put reusable business logic behind typed models with services and pipeline middleware.

# @hile/model

Put reusable business logic behind typed models with services and pipeline middleware.

## Choose This Package When

| User asks for                         | Use           | Also read                   |
| ------------------------------------- | ------------- | --------------------------- |
| Put business logic somewhere reusable | `@hile/model` | `packages/model-context.md` |

## Use When

Use `@hile/model` for reusable business logic and `@hile/context` for request/work-unit context that should flow through async calls, micro messages, and queue jobs.

## Do Not Use When

* Do not put business logic only in controllers when it will be reused by jobs, pages, or message handlers.
* Do not add fixed business fields to `@hile/context`; each app owns its context shape.

## Install

```bash theme={null}
pnpm add @hile/model @hile/context
```

## Imports

```ts theme={null}
import { defineModel, loadModel } from '@hile/model'
import { contextHttp, contextModel, getContext, requireContext, runWithContext } from '@hile/context'
```

## Copy-Paste Example

```ts theme={null}
import { defineModel } from '@hile/model'

export default defineModel(async (input: { name: string }) => {
  return { greeting: `Hello ${input.name}` }
})
```

Use it:

```ts theme={null}
const result = await loadModel(greetModel, { name: 'Ada' })
```

## More Examples

```ts theme={null}
import { defineModel } from '@hile/model'
import typeormService from '@hile/typeorm'

export const findUser = defineModel({
  services: [typeormService] as const,
  pipelines: [
    async (ctx, next) => {
      if (!ctx.args.userId) throw new Error('userId is required')
      await next()
    },
  ],
  async main([ds], input: { userId: string }) {
    return ds.getRepository(User).findOneBy({ id: input.userId })
  },
})
```

Context in HTTP:

```ts theme={null}
http.use(contextHttp({
  read: (ctx) => ({ requestId: String(ctx.headers['x-request-id'] ?? crypto.randomUUID()) }),
  write: (context, ctx) => ctx.set('x-request-id', String(context.requestId ?? '')),
}))
```

Context in model pipeline:

```ts theme={null}
const withTenant = contextModel<{ tenantId: string }>({
  read: (input) => ({ tenantId: input.tenantId }),
})
```

## Runtime And Lifecycle Notes

* `loadModel(model, input)` rejects if the first argument was not created by `defineModel()`.
* Model input must be an object.
* Services are loaded in the order of the `services` tuple.
* Pipeline middleware follows Koa-style `await next()`.
* The terminal model result is stored in `ctx.state.result` and returned by `loadModel()`.
* `runWithContext()` merges with parent context by default; pass `{ merge: false }` to replace.
* `getContext()` returns a frozen shallow snapshot.
* `requireContext(keys)` throws when selected keys are missing.

## Anti-Patterns

* Passing primitives to `loadModel()`.
* Mutating context snapshots.
* Logging whole context objects by default.
* Assuming context propagation changes business payloads; it should stay in metadata or async storage.

## Verification Checklist

* Models export `defineModel(...)` results.
* Controllers and pages call `loadModel(model, objectInput)`.
* Pipeline middleware writes derived state to `ctx.state`.
* Context keys are app-defined and JSON-serializable when crossing process boundaries.

## Related Recipes

### HTTP API + Model + TypeORM

## Complete Example

Controller:

```ts theme={null}
// src/controllers/users/[id].controller.ts
import { defineController } from '@hile/http'
import { loadModel } from '@hile/model'
import { getUser } from '../../models/users/get-user.model'

export default defineController('GET', async (ctx) => {
  return loadModel(getUser, { userId: String(ctx.params.id) })
})
```

Model:

```ts theme={null}
// src/models/users/get-user.model.ts
import { defineModel } from '@hile/model'
import typeormService from '@hile/typeorm'
import { User } from '../../entities/user.entity'

export const getUser = defineModel({
  services: [typeormService] as const,
  async main([ds], input: { userId: string }) {
    const user = await ds.getRepository(User).findOneBy({ id: input.userId })
    if (!user) return { found: false }
    return { found: true, user }
  },
})
```

Boot file:

```ts theme={null}
// src/services/http.boot.ts
import { defineService } from '@hile/core'
import { Http } from '@hile/http'

export default defineService('http', async (shutdown) => {
  const http = new Http({ port: Number(process.env.HTTP_PORT ?? 3000) })
  await http.load(new URL('../controllers', import.meta.url).pathname)
  const close = await http.listen()
  shutdown(close)
  return http
})
```

Package config:

```json theme={null}
{
  "type": "module",
  "scripts": {
    "dev": "hile start --dev --env-file .env",
    "start": "hile start --env-file .env.prod"
  },
  "hile": {
    "auto_load_packages": ["@hile/typeorm"]
  }
}
```

## Package-Local AI Guide

This package also ships `AI.md` in npm so agents can read accurate examples after installation.
