yayson 3.0.0 → 4.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/README.md +440 -68
- package/build/legacy.cjs +278 -0
- package/build/legacy.d.cts +105 -0
- package/build/legacy.d.cts.map +1 -0
- package/build/legacy.d.mts +105 -0
- package/build/legacy.d.mts.map +1 -0
- package/build/legacy.mjs +279 -0
- package/build/legacy.mjs.map +1 -0
- package/build/symbols-DSjKJ8vh.mjs +26 -0
- package/build/symbols-DSjKJ8vh.mjs.map +1 -0
- package/build/symbols-nFs99aEX.cjs +61 -0
- package/build/types-BsfPiPEw.d.mts +122 -0
- package/build/types-BsfPiPEw.d.mts.map +1 -0
- package/build/types-DbMIe8E2.d.cts +122 -0
- package/build/types-DbMIe8E2.d.cts.map +1 -0
- package/build/utils.cjs +31 -0
- package/build/utils.d.cts +11 -0
- package/build/utils.d.cts.map +1 -0
- package/build/utils.d.mts +11 -0
- package/build/utils.d.mts.map +1 -0
- package/build/utils.mjs +22 -0
- package/build/utils.mjs.map +1 -0
- package/build/yayson-BJv2Z0Z6.mjs +391 -0
- package/build/yayson-BJv2Z0Z6.mjs.map +1 -0
- package/build/yayson-BKEyEcGk.d.cts +69 -0
- package/build/yayson-BKEyEcGk.d.cts.map +1 -0
- package/build/yayson-CRbukIqr.d.mts +69 -0
- package/build/yayson-CRbukIqr.d.mts.map +1 -0
- package/build/yayson-CUfrxK9d.cjs +407 -0
- package/build/yayson.cjs +3 -0
- package/build/yayson.d.cts +3 -0
- package/build/yayson.d.mts +3 -0
- package/build/yayson.mjs +3 -0
- package/package.json +72 -28
- package/skill/yayson/SKILL.md +233 -0
- package/skill/yayson/references/api.md +266 -0
- package/.eslintrc.json +0 -28
- package/.github/workflows/node.js.yml +0 -30
- package/.mocharc.js +0 -15
- package/.nvmrc +0 -1
- package/RELEASE.md +0 -3
- package/babel.config.json +0 -4
- package/legacy.js +0 -2
- package/prettier.config.js +0 -5
- package/src/legacy.js +0 -14
- package/src/yayson/adapter.js +0 -18
- package/src/yayson/adapters/index.js +0 -1
- package/src/yayson/adapters/sequelize.js +0 -11
- package/src/yayson/legacy-presenter.js +0 -121
- package/src/yayson/legacy-store.js +0 -156
- package/src/yayson/presenter.js +0 -198
- package/src/yayson/store.js +0 -194
- package/src/yayson.js +0 -23
- package/tags +0 -59
- package/webpack.browser.js +0 -27
- package/webpack.common.js +0 -39
- package/webpack.dist.js +0 -21
package/README.md
CHANGED
|
@@ -2,19 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
A library for serializing and reading [JSON API](http://jsonapi.org) data in JavaScript.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
YAYSON supports both ESM and CommonJS, has zero dependencies, and works in the browser and in Node.js 20+.
|
|
6
6
|
|
|
7
7
|
[](https://nodei.co/npm/yayson/)
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
## Installing
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
```
|
|
12
|
+
npm install yayson
|
|
13
|
+
```
|
|
14
|
+
|
|
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
|
|
13
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)
|
|
14
56
|
```
|
|
15
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'
|
|
16
66
|
|
|
17
|
-
|
|
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 }
|
|
18
92
|
```
|
|
19
93
|
|
|
20
94
|
## Presenting data
|
|
@@ -22,6 +96,11 @@ $ npm i yayson
|
|
|
22
96
|
A basic `Presenter` can look like this:
|
|
23
97
|
|
|
24
98
|
```javascript
|
|
99
|
+
// ESM
|
|
100
|
+
import yayson from 'yayson'
|
|
101
|
+
const { Presenter } = yayson()
|
|
102
|
+
|
|
103
|
+
// CommonJS
|
|
25
104
|
const yayson = require('yayson')
|
|
26
105
|
const { Presenter } = yayson()
|
|
27
106
|
|
|
@@ -31,29 +110,26 @@ class BikePresenter extends Presenter {
|
|
|
31
110
|
|
|
32
111
|
const bike = {
|
|
33
112
|
id: 5,
|
|
34
|
-
name: 'Monark'
|
|
35
|
-
}
|
|
113
|
+
name: 'Monark',
|
|
114
|
+
}
|
|
36
115
|
|
|
37
|
-
BikePresenter.render(bike)
|
|
116
|
+
BikePresenter.render(bike)
|
|
38
117
|
```
|
|
39
118
|
|
|
40
|
-
|
|
41
119
|
This would produce:
|
|
42
120
|
|
|
43
121
|
```javascript
|
|
44
122
|
|
|
45
|
-
|
|
46
|
-
|
|
47
123
|
{
|
|
48
124
|
data: {
|
|
49
|
-
id: 5,
|
|
125
|
+
id: '5',
|
|
50
126
|
type: 'bikes',
|
|
51
127
|
attributes: {
|
|
52
|
-
id: 5,
|
|
53
128
|
name: 'Monark'
|
|
54
129
|
}
|
|
55
130
|
}
|
|
56
131
|
}
|
|
132
|
+
|
|
57
133
|
```
|
|
58
134
|
|
|
59
135
|
It also works with arrays, so if you send an array to render, "data" will
|
|
@@ -61,10 +137,8 @@ be an array.
|
|
|
61
137
|
|
|
62
138
|
A bit more advanced example:
|
|
63
139
|
|
|
64
|
-
|
|
65
140
|
```javascript
|
|
66
|
-
|
|
67
|
-
const yayson = require('yayson')
|
|
141
|
+
import yayson from 'yayson'
|
|
68
142
|
const { Presenter } = yayson()
|
|
69
143
|
|
|
70
144
|
class WheelPresenter extends Presenter {
|
|
@@ -81,125 +155,423 @@ class BikePresenter extends Presenter {
|
|
|
81
155
|
return { wheels: WheelPresenter }
|
|
82
156
|
}
|
|
83
157
|
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Filtering attributes with fields
|
|
161
|
+
|
|
162
|
+
Use the static `fields` property to limit which attributes are included in the output:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
class UserPresenter extends Presenter {
|
|
166
|
+
static type = 'users'
|
|
167
|
+
static fields = ['name', 'email', 'createdAt']
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const user = {
|
|
171
|
+
id: 1,
|
|
172
|
+
name: 'John',
|
|
173
|
+
email: 'john@example.com',
|
|
174
|
+
password: 'secret',
|
|
175
|
+
createdAt: '2024-01-01',
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
UserPresenter.render(user)
|
|
179
|
+
```
|
|
84
180
|
|
|
181
|
+
This would produce:
|
|
85
182
|
|
|
183
|
+
```javascript
|
|
184
|
+
{
|
|
185
|
+
data: {
|
|
186
|
+
id: '1',
|
|
187
|
+
type: 'users',
|
|
188
|
+
attributes: {
|
|
189
|
+
name: 'John',
|
|
190
|
+
email: 'john@example.com',
|
|
191
|
+
createdAt: '2024-01-01'
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
86
195
|
```
|
|
87
196
|
|
|
197
|
+
The `password` field is excluded because it's not in the `fields` array. This is useful for hiding sensitive data or reducing payload size without overriding the `attributes()` method.
|
|
198
|
+
|
|
88
199
|
### Sequelize support
|
|
89
200
|
|
|
90
201
|
By default it is set up to handle standard JS objects. You can also make
|
|
91
202
|
it handle Sequelize.js models like this:
|
|
92
203
|
|
|
93
204
|
```javascript
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
{Presenter} = yayson({adapter: 'sequelize'})
|
|
97
|
-
|
|
205
|
+
import yayson from 'yayson'
|
|
206
|
+
const { Presenter } = yayson({ adapter: 'sequelize' })
|
|
98
207
|
```
|
|
99
208
|
|
|
100
209
|
You can also define your own adapter globally:
|
|
101
210
|
|
|
102
211
|
```javascript
|
|
212
|
+
import yayson from 'yayson'
|
|
213
|
+
const { Presenter } = yayson({
|
|
214
|
+
adapter: {
|
|
215
|
+
id: function (model) {
|
|
216
|
+
return 'omg' + model.id
|
|
217
|
+
},
|
|
218
|
+
get: function (model, key) {
|
|
219
|
+
return model[key]
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
```
|
|
103
224
|
|
|
225
|
+
Take a look at the SequelizeAdapter if you want to extend YAYSON to your ORM. Pull requests are welcome. :)
|
|
104
226
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
227
|
+
### render() options
|
|
228
|
+
|
|
229
|
+
The second argument to `render()` accepts an options object with `meta` and `links`:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
ItemsPresenter.render(items, {
|
|
233
|
+
meta: { total: 100, page: 1 },
|
|
234
|
+
links: {
|
|
235
|
+
self: 'http://example.com/items?page=1',
|
|
236
|
+
next: 'http://example.com/items?page=2',
|
|
237
|
+
},
|
|
109
238
|
})
|
|
239
|
+
```
|
|
110
240
|
|
|
241
|
+
This would produce:
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
{
|
|
245
|
+
meta: {
|
|
246
|
+
total: 100,
|
|
247
|
+
page: 1
|
|
248
|
+
},
|
|
249
|
+
links: {
|
|
250
|
+
self: 'http://example.com/items?page=1',
|
|
251
|
+
next: 'http://example.com/items?page=2'
|
|
252
|
+
},
|
|
253
|
+
data: [...]
|
|
254
|
+
}
|
|
111
255
|
```
|
|
112
256
|
|
|
257
|
+
Both `meta` and `links` are optional and add top-level properties to the JSON API document.
|
|
113
258
|
|
|
114
|
-
|
|
259
|
+
## Parsing data
|
|
115
260
|
|
|
116
|
-
|
|
261
|
+
You can use a `Store` like this:
|
|
117
262
|
|
|
118
|
-
|
|
263
|
+
```javascript
|
|
264
|
+
import yayson from 'yayson'
|
|
265
|
+
const { Store } = yayson()
|
|
266
|
+
const store = new Store()
|
|
119
267
|
|
|
120
|
-
|
|
268
|
+
const data = await adapter.get({ path: '/events/' + id })
|
|
269
|
+
const event = store.sync(data) // single resource → model directly
|
|
270
|
+
```
|
|
121
271
|
|
|
272
|
+
The `sync()` method returns a single model for single resources and an array for collections. Use `syncAll()` if you always want an array.
|
|
122
273
|
|
|
123
|
-
|
|
274
|
+
```javascript
|
|
275
|
+
const allSynced = store.syncAll(data) // always returns array
|
|
124
276
|
```
|
|
125
277
|
|
|
126
|
-
|
|
278
|
+
### Filtering by type with retrieveAll
|
|
279
|
+
|
|
280
|
+
Use `retrieveAll()` to sync data and return only models of a specific type:
|
|
127
281
|
|
|
128
282
|
```javascript
|
|
283
|
+
const data = await adapter.get({ path: '/events/' })
|
|
129
284
|
|
|
285
|
+
// Sync and return only events (filters out included resources)
|
|
286
|
+
const events = store.retrieveAll('events', data)
|
|
287
|
+
```
|
|
130
288
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type: '
|
|
138
|
-
attributes: {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
289
|
+
This is useful when the response contains multiple types but you only need the primary resources:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// Response contains events and their related images
|
|
293
|
+
const response = {
|
|
294
|
+
data: [
|
|
295
|
+
{ type: 'events', id: '1', attributes: { name: 'Conference' } },
|
|
296
|
+
{ type: 'events', id: '2', attributes: { name: 'Meetup' } },
|
|
297
|
+
],
|
|
298
|
+
included: [{ type: 'images', id: '10', attributes: { url: 'http://example.com/img.jpg' } }],
|
|
143
299
|
}
|
|
300
|
+
|
|
301
|
+
// Get only the events, not the included images
|
|
302
|
+
const events = store.retrieveAll('events', response)
|
|
303
|
+
// events.length === 2
|
|
144
304
|
```
|
|
145
305
|
|
|
146
|
-
|
|
306
|
+
### Finding synced data
|
|
147
307
|
|
|
148
|
-
You can
|
|
308
|
+
You can also find in previously synced data:
|
|
149
309
|
|
|
150
310
|
```javascript
|
|
151
|
-
|
|
152
|
-
|
|
311
|
+
const event = store.find('events', id)
|
|
312
|
+
|
|
313
|
+
const images = store.findAll('images')
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Schema Validation and Type Inference
|
|
317
|
+
|
|
318
|
+
YAYSON supports optional schema validation using [Zod](https://github.com/colinhacks/zod) or any compatible schema library. This enables:
|
|
319
|
+
|
|
320
|
+
- **Runtime validation**: Ensure your data matches expected schemas
|
|
321
|
+
- **TypeScript type inference**: Get full type safety without manual type definitions
|
|
322
|
+
- **Strict or safe modes**: Throw errors or collect validation issues
|
|
323
|
+
|
|
324
|
+
#### Basic Usage with Zod
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { z } from 'zod'
|
|
328
|
+
import yayson from 'yayson'
|
|
329
|
+
|
|
330
|
+
const { Store } = yayson()
|
|
153
331
|
|
|
154
|
-
|
|
155
|
-
|
|
332
|
+
const eventSchema = z
|
|
333
|
+
.object({
|
|
334
|
+
id: z.string(),
|
|
335
|
+
name: z.string(),
|
|
336
|
+
date: z.string(),
|
|
337
|
+
})
|
|
338
|
+
.passthrough()
|
|
339
|
+
|
|
340
|
+
const schemas = {
|
|
341
|
+
events: eventSchema,
|
|
342
|
+
} as const
|
|
343
|
+
|
|
344
|
+
const store = new Store({ schemas, strict: true })
|
|
345
|
+
|
|
346
|
+
store.sync({
|
|
347
|
+
data: {
|
|
348
|
+
type: 'events',
|
|
349
|
+
id: '1',
|
|
350
|
+
attributes: { name: 'TypeScript Meetup', date: '2025-01-15' },
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
// TypeScript infers the correct type automatically
|
|
355
|
+
const event = store.find('events', '1')
|
|
356
|
+
// event.name, event.date are fully typed!
|
|
156
357
|
```
|
|
157
358
|
|
|
158
|
-
|
|
359
|
+
#### Validation Modes
|
|
159
360
|
|
|
160
|
-
|
|
361
|
+
**Strict mode** (throws errors on validation failure):
|
|
161
362
|
|
|
363
|
+
```typescript
|
|
364
|
+
const store = new Store({ schemas, strict: true })
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Safe mode** (collects errors without throwing):
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const store = new Store({ schemas, strict: false })
|
|
371
|
+
|
|
372
|
+
store.sync(invalidData)
|
|
373
|
+
|
|
374
|
+
if (store.validationErrors.length > 0) {
|
|
375
|
+
console.warn('Validation issues:', store.validationErrors)
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Schemas must be Zod-like objects with `parse()` and `safeParse()` methods. Any library that provides this interface will work.
|
|
380
|
+
|
|
381
|
+
### Utilities
|
|
382
|
+
|
|
383
|
+
YAYSON stores metadata on models using Symbol keys. The `yayson/utils` entry point provides helpers for reading them:
|
|
162
384
|
|
|
163
385
|
```javascript
|
|
164
|
-
|
|
386
|
+
import { getType, getLinks, getMeta, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
387
|
+
|
|
388
|
+
const events = store.syncAll(data)
|
|
389
|
+
const event = events[0]
|
|
165
390
|
|
|
166
|
-
|
|
391
|
+
getType(event) // 'events'
|
|
392
|
+
getLinks(event) // { self: 'http://...' }
|
|
393
|
+
getMeta(event) // { createdBy: 'admin' }
|
|
394
|
+
getRelationshipLinks(event.image) // { self: 'http://.../relationships/image' }
|
|
395
|
+
getRelationshipMeta(event.image) // { permission: 'read' }
|
|
167
396
|
```
|
|
168
397
|
|
|
398
|
+
The raw symbols (`TYPE`, `LINKS`, `META`, `REL_LINKS`, `REL_META`) are also exported from `yayson/utils` if you prefer direct access.
|
|
399
|
+
|
|
400
|
+
Note that `syncAll()` and `retrieveAll()` return arrays with a `META` symbol property for document-level metadata. Array methods like `.filter()` and `.map()` return plain arrays without this property, so extract it before transforming:
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
import { META } from 'yayson/utils'
|
|
404
|
+
|
|
405
|
+
const result = store.syncAll(data)
|
|
406
|
+
const meta = result[META] // { total: 100, page: 1 }
|
|
407
|
+
const filtered = result.filter((e) => e.name === 'Demo')
|
|
408
|
+
// filtered[META] is undefined — use the extracted `meta` instead
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Claude Code skill
|
|
412
|
+
|
|
413
|
+
YAYSON includes a [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code/skills) that helps you write Presenters and set up Stores. To install it, copy the `skill/yayson` directory to your project's `.claude/skills/` or your personal `~/.claude/skills/`:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Project-level (available to everyone working on that project)
|
|
417
|
+
cp -r node_modules/yayson/skill/yayson .claude/skills/
|
|
418
|
+
|
|
419
|
+
# Personal (available across all your projects)
|
|
420
|
+
cp -r node_modules/yayson/skill/yayson ~/.claude/skills/
|
|
421
|
+
```
|
|
169
422
|
|
|
170
423
|
## Use in the browser
|
|
171
424
|
|
|
172
425
|
Recommended way is to use it via [webpack](https://github.com/webpack/webpack) or similar build system wich lets you just require the package as usual.
|
|
173
426
|
|
|
174
|
-
|
|
175
|
-
|
|
427
|
+
## Legacy support
|
|
428
|
+
|
|
429
|
+
Earlier versions of JSON API worked a bit different from 1.0. Therefore YAYSON provides legacy presenters and stores in order to have interoperability between the versions.
|
|
430
|
+
|
|
431
|
+
### Basic Usage
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// ESM
|
|
435
|
+
import yayson from 'yayson/legacy'
|
|
436
|
+
const { Presenter, Store } = yayson()
|
|
176
437
|
|
|
177
|
-
|
|
438
|
+
// CommonJS
|
|
439
|
+
const yayson = require('yayson/legacy')
|
|
440
|
+
const { Presenter, Store } = yayson()
|
|
178
441
|
```
|
|
179
|
-
Then you can `var yayson = window.yayson()` use the `yayson.Presenter` and `yayson.Store` as usual.
|
|
180
442
|
|
|
181
|
-
|
|
443
|
+
```javascript
|
|
444
|
+
const store = new Store({
|
|
445
|
+
types: {
|
|
446
|
+
events: 'event',
|
|
447
|
+
images: 'image',
|
|
448
|
+
},
|
|
449
|
+
})
|
|
182
450
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- Safari iOS
|
|
451
|
+
const event = store.sync({
|
|
452
|
+
event: { id: '1', name: 'Demo Event' },
|
|
453
|
+
})
|
|
454
|
+
// single resource → returns model directly
|
|
188
455
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
456
|
+
const allSynced = store.syncAll({
|
|
457
|
+
event: { id: '1', name: 'Demo Event' },
|
|
458
|
+
images: [{ id: '2', url: 'http://example.com/image.jpg' }],
|
|
459
|
+
})
|
|
460
|
+
// syncAll always returns array
|
|
192
461
|
|
|
462
|
+
const event = store.find('event', '1')
|
|
463
|
+
```
|
|
193
464
|
|
|
194
|
-
|
|
465
|
+
#### Filtering by type with retrieveAll
|
|
195
466
|
|
|
196
|
-
|
|
467
|
+
Use `retrieveAll()` to sync data and return only models of a specific type:
|
|
197
468
|
|
|
198
469
|
```javascript
|
|
470
|
+
const events = store.retrieveAll('event', {
|
|
471
|
+
event: [
|
|
472
|
+
{ id: '1', name: 'Event 1' },
|
|
473
|
+
{ id: '2', name: 'Event 2' },
|
|
474
|
+
],
|
|
475
|
+
image: [{ id: '3', url: 'http://example.com/image.jpg' }],
|
|
476
|
+
})
|
|
477
|
+
// events.length === 2 (only events, not images)
|
|
478
|
+
```
|
|
199
479
|
|
|
200
|
-
|
|
201
|
-
const { Presenter } = yayson()
|
|
480
|
+
Options can be passed when creating the store:
|
|
202
481
|
|
|
482
|
+
```javascript
|
|
483
|
+
const store = new Store({
|
|
484
|
+
types: { events: 'event' },
|
|
485
|
+
})
|
|
203
486
|
```
|
|
204
487
|
|
|
488
|
+
### Schema Validation for Legacy Store
|
|
489
|
+
|
|
490
|
+
The legacy store also supports schema validation and type inference, maintaining full backward compatibility:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import yayson from 'yayson/legacy'
|
|
494
|
+
import { z } from 'zod'
|
|
495
|
+
|
|
496
|
+
const { Store } = yayson()
|
|
497
|
+
|
|
498
|
+
const eventSchema = z.object({
|
|
499
|
+
id: z.string(),
|
|
500
|
+
name: z.string(),
|
|
501
|
+
date: z.string(),
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
const store = new Store({
|
|
505
|
+
schemas: { event: eventSchema },
|
|
506
|
+
strict: true,
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
store.sync({ event: { id: '1', name: 'Event', date: '2025-01-15' } })
|
|
510
|
+
|
|
511
|
+
// TypeScript infers the correct type
|
|
512
|
+
const event = store.find('event', '1')
|
|
513
|
+
// event.name, event.date are fully typed!
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### Validation with Type Mapping
|
|
517
|
+
|
|
518
|
+
You can combine type mapping with schemas:
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
const store = new Store({
|
|
522
|
+
types: {
|
|
523
|
+
events: 'event', // Map plural to singular
|
|
524
|
+
},
|
|
525
|
+
schemas: {
|
|
526
|
+
event: eventSchema, // Schema uses normalized type
|
|
527
|
+
},
|
|
528
|
+
strict: false, // Safe mode
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
store.sync({ events: [{ id: '1', name: 'Event' }] })
|
|
532
|
+
|
|
533
|
+
if (store.validationErrors.length > 0) {
|
|
534
|
+
console.warn('Validation issues:', store.validationErrors)
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### Validation with Relations
|
|
539
|
+
|
|
540
|
+
Schemas validate the complete model after relations are resolved:
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
const eventSchema = z.object({
|
|
544
|
+
id: z.string(),
|
|
545
|
+
name: z.string(),
|
|
546
|
+
images: z.array(
|
|
547
|
+
z.object({
|
|
548
|
+
id: z.string(),
|
|
549
|
+
url: z.string(),
|
|
550
|
+
}),
|
|
551
|
+
),
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
const imageSchema = z.object({
|
|
555
|
+
id: z.string(),
|
|
556
|
+
url: z.string(),
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
const store = new Store({
|
|
560
|
+
schemas: { event: eventSchema, image: imageSchema },
|
|
561
|
+
strict: true,
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
store.sync({
|
|
565
|
+
links: {
|
|
566
|
+
'event.images': { type: 'image' },
|
|
567
|
+
},
|
|
568
|
+
event: { id: '1', name: 'Event', images: ['2'] },
|
|
569
|
+
image: [{ id: '2', url: 'http://example.com/image.jpg' }],
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
const event = store.find('event', '1')
|
|
573
|
+
// Relations are resolved before validation
|
|
574
|
+
// event.images is an array of validated image objects
|
|
575
|
+
```
|
|
205
576
|
|
|
577
|
+
**Note**: Validation happens eagerly during `sync()` when schemas are configured. This allows you to check `store.validationErrors` immediately after syncing.
|