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
Added on

NPX Install

npx skill4agent add cevio/hile hile-core

SKILL.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
Container
(service container). All services must go through two steps: Define → Load before they can be used. The container guarantees:
  • 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:
defineService
/
loadService
.

2. 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
    shutdown
    , with the type
    ServiceCutDownHandler
  • The service function should be declared with
    async
    (to ensure the destruction mechanism is triggered correctly on failure)
  • The return value of
    defineService
    must be assigned to a module-level constant and
    export
    ed
  • The constant name must end with
    Service
    (e.g.,
    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
    loadService()
    to get service instances, never call the service function directly
  • loadService
    returns a
    Promise
    , must use
    await
  • You can call
    loadService(the same service)
    multiple times anywhere, the container guarantees it will only execute once

3.3 Inter-service Dependencies

When one service depends on another, load the dependency via
loadService
inside the service function:
typescript
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
    ServiceRegisterProps
    of the dependent service via
    import
    , then load it with
    loadService
    inside the function body
  • Do not cache the result of
    loadService
    in a module-scoped variable
  • Do not call
    loadService
    outside the service function to get another service and pass it in — load it inside the service function instead

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
    shutdown()
    to register the corresponding cleanup function
  • 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
    async
    , the container will
    await
    them sequentially
  • Passing the same function reference to
    shutdown()
    multiple times will only register it once

3.5 Manually Destroy All Services

When the application exits or needs to shut down gracefully, call
container.shutdown()
to manually trigger all registered destruction callbacks:
Template:
typescript
import container from '@hile/core'

process.on('SIGTERM', async () => {
  await container.shutdown()
  process.exit(0)
})
Rules:
  • shutdown()
    returns a
    Promise
    , must use
    await
  • 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
    shutdown()
    completes, calling it again will not execute repeatedly (the destruction queue has been cleared)

4. Mandatory Rules (Must Follow When Generating Code)

#RuleReason
1Service functions must be declared with
async
Synchronous
throw
will not trigger the destruction mechanism, only asynchronous reject will
2The first parameter of the service function must be
shutdown
This is the destruction registrar injected by the container, declare it even if not used
3The result of
defineService
must be assigned to a module-level
export const
Services are deduplicated based on function references, so references must be stable
4Do not directly write anonymous functions inside
defineService
and pass them to another function
A new reference is created each call, leading to duplicate registration
5Do not manually construct
ServiceRegisterProps
objects
Must obtain via
defineService
or
container.register
, the internal
flag
is an unforgeable Symbol
6Do not cache the result of
loadService
in a top-level module variable
The service may not be initialized yet, should
await loadService()
when needed
7Immediately register
shutdown
after initializing each external resource
Ensure that resources created during initialization can be cleaned up correctly if initialization fails midway
8Define only one service per fileKeep 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)

FunctionSignatureDescription
defineService
<R>(fn: ServiceFunction<R>) => ServiceRegisterProps<R>
Register a service, return registration information
loadService
<R>(props: ServiceRegisterProps<R>) => Promise<R>
Load a service, return service instance
isService
<R>(props: ServiceRegisterProps<R>) => boolean
Determine if an object is valid service registration information (verified via internal Symbol)

Container Class (For Creating Isolated Containers)

MethodSignatureDescription
register
<R>(fn: ServiceFunction<R>) => ServiceRegisterProps<R>
Register a service. The same function reference is only registered once
resolve
<R>(props: ServiceRegisterProps<R>) => Promise<R>
Resolve a service (see state machine below)
hasService
<R>(fn: ServiceFunction<R>) => boolean
Check if a service has been registered
hasMeta
(id: number) => boolean
Check if a service has been run
getIdByService
<R>(fn: ServiceFunction<R>) => number | undefined
Query service ID by function reference
getMetaById
(id: number) => Paddings | undefined
Query runtime metadata by service ID
shutdown
() => Promise<void>
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

FieldTypeDescription
packages
Map<Function, number>
Function reference → Service ID
paddings
Map<number, Paddings>
Service ID → Runtime status
shutdownFunctions
Map<number, ServiceCutDownFunction[]>
Service ID → Destruction callback array
shutdownQueues
number[]
Queue of service IDs that have registered destruction callbacks (ordered)

Paddings Structure

FieldTypeDescription
status
-1 | 0 | 1
-1 Failed / 0 Running / 1 Succeeded
value
R
Return value on success
error
any
Error on failure
queue
Set<{ resolve, reject }>
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
      container.shutdown()
      is called manually (in reverse order of service registration)

Service Identification Mechanism (sericeFlag)

The container internally uses
const sericeFlag = Symbol('service')
as the identifier for service registration information. The object returned by
register
carries the
flag: sericeFlag
field.
isService()
determines if an object is valid service registration information by checking
flag === sericeFlag
and the types of
id
and
fn
. Since Symbols are unforgeable, external parties cannot manually construct an object that passes the
isService
check.

Function 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