@haddowg/json-api-client 1.0.0
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 +344 -0
- package/dist/ajv.cjs +62 -0
- package/dist/ajv.d.cts +55 -0
- package/dist/ajv.d.cts.map +1 -0
- package/dist/ajv.d.mts +55 -0
- package/dist/ajv.d.mts.map +1 -0
- package/dist/ajv.mjs +62 -0
- package/dist/ajv.mjs.map +1 -0
- package/dist/index.cjs +1254 -0
- package/dist/index.d.cts +198 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +198 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1232 -0
- package/dist/index.mjs.map +1 -0
- package/dist/validate-BmyPbJCK.d.cts +1254 -0
- package/dist/validate-BmyPbJCK.d.cts.map +1 -0
- package/dist/validate-BmyPbJCK.d.mts +1254 -0
- package/dist/validate-BmyPbJCK.d.mts.map +1 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gregory Haddow
|
|
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
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# @haddowg/json-api-client
|
|
2
|
+
|
|
3
|
+
> **Part of the [jsonapi.rest](https://jsonapi.rest) suite** — a complete, spec-compliant
|
|
4
|
+
> JSON:API 1.1 stack: a framework-agnostic PHP [core](https://github.com/haddowg/json-api), a
|
|
5
|
+
> [Symfony bundle](https://github.com/haddowg/json-api-symfony), a
|
|
6
|
+
> [Laravel package](https://github.com/haddowg/json-api-laravel), and this **typed TypeScript
|
|
7
|
+
> client**, bound together by one conformance-tested OpenAPI 3.1 contract.
|
|
8
|
+
> Full documentation: **[haddowg.github.io/json-api-ts](https://haddowg.github.io/json-api-ts/)**.
|
|
9
|
+
|
|
10
|
+
The generic, framework-agnostic runtime for a typesafe JSON:API client. Parameterised by a
|
|
11
|
+
descriptor emitted by [`@haddowg/json-api-codegen`](../codegen) — the codegen reads your API's
|
|
12
|
+
OpenAPI document; this package does the de/serialisation, hydration, and the fluent surface.
|
|
13
|
+
|
|
14
|
+
Works standalone (`await client.albums.list()` needs no framework). For TanStack Query caching +
|
|
15
|
+
normalization, add [`@haddowg/json-api-query`](../query).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @haddowg/json-api-client
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You generally don't construct the client from this package directly — the generated module
|
|
24
|
+
re-exports a descriptor-bound `createClient`. The snippets below assume that generated
|
|
25
|
+
`createClient` (see the [codegen docs](../codegen)).
|
|
26
|
+
|
|
27
|
+
## Creating a client
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createClient } from './api/music.gen'
|
|
31
|
+
|
|
32
|
+
const client = createClient({
|
|
33
|
+
baseUrl: 'https://music.example',
|
|
34
|
+
// transport?: defaults to the global `fetch` adapter when omitted
|
|
35
|
+
// headers?: per-request header provider (e.g. bearer auth); may be async
|
|
36
|
+
headers: async () => ({ Authorization: `Bearer ${await getToken()}` }),
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`ClientOptions`:
|
|
41
|
+
|
|
42
|
+
| Option | Type | Notes |
|
|
43
|
+
| ----------- | ------------------------------------------- | ------------------------------------------------------------ |
|
|
44
|
+
| `baseUrl` | `string` | Base URL the generated path templates resolve against. |
|
|
45
|
+
| `transport` | `(req) => Promise<res>` | The transport seam; defaults to a `fetch` adapter. |
|
|
46
|
+
| `headers` | `() => HeadersInit \| Promise<HeadersInit>` | Per-request header provider (bearer auth, etc.). |
|
|
47
|
+
| `atomic` | `AtomicDescriptor \| null` | Atomic endpoint; the generated `createClient` threads it in. |
|
|
48
|
+
| `validate` | `ValidationOption` | Opt-in per-field validation seam (off by default). |
|
|
49
|
+
|
|
50
|
+
## Reads
|
|
51
|
+
|
|
52
|
+
A resource read returns a **materialised** value: attributes and `type`/`id` are flat, enumerable
|
|
53
|
+
props (clean to spread / `JSON.stringify`); the envelope hangs off non-enumerable `$`-accessors.
|
|
54
|
+
|
|
55
|
+
### List a collection — filter / sort / include / fields / page
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const albums = await client.albums.list({
|
|
59
|
+
filter: { status: 'released' },
|
|
60
|
+
sort: '-releasedAt',
|
|
61
|
+
include: ['artist'], // widens `artist` to a hydrated resource in the result type
|
|
62
|
+
fields: { albums: ['title', 'status', 'artist'] }, // sparse — other attrs are statically absent
|
|
63
|
+
page: { number: 1 },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
albums[0]!.title // string
|
|
67
|
+
albums[0]!.artist?.name // hydrated artist resource (its attributes are typed)
|
|
68
|
+
albums.$page.kind // 'page' | 'offset' | 'cursor' | 'none'
|
|
69
|
+
albums.$meta?.['page'] // raw top-level meta
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> A relation kept in `include` must also stay in the `fields` set for that type, or it is narrowed
|
|
73
|
+
> off the result type.
|
|
74
|
+
|
|
75
|
+
### Get one — with a hydrated compound document
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const ok = await client.albums.get('1', { include: ['artist', 'tracks'] })
|
|
79
|
+
|
|
80
|
+
ok.title // string
|
|
81
|
+
ok.$self // resource-level self link (non-enumerable)
|
|
82
|
+
ok.tracks // the to-many `tracks` is a hydrated augmented array of track resources
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Resource-level `$`-accessors
|
|
86
|
+
|
|
87
|
+
A materialised resource carries (all non-enumerable):
|
|
88
|
+
|
|
89
|
+
- `$meta` — resource-level meta
|
|
90
|
+
- `$links` / `$self` — resource-level links (`$self` is the `self` link shorthand)
|
|
91
|
+
- `$document` — the shared, trimmed top-level `{ jsonapi, meta, links }`
|
|
92
|
+
- `$raw` — the original JSON:API resource object (escape hatch)
|
|
93
|
+
- `$rel(name)` — `{ data, links, meta }` for a relation (links-only relations + introspection)
|
|
94
|
+
|
|
95
|
+
### Follow a relationship
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// The related RESOURCES (full objects): GET /albums/1/tracks
|
|
99
|
+
const tracks = await client.albums
|
|
100
|
+
.id('1')
|
|
101
|
+
.rel('tracks')
|
|
102
|
+
.related({ page: { size: 2 } })
|
|
103
|
+
tracks.map((t) => t.title)
|
|
104
|
+
tracks.$page.kind
|
|
105
|
+
|
|
106
|
+
// The LINKAGE (resource identifiers): GET /albums/1/relationships/tracks
|
|
107
|
+
const linkage = await client.albums.id('1').rel('tracks').get()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pivot data on a many-to-many edge
|
|
111
|
+
|
|
112
|
+
Each member of a `belongsToMany` to-many carries its per-edge pivot under `$pivot` (typed sugar
|
|
113
|
+
over `meta.pivot`); the same resource in another parent carries a different pivot.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const ordered = await client.playlists.id(playlistId).rel('orderedTracks').related()
|
|
117
|
+
ordered[0]!.$pivot?.['position'] // edge-local pivot data
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `withCount` (the Countable profile)
|
|
121
|
+
|
|
122
|
+
`withCount` is constrained to the type's count tokens and only available on `list` / related
|
|
123
|
+
reads (no single-`get` endpoint advertises it). The client negotiates the Countable profile in
|
|
124
|
+
`Accept` automatically when `withCount` is present.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const albums = await client.albums.list({ withCount: ['tracks'] })
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Pagination & cursor
|
|
131
|
+
|
|
132
|
+
All three collection surfaces (top-level lists, related collections, to-many relationship arrays)
|
|
133
|
+
expose the same `$page` + `$next()` / `$prev()`. Navigation is **count-free-safe** — driven by
|
|
134
|
+
link presence, not a total. Cursor pagination is just a paginator `kind`; page with
|
|
135
|
+
`page[cursor]` / `page[size]` query params and walk it with `$next()`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
let page = await client.tracks.list({ page: { size: 50 } })
|
|
139
|
+
while (page) {
|
|
140
|
+
for (const track of page) {
|
|
141
|
+
/* … */
|
|
142
|
+
}
|
|
143
|
+
page = await page.$next() // undefined when there is no next link
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Writes
|
|
148
|
+
|
|
149
|
+
Flat ergonomic input — the client builds the JSON:API document. Create / update return the
|
|
150
|
+
materialised resource; delete resolves `void`.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
// POST /albums — `id` is required/optional/forbidden per the spec's per-type policy.
|
|
154
|
+
const created = await client.albums.create({ title: 'Kid A', status: 'released' })
|
|
155
|
+
|
|
156
|
+
// PATCH /albums/1 — a partial patch.
|
|
157
|
+
const updated = await client.albums.id('1').update({ title: 'OK Computer (Remaster)' })
|
|
158
|
+
|
|
159
|
+
// DELETE /albums/1 — resolves void (a 204).
|
|
160
|
+
await client.albums.id('1').delete()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Create / update accept an optional second arg `{ include?, fields? }` to shape the materialised
|
|
164
|
+
response, exactly like a read.
|
|
165
|
+
|
|
166
|
+
### Relationship mutation
|
|
167
|
+
|
|
168
|
+
A to-many relation exposes `add` / `remove` / `replace`; a to-one exposes `set(ref | null)`. A
|
|
169
|
+
verb is present only when the relation's endpoint advertises it (the descriptor's per-relation
|
|
170
|
+
`mutations` flags, derived from the bundle's `cannotAdd` / `cannotRemove` / `cannotReplace` +
|
|
171
|
+
endpoint exposure) — a forbidden verb is a compile error, not a runtime surprise.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// to-many: POST / DELETE / PATCH .../relationships/tracks
|
|
175
|
+
await client.albums
|
|
176
|
+
.id('1')
|
|
177
|
+
.rel('tracks')
|
|
178
|
+
.add([{ type: 'tracks', id: '4' }])
|
|
179
|
+
await client.albums
|
|
180
|
+
.id('1')
|
|
181
|
+
.rel('tracks')
|
|
182
|
+
.remove([{ type: 'tracks', id: '4' }])
|
|
183
|
+
await client.albums
|
|
184
|
+
.id('1')
|
|
185
|
+
.rel('tracks')
|
|
186
|
+
.replace([
|
|
187
|
+
{ type: 'tracks', id: '1' },
|
|
188
|
+
{ type: 'tracks', id: '3' },
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
// to-one: PATCH .../relationships/artist
|
|
192
|
+
await client.albums.id('1').rel('artist').set({ type: 'artists', id: '2' })
|
|
193
|
+
await client.albums.id('1').rel('artist').set(null) // clear
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> **Verb / relation collision:** relations are reachable via `.rel('name')` (and, for the common
|
|
197
|
+
> case, by name directly off the handle). A relation named like a reserved verb (`get`, `update`,
|
|
198
|
+
> `delete`, `actions`, …) is shadowed — the codegen warns at build time and you reach it via
|
|
199
|
+
> `.rel('name')`.
|
|
200
|
+
|
|
201
|
+
## Custom actions
|
|
202
|
+
|
|
203
|
+
Actions are reached via `.actions.<name>`: collection-scoped off the type accessor, resource-scoped
|
|
204
|
+
off a handle (`.id(id)`). The body shape follows the action's declared `input`
|
|
205
|
+
(`none` / `document` / `raw`); a `document` output is materialised, a `none` output resolves
|
|
206
|
+
`undefined`.
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
// Resource-scoped, document in / document out.
|
|
210
|
+
const reissued = await client.albums.id('1').actions.reissue({
|
|
211
|
+
data: { type: 'albums', attributes: { title: 'OK Computer' } },
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// Collection-scoped, no input.
|
|
215
|
+
const summary = await client.albums.actions.summary()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Atomic operations
|
|
219
|
+
|
|
220
|
+
`client.atomic(tx => …)` records ops in order and posts them as one all-or-nothing batch (the
|
|
221
|
+
atomic ext media type). A `tx.create(...)` handle **doubles as a `lid` ref**, so a just-created
|
|
222
|
+
resource wires into later ops without a server id. Results are typed positionally by op index.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const [artist, geogaddi] = await client.atomic((tx) => {
|
|
226
|
+
const newArtist = tx.create({ type: 'artists', name: 'Boards of Canada' })
|
|
227
|
+
const newAlbum = tx.create({ type: 'albums', title: 'Geogaddi', artist: newArtist })
|
|
228
|
+
return [newArtist, newAlbum] as const
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
artist.data.id // '99'
|
|
232
|
+
geogaddi.data.title // 'Geogaddi'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`tx` also exposes `tx.update({ type, id, …fields })` and `tx.delete({ type, id } | ref)`. Calling
|
|
236
|
+
`client.atomic` on a server that exposes no atomic endpoint throws.
|
|
237
|
+
|
|
238
|
+
## Errors
|
|
239
|
+
|
|
240
|
+
A non-2xx throws a typed `JsonApiError` carrying `status` + `errors: JsonApiErrorObject[]`, with
|
|
241
|
+
expressive status matchers and pointer grouping:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import type { JsonApiError } from '@haddowg/json-api-client'
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await client.albums.create({ title: '' })
|
|
248
|
+
} catch (e) {
|
|
249
|
+
const error = e as JsonApiError
|
|
250
|
+
error.isUnprocessable() // 422 (alias: isValidationError())
|
|
251
|
+
// Server pointers (`/data/attributes/title`) are remapped to your FLAT input path (`title`).
|
|
252
|
+
error.byPath()['title']?.[0]?.detail // 'must not be blank'
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Status matchers: `hasStatus(n)`, `is4xx()`, `is5xx()`, `isBadRequest()` /400,
|
|
257
|
+
`isUnauthorized()` /401, `isForbidden()` /403, `isNotFound()` /404, `isNotAcceptable()` /406,
|
|
258
|
+
`isConflict()` /409, `isUnsupportedMediaType()` /415, `isUnprocessable()` /
|
|
259
|
+
`isValidationError()` /422, `isRateLimited()` /429.
|
|
260
|
+
|
|
261
|
+
`byPath()` groups errors by the flat input path (falling back to the raw pointer, then the query
|
|
262
|
+
`parameter`) — the payoff for form/validation UX. Each error keeps its raw `source.pointer` as an
|
|
263
|
+
escape hatch; atomic errors also carry an `opIndex`.
|
|
264
|
+
|
|
265
|
+
> A response that parses but violates the JSON:API envelope shape (not a document, a member
|
|
266
|
+
> missing `type`/`id`) throws a `StructuralGuardError` — distinct from a server-reported
|
|
267
|
+
> `JsonApiError`. These light structural guards are always on; full per-field validation is opt-in
|
|
268
|
+
> (below).
|
|
269
|
+
|
|
270
|
+
## Transport
|
|
271
|
+
|
|
272
|
+
The transport is a tiny `fetch`-shaped function, so any impl (native `fetch`, undici, an axios
|
|
273
|
+
adapter, or a test mock) drops in:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import type {
|
|
277
|
+
JsonApiTransport,
|
|
278
|
+
TransportRequest,
|
|
279
|
+
TransportResponse,
|
|
280
|
+
} from '@haddowg/json-api-client'
|
|
281
|
+
|
|
282
|
+
const transport: JsonApiTransport = async (req: TransportRequest): Promise<TransportResponse> => {
|
|
283
|
+
const res = await myHttpLib(req.method, req.url, { headers: req.headers, body: req.body })
|
|
284
|
+
return { status: res.status, headers: res.headers, body: res.text }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const client = createClient({ baseUrl: 'https://music.example', transport })
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Retries are deliberately out of scope here — the transport (or the TanStack layer) owns them.
|
|
291
|
+
|
|
292
|
+
## Opt-in validation
|
|
293
|
+
|
|
294
|
+
The core runtime trusts the wire (you own both ends; the envelope is invariant) and only runs the
|
|
295
|
+
light structural guards. Full per-field validation is **opt-in** via the `validate?` option, fed by
|
|
296
|
+
the codegen-emitted per-type `schemas` map. The validation engine is brought by you, never in the
|
|
297
|
+
core dep tree.
|
|
298
|
+
|
|
299
|
+
### With the bundled ajv adapter
|
|
300
|
+
|
|
301
|
+
`ajv` is an optional peer dependency; the adapter lives at the `@haddowg/json-api-client/ajv`
|
|
302
|
+
sub-path, so importing the main entry never pulls ajv in.
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
pnpm add ajv
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import Ajv2020 from 'ajv/dist/2020'
|
|
310
|
+
import { createClient } from './api/music.gen'
|
|
311
|
+
import { createAjvValidator } from '@haddowg/json-api-client/ajv'
|
|
312
|
+
import { schemas } from './api/music.schemas.gen' // the codegen's `--schemas` output
|
|
313
|
+
|
|
314
|
+
// The bundle emits JSON Schema 2020-12, so use `Ajv2020`. `strict: false` tolerates the
|
|
315
|
+
// schemas' `x-enum-*` annotations; `allErrors` aggregates every failing field.
|
|
316
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false, validateFormats: false })
|
|
317
|
+
|
|
318
|
+
const client = createClient({
|
|
319
|
+
baseUrl: 'https://music.example',
|
|
320
|
+
validate: createAjvValidator(ajv, schemas),
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Every wire resource is now validated against `schemas[type]`; a type the bundle does not cover is
|
|
325
|
+
skipped (graceful partial coverage). A failure throws an `AjvValidationError` carrying every
|
|
326
|
+
failing `{ type, id?, pointer, keyword, message }`.
|
|
327
|
+
|
|
328
|
+
### With your own engine
|
|
329
|
+
|
|
330
|
+
`validate` also accepts the schema-driven config `{ schemas, validator }` or a bare
|
|
331
|
+
`(resource, schema) => void` validator that owns its own schema lookup:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const client = createClient({
|
|
335
|
+
baseUrl: 'https://music.example',
|
|
336
|
+
validate: (resource) => {
|
|
337
|
+
// throw on an invalid resource; return for a valid one
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT
|
package/dist/ajv.cjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/ajv.ts
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by the ajv adapter when a wire resource fails its per-type schema. Aggregates every
|
|
5
|
+
* ajv error into {@link AjvValidationFailure}s (one per failing field) and renders a message
|
|
6
|
+
* listing the failing pointers, so a caller can both read the summary and walk `failures`.
|
|
7
|
+
*/
|
|
8
|
+
var AjvValidationError = class extends Error {
|
|
9
|
+
/** Every failing field across the resource (ajv runs with `allErrors`-aware reporting if enabled). */
|
|
10
|
+
failures;
|
|
11
|
+
/** The JSON:API type of the resource that failed. */
|
|
12
|
+
type;
|
|
13
|
+
/** The id of the resource that failed, when present. */
|
|
14
|
+
resourceId;
|
|
15
|
+
constructor(type, id, failures) {
|
|
16
|
+
const at = id === void 0 ? type : `${type}:${id}`;
|
|
17
|
+
const pointers = failures.map((f) => `${f.pointer || "/"} ${f.message}`).join("; ");
|
|
18
|
+
super(`Resource ${at} failed schema validation: ${pointers}`);
|
|
19
|
+
this.name = "AjvValidationError";
|
|
20
|
+
this.type = type;
|
|
21
|
+
if (id !== void 0) this.resourceId = id;
|
|
22
|
+
this.failures = failures;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** Map ajv's raw `ErrorObject`s for one resource to the adapter's typed failures. */
|
|
26
|
+
function toFailures(type, id, errors) {
|
|
27
|
+
return errors.map((e) => {
|
|
28
|
+
const failure = {
|
|
29
|
+
type,
|
|
30
|
+
pointer: e.instancePath,
|
|
31
|
+
keyword: e.keyword,
|
|
32
|
+
message: e.message ?? "is invalid"
|
|
33
|
+
};
|
|
34
|
+
if (id !== void 0) failure.id = id;
|
|
35
|
+
return failure;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the client's {@link Validator} from a user-brought ajv instance and the codegen's per-type
|
|
40
|
+
* `schemas` map. Each schema is compiled ONCE up front (keyed by JSON:API type) with the instance's
|
|
41
|
+
* configured dialect — pass `new Ajv2020()` for the 2020-12 dialect the bundle emits. The returned
|
|
42
|
+
* function is the bare-`Validator` seam form: it looks the compiled validator up by `resource.type`,
|
|
43
|
+
* skips a type the bundle does not cover (graceful partial coverage), and on a failing resource
|
|
44
|
+
* throws an aggregated {@link AjvValidationError} listing the failing pointers.
|
|
45
|
+
*
|
|
46
|
+
* @param ajv A configured ajv instance ({@link AjvLike}) — typically `new Ajv2020({ allErrors: true })`.
|
|
47
|
+
* @param schemas The codegen-emitted `schemas` map (type -> resource-object JSON Schema 2020-12 doc).
|
|
48
|
+
*/
|
|
49
|
+
function createAjvValidator(ajv, schemas) {
|
|
50
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const [type, schema] of Object.entries(schemas)) compiled.set(type, ajv.compile(schema));
|
|
52
|
+
const validator = (resource) => {
|
|
53
|
+
const { type, id } = resource;
|
|
54
|
+
const validate = compiled.get(type);
|
|
55
|
+
if (validate === void 0) return;
|
|
56
|
+
if (!validate(resource)) throw new AjvValidationError(type, id, toFailures(type, id, validate.errors ?? []));
|
|
57
|
+
};
|
|
58
|
+
return validator;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
exports.AjvValidationError = AjvValidationError;
|
|
62
|
+
exports.createAjvValidator = createAjvValidator;
|
package/dist/ajv.d.cts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { a as Validator, n as SchemaMap } from "./validate-BmyPbJCK.cjs";
|
|
2
|
+
import { ValidateFunction } from "ajv";
|
|
3
|
+
|
|
4
|
+
//#region src/ajv.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The minimal ajv surface the adapter uses: `compile(schema)` -> a validate function. Any
|
|
7
|
+
* ajv-core instance satisfies it — typically `new Ajv2020()` (the JSON Schema 2020-12 dialect
|
|
8
|
+
* the bundle emits), but a base `Ajv` with the 2020 meta-schema added works too. Typed
|
|
9
|
+
* structurally so the adapter binds to the capability, not a specific ajv subclass.
|
|
10
|
+
*/
|
|
11
|
+
interface AjvLike {
|
|
12
|
+
compile(schema: unknown): ValidateFunction;
|
|
13
|
+
}
|
|
14
|
+
/** One failing field: the resource it came from plus ajv's pointer, keyword, and message. */
|
|
15
|
+
interface AjvValidationFailure {
|
|
16
|
+
/** The JSON:API type of the resource that failed (`schemas[type]` was applied). */
|
|
17
|
+
type: string;
|
|
18
|
+
/** The resource id, when present (a bare identifier or a full resource object both carry one). */
|
|
19
|
+
id?: string;
|
|
20
|
+
/** ajv's `instancePath` into the resource (e.g. `/attributes/title`); empty string for the root. */
|
|
21
|
+
pointer: string;
|
|
22
|
+
/** The failing ajv keyword (e.g. `type`, `maxLength`, `enum`). */
|
|
23
|
+
keyword: string;
|
|
24
|
+
/** ajv's human-readable message (e.g. `must be string`). */
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown by the ajv adapter when a wire resource fails its per-type schema. Aggregates every
|
|
29
|
+
* ajv error into {@link AjvValidationFailure}s (one per failing field) and renders a message
|
|
30
|
+
* listing the failing pointers, so a caller can both read the summary and walk `failures`.
|
|
31
|
+
*/
|
|
32
|
+
declare class AjvValidationError extends Error {
|
|
33
|
+
/** Every failing field across the resource (ajv runs with `allErrors`-aware reporting if enabled). */
|
|
34
|
+
readonly failures: readonly AjvValidationFailure[];
|
|
35
|
+
/** The JSON:API type of the resource that failed. */
|
|
36
|
+
readonly type: string;
|
|
37
|
+
/** The id of the resource that failed, when present. */
|
|
38
|
+
readonly resourceId?: string;
|
|
39
|
+
constructor(type: string, id: string | undefined, failures: readonly AjvValidationFailure[]);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the client's {@link Validator} from a user-brought ajv instance and the codegen's per-type
|
|
43
|
+
* `schemas` map. Each schema is compiled ONCE up front (keyed by JSON:API type) with the instance's
|
|
44
|
+
* configured dialect — pass `new Ajv2020()` for the 2020-12 dialect the bundle emits. The returned
|
|
45
|
+
* function is the bare-`Validator` seam form: it looks the compiled validator up by `resource.type`,
|
|
46
|
+
* skips a type the bundle does not cover (graceful partial coverage), and on a failing resource
|
|
47
|
+
* throws an aggregated {@link AjvValidationError} listing the failing pointers.
|
|
48
|
+
*
|
|
49
|
+
* @param ajv A configured ajv instance ({@link AjvLike}) — typically `new Ajv2020({ allErrors: true })`.
|
|
50
|
+
* @param schemas The codegen-emitted `schemas` map (type -> resource-object JSON Schema 2020-12 doc).
|
|
51
|
+
*/
|
|
52
|
+
declare function createAjvValidator(ajv: AjvLike, schemas: SchemaMap): Validator;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { AjvLike, AjvValidationError, AjvValidationFailure, createAjvValidator };
|
|
55
|
+
//# sourceMappingURL=ajv.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajv.d.cts","names":[],"sources":["../src/ajv.ts"],"mappings":";;;;;;;;;;UA2CiB,OAAA;EACf,OAAA,CAAQ,MAAA,YAAkB,gBAAgB;AAAA;;UAI3B,oBAAA;EAsEuB;EApEtC,IAAA;EAoEoE;EAlEpE,EAAA;EAkE6E;EAhE7E,OAAA;EAgEiC;EA9DjC,OAAA;EA8D+C;EA5D/C,OAAA;AAAA;AA4D6E;;;;;AAAA,cApDlE,kBAAA,SAA2B,KAAA;;WAE7B,QAAA,WAAmB,oBAAA;;WAEnB,IAAA;;WAEA,UAAA;cAEG,IAAA,UAAc,EAAA,sBAAwB,QAAA,WAAmB,oBAAA;AAAA;;;;;;;;;;;;iBA4CvD,kBAAA,CAAmB,GAAA,EAAK,OAAA,EAAS,OAAA,EAAS,SAAA,GAAY,SAAA"}
|
package/dist/ajv.d.mts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { a as Validator, n as SchemaMap } from "./validate-BmyPbJCK.mjs";
|
|
2
|
+
import { ValidateFunction } from "ajv";
|
|
3
|
+
|
|
4
|
+
//#region src/ajv.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The minimal ajv surface the adapter uses: `compile(schema)` -> a validate function. Any
|
|
7
|
+
* ajv-core instance satisfies it — typically `new Ajv2020()` (the JSON Schema 2020-12 dialect
|
|
8
|
+
* the bundle emits), but a base `Ajv` with the 2020 meta-schema added works too. Typed
|
|
9
|
+
* structurally so the adapter binds to the capability, not a specific ajv subclass.
|
|
10
|
+
*/
|
|
11
|
+
interface AjvLike {
|
|
12
|
+
compile(schema: unknown): ValidateFunction;
|
|
13
|
+
}
|
|
14
|
+
/** One failing field: the resource it came from plus ajv's pointer, keyword, and message. */
|
|
15
|
+
interface AjvValidationFailure {
|
|
16
|
+
/** The JSON:API type of the resource that failed (`schemas[type]` was applied). */
|
|
17
|
+
type: string;
|
|
18
|
+
/** The resource id, when present (a bare identifier or a full resource object both carry one). */
|
|
19
|
+
id?: string;
|
|
20
|
+
/** ajv's `instancePath` into the resource (e.g. `/attributes/title`); empty string for the root. */
|
|
21
|
+
pointer: string;
|
|
22
|
+
/** The failing ajv keyword (e.g. `type`, `maxLength`, `enum`). */
|
|
23
|
+
keyword: string;
|
|
24
|
+
/** ajv's human-readable message (e.g. `must be string`). */
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown by the ajv adapter when a wire resource fails its per-type schema. Aggregates every
|
|
29
|
+
* ajv error into {@link AjvValidationFailure}s (one per failing field) and renders a message
|
|
30
|
+
* listing the failing pointers, so a caller can both read the summary and walk `failures`.
|
|
31
|
+
*/
|
|
32
|
+
declare class AjvValidationError extends Error {
|
|
33
|
+
/** Every failing field across the resource (ajv runs with `allErrors`-aware reporting if enabled). */
|
|
34
|
+
readonly failures: readonly AjvValidationFailure[];
|
|
35
|
+
/** The JSON:API type of the resource that failed. */
|
|
36
|
+
readonly type: string;
|
|
37
|
+
/** The id of the resource that failed, when present. */
|
|
38
|
+
readonly resourceId?: string;
|
|
39
|
+
constructor(type: string, id: string | undefined, failures: readonly AjvValidationFailure[]);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the client's {@link Validator} from a user-brought ajv instance and the codegen's per-type
|
|
43
|
+
* `schemas` map. Each schema is compiled ONCE up front (keyed by JSON:API type) with the instance's
|
|
44
|
+
* configured dialect — pass `new Ajv2020()` for the 2020-12 dialect the bundle emits. The returned
|
|
45
|
+
* function is the bare-`Validator` seam form: it looks the compiled validator up by `resource.type`,
|
|
46
|
+
* skips a type the bundle does not cover (graceful partial coverage), and on a failing resource
|
|
47
|
+
* throws an aggregated {@link AjvValidationError} listing the failing pointers.
|
|
48
|
+
*
|
|
49
|
+
* @param ajv A configured ajv instance ({@link AjvLike}) — typically `new Ajv2020({ allErrors: true })`.
|
|
50
|
+
* @param schemas The codegen-emitted `schemas` map (type -> resource-object JSON Schema 2020-12 doc).
|
|
51
|
+
*/
|
|
52
|
+
declare function createAjvValidator(ajv: AjvLike, schemas: SchemaMap): Validator;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { AjvLike, AjvValidationError, AjvValidationFailure, createAjvValidator };
|
|
55
|
+
//# sourceMappingURL=ajv.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajv.d.mts","names":[],"sources":["../src/ajv.ts"],"mappings":";;;;;;;;;;UA2CiB,OAAA;EACf,OAAA,CAAQ,MAAA,YAAkB,gBAAgB;AAAA;;UAI3B,oBAAA;EAsEuB;EApEtC,IAAA;EAoEoE;EAlEpE,EAAA;EAkE6E;EAhE7E,OAAA;EAgEiC;EA9DjC,OAAA;EA8D+C;EA5D/C,OAAA;AAAA;AA4D6E;;;;;AAAA,cApDlE,kBAAA,SAA2B,KAAA;;WAE7B,QAAA,WAAmB,oBAAA;;WAEnB,IAAA;;WAEA,UAAA;cAEG,IAAA,UAAc,EAAA,sBAAwB,QAAA,WAAmB,oBAAA;AAAA;;;;;;;;;;;;iBA4CvD,kBAAA,CAAmB,GAAA,EAAK,OAAA,EAAS,OAAA,EAAS,SAAA,GAAY,SAAA"}
|
package/dist/ajv.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/ajv.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by the ajv adapter when a wire resource fails its per-type schema. Aggregates every
|
|
4
|
+
* ajv error into {@link AjvValidationFailure}s (one per failing field) and renders a message
|
|
5
|
+
* listing the failing pointers, so a caller can both read the summary and walk `failures`.
|
|
6
|
+
*/
|
|
7
|
+
var AjvValidationError = class extends Error {
|
|
8
|
+
/** Every failing field across the resource (ajv runs with `allErrors`-aware reporting if enabled). */
|
|
9
|
+
failures;
|
|
10
|
+
/** The JSON:API type of the resource that failed. */
|
|
11
|
+
type;
|
|
12
|
+
/** The id of the resource that failed, when present. */
|
|
13
|
+
resourceId;
|
|
14
|
+
constructor(type, id, failures) {
|
|
15
|
+
const at = id === void 0 ? type : `${type}:${id}`;
|
|
16
|
+
const pointers = failures.map((f) => `${f.pointer || "/"} ${f.message}`).join("; ");
|
|
17
|
+
super(`Resource ${at} failed schema validation: ${pointers}`);
|
|
18
|
+
this.name = "AjvValidationError";
|
|
19
|
+
this.type = type;
|
|
20
|
+
if (id !== void 0) this.resourceId = id;
|
|
21
|
+
this.failures = failures;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Map ajv's raw `ErrorObject`s for one resource to the adapter's typed failures. */
|
|
25
|
+
function toFailures(type, id, errors) {
|
|
26
|
+
return errors.map((e) => {
|
|
27
|
+
const failure = {
|
|
28
|
+
type,
|
|
29
|
+
pointer: e.instancePath,
|
|
30
|
+
keyword: e.keyword,
|
|
31
|
+
message: e.message ?? "is invalid"
|
|
32
|
+
};
|
|
33
|
+
if (id !== void 0) failure.id = id;
|
|
34
|
+
return failure;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the client's {@link Validator} from a user-brought ajv instance and the codegen's per-type
|
|
39
|
+
* `schemas` map. Each schema is compiled ONCE up front (keyed by JSON:API type) with the instance's
|
|
40
|
+
* configured dialect — pass `new Ajv2020()` for the 2020-12 dialect the bundle emits. The returned
|
|
41
|
+
* function is the bare-`Validator` seam form: it looks the compiled validator up by `resource.type`,
|
|
42
|
+
* skips a type the bundle does not cover (graceful partial coverage), and on a failing resource
|
|
43
|
+
* throws an aggregated {@link AjvValidationError} listing the failing pointers.
|
|
44
|
+
*
|
|
45
|
+
* @param ajv A configured ajv instance ({@link AjvLike}) — typically `new Ajv2020({ allErrors: true })`.
|
|
46
|
+
* @param schemas The codegen-emitted `schemas` map (type -> resource-object JSON Schema 2020-12 doc).
|
|
47
|
+
*/
|
|
48
|
+
function createAjvValidator(ajv, schemas) {
|
|
49
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const [type, schema] of Object.entries(schemas)) compiled.set(type, ajv.compile(schema));
|
|
51
|
+
const validator = (resource) => {
|
|
52
|
+
const { type, id } = resource;
|
|
53
|
+
const validate = compiled.get(type);
|
|
54
|
+
if (validate === void 0) return;
|
|
55
|
+
if (!validate(resource)) throw new AjvValidationError(type, id, toFailures(type, id, validate.errors ?? []));
|
|
56
|
+
};
|
|
57
|
+
return validator;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { AjvValidationError, createAjvValidator };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=ajv.mjs.map
|
package/dist/ajv.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajv.mjs","names":[],"sources":["../src/ajv.ts"],"sourcesContent":["/**\n * @haddowg/json-api-client/ajv — an optional ajv adapter for the validation seam.\n *\n * Turns a user-brought ajv instance + the codegen-emitted `schemas` map into the\n * {@link Validator} the client's `validate?` option accepts, so opting into per-field\n * validation is a few lines:\n *\n * ```ts\n * import Ajv2020 from 'ajv/dist/2020'\n * import { createClient } from '@haddowg/json-api-client'\n * import { createAjvValidator } from '@haddowg/json-api-client/ajv'\n * import { schemas } from './japi.gen' // the codegen's `schemas` constant\n *\n * // The bundle emits the JSON Schema 2020-12 dialect, so use `Ajv2020`. `strict: false`\n * // tolerates the schemas' `x-enum-*` annotations; `allErrors` aggregates every failing field.\n * const ajv = new Ajv2020({ strict: false, allErrors: true })\n * const client = createClient(descriptor, {\n * baseUrl: 'https://music.example',\n * validate: createAjvValidator(ajv, schemas),\n * })\n * ```\n *\n * The schemas also carry `format` annotations (`date-time`, `date`, `uuid`, `time`, `uri`). ajv\n * ignores unknown formats by default; to ENFORCE them attach `ajv-formats`\n * (`import addFormats from 'ajv-formats'; addFormats(ajv)`). Note the wire's `time` is a\n * duration-style value the strict `time` format rejects — pass `{ mode: 'fast' }` or omit `time`\n * if you hit that, since formats are advisory and the client's posture is to validate structure\n * and types, trusting the wire on format minutiae.\n *\n * `ajv` is an OPTIONAL peer dependency of this package — it is imported here as a TYPE\n * only (`import type`), so the engine is brought by the caller and never enters the core\n * client's runtime dependency tree. This module is its own sub-path export\n * (`@haddowg/json-api-client/ajv`); importing the main entry never pulls ajv in.\n */\nimport type { ErrorObject, ValidateFunction } from 'ajv'\nimport type { SchemaMap, Validator, WireResource } from './validate'\n\n/**\n * The minimal ajv surface the adapter uses: `compile(schema)` -> a validate function. Any\n * ajv-core instance satisfies it — typically `new Ajv2020()` (the JSON Schema 2020-12 dialect\n * the bundle emits), but a base `Ajv` with the 2020 meta-schema added works too. Typed\n * structurally so the adapter binds to the capability, not a specific ajv subclass.\n */\nexport interface AjvLike {\n compile(schema: unknown): ValidateFunction\n}\n\n/** One failing field: the resource it came from plus ajv's pointer, keyword, and message. */\nexport interface AjvValidationFailure {\n /** The JSON:API type of the resource that failed (`schemas[type]` was applied). */\n type: string\n /** The resource id, when present (a bare identifier or a full resource object both carry one). */\n id?: string\n /** ajv's `instancePath` into the resource (e.g. `/attributes/title`); empty string for the root. */\n pointer: string\n /** The failing ajv keyword (e.g. `type`, `maxLength`, `enum`). */\n keyword: string\n /** ajv's human-readable message (e.g. `must be string`). */\n message: string\n}\n\n/**\n * Thrown by the ajv adapter when a wire resource fails its per-type schema. Aggregates every\n * ajv error into {@link AjvValidationFailure}s (one per failing field) and renders a message\n * listing the failing pointers, so a caller can both read the summary and walk `failures`.\n */\nexport class AjvValidationError extends Error {\n /** Every failing field across the resource (ajv runs with `allErrors`-aware reporting if enabled). */\n readonly failures: readonly AjvValidationFailure[]\n /** The JSON:API type of the resource that failed. */\n readonly type: string\n /** The id of the resource that failed, when present. */\n readonly resourceId?: string\n\n constructor(type: string, id: string | undefined, failures: readonly AjvValidationFailure[]) {\n const at = id === undefined ? type : `${type}:${id}`\n const pointers = failures.map((f) => `${f.pointer || '/'} ${f.message}`).join('; ')\n super(`Resource ${at} failed schema validation: ${pointers}`)\n this.name = 'AjvValidationError'\n this.type = type\n if (id !== undefined) {\n this.resourceId = id\n }\n this.failures = failures\n }\n}\n\n/** Map ajv's raw `ErrorObject`s for one resource to the adapter's typed failures. */\nfunction toFailures(\n type: string,\n id: string | undefined,\n errors: ErrorObject[],\n): AjvValidationFailure[] {\n return errors.map((e) => {\n const failure: AjvValidationFailure = {\n type,\n pointer: e.instancePath,\n keyword: e.keyword,\n message: e.message ?? 'is invalid',\n }\n if (id !== undefined) {\n failure.id = id\n }\n return failure\n })\n}\n\n/**\n * Build the client's {@link Validator} from a user-brought ajv instance and the codegen's per-type\n * `schemas` map. Each schema is compiled ONCE up front (keyed by JSON:API type) with the instance's\n * configured dialect — pass `new Ajv2020()` for the 2020-12 dialect the bundle emits. The returned\n * function is the bare-`Validator` seam form: it looks the compiled validator up by `resource.type`,\n * skips a type the bundle does not cover (graceful partial coverage), and on a failing resource\n * throws an aggregated {@link AjvValidationError} listing the failing pointers.\n *\n * @param ajv A configured ajv instance ({@link AjvLike}) — typically `new Ajv2020({ allErrors: true })`.\n * @param schemas The codegen-emitted `schemas` map (type -> resource-object JSON Schema 2020-12 doc).\n */\nexport function createAjvValidator(ajv: AjvLike, schemas: SchemaMap): Validator {\n // Compile every per-type schema once, keyed by type. ajv key-collision (a duplicate `$id`) is the\n // engine's concern; we compile each schema independently so one bad type does not block the rest.\n const compiled = new Map<string, ValidateFunction>()\n for (const [type, schema] of Object.entries(schemas)) {\n compiled.set(type, ajv.compile(schema))\n }\n\n // The bare-`Validator` seam form: `(resource, schema) => void`. The adapter owns its own compiled\n // lookup (by `resource.type`), so the seam's `schema` argument is unused (the runtime passes\n // `undefined` for a bare validator); it is accepted only to satisfy the {@link Validator} signature.\n const validator: Validator = (resource: WireResource): void => {\n // Read `type`/`id` up front: ajv's `ValidateFunction` is a type guard, so calling it narrows\n // `resource` in the failing branch and the props would no longer be reachable off it.\n const { type, id } = resource\n const validate = compiled.get(type)\n // A type the bundle does not cover is skipped, not failed — matches the seam's graceful posture.\n if (validate === undefined) {\n return\n }\n if (!validate(resource)) {\n const errors = validate.errors ?? []\n throw new AjvValidationError(type, id, toFailures(type, id, errors))\n }\n }\n return validator\n}\n"],"mappings":";;;;;;AAkEA,IAAa,qBAAb,cAAwC,MAAM;;CAE5C;;CAEA;;CAEA;CAEA,YAAY,MAAc,IAAwB,UAA2C;EAC3F,MAAM,KAAK,OAAO,KAAA,IAAY,OAAO,GAAG,KAAK,GAAG;EAChD,MAAM,WAAW,SAAS,KAAK,MAAM,GAAG,EAAE,WAAW,IAAI,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI;EAClF,MAAM,YAAY,GAAG,6BAA6B,UAAU;EAC5D,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,IAAI,OAAO,KAAA,GACT,KAAK,aAAa;EAEpB,KAAK,WAAW;CAClB;AACF;;AAGA,SAAS,WACP,MACA,IACA,QACwB;CACxB,OAAO,OAAO,KAAK,MAAM;EACvB,MAAM,UAAgC;GACpC;GACA,SAAS,EAAE;GACX,SAAS,EAAE;GACX,SAAS,EAAE,WAAW;EACxB;EACA,IAAI,OAAO,KAAA,GACT,QAAQ,KAAK;EAEf,OAAO;CACT,CAAC;AACH;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,KAAc,SAA+B;CAG9E,MAAM,2BAAW,IAAI,IAA8B;CACnD,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,OAAO,GACjD,SAAS,IAAI,MAAM,IAAI,QAAQ,MAAM,CAAC;CAMxC,MAAM,aAAwB,aAAiC;EAG7D,MAAM,EAAE,MAAM,OAAO;EACrB,MAAM,WAAW,SAAS,IAAI,IAAI;EAElC,IAAI,aAAa,KAAA,GACf;EAEF,IAAI,CAAC,SAAS,QAAQ,GAEpB,MAAM,IAAI,mBAAmB,MAAM,IAAI,WAAW,MAAM,IADzC,SAAS,UAAU,CAAC,CAC+B,CAAC;CAEvE;CACA,OAAO;AACT"}
|