yayson 4.0.0 → 4.2.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/README.md +272 -82
- package/build/legacy.cjs +88 -35
- package/build/legacy.d.cts +44 -8
- package/build/legacy.d.cts.map +1 -1
- package/build/legacy.d.mts +44 -8
- package/build/legacy.d.mts.map +1 -1
- package/build/legacy.mjs +88 -35
- package/build/legacy.mjs.map +1 -1
- package/build/{symbols-nFs99aEX.cjs → symbols-B--FS78o.cjs} +3 -0
- package/build/{symbols-DSjKJ8vh.mjs → symbols-BfU4k1el.mjs} +4 -1
- package/build/symbols-BfU4k1el.mjs.map +1 -0
- package/build/{types-DbMIe8E2.d.cts → types-KZiF6x7A.d.mts} +21 -2
- package/build/types-KZiF6x7A.d.mts.map +1 -0
- package/build/{types-BsfPiPEw.d.mts → types-iC38_iCI.d.cts} +21 -2
- package/build/types-iC38_iCI.d.cts.map +1 -0
- package/build/utils.cjs +1 -1
- package/build/utils.d.cts +1 -1
- package/build/utils.d.mts +1 -1
- package/build/utils.mjs +1 -1
- package/build/{yayson-BKEyEcGk.d.cts → yayson-BvwMr4Ad.d.mts} +18 -4
- package/build/yayson-BvwMr4Ad.d.mts.map +1 -0
- package/build/{yayson-CUfrxK9d.cjs → yayson-C4P8J4sn.cjs} +127 -51
- package/build/{yayson-BJv2Z0Z6.mjs → yayson-DIQ_olLX.mjs} +128 -52
- package/build/yayson-DIQ_olLX.mjs.map +1 -0
- package/build/{yayson-CRbukIqr.d.mts → yayson-DTMLeA5k.d.cts} +18 -4
- package/build/yayson-DTMLeA5k.d.cts.map +1 -0
- package/build/yayson.cjs +1 -1
- package/build/yayson.d.cts +2 -2
- package/build/yayson.d.mts +2 -2
- package/build/yayson.mjs +1 -1
- package/package.json +1 -1
- package/skill/yayson/SKILL.md +20 -0
- package/skill/yayson/references/api.md +27 -2
- package/build/symbols-DSjKJ8vh.mjs.map +0 -1
- package/build/types-BsfPiPEw.d.mts.map +0 -1
- package/build/types-DbMIe8E2.d.cts.map +0 -1
- package/build/yayson-BJv2Z0Z6.mjs.map +0 -1
- package/build/yayson-BKEyEcGk.d.cts.map +0 -1
- package/build/yayson-CRbukIqr.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A library for serializing and reading [JSON API](http://jsonapi.org) data in JavaScript.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- Zero dependencies
|
|
6
|
+
- Full TypeScript support with type inference from schema registries
|
|
7
|
+
- Optional runtime validation with [Zod](https://github.com/colinhacks/zod) or any compatible library
|
|
8
|
+
- Dual ESM/CJS package — works in browsers and Node.js 20+
|
|
6
9
|
|
|
7
10
|
[](https://nodei.co/npm/yayson/)
|
|
8
11
|
|
|
@@ -12,85 +15,6 @@ YAYSON supports both ESM and CommonJS, has zero dependencies, and works in the b
|
|
|
12
15
|
npm install yayson
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
## Upgrading from 3.x
|
|
16
|
-
|
|
17
|
-
Version 4.x is a full TypeScript rewrite with several new features and breaking changes.
|
|
18
|
-
|
|
19
|
-
### New features
|
|
20
|
-
|
|
21
|
-
- **TypeScript support** — Full type definitions included, with type inference from schema registries
|
|
22
|
-
- **Schema validation** — Optional runtime validation using [Zod](https://github.com/colinhacks/zod) or any compatible library with `parse()`/`safeParse()` methods
|
|
23
|
-
- **Dual ESM/CJS package** — Native ES module support alongside CommonJS
|
|
24
|
-
- **`yayson/utils` entry point** — Helper functions (`getType`, `getLinks`, `getMeta`, `getRelationshipLinks`, `getRelationshipMeta`) for reading model metadata
|
|
25
|
-
- **`retrieveAll(type, data)`** — Sync data and return only models of a specific type
|
|
26
|
-
- **`syncAll()`** — Always returns an array (useful when you always want array behavior)
|
|
27
|
-
- **`fields` property** — Limit which attributes a Presenter includes in its output
|
|
28
|
-
|
|
29
|
-
### Breaking changes
|
|
30
|
-
|
|
31
|
-
#### Node.js version
|
|
32
|
-
|
|
33
|
-
Node.js 20+ is now required (was 14+).
|
|
34
|
-
|
|
35
|
-
#### Metadata uses Symbols instead of plain properties
|
|
36
|
-
|
|
37
|
-
In 3.x, resource type, links, and meta were stored as plain properties on models (`model.type`, `model.links`, `model.meta`). Relationship metadata used `model._links` and `model._meta`.
|
|
38
|
-
|
|
39
|
-
In 4.x, these use Symbol keys to avoid collisions with your data. Use the helpers from `yayson/utils`:
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
// 3.x
|
|
43
|
-
model.type
|
|
44
|
-
model.links
|
|
45
|
-
model.meta
|
|
46
|
-
relatedModel._links
|
|
47
|
-
relatedModel._meta
|
|
48
|
-
|
|
49
|
-
// 4.x
|
|
50
|
-
import { getType, getLinks, getMeta, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
51
|
-
getType(model)
|
|
52
|
-
getLinks(model)
|
|
53
|
-
getMeta(model)
|
|
54
|
-
getRelationshipLinks(relatedModel)
|
|
55
|
-
getRelationshipMeta(relatedModel)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
#### `sync()` restores 3.x behavior
|
|
59
|
-
|
|
60
|
-
`sync()` now matches 3.x behavior: single resources return a model directly, collections return an array. If you always want an array, use `syncAll()`.
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
// Single resource — returns model directly
|
|
64
|
-
const event = store.sync({ data: { type: 'events', id: '1', attributes: { name: 'Demo' } } })
|
|
65
|
-
event.name // 'Demo'
|
|
66
|
-
|
|
67
|
-
// Collection — returns array
|
|
68
|
-
const events = store.sync({ data: [{ type: 'events', id: '1', attributes: { name: 'Demo' } }] })
|
|
69
|
-
events.length // 1
|
|
70
|
-
|
|
71
|
-
// Always returns array
|
|
72
|
-
const events = store.syncAll(data)
|
|
73
|
-
|
|
74
|
-
// retrieve/retrieveAll also available
|
|
75
|
-
const event = store.retrieve('events', data)
|
|
76
|
-
const events = store.retrieveAll('events', data)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
#### Document-level meta uses Symbols
|
|
80
|
-
|
|
81
|
-
In 3.x, document-level metadata was stored as `result.meta`. In 4.x, it uses a Symbol key:
|
|
82
|
-
|
|
83
|
-
```javascript
|
|
84
|
-
// 3.x
|
|
85
|
-
const result = store.sync(data)
|
|
86
|
-
result.meta // { total: 100 }
|
|
87
|
-
|
|
88
|
-
// 4.x
|
|
89
|
-
import { META } from 'yayson/utils'
|
|
90
|
-
const result = store.sync(data)
|
|
91
|
-
result[META] // { total: 100 }
|
|
92
|
-
```
|
|
93
|
-
|
|
94
18
|
## Presenting data
|
|
95
19
|
|
|
96
20
|
A basic `Presenter` can look like this:
|
|
@@ -157,6 +81,49 @@ class BikePresenter extends Presenter {
|
|
|
157
81
|
}
|
|
158
82
|
```
|
|
159
83
|
|
|
84
|
+
### Declaring cardinality and optional relationships
|
|
85
|
+
|
|
86
|
+
By default a missing relationship renders as `data: null`. This isn't valid JSON:API for to-many relationships (the spec requires `[]`) and can mislead clients when a relationship simply wasn't loaded. Use the config form to declare cardinality and optional semantics:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
class BikePresenter extends Presenter {
|
|
90
|
+
static type = 'bikes'
|
|
91
|
+
|
|
92
|
+
relationships() {
|
|
93
|
+
return {
|
|
94
|
+
wheels: { presenter: WheelPresenter, hasMany: true },
|
|
95
|
+
manufacturer: { presenter: ManufacturerPresenter, optional: true },
|
|
96
|
+
accessories: { presenter: AccessoryPresenter, hasMany: true, optional: true },
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
BikePresenter.render({ id: 1 })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This produces:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
{
|
|
108
|
+
data: {
|
|
109
|
+
id: '1',
|
|
110
|
+
type: 'bikes',
|
|
111
|
+
attributes: {},
|
|
112
|
+
relationships: {
|
|
113
|
+
wheels: { data: [] } // hasMany + no data → empty array, not null
|
|
114
|
+
// manufacturer and accessories omitted entirely — they weren't loaded
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **`hasMany: true`** — declares a to-many relationship. Empty or missing data renders as `data: []` instead of `data: null`.
|
|
121
|
+
- **`optional: true`** — when the relationship key is absent from the instance, the relationship is omitted from the output (or rendered with just `links` if `links()` configures one for that key). An explicit `null` on the instance still renders as `data: null` — `optional` distinguishes "not loaded" from "explicitly empty".
|
|
122
|
+
|
|
123
|
+
In `payload()` output, `optional` omission is disabled (a write request asserts state, so dropping a relationship would be misleading), but `hasMany` still applies so a client can correctly clear a to-many with `data: []`.
|
|
124
|
+
|
|
125
|
+
The bare-class form (`relationships() { return { wheels: WheelPresenter } }`) is unchanged.
|
|
126
|
+
|
|
160
127
|
### Filtering attributes with fields
|
|
161
128
|
|
|
162
129
|
Use the static `fields` property to limit which attributes are included in the output:
|
|
@@ -256,6 +223,49 @@ This would produce:
|
|
|
256
223
|
|
|
257
224
|
Both `meta` and `links` are optional and add top-level properties to the JSON API document.
|
|
258
225
|
|
|
226
|
+
### Creating payloads with payload()
|
|
227
|
+
|
|
228
|
+
Use `payload()` to serialize a single resource for create or update API requests. Unlike `render()`, it omits `included` resources — only the primary resource with relationship linkage is produced:
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
class WheelPresenter extends Presenter {
|
|
232
|
+
static type = 'wheels'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class BikePresenter extends Presenter {
|
|
236
|
+
static type = 'bikes'
|
|
237
|
+
relationships() {
|
|
238
|
+
return { wheels: WheelPresenter }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Create (no id)
|
|
243
|
+
BikePresenter.payload({ name: 'Monark', wheels: [{ id: 1 }, { id: 2 }] })
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This would produce:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
{
|
|
250
|
+
data: {
|
|
251
|
+
type: 'bikes',
|
|
252
|
+
attributes: {
|
|
253
|
+
name: 'Monark'
|
|
254
|
+
},
|
|
255
|
+
relationships: {
|
|
256
|
+
wheels: {
|
|
257
|
+
data: [
|
|
258
|
+
{ type: 'wheels', id: '1' },
|
|
259
|
+
{ type: 'wheels', id: '2' }
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
For updates, include an `id` and it will appear in the output. `payload()` also accepts `meta` and `links` options like `render()`. It throws on arrays and null — use `render()` for collections.
|
|
268
|
+
|
|
259
269
|
## Parsing data
|
|
260
270
|
|
|
261
271
|
You can use a `Store` like this:
|
|
@@ -266,7 +276,7 @@ const { Store } = yayson()
|
|
|
266
276
|
const store = new Store()
|
|
267
277
|
|
|
268
278
|
const data = await adapter.get({ path: '/events/' + id })
|
|
269
|
-
const event = store.sync(data) // single resource
|
|
279
|
+
const event = store.sync(data) // single resource -> model directly
|
|
270
280
|
```
|
|
271
281
|
|
|
272
282
|
The `sync()` method returns a single model for single resources and an array for collections. Use `syncAll()` if you always want an array.
|
|
@@ -303,6 +313,55 @@ const events = store.retrieveAll('events', response)
|
|
|
303
313
|
// events.length === 2
|
|
304
314
|
```
|
|
305
315
|
|
|
316
|
+
### Building models from create payloads
|
|
317
|
+
|
|
318
|
+
Use `build()` to create a model from a JSON:API document without storing it. This is useful for create payloads where the `id` may not yet exist:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
const model = store.build({
|
|
322
|
+
data: {
|
|
323
|
+
type: 'events',
|
|
324
|
+
attributes: { name: 'New Event' },
|
|
325
|
+
},
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
model.name // 'New Event'
|
|
329
|
+
model.id // undefined
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
If the store already has synced data, `build()` resolves relationships against it. Unresolved references become stubs with just `id` and type:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
store.syncAll({
|
|
336
|
+
data: [{ type: 'images', id: '5', attributes: { url: 'http://example.com/img.jpg' } }],
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const model = store.build({
|
|
340
|
+
data: {
|
|
341
|
+
type: 'events',
|
|
342
|
+
attributes: { name: 'New Event' },
|
|
343
|
+
relationships: {
|
|
344
|
+
image: { data: { type: 'images', id: '5' } },
|
|
345
|
+
sponsor: { data: { type: 'sponsors', id: '99' } },
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
model.image.url // 'http://example.com/img.jpg' (resolved from store)
|
|
351
|
+
model.sponsor.id // '99' (stub — not in store)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
A static version is also available when you don't need a store instance:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
const model = Store.build({
|
|
358
|
+
data: {
|
|
359
|
+
type: 'events',
|
|
360
|
+
attributes: { name: 'New Event' },
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
306
365
|
### Finding synced data
|
|
307
366
|
|
|
308
367
|
You can also find in previously synced data:
|
|
@@ -451,17 +510,39 @@ const store = new Store({
|
|
|
451
510
|
const event = store.sync({
|
|
452
511
|
event: { id: '1', name: 'Demo Event' },
|
|
453
512
|
})
|
|
454
|
-
// single resource
|
|
513
|
+
// single resource -> returns model directly
|
|
455
514
|
|
|
456
515
|
const allSynced = store.syncAll({
|
|
457
516
|
event: { id: '1', name: 'Demo Event' },
|
|
458
517
|
images: [{ id: '2', url: 'http://example.com/image.jpg' }],
|
|
459
518
|
})
|
|
460
519
|
// syncAll always returns array
|
|
520
|
+
```
|
|
461
521
|
|
|
522
|
+
```javascript
|
|
462
523
|
const event = store.find('event', '1')
|
|
463
524
|
```
|
|
464
525
|
|
|
526
|
+
#### Creating payloads with payload()
|
|
527
|
+
|
|
528
|
+
The legacy presenter also supports `payload()` for create/update requests. It produces the legacy envelope format without `links` or sideloaded collections:
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
class TirePresenter extends Presenter {
|
|
532
|
+
static type = 'tire'
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
class CarPresenter extends Presenter {
|
|
536
|
+
static type = 'car'
|
|
537
|
+
relationships() {
|
|
538
|
+
return { tires: TirePresenter }
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
CarPresenter.payload({ name: 'Volvo', tires: [{ id: 1 }, { id: 2 }] })
|
|
543
|
+
// { car: { name: 'Volvo', tires: [1, 2] } }
|
|
544
|
+
```
|
|
545
|
+
|
|
465
546
|
#### Filtering by type with retrieveAll
|
|
466
547
|
|
|
467
548
|
Use `retrieveAll()` to sync data and return only models of a specific type:
|
|
@@ -485,6 +566,40 @@ const store = new Store({
|
|
|
485
566
|
})
|
|
486
567
|
```
|
|
487
568
|
|
|
569
|
+
#### Building models from create payloads
|
|
570
|
+
|
|
571
|
+
The legacy store also supports `build()` for creating models without storing them:
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
const model = store.build({
|
|
575
|
+
event: { name: 'New Event' },
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
model.name // 'New Event'
|
|
579
|
+
model.id // undefined
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Relationships are resolved against existing store data when available:
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
store.syncAll({
|
|
586
|
+
links: { 'event.images': { type: 'images' } },
|
|
587
|
+
images: [{ id: '2', name: 'Header' }],
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
const model = store.build({
|
|
591
|
+
event: { name: 'New Event', images: [2] },
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
model.images[0].name // 'Header' (resolved from store)
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
A static version is also available:
|
|
598
|
+
|
|
599
|
+
```javascript
|
|
600
|
+
const model = Store.build({ event: { name: 'New Event' } })
|
|
601
|
+
```
|
|
602
|
+
|
|
488
603
|
### Schema Validation for Legacy Store
|
|
489
604
|
|
|
490
605
|
The legacy store also supports schema validation and type inference, maintaining full backward compatibility:
|
|
@@ -575,3 +690,78 @@ const event = store.find('event', '1')
|
|
|
575
690
|
```
|
|
576
691
|
|
|
577
692
|
**Note**: Validation happens eagerly during `sync()` when schemas are configured. This allows you to check `store.validationErrors` immediately after syncing.
|
|
693
|
+
|
|
694
|
+
## Upgrading from 4.0
|
|
695
|
+
|
|
696
|
+
### Unresolved relationships resolve to stubs
|
|
697
|
+
|
|
698
|
+
References missing from `included` now resolve to a stub `{ id, [TYPE]: type }` instead of `null`. References with `id: null` are dropped (arrays filter them out; to-one becomes `null`).
|
|
699
|
+
|
|
700
|
+
Tighten schemas that typed relationships as nullable, and detect "not sideloaded" by absence of attributes rather than `=== null`.
|
|
701
|
+
|
|
702
|
+
## Upgrading from 3.x
|
|
703
|
+
|
|
704
|
+
Version 4.x is a full TypeScript rewrite with several breaking changes.
|
|
705
|
+
|
|
706
|
+
### Node.js version
|
|
707
|
+
|
|
708
|
+
Node.js 20+ is now required (was 14+).
|
|
709
|
+
|
|
710
|
+
### Metadata uses Symbols instead of plain properties
|
|
711
|
+
|
|
712
|
+
In 3.x, resource type, links, and meta were stored as plain properties on models (`model.type`, `model.links`, `model.meta`). Relationship metadata used `model._links` and `model._meta`.
|
|
713
|
+
|
|
714
|
+
In 4.x, these use Symbol keys to avoid collisions with your data. Use the helpers from `yayson/utils`:
|
|
715
|
+
|
|
716
|
+
```javascript
|
|
717
|
+
// 3.x
|
|
718
|
+
model.type
|
|
719
|
+
model.links
|
|
720
|
+
model.meta
|
|
721
|
+
relatedModel._links
|
|
722
|
+
relatedModel._meta
|
|
723
|
+
|
|
724
|
+
// 4.x
|
|
725
|
+
import { getType, getLinks, getMeta, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
726
|
+
getType(model)
|
|
727
|
+
getLinks(model)
|
|
728
|
+
getMeta(model)
|
|
729
|
+
getRelationshipLinks(relatedModel)
|
|
730
|
+
getRelationshipMeta(relatedModel)
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### `sync()` restores 3.x behavior
|
|
734
|
+
|
|
735
|
+
`sync()` now matches 3.x behavior: single resources return a model directly, collections return an array. If you always want an array, use `syncAll()`.
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
// Single resource — returns model directly
|
|
739
|
+
const event = store.sync({ data: { type: 'events', id: '1', attributes: { name: 'Demo' } } })
|
|
740
|
+
event.name // 'Demo'
|
|
741
|
+
|
|
742
|
+
// Collection — returns array
|
|
743
|
+
const events = store.sync({ data: [{ type: 'events', id: '1', attributes: { name: 'Demo' } }] })
|
|
744
|
+
events.length // 1
|
|
745
|
+
|
|
746
|
+
// Always returns array
|
|
747
|
+
const events = store.syncAll(data)
|
|
748
|
+
|
|
749
|
+
// retrieve/retrieveAll also available
|
|
750
|
+
const event = store.retrieve('events', data)
|
|
751
|
+
const events = store.retrieveAll('events', data)
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Document-level meta uses Symbols
|
|
755
|
+
|
|
756
|
+
In 3.x, document-level metadata was stored as `result.meta`. In 4.x, it uses a Symbol key:
|
|
757
|
+
|
|
758
|
+
```javascript
|
|
759
|
+
// 3.x
|
|
760
|
+
const result = store.sync(data)
|
|
761
|
+
result.meta // { total: 100 }
|
|
762
|
+
|
|
763
|
+
// 4.x
|
|
764
|
+
import { META } from 'yayson/utils'
|
|
765
|
+
const result = store.sync(data)
|
|
766
|
+
result[META] // { total: 100 }
|
|
767
|
+
```
|
package/build/legacy.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const require_symbols = require('./symbols-
|
|
2
|
-
const require_yayson = require('./yayson-
|
|
1
|
+
const require_symbols = require('./symbols-B--FS78o.cjs');
|
|
2
|
+
const require_yayson = require('./yayson-C4P8J4sn.cjs');
|
|
3
3
|
|
|
4
4
|
//#region src/yayson/legacy-presenter.ts
|
|
5
|
-
function hasId(value) {
|
|
5
|
+
function hasId$1(value) {
|
|
6
6
|
return typeof value === "object" && value !== null && "id" in value;
|
|
7
7
|
}
|
|
8
8
|
function createLegacyPresenter(Presenter) {
|
|
@@ -27,8 +27,8 @@ function createLegacyPresenter(Presenter) {
|
|
|
27
27
|
if (data == null) {
|
|
28
28
|
id = attributes[key + "Id"];
|
|
29
29
|
if (id != null) attributes[key] = id;
|
|
30
|
-
} else if (Array.isArray(data)) attributes[key] = data.map((obj) => hasId(obj) ? obj.id : obj);
|
|
31
|
-
else if (hasId(data)) attributes[key] = data.id;
|
|
30
|
+
} else if (Array.isArray(data)) attributes[key] = data.map((obj) => hasId$1(obj) ? obj.id : obj);
|
|
31
|
+
else if (hasId$1(data)) attributes[key] = data.id;
|
|
32
32
|
}
|
|
33
33
|
const relationshipKeys = relationships ? Object.keys(relationships) : [];
|
|
34
34
|
return require_yayson.filterByFields(attributes, this.constructor.fields ? [...this.constructor.fields, ...relationshipKeys] : void 0);
|
|
@@ -39,9 +39,9 @@ function createLegacyPresenter(Presenter) {
|
|
|
39
39
|
const result = [];
|
|
40
40
|
if (!relationships) return result;
|
|
41
41
|
for (const key in relationships) {
|
|
42
|
-
const
|
|
43
|
-
if (!
|
|
44
|
-
const presenter = new
|
|
42
|
+
const entry = relationships[key];
|
|
43
|
+
if (!entry) throw new Error(`Presenter for ${key} in ${this.constructor.type} is not defined`);
|
|
44
|
+
const presenter = new (typeof entry === "function" ? entry : entry.presenter)(scope);
|
|
45
45
|
const data = this.constructor.adapter.get(instance, key);
|
|
46
46
|
if (data != null) presenter.toJSON(data, { defaultPlural: true });
|
|
47
47
|
const type = scope[this.pluralType()] != null ? this.pluralType() : this.constructor.type;
|
|
@@ -72,7 +72,7 @@ function createLegacyPresenter(Presenter) {
|
|
|
72
72
|
}
|
|
73
73
|
if (this.scope[this.constructor.type] && !this.scope[this.pluralType()]) {
|
|
74
74
|
const existingValue = this.scope[this.constructor.type];
|
|
75
|
-
if (attrs && hasId(existingValue) && existingValue.id !== attrs.id) {
|
|
75
|
+
if (attrs && hasId$1(existingValue) && existingValue.id !== attrs.id) {
|
|
76
76
|
this.scope[this.pluralType()] = [this.scope[this.constructor.type]];
|
|
77
77
|
delete this.scope[this.constructor.type];
|
|
78
78
|
const pluralArray = this.scope[this.pluralType()];
|
|
@@ -80,7 +80,7 @@ function createLegacyPresenter(Presenter) {
|
|
|
80
80
|
} else added = false;
|
|
81
81
|
} else if (this.scope[this.pluralType()]) {
|
|
82
82
|
const existing = this.scope[this.pluralType()];
|
|
83
|
-
if (Array.isArray(existing)) if (attrs && !existing.some((i) => hasId(i) && i.id === attrs.id)) existing.push(attrs);
|
|
83
|
+
if (Array.isArray(existing)) if (attrs && !existing.some((i) => hasId$1(i) && i.id === attrs.id)) existing.push(attrs);
|
|
84
84
|
else added = false;
|
|
85
85
|
} else if (opts.defaultPlural) this.scope[this.pluralType()] = attrs ? [attrs] : [];
|
|
86
86
|
else this.scope[this.constructor.type] = attrs;
|
|
@@ -88,6 +88,11 @@ function createLegacyPresenter(Presenter) {
|
|
|
88
88
|
}
|
|
89
89
|
return this.scope;
|
|
90
90
|
}
|
|
91
|
+
payload(instance) {
|
|
92
|
+
if (Array.isArray(instance)) throw new Error("payload() expects a single resource, not an array");
|
|
93
|
+
if (instance == null) throw new Error("payload() requires a resource, got null");
|
|
94
|
+
return { [this.constructor.type]: this.attributes(instance) };
|
|
95
|
+
}
|
|
91
96
|
render(instanceOrCollection) {
|
|
92
97
|
return this.toJSON(instanceOrCollection);
|
|
93
98
|
}
|
|
@@ -97,12 +102,18 @@ function createLegacyPresenter(Presenter) {
|
|
|
97
102
|
static render(instanceOrCollection, _options) {
|
|
98
103
|
return new this().render(instanceOrCollection);
|
|
99
104
|
}
|
|
105
|
+
static payload(instance) {
|
|
106
|
+
return new this().payload(instance);
|
|
107
|
+
}
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
//#endregion
|
|
104
112
|
//#region src/yayson/legacy-store.ts
|
|
105
|
-
|
|
113
|
+
function hasId(model) {
|
|
114
|
+
return model.id != null;
|
|
115
|
+
}
|
|
116
|
+
var LegacyStore = class LegacyStore {
|
|
106
117
|
types;
|
|
107
118
|
records;
|
|
108
119
|
relations;
|
|
@@ -125,33 +136,56 @@ var LegacyStore = class {
|
|
|
125
136
|
this.validationErrors = [];
|
|
126
137
|
this.models = {};
|
|
127
138
|
}
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
id: rec.data.id
|
|
135
|
-
};
|
|
136
|
-
model[require_symbols.TYPE] = type;
|
|
137
|
-
if (rec.data.meta != null) model[require_symbols.META] = rec.data.meta;
|
|
138
|
-
if (rec.data.links != null) model[require_symbols.LINKS] = rec.data.links;
|
|
139
|
-
models[type][idStr] = model;
|
|
139
|
+
#createStub(type, id) {
|
|
140
|
+
const stub = { id };
|
|
141
|
+
stub[require_symbols.TYPE] = type;
|
|
142
|
+
return stub;
|
|
143
|
+
}
|
|
144
|
+
#resolveRelationships(model, type, resolver) {
|
|
140
145
|
const relations = this.relations[type];
|
|
141
|
-
if (relations)
|
|
146
|
+
if (!relations) return;
|
|
147
|
+
for (const attribute in relations) {
|
|
142
148
|
const relationType = relations[attribute];
|
|
143
149
|
const value = model[attribute];
|
|
144
|
-
|
|
150
|
+
if (Array.isArray(value)) model[attribute] = value.filter((id) => id != null).map((id) => resolver(relationType, String(id)));
|
|
151
|
+
else if (value != null) model[attribute] = resolver(relationType, String(value));
|
|
152
|
+
else model[attribute] = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
#createModel(rec, type, options) {
|
|
156
|
+
const models = options?.models;
|
|
157
|
+
const model = { ...rec.data };
|
|
158
|
+
if (rec.data.id != null) model.id = rec.data.id;
|
|
159
|
+
model[require_symbols.TYPE] = type;
|
|
160
|
+
if (rec.data.meta != null) model[require_symbols.META] = rec.data.meta;
|
|
161
|
+
if (rec.data.links != null) model[require_symbols.LINKS] = rec.data.links;
|
|
162
|
+
if (models && hasId(model)) {
|
|
163
|
+
const idStr = String(model.id);
|
|
164
|
+
if (!models[type]) models[type] = {};
|
|
165
|
+
models[type][idStr] = model;
|
|
145
166
|
}
|
|
167
|
+
const resolver = (relationType, id) => {
|
|
168
|
+
return this.#findModel(relationType, id, models ?? {}) ?? this.#createStub(relationType, id);
|
|
169
|
+
};
|
|
170
|
+
this.#resolveRelationships(model, type, resolver);
|
|
171
|
+
return model;
|
|
172
|
+
}
|
|
173
|
+
toModel(rec, type, models) {
|
|
174
|
+
const idStr = String(rec.data.id);
|
|
175
|
+
if (!models[type]) models[type] = {};
|
|
176
|
+
if (models[type][idStr]) return models[type][idStr];
|
|
177
|
+
const result = this.#createModel(rec, type, { models });
|
|
178
|
+
if (!hasId(result)) throw new Error(`Expected model of type ${type} to have an id`);
|
|
179
|
+
const model = result;
|
|
146
180
|
if (this.schemas && this.schemas[type]) {
|
|
147
181
|
const schema = this.schemas[type];
|
|
148
|
-
const result = require_yayson.validate(schema, model, this.strict);
|
|
149
|
-
if (!result.valid) this.validationErrors.push({
|
|
182
|
+
const result$1 = require_yayson.validate(schema, model, this.strict);
|
|
183
|
+
if (!result$1.valid) this.validationErrors.push({
|
|
150
184
|
type,
|
|
151
185
|
id: idStr,
|
|
152
|
-
error: result.error
|
|
186
|
+
error: result$1.error
|
|
153
187
|
});
|
|
154
|
-
const validatedModel = result.data;
|
|
188
|
+
const validatedModel = result$1.data;
|
|
155
189
|
validatedModel[require_symbols.TYPE] = model[require_symbols.TYPE];
|
|
156
190
|
validatedModel[require_symbols.LINKS] = model[require_symbols.LINKS];
|
|
157
191
|
validatedModel[require_symbols.META] = model[require_symbols.META];
|
|
@@ -177,6 +211,30 @@ var LegacyStore = class {
|
|
|
177
211
|
findRecords(type) {
|
|
178
212
|
return this.records.filter((r) => r.type === type);
|
|
179
213
|
}
|
|
214
|
+
#findModel(type, id, models) {
|
|
215
|
+
const rec = this.findRecord(type, id);
|
|
216
|
+
if (rec == null) return null;
|
|
217
|
+
return models[type]?.[id] ?? this.toModel(rec, type, models);
|
|
218
|
+
}
|
|
219
|
+
static build(data) {
|
|
220
|
+
return new LegacyStore().build(data);
|
|
221
|
+
}
|
|
222
|
+
build(data) {
|
|
223
|
+
if (data.links) this.setupRelations(data.links);
|
|
224
|
+
let name;
|
|
225
|
+
for (const key in data) if (key !== "meta" && key !== "links") {
|
|
226
|
+
name = key;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
if (name == null) throw new Error("build() expects a single resource, not an array");
|
|
230
|
+
const value = data[name];
|
|
231
|
+
if (value == null || Array.isArray(value)) throw new Error("build() expects a single resource, not an array");
|
|
232
|
+
const type = this.types[name] || name;
|
|
233
|
+
return this.#createModel({
|
|
234
|
+
type,
|
|
235
|
+
data: value
|
|
236
|
+
}, type);
|
|
237
|
+
}
|
|
180
238
|
/** @deprecated Use retrieve() instead. */
|
|
181
239
|
retrive(type, data) {
|
|
182
240
|
return this.retrieve(type, data);
|
|
@@ -190,12 +248,7 @@ var LegacyStore = class {
|
|
|
190
248
|
return model;
|
|
191
249
|
}
|
|
192
250
|
find(type, id, models) {
|
|
193
|
-
|
|
194
|
-
const idStr = String(id);
|
|
195
|
-
const rec = this.findRecord(type, idStr);
|
|
196
|
-
if (rec == null) return null;
|
|
197
|
-
if (!modelsObj[type]) modelsObj[type] = {};
|
|
198
|
-
return modelsObj[type][idStr] || this.toModel(rec, type, modelsObj);
|
|
251
|
+
return this.#findModel(type, String(id), models ?? this.models);
|
|
199
252
|
}
|
|
200
253
|
findAll(type, models) {
|
|
201
254
|
const modelsObj = models ?? this.models;
|