@xrystal/dto 26.6.27-v13 → 26.6.27-v14
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/README.md +273 -38
- package/dist/adapters/elysia.cjs +251 -225
- package/dist/adapters/elysia.js +250 -226
- package/dist/adapters/express.cjs +237 -223
- package/dist/adapters/express.js +237 -224
- package/dist/adapters/fetch.cjs +832 -0
- package/dist/adapters/fetch.js +826 -0
- package/dist/adapters/node.cjs +257 -224
- package/dist/adapters/node.js +257 -225
- package/dist/adapters/socketio.cjs +204 -193
- package/dist/adapters/socketio.js +204 -194
- package/dist/adapters/websocket.cjs +207 -193
- package/dist/adapters/websocket.js +207 -194
- package/dist/dto/adapters/bind.d.ts +40 -0
- package/dist/dto/adapters/elysia.d.ts +1 -0
- package/dist/dto/adapters/express.d.ts +1 -0
- package/dist/dto/adapters/fetch.d.ts +1 -0
- package/dist/dto/adapters/http.d.ts +231 -0
- package/dist/dto/adapters/node.d.ts +1 -0
- package/dist/dto/adapters/realtime.d.ts +53 -0
- package/dist/dto/adapters/socketio.d.ts +1 -0
- package/dist/dto/adapters/websocket.d.ts +1 -0
- package/dist/dto/factories.d.ts +11 -0
- package/dist/dto/index.cjs +551 -499
- package/dist/dto/index.d.ts +4 -5
- package/dist/dto/index.js +536 -500
- package/dist/dto/log/index.cjs +20 -39
- package/dist/dto/log/index.js +20 -39
- package/dist/dto/queue/index.cjs +20 -33
- package/dist/dto/queue/index.js +20 -33
- package/dist/dto/response/index.cjs +232 -78
- package/dist/dto/response/index.js +218 -79
- package/dist/dto/runtime/index.cjs +523 -486
- package/dist/dto/runtime/index.d.ts +71 -4
- package/dist/dto/runtime/index.js +523 -487
- package/dist/dto/{response → send}/builder.d.ts +3 -2
- package/dist/dto/send/index.d.ts +7 -0
- package/dist/dto/{response → send}/presets.d.ts +23 -19
- package/dist/dto/send/transport.d.ts +50 -0
- package/dist/dto/{log/builder.d.ts → shapes/log.d.ts} +28 -12
- package/dist/dto/{queue/builder.d.ts → shapes/queue.d.ts} +36 -9
- package/dist/dto/{response/dto.d.ts → shapes/response.d.ts} +8 -6
- package/dist/dto/shared.d.ts +15 -0
- package/dist/index.cjs +529 -420
- package/dist/index.d.ts +12 -12
- package/dist/index.js +512 -421
- package/dist/types/builder.d.ts +2 -1
- package/dist/types/log.d.ts +1 -1
- package/dist/types/queue.d.ts +1 -1
- package/package.json +6 -1
- package/dist/adapters/elysia.d.ts +0 -138
- package/dist/adapters/express.d.ts +0 -49
- package/dist/adapters/index.d.ts +0 -1
- package/dist/adapters/node.d.ts +0 -27
- package/dist/adapters/shared.d.ts +0 -11
- package/dist/adapters/socketio.d.ts +0 -31
- package/dist/adapters/websocket.d.ts +0 -31
- package/dist/dto/log/assemble.d.ts +0 -3
- package/dist/dto/log/index.d.ts +0 -3
- package/dist/dto/log/types.d.ts +0 -26
- package/dist/dto/queue/assemble.d.ts +0 -2
- package/dist/dto/queue/index.d.ts +0 -3
- package/dist/dto/queue/types.d.ts +0 -34
- package/dist/dto/response/assemble.d.ts +0 -9
- package/dist/dto/response/config.d.ts +0 -10
- package/dist/dto/response/index.d.ts +0 -7
- package/dist/dto/runtime/context.d.ts +0 -5
- package/dist/dto/runtime/dto.d.ts +0 -64
- package/dist/dto/runtime/hooks.d.ts +0 -8
- package/dist/dto/runtime/metadata.d.ts +0 -6
- package/dist/dto/shared/base-dto.d.ts +0 -12
- package/dist/dto/shared/schema-utils.d.ts +0 -1
- /package/dist/dto/{response → send}/plugins.d.ts +0 -0
- /package/dist/{enums/node-env.d.ts → utils/constants/enums.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -41,13 +41,15 @@ This attaches `req.ctx` and `res.builder` on every request.
|
|
|
41
41
|
|
|
42
42
|
### 2. Respond in a handler
|
|
43
43
|
|
|
44
|
+
Every chain ends with a **verb** — `.send()` (go to client) or `.build()` (schema only):
|
|
45
|
+
|
|
44
46
|
```typescript
|
|
45
47
|
app.get('/users/:id', (req, res) => {
|
|
46
48
|
return res.builder.as.ok(
|
|
47
49
|
{ id: req.params.id },
|
|
48
50
|
'User fetched',
|
|
49
51
|
{ meta: { requestId: req.ctx.requestId } },
|
|
50
|
-
)
|
|
52
|
+
).send()
|
|
51
53
|
})
|
|
52
54
|
```
|
|
53
55
|
|
|
@@ -62,7 +64,12 @@ Sent JSON (fixed order: `status → message → payload? → code`):
|
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
| Verb | What it does |
|
|
68
|
+
|------|----------------|
|
|
69
|
+
| `.send()` | Assembles + **pushes** via send sink (HTTP write, WebSocket emit, …) |
|
|
70
|
+
| `.build()` | Assembles **schema only** — no network side-effect |
|
|
71
|
+
|
|
72
|
+
Presets (`.as.ok`, `.as.notFound`, …) configure the body and return a terminal — you still call `.send()` or `.build()`.
|
|
66
73
|
|
|
67
74
|
### 3. Static shape DTOs (no request)
|
|
68
75
|
|
|
@@ -102,52 +109,226 @@ const runtime = new DTO()
|
|
|
102
109
|
new Elysia()
|
|
103
110
|
.use(runtime.elysia())
|
|
104
111
|
.get('/users/:id', ({ builder, requestId, params }) =>
|
|
105
|
-
builder.as.ok({ id: params.id }, 'User fetched', { meta: { requestId } }),
|
|
112
|
+
builder.as.ok({ id: params.id }, 'User fetched', { meta: { requestId } }).send(),
|
|
106
113
|
)
|
|
107
114
|
```
|
|
108
115
|
|
|
116
|
+
### Fetch API — Workers, Hono, Bun.serve
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { DTO } from '@xrystal/dto'
|
|
120
|
+
|
|
121
|
+
const runtime = new DTO({ withDefaultHeaders: true })
|
|
122
|
+
|
|
123
|
+
export async function GET(request: Request) {
|
|
124
|
+
const { builder, requestId } = runtime.forRequest(request)
|
|
125
|
+
return builder.as.ok({ ok: true }, 'Healthy', { meta: { requestId } }).send()
|
|
126
|
+
// → Web Response
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
109
130
|
### When to use what
|
|
110
131
|
|
|
111
132
|
| Situation | Use |
|
|
112
133
|
|-----------|-----|
|
|
113
|
-
| Express / Node / Elysia handler | `
|
|
114
|
-
| WebSocket / Socket.IO | `
|
|
134
|
+
| Express / Node / Elysia / Fetch handler | `builder…send()` |
|
|
135
|
+
| WebSocket / Socket.IO | `socket.builder…send()` |
|
|
136
|
+
| Schema only (log, queue, test, transform) | `builder…build()` or `DTO.*.build()` |
|
|
115
137
|
| Cron, worker, logger, queue publish | `DTO.Log`, `DTO.Queue`, `DTO.Response` |
|
|
116
|
-
| Custom HTTP status + headers | `
|
|
138
|
+
| Custom HTTP status + headers | `builder.httpStatus(429).header(...).schema({...}).send()` |
|
|
117
139
|
|
|
118
140
|
Plugins and custom `codes` on `new DTO({ … })` apply **only to `builder`** — not to static `DTO.Log` / `DTO.Queue`.
|
|
119
141
|
|
|
120
142
|
---
|
|
121
143
|
|
|
122
|
-
##
|
|
144
|
+
## Adapters & send sinks
|
|
145
|
+
|
|
146
|
+
Each runtime has an **adapter** (wires `builder` per request) and a **send sink** (writes `FinalResult` to the transport on `.send()`).
|
|
147
|
+
|
|
148
|
+
| Piece | Role |
|
|
149
|
+
|-------|------|
|
|
150
|
+
| **Adapter** | Framework middleware / derive — creates `requestId`, attaches `builder`, applies default headers |
|
|
151
|
+
| **Send sink** | `{ dispatch(result) }` object — one line per runtime, delegates to `send/transport.ts` |
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Handler chain Core Transport
|
|
155
|
+
───────────── ───── ─────────
|
|
156
|
+
builder.as.ok() → .build() → FinalResult
|
|
157
|
+
→ .send() → dispatchResult() → sink.dispatch()
|
|
158
|
+
→ send/transport.ts
|
|
159
|
+
→ res.json / Response / ws.send / …
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Core dispatch lives in `dto/send/transport.ts`. Adapters pass a sink into `createBuilderRuntime()` — no separate dispatcher classes needed for normal use. `ExpressDispatcher` and friends remain as deprecated aliases for custom wiring.
|
|
163
|
+
|
|
164
|
+
### Runtime map
|
|
165
|
+
|
|
166
|
+
| Runtime | Adapter | Sink | Attaches | `.send()` output |
|
|
167
|
+
|---------|---------|------|----------|------------------|
|
|
168
|
+
| **Express** | `ExpressAdapter` | `expressSink(res)` | `req.ctx`, `res.builder` | writes `res.status().json()` |
|
|
169
|
+
| **Node http** | `NodeAdapter` | `nodeSink(res)` | `req.ctx`, `res.builder` | writes `res.end(JSON)` + Set-Cookie |
|
|
170
|
+
| **Fetch API** | `FetchAdapter` | `fetchSink` | `builder`, `requestId` per `Request` | returns `Response` |
|
|
171
|
+
| **Elysia** | `ElysiaAdapter` | `elysiaSink(set)` | `builder`, `requestId`, `startTime` on ctx | mutates `set`, returns body |
|
|
172
|
+
| **WebSocket** | `WebSocketAdapter` | `websocketSink(socket)` | `socket.ctx`, `socket.builder` | `socket.send(wire envelope JSON)` |
|
|
173
|
+
| **Socket.IO** | `SocketIoAdapter` | `socketIoSink(socket, event)` | `socket.ctx`, `socket.builder` | `socket.emit(event, wire envelope)` |
|
|
174
|
+
|
|
175
|
+
WebSocket and Socket.IO send the full transport envelope (`statusCode`, `headers`, `cookies`, `body`) so `.httpStatus()` and `.header()` are not lost on realtime transports.
|
|
176
|
+
|
|
177
|
+
### `new DTO()` shortcuts
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const runtime = new DTO({ withDefaultHeaders: true, socket: { event: 'api:response' } })
|
|
181
|
+
|
|
182
|
+
runtime.express() // → Express middleware
|
|
183
|
+
runtime.node() // → Node http middleware
|
|
184
|
+
runtime.elysia() // → Elysia plugin
|
|
185
|
+
runtime.fetch() // → (request: Request) => { builder, requestId, … }
|
|
186
|
+
runtime.forRequest(request) // → one-shot Fetch context
|
|
187
|
+
|
|
188
|
+
runtime.websocket() // → (socket, next?) => void
|
|
189
|
+
runtime.attachWebSocket(ws) // → attach builder to one socket
|
|
190
|
+
|
|
191
|
+
runtime.socketIo() // → (socket, next?) => void
|
|
192
|
+
runtime.attachSocketIo(socket) // → attach builder to one socket
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### When to import adapters directly
|
|
196
|
+
|
|
197
|
+
Most apps only need `new DTO().express()` (etc.). Import adapters / sinks when you:
|
|
198
|
+
|
|
199
|
+
- Integrate a **custom framework** — `createBuilderRuntime(expressSink(res), options, context)`
|
|
200
|
+
- **Unit-test** transport without full middleware
|
|
201
|
+
- Build an **internal wrapper** (e.g. Fastify plugin)
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { createBuilderRuntime, expressSink, ContextFactory } from '@xrystal/dto'
|
|
205
|
+
|
|
206
|
+
// Custom wiring example
|
|
207
|
+
const context = ContextFactory.create(req)
|
|
208
|
+
const { builder } = createBuilderRuntime(expressSink(res), options, context)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Subpath imports (tree-shake friendly):
|
|
212
|
+
|
|
213
|
+
| Import | Contents |
|
|
214
|
+
|--------|----------|
|
|
215
|
+
| `@xrystal/dto/express` | `ExpressAdapter`, `ExpressDispatcher`, `expressMiddleware` |
|
|
216
|
+
| `@xrystal/dto/node` | `NodeAdapter`, `NodeDispatcher`, `nodeMiddleware` |
|
|
217
|
+
| `@xrystal/dto/fetch` | `FetchAdapter`, `FetchDispatcher`, `createFetchDerive` |
|
|
218
|
+
| `@xrystal/dto/elysia` | `ElysiaAdapter`, `ElysiaDispatcher`, `createElysiaDerive` |
|
|
219
|
+
| `@xrystal/dto/websocket` | `WebSocketAdapter`, `WebSocketDispatcher` |
|
|
220
|
+
| `@xrystal/dto/socketio` | `SocketIoAdapter`, `SocketIoDispatcher` |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Core vs DTO — tek adapter yeter mi?
|
|
225
|
+
|
|
226
|
+
Kütüphanenin **iki işi** var; mimari buna göre bölündü:
|
|
123
227
|
|
|
124
|
-
|
|
228
|
+
| İş | Katman | Adapter gerekir mi? |
|
|
229
|
+
|----|--------|---------------------|
|
|
230
|
+
| **Şema üret** | `shapes/` + `shared.ts` | **Hayır** — `DTO.Log.build()`, `DTO.Response.build()`, testler |
|
|
231
|
+
| **Client'a gönder** | `send/` + `adapters/` | **Evet** — `.send()` bir transport'a yazmalı |
|
|
125
232
|
|
|
126
233
|
```
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
│
|
|
130
|
-
|
|
131
|
-
│
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
234
|
+
┌─────────────────────────────────────────────┐
|
|
235
|
+
│ new DTO() — tek giriş noktası │
|
|
236
|
+
│ runtime.express() / .fetch() / … │
|
|
237
|
+
└──────────────────┬──────────────────────────┘
|
|
238
|
+
│
|
|
239
|
+
┌──────────────────▼──────────────────────────┐
|
|
240
|
+
│ adapters/ — sadece runtime wiring │
|
|
241
|
+
│ middleware → res.builder + send sink │
|
|
242
|
+
└──────────────────┬──────────────────────────┘
|
|
243
|
+
│
|
|
244
|
+
┌──────────────────▼──────────────────────────┐
|
|
245
|
+
│ send/ — Builder, presets, transport │
|
|
246
|
+
│ .build() şema | .send() client'a yazar │
|
|
247
|
+
└──────────────────┬──────────────────────────┘
|
|
248
|
+
│
|
|
249
|
+
┌──────────────────▼──────────────────────────┐
|
|
250
|
+
│ shapes/ — ResponseDTO, LogDTO, QueueDTO │
|
|
251
|
+
│ field sırası, .schema() ezme kuralları │
|
|
252
|
+
└─────────────────────────────────────────────┘
|
|
139
253
|
```
|
|
140
254
|
|
|
255
|
+
### Tek adapter bağlamak yeter mi?
|
|
256
|
+
|
|
257
|
+
| Senaryo | Ne yaparsın |
|
|
258
|
+
|---------|-------------|
|
|
259
|
+
| **Normal HTTP app (Express vb.)** | `new DTO().express()` — başka adapter import etmezsin |
|
|
260
|
+
| **Sadece JSON üret (log, queue, test)** | `DTO.Log` / `DTO.Queue` / `builder.build()` — adapter yok |
|
|
261
|
+
| **Custom framework (Fastify vb.)** | `createBuilderRuntime(expressSink(res), …)` veya kendi sink'in |
|
|
262
|
+
| **WS + HTTP aynı app'te** | Her transport için bir kez wire: `.express()` + `.websocket()` |
|
|
263
|
+
|
|
264
|
+
**Özet:** Handler'dan client'a cevap gönderiyorsan bir adapter (veya sink) şart — ama çoğu app'te `new DTO().express()` tek satır yeter; `ExpressAdapter` import etmezsin. Dispatcher sınıfına gerek yok; adapter içinde `expressSink(res)` gibi tek satırlık `{ dispatch }` objesi var.
|
|
265
|
+
|
|
266
|
+
| Layer | Import eder misin? | Ne yapar |
|
|
267
|
+
|-------|-------------------|----------|
|
|
268
|
+
| **shapes** | Nadiren (`@xrystal/dto/log`) | Saf JSON şema |
|
|
269
|
+
| **send** | Nadiren (`@xrystal/dto/response`) | Builder, transport, presets |
|
|
270
|
+
| **adapters** | Custom entegrasyon / subpath | `builder`'ı request'e takar |
|
|
271
|
+
| **DTO** | Her zaman | Config + `runtime.express()` shortcut |
|
|
272
|
+
|
|
273
|
+
### Request lifecycle (Express)
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
1. Request gelir
|
|
277
|
+
2. ExpressAdapter middleware:
|
|
278
|
+
ContextFactory → requestId
|
|
279
|
+
createBuilderRuntime(expressSink(res), options, ctx)
|
|
280
|
+
res.builder = builder
|
|
281
|
+
3. Handler:
|
|
282
|
+
res.builder.as.ok(data).send()
|
|
283
|
+
→ .build() → FinalResult
|
|
284
|
+
→ .send() → transport.ts → expressSink → res.json()
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Şema step 2'de oluşmaz — `.build()` veya `.send()` çağrıldığında assemble edilir.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Architecture
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
src/dto/
|
|
295
|
+
├── shared.ts BaseDTO, field rules, builder config
|
|
296
|
+
├── factories.ts ContextFactory, MetadataFactory
|
|
297
|
+
├── shapes/ ← görev 1: şema
|
|
298
|
+
│ ├── response.ts
|
|
299
|
+
│ ├── log.ts
|
|
300
|
+
│ └── queue.ts
|
|
301
|
+
├── send/ ← görev 2: build + push
|
|
302
|
+
│ ├── builder.ts
|
|
303
|
+
│ ├── presets.ts
|
|
304
|
+
│ ├── transport.ts
|
|
305
|
+
│ └── plugins.ts
|
|
306
|
+
├── adapters/ runtime wiring
|
|
307
|
+
│ ├── http.ts Express, Node, Fetch, Elysia + *Sink helpers
|
|
308
|
+
│ ├── realtime.ts WebSocket, Socket.IO
|
|
309
|
+
│ ├── bind.ts DTO.express() shortcuts
|
|
310
|
+
│ └── express.ts … @xrystal/dto/express subpath shims
|
|
311
|
+
└── runtime/
|
|
312
|
+
└── index.ts DTO class, Hooks, createBuilderRuntime
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Subpath exports (`@xrystal/dto/express`, `/response`, …) unchanged — shims map to the folders above.
|
|
316
|
+
|
|
141
317
|
### Package exports
|
|
142
318
|
|
|
143
319
|
| Import | Contents |
|
|
144
320
|
|--------|----------|
|
|
145
|
-
| `@xrystal/dto` | Everything |
|
|
321
|
+
| `@xrystal/dto` | Everything (including all adapters & dispatchers) |
|
|
146
322
|
| `@xrystal/dto/dto` | `DTO`, `createDTO`, runtime helpers |
|
|
147
|
-
| `@xrystal/dto/response` | `ResponseDTO`, `Builder`,
|
|
323
|
+
| `@xrystal/dto/response` | `ResponseDTO`, `Builder`, `PresetTerminal`, transport helpers |
|
|
148
324
|
| `@xrystal/dto/log` | `LogDTO` |
|
|
149
325
|
| `@xrystal/dto/queue` | `QueueDTO` |
|
|
150
|
-
| `@xrystal/dto/express` | Express
|
|
326
|
+
| `@xrystal/dto/express` | Express adapter |
|
|
327
|
+
| `@xrystal/dto/node` | Node http adapter |
|
|
328
|
+
| `@xrystal/dto/fetch` | Fetch API adapter |
|
|
329
|
+
| `@xrystal/dto/elysia` | Elysia adapter |
|
|
330
|
+
| `@xrystal/dto/websocket` | WebSocket adapter |
|
|
331
|
+
| `@xrystal/dto/socketio` | Socket.IO adapter |
|
|
151
332
|
|
|
152
333
|
---
|
|
153
334
|
|
|
@@ -168,17 +349,20 @@ Transport (HTTP status, headers, cookies) lives on **`builder`**, not on `DTO.Re
|
|
|
168
349
|
|
|
169
350
|
### Presets (recommended)
|
|
170
351
|
|
|
352
|
+
Presets return a terminal — always end with `.send()` or `.build()`:
|
|
353
|
+
|
|
171
354
|
```typescript
|
|
172
|
-
res.builder.as.ok(data, 'Done')
|
|
173
|
-
res.builder.as.fail('Not found')
|
|
174
|
-
res.builder.as.notFound('User missing')
|
|
175
|
-
res.builder.as.tooManyRequests('Slow down')
|
|
355
|
+
return res.builder.as.ok(data, 'Done').send()
|
|
356
|
+
return res.builder.as.fail('Not found').send()
|
|
357
|
+
return res.builder.as.notFound('User missing').send()
|
|
358
|
+
return res.builder.as.tooManyRequests('Slow down').send()
|
|
359
|
+
|
|
360
|
+
// Headers before preset
|
|
361
|
+
return res.builder.header('X-Trace', traceId).as.ok(data).send()
|
|
176
362
|
```
|
|
177
363
|
|
|
178
364
|
### Manual chain
|
|
179
365
|
|
|
180
|
-
End with `.build()` when you need headers, cookies, or fine control:
|
|
181
|
-
|
|
182
366
|
```typescript
|
|
183
367
|
return res.builder
|
|
184
368
|
.httpStatus(429)
|
|
@@ -189,7 +373,14 @@ return res.builder
|
|
|
189
373
|
code: 4,
|
|
190
374
|
payload: { data: { retryAfter: 60 } },
|
|
191
375
|
})
|
|
192
|
-
.
|
|
376
|
+
.send()
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Schema only (no dispatch):
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const result = res.builder.schema({ payload: { data: user } }).build()
|
|
383
|
+
await auditLog.write(result.body)
|
|
193
384
|
```
|
|
194
385
|
|
|
195
386
|
| Chain rule | Detail |
|
|
@@ -197,6 +388,7 @@ return res.builder
|
|
|
197
388
|
| Last call wins | Later `.schema()` or `.status()` overrides earlier values |
|
|
198
389
|
| `.schema()` is body-only | HTTP status and headers stay on the chain |
|
|
199
390
|
| `payload` in `.schema()` | Replaces entire payload — no merge |
|
|
391
|
+
| Preset terminal | `.as.*()` exposes only `.build()` and `.send()` |
|
|
200
392
|
|
|
201
393
|
---
|
|
202
394
|
|
|
@@ -272,10 +464,10 @@ io.use(runtime.socketIo())
|
|
|
272
464
|
|
|
273
465
|
| Option | Affects |
|
|
274
466
|
|--------|---------|
|
|
275
|
-
| `plugins` | `builder` pipeline only |
|
|
467
|
+
| `plugins` | `builder` pipeline only (`beforeDispatch` runs on `.send()`, not `.build()`) |
|
|
276
468
|
| `codes` | `builder` presets and `.code()` |
|
|
277
|
-
| `withDefaultHeaders` | HTTP middleware |
|
|
278
|
-
| `socket.event` |
|
|
469
|
+
| `withDefaultHeaders` | HTTP / Fetch middleware |
|
|
470
|
+
| `socket.event` | Socket.IO emit name |
|
|
279
471
|
|
|
280
472
|
---
|
|
281
473
|
|
|
@@ -285,6 +477,7 @@ io.use(runtime.socketIo())
|
|
|
285
477
|
runtime.express()
|
|
286
478
|
runtime.node()
|
|
287
479
|
runtime.elysia()
|
|
480
|
+
runtime.fetch() / runtime.forRequest(request)
|
|
288
481
|
runtime.websocket() / runtime.attachWebSocket(ws)
|
|
289
482
|
runtime.socketIo() / runtime.attachSocketIo(socket)
|
|
290
483
|
```
|
|
@@ -294,9 +487,9 @@ runtime.socketIo() / runtime.attachSocketIo(socket)
|
|
|
294
487
|
```typescript
|
|
295
488
|
try {
|
|
296
489
|
const user = await fetchUser(id)
|
|
297
|
-
return res.builder.as.ok(user, 'User fetched')
|
|
490
|
+
return res.builder.as.ok(user, 'User fetched').send()
|
|
298
491
|
} catch (err) {
|
|
299
|
-
return res.builder.fromError(err).
|
|
492
|
+
return res.builder.fromError(err).send()
|
|
300
493
|
}
|
|
301
494
|
```
|
|
302
495
|
|
|
@@ -316,7 +509,7 @@ app.use(runtime.express())
|
|
|
316
509
|
|
|
317
510
|
app.get('/users/:id', async (req, res) => {
|
|
318
511
|
const user = await db.findUser(req.params.id)
|
|
319
|
-
if (!user) return res.builder.as.notFound('User not found')
|
|
512
|
+
if (!user) return res.builder.as.notFound('User not found').send()
|
|
320
513
|
|
|
321
514
|
console.log(
|
|
322
515
|
DTO.Log.level('info')
|
|
@@ -328,7 +521,7 @@ app.get('/users/:id', async (req, res) => {
|
|
|
328
521
|
|
|
329
522
|
return res.builder.as.ok(user, 'User fetched', {
|
|
330
523
|
meta: { requestId: req.ctx.requestId },
|
|
331
|
-
})
|
|
524
|
+
}).send()
|
|
332
525
|
})
|
|
333
526
|
```
|
|
334
527
|
|
|
@@ -346,7 +539,49 @@ app.get('/users/:id', async (req, res) => {
|
|
|
346
539
|
| `.tooManyRequests()` | 429 | CLIENT |
|
|
347
540
|
| `.error()` | 500 | SERVER |
|
|
348
541
|
|
|
349
|
-
See source `dto/
|
|
542
|
+
See source `dto/send/presets.ts` for the full list.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### Breaking / API changes
|
|
547
|
+
|
|
548
|
+
- **Explicit terminal verbs** — every handler chain must end with `.send()` (dispatch to client) or `.build()` (schema only). No automatic dispatch.
|
|
549
|
+
- **Preset terminal** — `.as.ok()`, `.as.fail()`, … return `PresetTerminal` with only `.build()` and `.send()`. No further chaining after presets (put headers **before** preset: `builder.header('X').as.ok(data).send()`).
|
|
550
|
+
- **WebSocket / Socket.IO wire format** — emits full transport envelope (`statusCode`, `headers`, `cookies`, `body`) instead of bare body JSON. Update clients if they parsed the old shape.
|
|
551
|
+
|
|
552
|
+
### New
|
|
553
|
+
|
|
554
|
+
- **Fetch API adapter** — `runtime.fetch()`, `runtime.forRequest(request)`, `@xrystal/dto/fetch`. Returns Web `Response` from `.send()`. Works with Workers, Hono, Bun.serve, Next.js route handlers.
|
|
555
|
+
- **Core transport layer** — `dto/send/transport.ts` centralises headers, cookies, JSON body, Fetch `Response`, and realtime wire envelope.
|
|
556
|
+
- **Exported adapters & dispatchers** — `ExpressDispatcher`, `FetchDispatcher`, `ElysiaDispatcher`, … available from main entry and subpaths for custom integrations.
|
|
557
|
+
|
|
558
|
+
### Fixes & consistency
|
|
559
|
+
|
|
560
|
+
- **Elysia** — inline dispatcher replaced with `ElysiaDispatcher` class (same pattern as Express/Node/Fetch).
|
|
561
|
+
- **Node http** — `Set-Cookie` support added (was Express-only via `res.cookie`).
|
|
562
|
+
- **Dispatch hooks** — `beforeDispatch` / `afterDispatch` plugins run on `.send()` only, not on `.build()`.
|
|
563
|
+
|
|
564
|
+
### Package surface
|
|
565
|
+
|
|
566
|
+
| Runtimes | 6 — Express, Node, Fetch, Elysia, WebSocket, Socket.IO |
|
|
567
|
+
| Tests | 77 passing |
|
|
568
|
+
| Subpaths | `/express`, `/node`, `/fetch`, `/elysia`, `/websocket`, `/socketio`, `/response`, `/log`, `/queue`, `/dto` |
|
|
569
|
+
|
|
570
|
+
### Migration cheat sheet
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// Before
|
|
574
|
+
return res.builder.as.ok(data, 'Done')
|
|
575
|
+
|
|
576
|
+
// After
|
|
577
|
+
return res.builder.as.ok(data, 'Done').send()
|
|
578
|
+
|
|
579
|
+
// Schema only (unchanged intent, now explicit)
|
|
580
|
+
const body = res.builder.as.ok(data).build()
|
|
581
|
+
|
|
582
|
+
// WS client — expect envelope now
|
|
583
|
+
// { statusCode, headers, cookies, body: { status, message, … } }
|
|
584
|
+
```
|
|
350
585
|
|
|
351
586
|
---
|
|
352
587
|
|