@hile/context 3.0.1 → 3.0.2
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/AI.md +154 -0
- package/README.md +31 -136
- package/package.json +4 -4
- package/SKILL.md +0 -40
package/AI.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# AI Guide For @hile/context
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Purpose: Propagate request/work-unit context through async calls, models, micro messages, queues, and logger bindings.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Use this file when an AI agent installs the npm package and needs package-local examples, package selection rules, boundaries, and verification steps.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Package Selection
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
| User asks for | Use | Also read |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Propagate request id, tenant id, logger bindings, or micro context | `@hile/context` | `packages/model-context.md` |
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Model And Context
|
|
28
|
+
|
|
29
|
+
Packages: `@hile/model`, `@hile/context`.
|
|
30
|
+
|
|
31
|
+
## Use When
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
## Do Not Use When
|
|
36
|
+
|
|
37
|
+
- Do not put business logic only in controllers when it will be reused by jobs, pages, or message handlers.
|
|
38
|
+
- Do not add fixed business fields to `@hile/context`; each app owns its context shape.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @hile/model @hile/context
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Imports
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { defineModel, loadModel } from '@hile/model'
|
|
50
|
+
import { contextHttp, contextModel, getContext, requireContext, runWithContext } from '@hile/context'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Copy-Paste Example
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { defineModel } from '@hile/model'
|
|
57
|
+
|
|
58
|
+
export default defineModel(async (input: { name: string }) => {
|
|
59
|
+
return { greeting: `Hello ${input.name}` }
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use it:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const result = await loadModel(greetModel, { name: 'Ada' })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## More Examples
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { defineModel } from '@hile/model'
|
|
73
|
+
import typeormService from '@hile/typeorm'
|
|
74
|
+
|
|
75
|
+
export const findUser = defineModel({
|
|
76
|
+
services: [typeormService] as const,
|
|
77
|
+
pipelines: [
|
|
78
|
+
async (ctx, next) => {
|
|
79
|
+
if (!ctx.args.userId) throw new Error('userId is required')
|
|
80
|
+
await next()
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
async main([ds], input: { userId: string }) {
|
|
84
|
+
return ds.getRepository(User).findOneBy({ id: input.userId })
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Context in HTTP:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
http.use(contextHttp({
|
|
93
|
+
read: (ctx) => ({ requestId: String(ctx.headers['x-request-id'] ?? crypto.randomUUID()) }),
|
|
94
|
+
write: (context, ctx) => ctx.set('x-request-id', String(context.requestId ?? '')),
|
|
95
|
+
}))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Context in model pipeline:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const withTenant = contextModel<{ tenantId: string }>({
|
|
102
|
+
read: (input) => ({ tenantId: input.tenantId }),
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Compose With
|
|
107
|
+
|
|
108
|
+
- `@hile/http` controllers should call `loadModel()`.
|
|
109
|
+
- `@hile/redis-idempotency` and `@hile/redis-rate-limit` provide model pipeline middleware.
|
|
110
|
+
- `@hile/micro` propagates context in message metadata.
|
|
111
|
+
- `@hile/redis-stream-queue` snapshots context when enqueuing and restores it in workers.
|
|
112
|
+
|
|
113
|
+
## Runtime And Lifecycle Notes
|
|
114
|
+
|
|
115
|
+
- `loadModel(model, input)` rejects if the first argument was not created by `defineModel()`.
|
|
116
|
+
- Model input must be an object.
|
|
117
|
+
- Services are loaded in the order of the `services` tuple.
|
|
118
|
+
- Pipeline middleware follows Koa-style `await next()`.
|
|
119
|
+
- The terminal model result is stored in `ctx.state.result` and returned by `loadModel()`.
|
|
120
|
+
- `runWithContext()` merges with parent context by default; pass `{ merge: false }` to replace.
|
|
121
|
+
- `getContext()` returns a frozen shallow snapshot.
|
|
122
|
+
- `requireContext(keys)` throws when selected keys are missing.
|
|
123
|
+
|
|
124
|
+
## Anti-Patterns
|
|
125
|
+
|
|
126
|
+
- Passing primitives to `loadModel()`.
|
|
127
|
+
- Mutating context snapshots.
|
|
128
|
+
- Logging whole context objects by default.
|
|
129
|
+
- Assuming context propagation changes business payloads; it should stay in metadata or async storage.
|
|
130
|
+
|
|
131
|
+
## Verification Checklist
|
|
132
|
+
|
|
133
|
+
- Models export `defineModel(...)` results.
|
|
134
|
+
- Controllers and pages call `loadModel(model, objectInput)`.
|
|
135
|
+
- Pipeline middleware writes derived state to `ctx.state`.
|
|
136
|
+
- Context keys are app-defined and JSON-serializable when crossing process boundaries.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Global Guardrails
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## Never Generate These Patterns
|
|
145
|
+
|
|
146
|
+
- Do not call `loadService()` at module top level; it starts resources during import.
|
|
147
|
+
- Do not default-export plain functions from `*.boot.*` files; `hile start` expects a Hile service.
|
|
148
|
+
- Do not set `ctx.body` and also return a controller value.
|
|
149
|
+
- Do not assume `@hile/http` Zod validation mutates or coerces `ctx.query`, `ctx.params`, or `ctx.request.body`.
|
|
150
|
+
- Do not put reusable business logic only in controllers, pages, queue workers, or message handlers.
|
|
151
|
+
- Do not use old message examples that append a secondary response getter; current request APIs return promises directly.
|
|
152
|
+
- Do not claim exactly-once delivery or execution from Redis locks, queues, idempotency, or rate limits.
|
|
153
|
+
- Do not use queue `jobId` as the only side-effect idempotency boundary.
|
|
154
|
+
- Do not log the entire async context by default.
|
package/README.md
CHANGED
|
@@ -1,161 +1,56 @@
|
|
|
1
1
|
# @hile/context
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Propagate request/work-unit context through async calls, models, micro messages, queues, and logger bindings.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pnpm add @hile/context
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Quick Start
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { getContext, runWithContext } from '@hile/context'
|
|
17
|
-
|
|
18
|
-
type AppContext = {
|
|
19
|
-
shopId: string
|
|
20
|
-
memberId: string
|
|
21
|
-
channel: 'web' | 'wechat'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
await runWithContext<AppContext>({
|
|
25
|
-
shopId: 'shop-1',
|
|
26
|
-
memberId: 'member-1',
|
|
27
|
-
channel: 'web',
|
|
28
|
-
}, async () => {
|
|
29
|
-
const context = getContext<AppContext>()
|
|
30
|
-
console.log(context.shopId)
|
|
31
|
-
})
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Outside `runWithContext()`, `getContext()` returns an empty object.
|
|
35
|
-
|
|
36
|
-
## Core API
|
|
37
|
-
|
|
38
|
-
### runWithContext(context, callback, options?)
|
|
39
|
-
|
|
40
|
-
Runs `callback` inside an `AsyncLocalStorage` scope.
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
await runWithContext<AppContext>({ shopId: 'shop-1' }, async () => {
|
|
44
|
-
await doWork()
|
|
45
|
-
})
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Nested calls merge with the parent context by default:
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
await runWithContext<AppContext>({ shopId: 'shop-1', channel: 'web' }, async () => {
|
|
52
|
-
await runWithContext<AppContext>({ channel: 'wechat' }, async () => {
|
|
53
|
-
getContext<AppContext>() // { shopId: 'shop-1', channel: 'wechat' }
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Pass `{ merge: false }` to replace the parent context.
|
|
59
|
-
|
|
60
|
-
### getContext()
|
|
7
|
+
This README is intentionally short and example-first. The complete AI-facing guide ships in `AI.md` in this package.
|
|
61
8
|
|
|
62
|
-
|
|
9
|
+
## When To Use
|
|
63
10
|
|
|
64
|
-
|
|
11
|
+
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.
|
|
65
12
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### requireContext(keys)
|
|
69
|
-
|
|
70
|
-
Asserts that selected application-defined keys are present.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
const context = requireContext<AppContext>(['shopId', 'channel'])
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
It throws `MissingContextError` when a selected key is missing.
|
|
77
|
-
|
|
78
|
-
## HTTP Adapter
|
|
79
|
-
|
|
80
|
-
`contextHttp()` is mapping-only. The package does not prescribe header names.
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import { contextHttp } from '@hile/context'
|
|
13
|
+
## Install
|
|
84
14
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
shopId: ctx.get('x-shop'),
|
|
88
|
-
channel: ctx.get('x-channel') as AppContext['channel'],
|
|
89
|
-
}),
|
|
90
|
-
write: (context, ctx) => {
|
|
91
|
-
if (context.shopId) ctx.set('x-current-shop', context.shopId)
|
|
92
|
-
},
|
|
93
|
-
}))
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @hile/context
|
|
94
17
|
```
|
|
95
18
|
|
|
96
|
-
##
|
|
19
|
+
## Copy-Paste Example
|
|
97
20
|
|
|
98
|
-
```
|
|
99
|
-
import { contextModel, requireContextModel } from '@hile/context'
|
|
21
|
+
```ts
|
|
100
22
|
import { defineModel } from '@hile/model'
|
|
101
23
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
contextModel<{ store: string; source: 'web' | 'wechat' }, AppContext>({
|
|
105
|
-
read: input => ({
|
|
106
|
-
shopId: input.store,
|
|
107
|
-
channel: input.source,
|
|
108
|
-
}),
|
|
109
|
-
}),
|
|
110
|
-
requireContextModel<{ store: string; source: 'web' | 'wechat' }, AppContext>(['shopId']),
|
|
111
|
-
],
|
|
112
|
-
async main(input) {
|
|
113
|
-
return getContext<AppContext>()
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Logger Binding
|
|
119
|
-
|
|
120
|
-
Logger bindings are opt-in. Without `pick` or `map`, no context fields are logged.
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
import { withContextLogger } from '@hile/context'
|
|
124
|
-
|
|
125
|
-
const logger = withContextLogger<AppContext>(baseLogger, {
|
|
126
|
-
pick: ['shopId', 'channel'],
|
|
24
|
+
export default defineModel(async (input: { name: string }) => {
|
|
25
|
+
return { greeting: `Hello ${input.name}` }
|
|
127
26
|
})
|
|
128
|
-
|
|
129
|
-
logger.info({ event: 'checkout' }, 'checkout created')
|
|
130
27
|
```
|
|
131
28
|
|
|
132
|
-
|
|
29
|
+
Use it:
|
|
133
30
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
await runWithContext<AppContext>({ shopId: 'shop-1', channel: 'web' }, async () => {
|
|
138
|
-
await app.call('inventory', '/reserve', { sku: 'sku-1' })
|
|
139
|
-
})
|
|
31
|
+
```ts
|
|
32
|
+
const result = await loadModel(greetModel, { name: 'Ada' })
|
|
140
33
|
```
|
|
141
34
|
|
|
142
|
-
The receiving handler can call `getContext<AppContext>()`. The business `data` payload remains unchanged; context travels separately in `metadata.context`.
|
|
143
|
-
|
|
144
35
|
## Boundaries
|
|
145
36
|
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
- `getContext()` returns a shallow snapshot, not a deep clone.
|
|
149
|
-
- Cross-process propagation should use JSON-serializable values.
|
|
150
|
-
- Logger integration logs only fields selected by the application.
|
|
37
|
+
- Do not put business logic only in controllers when it will be reused by jobs, pages, or message handlers.
|
|
38
|
+
- Do not add fixed business fields to `@hile/context`; each app owns its context shape.
|
|
151
39
|
|
|
152
|
-
|
|
40
|
+
- Passing primitives to `loadModel()`.
|
|
41
|
+
- Mutating context snapshots.
|
|
42
|
+
- Logging whole context objects by default.
|
|
43
|
+
- Assuming context propagation changes business payloads; it should stay in metadata or async storage.
|
|
153
44
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
45
|
+
## Verify
|
|
46
|
+
|
|
47
|
+
- Models export `defineModel(...)` results.
|
|
48
|
+
- Controllers and pages call `loadModel(model, objectInput)`.
|
|
49
|
+
- Pipeline middleware writes derived state to `ctx.state`.
|
|
50
|
+
- Context keys are app-defined and JSON-serializable when crossing process boundaries.
|
|
158
51
|
|
|
159
|
-
##
|
|
52
|
+
## More Context
|
|
160
53
|
|
|
161
|
-
|
|
54
|
+
- `AI.md` in this package: full package-local AI guide.
|
|
55
|
+
- Root `llms-full.txt`: full monorepo AI context.
|
|
56
|
+
- Root `references/`: source files copied from `docs/ai`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/context",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Typed async context propagation primitives for Hile applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"README.md",
|
|
15
|
-
"
|
|
15
|
+
"AI.md"
|
|
16
16
|
],
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"publishConfig": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"vitest": "^4.0.18"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@hile/model": "^3.0.
|
|
27
|
+
"@hile/model": "^3.0.2"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "0985b6f8abc1f4de0a36324063585fdc3ac1375b"
|
|
30
30
|
}
|
package/SKILL.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: context
|
|
3
|
-
description: Use when implementing typed async context propagation, request/work-unit context, HTTP context mapping, model pipeline context, logger context bindings, or @hile/micro context propagation.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Context
|
|
7
|
-
|
|
8
|
-
Use `@hile/context` when a Hile app needs request/work-unit data to flow through async code and microservice calls without passing it through every function argument.
|
|
9
|
-
|
|
10
|
-
## Core Rule
|
|
11
|
-
|
|
12
|
-
Never bake business fields into the package. The library owns storage and propagation only; applications own the context shape.
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
type AppContext = {
|
|
16
|
-
shopId: string
|
|
17
|
-
channel: 'web' | 'wechat'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
await runWithContext<AppContext>({ shopId, channel }, async () => {
|
|
21
|
-
await doWork()
|
|
22
|
-
})
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Design Boundaries
|
|
26
|
-
|
|
27
|
-
- Core storage is an `AsyncLocalStorage<Record<string, unknown>>`.
|
|
28
|
-
- `getContext()` and `snapshotContext()` return readonly shallow snapshots.
|
|
29
|
-
- Nested `runWithContext()` calls merge by default; pass `{ merge: false }` to replace.
|
|
30
|
-
- `requireContext(keys)` validates only keys selected by the application.
|
|
31
|
-
- Do not add fixed fields like organization, user, or request dimensions to core types.
|
|
32
|
-
- Cross-process propagation should use JSON-serializable context values.
|
|
33
|
-
|
|
34
|
-
## Adapters
|
|
35
|
-
|
|
36
|
-
- Use `contextHttp({ read, write })` to map arbitrary HTTP request/response details to and from context. The package must not prescribe header names.
|
|
37
|
-
- Use `contextModel({ read })` inside `@hile/model` pipelines to seed context from model input.
|
|
38
|
-
- Use `requireContextModel(keys)` to enforce selected application-owned keys inside model pipelines.
|
|
39
|
-
- Use `withContextLogger(logger, { pick })` or `{ map }` to opt into logger bindings. Never log the whole context by default.
|
|
40
|
-
- `@hile/micro` propagates context in `metadata.context`; business payloads remain unchanged.
|