hile-core
Original:🇨🇳 Chinese
Translated
2 scriptsChecked / no sensitive code detected
Code generation and usage guidelines for the @hile/core asynchronous service container. Apply this when defining or loading Hile services, configuring lifecycle shutdown logic, or when users inquire about @hile/core, defineService, loadService, or service container patterns.
1installs
Sourcecevio/hile
Added on
NPX Install
npx skill4agent add cevio/hile hile-coreTags
Translated version includes tags in frontmatterSKILL.md Content (Chinese)
View Translation Comparison →Hile
Hile is a lightweight asynchronous service container. This document is a code generation specification for AI coding models and human developers. After reading it, you should be able to correctly use this library to write code that complies with architectural rules.
1. Architecture Overview
The core of Hile is the (service container). All services must go through two steps: Define → Load before they can be used. The container guarantees:
Container- Each service function is executed only once (Singleton)
- Concurrent requests for the same service are automatically merged (No duplicate execution)
- When a service fails to start, registered cleanup callbacks are automatically executed (Destruction mechanism)
The module exports a global container instance by default, and provides two convenient functions: / .
defineServiceloadService2. Type Signatures
When generating code, you must strictly follow the following types:
typescript
// 销毁回调:无参数,可返回 Promise
type ServiceCutDownFunction = () => unknown | Promise<unknown>;
// 销毁注册器:在服务函数内部调用,将清理回调注册到容器
type ServiceCutDownHandler = (fn: ServiceCutDownFunction) => void;
// 服务函数:第一个参数固定为销毁注册器,返回值为同步值或 Promise
type ServiceFunction<R> = (shutdown: ServiceCutDownHandler) => R | Promise<R>;
// 内部服务标识(Symbol,不可外部构造)
const sericeFlag = Symbol('service');
// 服务注册信息:由 defineService/register 返回,作为 loadService/resolve 的入参
interface ServiceRegisterProps<R> {
id: number;
fn: ServiceFunction<R>;
flag: typeof sericeFlag;
}3. Code Generation Templates
3.1 Define Service (Mandatory Pattern)
Template:
typescript
import { defineService } from '@hile/core'
export const xxxService = defineService(async (shutdown) => {
// 1. 初始化资源
const resource = await createResource()
// 2. 注册销毁回调(每创建一个资源就注册一个对应的清理)
shutdown(() => resource.close())
// 3. 返回服务实例
return resource
})Rules:
- The first parameter of the service function must be named , with the type
shutdownServiceCutDownHandler - The service function should be declared with (to ensure the destruction mechanism is triggered correctly on failure)
async - The return value of must be assigned to a module-level constant and
defineServiceedexport - The constant name must end with (e.g.,
Service,databaseService)cacheService - Each service definition should be placed in an independent file
3.2 Load Service
Template:
typescript
import { loadService } from '@hile/core'
import { databaseService } from './services/database'
const db = await loadService(databaseService)Rules:
- Always use to get service instances, never call the service function directly
loadService() - returns a
loadService, must usePromiseawait - You can call multiple times anywhere, the container guarantees it will only execute once
loadService(the same service)
3.3 Inter-service Dependencies
When one service depends on another, load the dependency via inside the service function:
loadServicetypescript
import { defineService, loadService } from '@hile/core'
import { databaseService } from './database'
import { configService } from './config'
export const userService = defineService(async (shutdown) => {
// 加载依赖的服务(若已完成则直接返回缓存,否则等待)
const config = await loadService(configService)
const db = await loadService(databaseService)
const repo = new UserRepository(db, config)
shutdown(() => repo.dispose())
return repo
})Rules:
- Import the of the dependent service via
ServiceRegisterProps, then load it withimportinside the function bodyloadService - Do not cache the result of in a module-scoped variable
loadService - Do not call outside the service function to get another service and pass it in — load it inside the service function instead
loadService
3.4 Register Shutdown Callback
Template:
typescript
export const connectionService = defineService(async (shutdown) => {
const primary = await connectPrimary()
shutdown(() => primary.disconnect()) // 注册第 1 个 → 最后执行
const replica = await connectReplica()
shutdown(() => replica.disconnect()) // 注册第 2 个
const cache = await initCache()
shutdown(() => cache.flush()) // 注册第 3 个 → 最先执行
return { primary, replica, cache }
})Rules:
- Immediately after initializing a resource that needs cleanup, call to register the corresponding cleanup function
shutdown() - Do not put all cleanup logic in a single shutdown call, each resource corresponds to one shutdown call
- Destruction functions are executed in reverse order (LIFO): later registered functions execute first, earlier registered ones execute last
- Destruction functions can be , the container will
asyncthem sequentiallyawait - Passing the same function reference to multiple times will only register it once
shutdown()
3.5 Manually Destroy All Services
When the application exits or needs to shut down gracefully, call to manually trigger all registered destruction callbacks:
container.shutdown()Template:
typescript
import container from '@hile/core'
process.on('SIGTERM', async () => {
await container.shutdown()
process.exit(0)
})Rules:
- returns a
shutdown(), must usePromiseawait - Destruction is executed in reverse order of service registration: later registered services are destroyed first
- The destruction callbacks inside each service are also executed in reverse order (LIFO)
- After completes, calling it again will not execute repeatedly (the destruction queue has been cleared)
shutdown()
4. Mandatory Rules (Must Follow When Generating Code)
| # | Rule | Reason |
|---|---|---|
| 1 | Service functions must be declared with | Synchronous |
| 2 | The first parameter of the service function must be | This is the destruction registrar injected by the container, declare it even if not used |
| 3 | The result of | Services are deduplicated based on function references, so references must be stable |
| 4 | Do not directly write anonymous functions inside | A new reference is created each call, leading to duplicate registration |
| 5 | Do not manually construct | Must obtain via |
| 6 | Do not cache the result of | The service may not be initialized yet, should |
| 7 | Immediately register | Ensure that resources created during initialization can be cleaned up correctly if initialization fails midway |
| 8 | Define only one service per file | Keep service responsibilities single and dependencies clear |
5. Complete Example: Project Structure
src/
├── services/
│ ├── config.ts # 配置服务
│ ├── database.ts # 数据库服务(依赖 config)
│ ├── cache.ts # 缓存服务(依赖 config)
│ └── user.ts # 用户服务(依赖 database, cache)
└── main.ts # 入口services/config.ts
typescript
import { defineService } from '@hile/core'
interface AppConfig {
dbUrl: string
cacheHost: string
}
export const configService = defineService(async (shutdown) => {
const config: AppConfig = {
dbUrl: process.env.DB_URL ?? 'postgres://localhost:5432/app',
cacheHost: process.env.CACHE_HOST ?? 'localhost',
}
return config
})services/database.ts
typescript
import { defineService, loadService } from '@hile/core'
import { configService } from './config'
export const databaseService = defineService(async (shutdown) => {
const config = await loadService(configService)
const pool = await createPool(config.dbUrl)
shutdown(() => pool.end())
return pool
})services/cache.ts
typescript
import { defineService, loadService } from '@hile/core'
import { configService } from './config'
export const cacheService = defineService(async (shutdown) => {
const config = await loadService(configService)
const client = await createRedisClient(config.cacheHost)
shutdown(() => client.quit())
return client
})services/user.ts
typescript
import { defineService, loadService } from '@hile/core'
import { databaseService } from './database'
import { cacheService } from './cache'
interface User {
id: number
name: string
}
export const userService = defineService(async (shutdown) => {
const db = await loadService(databaseService)
const cache = await loadService(cacheService)
return {
async getById(id: number): Promise<User> {
const cached = await cache.get(`user:${id}`)
if (cached) return JSON.parse(cached)
const user = await db.query('SELECT * FROM users WHERE id = $1', [id])
await cache.set(`user:${id}`, JSON.stringify(user))
return user
}
}
})main.ts
typescript
import { loadService } from '@hile/core'
import { userService } from './services/user'
async function main() {
const users = await loadService(userService)
const user = await users.getById(1)
console.log(user)
}
main()6. Anti-patterns (Must Avoid When Generating Code)
6.1 Do Not Use Synchronous Throw
typescript
// ✗ 错误:同步 throw 不会触发 shutdown 销毁机制
export const badService = defineService((shutdown) => {
const res = createResourceSync()
shutdown(() => res.close())
if (!res.isValid()) throw new Error('invalid')
return res
})
// ✓ 正确:使用 async 函数
export const goodService = defineService(async (shutdown) => {
const res = await createResource()
shutdown(() => res.close())
if (!res.isValid()) throw new Error('invalid')
return res
})6.2 Do Not Cache Service Results at Module Top Level
typescript
// ✗ 错误:模块加载时服务可能尚未就绪
const db = await loadService(databaseService)
export function query(sql: string) {
return db.query(sql)
}
// ✓ 正确:每次在函数内部加载
export async function query(sql: string) {
const db = await loadService(databaseService)
return db.query(sql)
}6.3 Do Not Inline Define Service Functions
typescript
// ✗ 错误:每次调用 getService() 都创建新函数引用,无法去重
function getService() {
return defineService(async (shutdown) => { ... })
}
// ✓ 正确:模块级常量
export const myService = defineService(async (shutdown) => { ... })6.4 Do Not Delay Registering Shutdown
typescript
// ✗ 错误:如果 doSomething 抛错,resourceA 不会被清理
export const badService = defineService(async (shutdown) => {
const a = await createResourceA()
const b = await doSomething(a)
shutdown(() => a.close()) // 太晚了!
shutdown(() => b.close())
return b
})
// ✓ 正确:创建后立即注册
export const goodService = defineService(async (shutdown) => {
const a = await createResourceA()
shutdown(() => a.close()) // 立即注册
const b = await doSomething(a)
shutdown(() => b.close())
return b
})7. API Quick Reference
Convenience Functions (Operate on Default Container)
| Function | Signature | Description |
|---|---|---|
| | Register a service, return registration information |
| | Load a service, return service instance |
| | Determine if an object is valid service registration information (verified via internal Symbol) |
Container Class (For Creating Isolated Containers)
| Method | Signature | Description |
|---|---|---|
| | Register a service. The same function reference is only registered once |
| | Resolve a service (see state machine below) |
| | Check if a service has been registered |
| | Check if a service has been run |
| | Query service ID by function reference |
| | Query runtime metadata by service ID |
| | Manually destroy all services, execute all destruction callbacks in reverse order of service registration |
resolve State Machine
resolve(props)
│
├─ No record in paddings → First run
│ → run(id, fn, callback)
│ → Create Paddings { status: 0 }
│ ├─ fn succeeds → status = 1, value = return value, notify all waiters in queue
│ └─ fn fails → status = -1, error = error
│ → First execute shutdown callbacks in reverse order
│ → Then notify all waiters in queue
│
├─ status = 0 (Running) → Join queue and wait
├─ status = 1 (Succeeded) → Directly resolve(cached value)
└─ status = -1 (Failed) → Directly reject(cached error)8. Internal Mechanisms (For Understanding, Not For Direct Calling)
Data Structures
| Field | Type | Description |
|---|---|---|
| | Function reference → Service ID |
| | Service ID → Runtime status |
| | Service ID → Destruction callback array |
| | Queue of service IDs that have registered destruction callbacks (ordered) |
Paddings Structure
| Field | Type | Description |
|---|---|---|
| | -1 Failed / 0 Running / 1 Succeeded |
| | Return value on success |
| | Error on failure |
| | Promise callbacks waiting |
Destruction Execution Order
- Inside a single service: Destruction callbacks are executed sequentially in reverse registration order (LIFO) with
await - Global destruction: Destroy services in reverse order of their registration
- Trigger timing:
- Automatically trigger the service's destruction callbacks when the service function fails asynchronously (rejects)
- Trigger destruction callbacks of all registered services when is called manually (in reverse order of service registration)
container.shutdown()
Service Identification Mechanism (sericeFlag)
The container internally uses as the identifier for service registration information. The object returned by carries the field. determines if an object is valid service registration information by checking and the types of and . Since Symbols are unforgeable, external parties cannot manually construct an object that passes the check.
const sericeFlag = Symbol('service')registerflag: sericeFlagisService()flag === sericeFlagidfnisServiceFunction Deduplication Mechanism
The container compares function references via . Two functions are considered different services even if their code is identical, as long as their references are different. Therefore, services must be defined as module-level constants.
===9. Development
bash
pnpm install
pnpm build # 编译
pnpm dev # 监听模式
pnpm test # 运行测试Tech Stack: TypeScript, Vitest
License
MIT