feat: add bun-fullstack agent and update skills
This commit is contained in:
207
.agent/skills/tech-stack/elysiajs/references/plugin.md
Normal file
207
.agent/skills/tech-stack/elysiajs/references/plugin.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Plugins
|
||||
|
||||
## Plugin = Decoupled Elysia Instance
|
||||
|
||||
```ts
|
||||
const plugin = new Elysia()
|
||||
.decorate('plugin', 'hi')
|
||||
.get('/plugin', ({ plugin }) => plugin)
|
||||
|
||||
const app = new Elysia()
|
||||
.use(plugin) // inherit properties
|
||||
.get('/', ({ plugin }) => plugin)
|
||||
```
|
||||
|
||||
**Inherits**: state, decorate
|
||||
**Does NOT inherit**: lifecycle (isolated by default)
|
||||
|
||||
## Dependency
|
||||
|
||||
Each instance runs independently like microservice. **Must explicitly declare dependencies**.
|
||||
|
||||
```ts
|
||||
const auth = new Elysia()
|
||||
.decorate('Auth', Auth)
|
||||
|
||||
// ❌ Missing dependency
|
||||
const main = new Elysia()
|
||||
.get('/', ({ Auth }) => Auth.getProfile())
|
||||
|
||||
// ✅ Declare dependency
|
||||
const main = new Elysia()
|
||||
.use(auth) // required for Auth
|
||||
.get('/', ({ Auth }) => Auth.getProfile())
|
||||
```
|
||||
|
||||
## Deduplication
|
||||
|
||||
**Every plugin re-executes by default**. Use `name` + optional `seed` to deduplicate:
|
||||
|
||||
```ts
|
||||
const ip = new Elysia({ name: 'ip' }) // unique identifier
|
||||
.derive({ as: 'global' }, ({ server, request }) => ({
|
||||
ip: server?.requestIP(request)
|
||||
}))
|
||||
|
||||
const router1 = new Elysia().use(ip)
|
||||
const router2 = new Elysia().use(ip)
|
||||
const server = new Elysia().use(router1).use(router2)
|
||||
// `ip` only executes once due to deduplication
|
||||
```
|
||||
|
||||
## Global vs Explicit Dependency
|
||||
|
||||
**Global plugin** (rare, apply everywhere):
|
||||
- Doesn't add types - cors, compress, helmet
|
||||
- Global lifecycle no instance controls - tracing, logging
|
||||
- Examples: OpenAPI docs, OpenTelemetry, logging
|
||||
|
||||
**Explicit dependency** (default, recommended):
|
||||
- Adds types - macro, state, model
|
||||
- Business logic instances interact with - Auth, DB
|
||||
- Examples: state management, ORM, auth, features
|
||||
|
||||
## Scope
|
||||
|
||||
**Lifecycle isolated by default**. Must specify scope to export.
|
||||
|
||||
```ts
|
||||
// ❌ NOT inherited by app
|
||||
const profile = new Elysia()
|
||||
.onBeforeHandle(({ cookie }) => throwIfNotSignIn(cookie))
|
||||
.get('/profile', () => 'Hi')
|
||||
|
||||
const app = new Elysia()
|
||||
.use(profile)
|
||||
.patch('/rename', ({ body }) => updateProfile(body)) // No sign-in check
|
||||
|
||||
// ✅ Exported to app
|
||||
const profile = new Elysia()
|
||||
.onBeforeHandle({ as: 'global' }, ({ cookie }) => throwIfNotSignIn(cookie))
|
||||
.get('/profile', () => 'Hi')
|
||||
```
|
||||
|
||||
## Scope Levels
|
||||
|
||||
1. **local** (default) - current + descendants only
|
||||
2. **scoped** - parent + current + descendants
|
||||
3. **global** - all instances (all parents, current, descendants)
|
||||
|
||||
Example with `.onBeforeHandle({ as: 'local' }, ...)`:
|
||||
|
||||
| type | child | current | parent | main |
|
||||
|------|-------|---------|--------|------|
|
||||
| local | ✅ | ✅ | ❌ | ❌ |
|
||||
| scoped | ✅ | ✅ | ✅ | ❌ |
|
||||
| global | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
## Config
|
||||
|
||||
```ts
|
||||
// Instance factory with config
|
||||
const version = (v = 1) => new Elysia()
|
||||
.get('/version', v)
|
||||
|
||||
const app = new Elysia()
|
||||
.use(version(1))
|
||||
```
|
||||
|
||||
## Functional Callback (not recommended)
|
||||
|
||||
```ts
|
||||
// Harder to handle scope/encapsulation
|
||||
const plugin = (app: Elysia) => app
|
||||
.state('counter', 0)
|
||||
.get('/plugin', () => 'Hi')
|
||||
|
||||
// Prefer new instance (better type inference, no perf diff)
|
||||
```
|
||||
|
||||
## Guard (Apply to Multiple Routes)
|
||||
|
||||
```ts
|
||||
.guard(
|
||||
{ body: t.Object({ username: t.String(), password: t.String() }) },
|
||||
(app) =>
|
||||
app.post('/sign-up', ({ body }) => signUp(body))
|
||||
.post('/sign-in', ({ body }) => signIn(body))
|
||||
)
|
||||
```
|
||||
|
||||
**Grouped guard** (merge group + guard):
|
||||
|
||||
```ts
|
||||
.group(
|
||||
'/v1',
|
||||
{ body: t.Literal('Rikuhachima Aru') }, // guard here
|
||||
(app) => app.post('/student', ({ body }) => body)
|
||||
)
|
||||
```
|
||||
|
||||
## Scope Casting
|
||||
|
||||
**3 methods to apply hook to parent**:
|
||||
|
||||
1. **Inline as** (single hook):
|
||||
```ts
|
||||
.derive({ as: 'scoped' }, () => ({ hi: 'ok' }))
|
||||
```
|
||||
|
||||
2. **Guard as** (multiple hooks, no derive/resolve):
|
||||
```ts
|
||||
.guard({
|
||||
as: 'scoped',
|
||||
response: t.String(),
|
||||
beforeHandle() { console.log('ok') }
|
||||
})
|
||||
```
|
||||
|
||||
3. **Instance as** (all hooks + schema):
|
||||
```ts
|
||||
const plugin = new Elysia()
|
||||
.derive(() => ({ hi: 'ok' }))
|
||||
.get('/child', ({ hi }) => hi)
|
||||
.as('scoped') // lift scope up
|
||||
```
|
||||
|
||||
`.as()` lifts scope: local → scoped → global
|
||||
|
||||
## Lazy Load
|
||||
|
||||
**Deferred module** (async plugin, non-blocking startup):
|
||||
|
||||
```ts
|
||||
// plugin.ts
|
||||
export const loadStatic = async (app: Elysia) => {
|
||||
const files = await loadAllFiles()
|
||||
files.forEach((asset) => app.get(asset, file(asset)))
|
||||
return app
|
||||
}
|
||||
|
||||
// main.ts
|
||||
const app = new Elysia().use(loadStatic)
|
||||
```
|
||||
|
||||
**Lazy-load module** (dynamic import):
|
||||
|
||||
```ts
|
||||
const app = new Elysia()
|
||||
.use(import('./plugin')) // loaded after startup
|
||||
```
|
||||
|
||||
**Testing** (wait for modules):
|
||||
|
||||
```ts
|
||||
await app.modules // ensure all deferred/lazy modules loaded
|
||||
```
|
||||
|
||||
## Notes
|
||||
[Inference] Based on docs patterns:
|
||||
- Use inline values for static resources (performance optimization)
|
||||
- Group routes by prefix for organization
|
||||
- Extend context minimally (separation of concerns)
|
||||
- Use `status()` over `set.status` for type safety
|
||||
- Prefer `resolve()` over `derive()` when type integrity matters
|
||||
- Plugins isolated by default (must declare scope explicitly)
|
||||
- Use `name` for deduplication when plugin used multiple times
|
||||
- Prefer explicit dependency over global (better modularity/tracking)
|
||||
Reference in New Issue
Block a user