@xrystal/dto 26.6.27-v15 → 26.6.28-v1
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/LICENSE +21 -0
- package/README.md +177 -65
- package/dist/adapters/elysia.cjs +125 -125
- package/dist/adapters/elysia.js +125 -125
- package/dist/adapters/express.cjs +149 -128
- package/dist/adapters/express.js +149 -128
- package/dist/adapters/fetch.cjs +122 -126
- package/dist/adapters/fetch.js +122 -126
- package/dist/adapters/node.cjs +122 -126
- package/dist/adapters/node.js +122 -126
- package/dist/adapters/socketio.cjs +121 -123
- package/dist/adapters/socketio.js +121 -123
- package/dist/adapters/websocket.cjs +121 -123
- package/dist/adapters/websocket.js +121 -123
- package/dist/dto/adapters/bind.d.ts +2 -2
- package/dist/dto/adapters/elysia-adapter.d.ts +79 -0
- package/dist/dto/adapters/elysia.d.ts +1 -1
- package/dist/dto/adapters/http.d.ts +1 -76
- package/dist/dto/index.cjs +46 -27
- package/dist/dto/index.js +46 -27
- package/dist/dto/response/index.cjs +12 -2
- package/dist/dto/response/index.js +12 -2
- package/dist/dto/runtime/builder-runtime.d.ts +11 -0
- package/dist/dto/runtime/index.cjs +57 -38
- package/dist/dto/runtime/index.d.ts +7 -15
- package/dist/dto/runtime/index.js +57 -38
- package/dist/index.cjs +46 -27
- package/dist/index.js +46 -27
- package/package.json +4 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xrystal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -24,20 +24,116 @@ npm install express elysia
|
|
|
24
24
|
| 2 | **`res.builder`** / **`ctx.builder`** | Inside a handler — send API response to client |
|
|
25
25
|
| 3 | **`DTO.Response` / `DTO.Log` / `DTO.Queue`** | No request context — cron, logger, queue, tests |
|
|
26
26
|
|
|
27
|
-
**Rule of thumb:** client
|
|
27
|
+
**Rule of thumb:** client response → `builder`. Plain JSON without transport → static `DTO.*`.
|
|
28
28
|
|
|
29
|
-
### 1.
|
|
29
|
+
### 1. Bootstrap — create DTO once, wire one adapter
|
|
30
|
+
|
|
31
|
+
**Step A — app entry (single instance):**
|
|
30
32
|
|
|
31
33
|
```typescript
|
|
32
|
-
import express from 'express'
|
|
33
34
|
import { DTO } from '@xrystal/dto'
|
|
34
35
|
|
|
35
|
-
const runtime = new DTO({
|
|
36
|
+
export const runtime = new DTO({
|
|
37
|
+
withDefaultHeaders: true,
|
|
38
|
+
// optional: codes, plugins, socket: { event: 'api:response' }
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Create `new DTO()` **once** at startup. Handlers reuse the same `runtime`; do not create a new DTO per request.
|
|
43
|
+
|
|
44
|
+
**Step B — register the adapter for your server (once, at startup):**
|
|
45
|
+
|
|
46
|
+
Each method returns **middleware or a plugin** — the framework calls it per request/connection. You do **not** call `runtime.express(req, res)` inside handlers.
|
|
47
|
+
|
|
48
|
+
| Stack | Wire once (bootstrap) | In handler |
|
|
49
|
+
|-------|------------------------|------------|
|
|
50
|
+
| **Express** | `app.use(runtime.express())` | `res.builder.as.ok(...).send()` |
|
|
51
|
+
| **Node `http`** | `server.on('request', chain(runtime.node(), handler))` | `res.builder…send()` |
|
|
52
|
+
| **Elysia** | `app.use(runtime.elysia())` | `builder.as.ok(...).send()` |
|
|
53
|
+
| **Fetch / Workers** | no global wire — `runtime.forRequest(request)` per call | `builder…send()` → `Response` |
|
|
54
|
+
| **WebSocket** | `wss.on('connection', ws => runtime.attachWebSocket(ws))` | `ws.builder…send()` |
|
|
55
|
+
| **Socket.IO** | `io.use(runtime.socketIo())` or `attachSocketIo` on connect | `socket.builder…send()` |
|
|
56
|
+
|
|
57
|
+
#### Express
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import express from 'express'
|
|
61
|
+
import { runtime } from './dto.js'
|
|
62
|
+
|
|
36
63
|
const app = express()
|
|
37
|
-
app.use(runtime.express())
|
|
64
|
+
app.use(runtime.express()) // ← once: attaches req.ctx + res.builder
|
|
65
|
+
|
|
66
|
+
app.get('/users/:id', (req, res) =>
|
|
67
|
+
res.builder.as.ok({ id: req.params.id }, 'User fetched').send(),
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Elysia
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { Elysia } from 'elysia'
|
|
75
|
+
import { runtime } from './dto.js'
|
|
76
|
+
|
|
77
|
+
new Elysia()
|
|
78
|
+
.use(runtime.elysia()) // ← once: derive injects builder, requestId, startTime
|
|
79
|
+
.get('/users/:id', ({ builder, requestId, params }) =>
|
|
80
|
+
builder.as.ok({ id: params.id }, 'User fetched', { meta: { requestId } }).send(),
|
|
81
|
+
)
|
|
38
82
|
```
|
|
39
83
|
|
|
40
|
-
|
|
84
|
+
#### Socket.IO
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { Server } from 'socket.io'
|
|
88
|
+
import { runtime } from './dto.js'
|
|
89
|
+
|
|
90
|
+
const io = new Server(httpServer)
|
|
91
|
+
|
|
92
|
+
// Option 1 — middleware on every connection
|
|
93
|
+
io.use(runtime.socketIo())
|
|
94
|
+
|
|
95
|
+
io.on('connection', (socket) => {
|
|
96
|
+
socket.on('getUser', ({ id }) =>
|
|
97
|
+
socket.builder!.as.ok({ id }, 'User fetched').send(),
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Option 2 — attach manually in the connection handler
|
|
102
|
+
io.on('connection', (socket) => {
|
|
103
|
+
runtime.attachSocketIo(socket)
|
|
104
|
+
// socket.builder is now available
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### WebSocket (raw `ws`)
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { WebSocketServer } from 'ws'
|
|
112
|
+
import { runtime } from './dto.js'
|
|
113
|
+
|
|
114
|
+
const wss = new WebSocketServer({ server: httpServer })
|
|
115
|
+
|
|
116
|
+
wss.on('connection', (ws) => {
|
|
117
|
+
runtime.attachWebSocket(ws) // or wss.use pattern via runtime.websocket() middleware
|
|
118
|
+
ws.on('message', () => ws.builder!.as.ok({ pong: true }).send())
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**HTTP + realtime in one app:** wire both transports at bootstrap — e.g. `app.use(runtime.express())` and `io.use(runtime.socketIo())`.
|
|
123
|
+
|
|
124
|
+
#### With xrystal-core
|
|
125
|
+
|
|
126
|
+
In a **standalone** app you pick the adapter explicitly (`runtime.express()`, `runtime.elysia()`, …).
|
|
127
|
+
|
|
128
|
+
In **xrystal-core**, framework selection lives in core via **`dtoMiddleware(config)`** — it reads your config and wires the correct adapter (`express`, `elysia`, `socketIo`, …). DTO does not pick the framework; core does.
|
|
129
|
+
|
|
130
|
+
Standalone apps call the adapter explicitly:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
runtime.express() // not runtime.middleware('express')
|
|
134
|
+
runtime.elysia()
|
|
135
|
+
runtime.socketIo()
|
|
136
|
+
```
|
|
41
137
|
|
|
42
138
|
### 2. Respond in a handler
|
|
43
139
|
|
|
@@ -99,20 +195,6 @@ Every shape DTO shares the same chain rules:
|
|
|
99
195
|
- `.schema({ … })` for partial input — **last call wins**
|
|
100
196
|
- `payload` in `.schema()` **replaces** the entire payload
|
|
101
197
|
|
|
102
|
-
### Elysia — same idea
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
import { Elysia } from 'elysia'
|
|
106
|
-
import { DTO } from '@xrystal/dto'
|
|
107
|
-
|
|
108
|
-
const runtime = new DTO()
|
|
109
|
-
new Elysia()
|
|
110
|
-
.use(runtime.elysia())
|
|
111
|
-
.get('/users/:id', ({ builder, requestId, params }) =>
|
|
112
|
-
builder.as.ok({ id: params.id }, 'User fetched', { meta: { requestId } }).send(),
|
|
113
|
-
)
|
|
114
|
-
```
|
|
115
|
-
|
|
116
198
|
### Fetch API — Workers, Hono, Bun.serve
|
|
117
199
|
|
|
118
200
|
```typescript
|
|
@@ -176,25 +258,29 @@ WebSocket and Socket.IO send the full transport envelope (`statusCode`, `headers
|
|
|
176
258
|
|
|
177
259
|
### `new DTO()` shortcuts
|
|
178
260
|
|
|
261
|
+
Register at **bootstrap** (once). Handlers only use `builder`.
|
|
262
|
+
|
|
179
263
|
```typescript
|
|
180
264
|
const runtime = new DTO({ withDefaultHeaders: true, socket: { event: 'api:response' } })
|
|
181
265
|
|
|
182
|
-
|
|
266
|
+
// HTTP — returns middleware / plugin; pass to app.use()
|
|
267
|
+
runtime.express() // → (req, res, next) => void
|
|
268
|
+
runtime.elysia() // → Elysia plugin (derive builder per request)
|
|
183
269
|
runtime.node() // → Node http middleware
|
|
184
|
-
runtime.elysia() // → Elysia plugin
|
|
185
270
|
runtime.fetch() // → (request: Request) => { builder, requestId, … }
|
|
186
271
|
runtime.forRequest(request) // → one-shot Fetch context
|
|
187
272
|
|
|
273
|
+
// Realtime — returns connection middleware or attach helper
|
|
188
274
|
runtime.websocket() // → (socket, next?) => void
|
|
189
275
|
runtime.attachWebSocket(ws) // → attach builder to one socket
|
|
190
276
|
|
|
191
|
-
runtime.socketIo() // → (socket, next?) => void
|
|
277
|
+
runtime.socketIo() // → (socket, next?) => void (Socket.IO middleware)
|
|
192
278
|
runtime.attachSocketIo(socket) // → attach builder to one socket
|
|
193
279
|
```
|
|
194
280
|
|
|
195
281
|
### When to import adapters directly
|
|
196
282
|
|
|
197
|
-
Most apps only need `new DTO().express()` (etc.). Import adapters / sinks when you:
|
|
283
|
+
Most apps only need `new DTO().elysia()` or `new DTO().express()` (etc.). Import adapters / sinks when you:
|
|
198
284
|
|
|
199
285
|
- Integrate a **custom framework** — `createBuilderRuntime(expressSink(res), options, context)`
|
|
200
286
|
- **Unit-test** transport without full middleware
|
|
@@ -221,59 +307,59 @@ Subpath imports (tree-shake friendly):
|
|
|
221
307
|
|
|
222
308
|
---
|
|
223
309
|
|
|
224
|
-
## Core vs DTO —
|
|
310
|
+
## Core vs DTO — is one adapter enough?
|
|
225
311
|
|
|
226
|
-
|
|
312
|
+
The library has **two jobs**; the layout reflects that:
|
|
227
313
|
|
|
228
|
-
|
|
|
229
|
-
|
|
230
|
-
|
|
|
231
|
-
| **
|
|
314
|
+
| Job | Layer | Adapter required? |
|
|
315
|
+
|-----|-------|-------------------|
|
|
316
|
+
| **Build schema** | `shapes/` + `shared.ts` | **No** — `DTO.Log.build()`, `DTO.Response.build()`, tests |
|
|
317
|
+
| **Push to client** | `send/` + `adapters/` | **Yes** — `.send()` must write to a transport |
|
|
232
318
|
|
|
233
319
|
```
|
|
234
320
|
┌─────────────────────────────────────────────┐
|
|
235
|
-
│ new DTO() —
|
|
321
|
+
│ new DTO() — single entry point │
|
|
236
322
|
│ runtime.express() / .fetch() / … │
|
|
237
323
|
└──────────────────┬──────────────────────────┘
|
|
238
324
|
│
|
|
239
325
|
┌──────────────────▼──────────────────────────┐
|
|
240
|
-
│ adapters/ —
|
|
326
|
+
│ adapters/ — runtime wiring only │
|
|
241
327
|
│ middleware → res.builder + send sink │
|
|
242
328
|
└──────────────────┬──────────────────────────┘
|
|
243
329
|
│
|
|
244
330
|
┌──────────────────▼──────────────────────────┐
|
|
245
|
-
│ send/ — Builder, presets, transport
|
|
246
|
-
│ .build()
|
|
331
|
+
│ send/ — Builder, presets, transport │
|
|
332
|
+
│ .build() schema | .send() writes client │
|
|
247
333
|
└──────────────────┬──────────────────────────┘
|
|
248
334
|
│
|
|
249
335
|
┌──────────────────▼──────────────────────────┐
|
|
250
|
-
│ shapes/ — ResponseDTO, LogDTO, QueueDTO
|
|
251
|
-
│ field
|
|
336
|
+
│ shapes/ — ResponseDTO, LogDTO, QueueDTO │
|
|
337
|
+
│ field order, .schema() override rules │
|
|
252
338
|
└─────────────────────────────────────────────┘
|
|
253
339
|
```
|
|
254
340
|
|
|
255
|
-
###
|
|
341
|
+
### Is wiring one adapter enough?
|
|
256
342
|
|
|
257
|
-
|
|
|
258
|
-
|
|
259
|
-
| **Normal HTTP app
|
|
260
|
-
| **
|
|
261
|
-
| **Custom framework (Fastify
|
|
262
|
-
| **WS + HTTP
|
|
343
|
+
| Scenario | What to do |
|
|
344
|
+
|----------|------------|
|
|
345
|
+
| **Normal HTTP app** | Pick your stack: `runtime.express()`, `runtime.elysia()`, `runtime.fetch()`, … |
|
|
346
|
+
| **JSON only (log, queue, test)** | `DTO.Log` / `DTO.Queue` / `builder.build()` — no adapter |
|
|
347
|
+
| **Custom framework (Fastify, etc.)** | `createBuilderRuntime(expressSink(res), …)` or your own sink |
|
|
348
|
+
| **WS + HTTP in the same app** | Wire once per transport: e.g. `.elysia()` + `.socketIo()` |
|
|
263
349
|
|
|
264
|
-
|
|
350
|
+
**Summary:** If you send handler responses to a client, you need an adapter — pick the one that matches your server. The adapter **attaches** the sink to the builder at request start; the sink runs **only** when `.send()` is called. `.build()` returns pure schema and never touches transport.
|
|
265
351
|
|
|
266
|
-
| Layer |
|
|
267
|
-
|
|
268
|
-
| **shapes** |
|
|
269
|
-
| **send** |
|
|
270
|
-
| **adapters** | Custom
|
|
271
|
-
| **DTO** |
|
|
352
|
+
| Layer | Do you import it? | Role |
|
|
353
|
+
|-------|-------------------|------|
|
|
354
|
+
| **shapes** | Rarely (`@xrystal/dto/log`) | Pure JSON schema |
|
|
355
|
+
| **send** | Rarely (`@xrystal/dto/response`) | Builder, transport, presets |
|
|
356
|
+
| **adapters** | Custom integration / subpath | Attaches `builder` to each request |
|
|
357
|
+
| **DTO** | Always | Config + `runtime.elysia()` / `runtime.express()` shortcuts |
|
|
272
358
|
|
|
273
359
|
### Request lifecycle (Express)
|
|
274
360
|
|
|
275
361
|
```
|
|
276
|
-
1. Request
|
|
362
|
+
1. Request arrives
|
|
277
363
|
2. ExpressAdapter middleware:
|
|
278
364
|
ContextFactory → requestId
|
|
279
365
|
createBuilderRuntime(expressSink(res), options, ctx)
|
|
@@ -284,7 +370,7 @@ Kütüphanenin **iki işi** var; mimari buna göre bölündü:
|
|
|
284
370
|
→ .send() → transport.ts → expressSink → res.json()
|
|
285
371
|
```
|
|
286
372
|
|
|
287
|
-
|
|
373
|
+
Schema is **not** assembled at step 2 — only when you call `.build()` or `.send()`.
|
|
288
374
|
|
|
289
375
|
---
|
|
290
376
|
|
|
@@ -294,22 +380,24 @@ Kütüphanenin **iki işi** var; mimari buna göre bölündü:
|
|
|
294
380
|
src/dto/
|
|
295
381
|
├── shared.ts BaseDTO, field rules, builder config
|
|
296
382
|
├── factories.ts ContextFactory, MetadataFactory
|
|
297
|
-
├── shapes/ ←
|
|
383
|
+
├── shapes/ ← job 1: schema
|
|
298
384
|
│ ├── response.ts
|
|
299
385
|
│ ├── log.ts
|
|
300
386
|
│ └── queue.ts
|
|
301
|
-
├── send/ ←
|
|
387
|
+
├── send/ ← job 2: build + push
|
|
302
388
|
│ ├── builder.ts
|
|
303
389
|
│ ├── presets.ts
|
|
304
390
|
│ ├── transport.ts
|
|
305
391
|
│ └── plugins.ts
|
|
306
392
|
├── adapters/ runtime wiring
|
|
307
|
-
│ ├── http.ts Express, Node, Fetch
|
|
393
|
+
│ ├── http.ts Express, Node, Fetch + *Sink helpers
|
|
394
|
+
│ ├── elysia-adapter.ts Elysia plugin + elysiaSink (isolated for tree-shaking)
|
|
308
395
|
│ ├── realtime.ts WebSocket, Socket.IO
|
|
309
396
|
│ ├── bind.ts DTO.express() shortcuts
|
|
310
397
|
│ └── express.ts … @xrystal/dto/express subpath shims
|
|
311
398
|
└── runtime/
|
|
312
|
-
|
|
399
|
+
├── builder-runtime.ts createBuilderRuntime (adapter-safe import)
|
|
400
|
+
└── index.ts DTO class, Hooks
|
|
313
401
|
```
|
|
314
402
|
|
|
315
403
|
Subpath exports (`@xrystal/dto/express`, `/response`, …) unchanged — shims map to the folders above.
|
|
@@ -318,7 +406,7 @@ Subpath exports (`@xrystal/dto/express`, `/response`, …) unchanged — shims m
|
|
|
318
406
|
|
|
319
407
|
| Import | Contents |
|
|
320
408
|
|--------|----------|
|
|
321
|
-
| `@xrystal/dto` | Everything (including all adapters &
|
|
409
|
+
| `@xrystal/dto` | Everything (including all adapters & send sinks) |
|
|
322
410
|
| `@xrystal/dto/dto` | `DTO`, `createDTO`, runtime helpers |
|
|
323
411
|
| `@xrystal/dto/response` | `ResponseDTO`, `Builder`, `PresetTerminal`, transport helpers |
|
|
324
412
|
| `@xrystal/dto/log` | `LogDTO` |
|
|
@@ -545,6 +633,7 @@ See source `dto/send/presets.ts` for the full list.
|
|
|
545
633
|
|
|
546
634
|
### Breaking / API changes
|
|
547
635
|
|
|
636
|
+
- **Removed `runtime.middleware()`** — DTO no longer resolves framework from a string. Pick an adapter explicitly (`runtime.express()`, `runtime.elysia()`, `runtime.socketIo()`, …) or use **`dtoMiddleware(config)`** from **xrystal-core** for config-driven wiring. Removed exports: `normalizeRuntimeKind`, `RuntimeKind`.
|
|
548
637
|
- **Explicit terminal verbs** — every handler chain must end with `.send()` (dispatch to client) or `.build()` (schema only). No automatic dispatch.
|
|
549
638
|
- **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
639
|
- **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.
|
|
@@ -564,12 +653,26 @@ See source `dto/send/presets.ts` for the full list.
|
|
|
564
653
|
### Package surface
|
|
565
654
|
|
|
566
655
|
| Runtimes | 6 — Express, Node, Fetch, Elysia, WebSocket, Socket.IO |
|
|
567
|
-
| Tests |
|
|
656
|
+
| Tests | 79 passing |
|
|
568
657
|
| Subpaths | `/express`, `/node`, `/fetch`, `/elysia`, `/websocket`, `/socketio`, `/response`, `/log`, `/queue`, `/dto` |
|
|
569
658
|
|
|
570
659
|
### Migration cheat sheet
|
|
571
660
|
|
|
572
661
|
```typescript
|
|
662
|
+
// Removed — use explicit adapter or xrystal-core dtoMiddleware(config)
|
|
663
|
+
runtime.middleware('express') // ✗
|
|
664
|
+
runtime.middleware() // ✗
|
|
665
|
+
runtime.middleware('socketio') // ✗
|
|
666
|
+
|
|
667
|
+
// Standalone
|
|
668
|
+
app.use(runtime.express())
|
|
669
|
+
app.use(runtime.elysia())
|
|
670
|
+
io.use(runtime.socketIo())
|
|
671
|
+
|
|
672
|
+
// xrystal-core (config-driven)
|
|
673
|
+
import { dtoMiddleware } from 'xrystal-core/handlers' // your core export path
|
|
674
|
+
app.use(dtoMiddleware(config))
|
|
675
|
+
|
|
573
676
|
// Before
|
|
574
677
|
return res.builder.as.ok(data, 'Done')
|
|
575
678
|
|
|
@@ -585,16 +688,25 @@ const body = res.builder.as.ok(data).build()
|
|
|
585
688
|
|
|
586
689
|
---
|
|
587
690
|
|
|
588
|
-
##
|
|
691
|
+
## Contributing
|
|
589
692
|
|
|
590
|
-
|
|
693
|
+
Contributions are welcome. Open an issue to discuss larger changes, or submit a pull request with a focused diff and tests where behaviour changes.
|
|
694
|
+
|
|
695
|
+
Before opening a PR, run:
|
|
591
696
|
|
|
592
697
|
```bash
|
|
593
|
-
npm
|
|
594
|
-
npm run
|
|
595
|
-
npm run version:check
|
|
698
|
+
npm test
|
|
699
|
+
npm run lint
|
|
596
700
|
```
|
|
597
701
|
|
|
598
|
-
|
|
702
|
+
## License
|
|
703
|
+
|
|
704
|
+
[MIT](LICENSE) © [xrystal](https://github.com/xdloper)
|
|
705
|
+
|
|
706
|
+
## Links
|
|
599
707
|
|
|
600
|
-
|
|
708
|
+
| | |
|
|
709
|
+
|---|---|
|
|
710
|
+
| **npm** | [`@xrystal/dto`](https://www.npmjs.com/package/@xrystal/dto) |
|
|
711
|
+
| **Repository** | [github.com/xdloper/dto](https://github.com/xdloper/dto) |
|
|
712
|
+
| **Issues** | [github.com/xdloper/dto/issues](https://github.com/xdloper/dto/issues) |
|