yayson 2.1.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 +475 -104
- 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 +74 -33
- package/skill/yayson/SKILL.md +233 -0
- package/skill/yayson/references/api.md +266 -0
- package/.circleci/config.yml +0 -35
- package/.github/workflows/node.js.yml +0 -30
- package/.mocharc.js +0 -15
- package/RELEASE.md +0 -3
- package/bower.json +0 -31
- package/coffeelint.json +0 -120
- package/gulpfile.coffee +0 -43
- package/lib/yayson/adapter.js +0 -22
- package/lib/yayson/adapters/index.js +0 -3
- package/lib/yayson/adapters/sequelize.js +0 -14
- package/lib/yayson/presenter.js +0 -209
- package/lib/yayson/store.js +0 -169
- package/lib/yayson/utils.js +0 -47
- package/lib/yayson.js +0 -53
- package/tags +0 -59
package/README.md
CHANGED
|
@@ -1,67 +1,135 @@
|
|
|
1
1
|
# YAYSON
|
|
2
2
|
|
|
3
|
-
A library for serializing and reading [JSON API](http://jsonapi.org) data in JavaScript.
|
|
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
|
+
[](https://nodei.co/npm/yayson/)
|
|
7
8
|
|
|
8
9
|
## Installing
|
|
9
10
|
|
|
10
|
-
Install yayson by running:
|
|
11
|
-
|
|
12
11
|
```
|
|
13
|
-
|
|
12
|
+
npm install yayson
|
|
14
13
|
```
|
|
15
14
|
|
|
16
|
-
##
|
|
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
|
|
17
20
|
|
|
18
|
-
|
|
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
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
{Presenter} = require('yayson')(adapter: 'default')
|
|
29
|
+
### Breaking changes
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
type: 'items'
|
|
31
|
+
#### Node.js version
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
id: 5
|
|
28
|
-
name: 'First'
|
|
33
|
+
Node.js 20+ is now required (was 14+).
|
|
29
34
|
|
|
30
|
-
|
|
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)
|
|
31
56
|
```
|
|
32
57
|
|
|
33
|
-
|
|
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()`.
|
|
34
61
|
|
|
35
62
|
```javascript
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
// Single resource — returns model directly
|
|
64
|
+
const event = store.sync({ data: { type: 'events', id: '1', attributes: { name: 'Demo' } } })
|
|
65
|
+
event.name // 'Demo'
|
|
39
66
|
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
// Collection — returns array
|
|
68
|
+
const events = store.sync({ data: [{ type: 'events', id: '1', attributes: { name: 'Demo' } }] })
|
|
69
|
+
events.length // 1
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
name: 'First'
|
|
46
|
-
};
|
|
71
|
+
// Always returns array
|
|
72
|
+
const events = store.syncAll(data)
|
|
47
73
|
|
|
48
|
-
|
|
74
|
+
// retrieve/retrieveAll also available
|
|
75
|
+
const event = store.retrieve('events', data)
|
|
76
|
+
const events = store.retrieveAll('events', data)
|
|
49
77
|
```
|
|
50
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
|
+
## Presenting data
|
|
95
|
+
|
|
96
|
+
A basic `Presenter` can look like this:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
// ESM
|
|
100
|
+
import yayson from 'yayson'
|
|
101
|
+
const { Presenter } = yayson()
|
|
102
|
+
|
|
103
|
+
// CommonJS
|
|
104
|
+
const yayson = require('yayson')
|
|
105
|
+
const { Presenter } = yayson()
|
|
106
|
+
|
|
107
|
+
class BikePresenter extends Presenter {
|
|
108
|
+
static type = 'bikes'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const bike = {
|
|
112
|
+
id: 5,
|
|
113
|
+
name: 'Monark',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
BikePresenter.render(bike)
|
|
117
|
+
```
|
|
51
118
|
|
|
52
119
|
This would produce:
|
|
53
120
|
|
|
54
121
|
```javascript
|
|
122
|
+
|
|
55
123
|
{
|
|
56
124
|
data: {
|
|
57
|
-
id: 5,
|
|
58
|
-
type: '
|
|
125
|
+
id: '5',
|
|
126
|
+
type: 'bikes',
|
|
59
127
|
attributes: {
|
|
60
|
-
|
|
61
|
-
name: 'First'
|
|
128
|
+
name: 'Monark'
|
|
62
129
|
}
|
|
63
130
|
}
|
|
64
131
|
}
|
|
132
|
+
|
|
65
133
|
```
|
|
66
134
|
|
|
67
135
|
It also works with arrays, so if you send an array to render, "data" will
|
|
@@ -69,138 +137,441 @@ be an array.
|
|
|
69
137
|
|
|
70
138
|
A bit more advanced example:
|
|
71
139
|
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class ItemsPresenter extends Presenter
|
|
76
|
-
type: 'items'
|
|
140
|
+
```javascript
|
|
141
|
+
import yayson from 'yayson'
|
|
142
|
+
const { Presenter } = yayson()
|
|
77
143
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
attrs.start = moment.utc(attrs.start).toDate()
|
|
81
|
-
attrs
|
|
144
|
+
class WheelPresenter extends Presenter {
|
|
145
|
+
static type = 'wheels'
|
|
82
146
|
|
|
83
|
-
|
|
84
|
-
|
|
147
|
+
relationships() {
|
|
148
|
+
return { bike: BikePresenter }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
85
151
|
|
|
86
|
-
|
|
152
|
+
class BikePresenter extends Presenter {
|
|
153
|
+
static type = 'bikes'
|
|
154
|
+
relationships() {
|
|
155
|
+
return { wheels: WheelPresenter }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
87
158
|
```
|
|
88
159
|
|
|
89
|
-
|
|
160
|
+
### Filtering attributes with fields
|
|
90
161
|
|
|
91
|
-
|
|
162
|
+
Use the static `fields` property to limit which attributes are included in the output:
|
|
92
163
|
|
|
93
|
-
|
|
164
|
+
```javascript
|
|
165
|
+
class UserPresenter extends Presenter {
|
|
166
|
+
static type = 'users'
|
|
167
|
+
static fields = ['name', 'email', 'createdAt']
|
|
168
|
+
}
|
|
94
169
|
|
|
95
|
-
|
|
96
|
-
|
|
170
|
+
const user = {
|
|
171
|
+
id: 1,
|
|
172
|
+
name: 'John',
|
|
173
|
+
email: 'john@example.com',
|
|
174
|
+
password: 'secret',
|
|
175
|
+
createdAt: '2024-01-01',
|
|
176
|
+
}
|
|
97
177
|
|
|
98
|
-
|
|
99
|
-
|
|
178
|
+
UserPresenter.render(user)
|
|
179
|
+
```
|
|
100
180
|
|
|
101
|
-
|
|
102
|
-
return attrs;
|
|
103
|
-
}
|
|
181
|
+
This would produce:
|
|
104
182
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
}
|
|
108
193
|
}
|
|
109
194
|
}
|
|
110
|
-
|
|
111
|
-
ItemsPresenter.render(item)
|
|
112
195
|
```
|
|
113
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
|
+
|
|
114
199
|
### Sequelize support
|
|
115
200
|
|
|
116
201
|
By default it is set up to handle standard JS objects. You can also make
|
|
117
202
|
it handle Sequelize.js models like this:
|
|
118
203
|
|
|
119
204
|
```javascript
|
|
120
|
-
|
|
121
|
-
|
|
205
|
+
import yayson from 'yayson'
|
|
206
|
+
const { Presenter } = yayson({ adapter: 'sequelize' })
|
|
122
207
|
```
|
|
123
208
|
|
|
124
209
|
You can also define your own adapter globally:
|
|
125
210
|
|
|
126
211
|
```javascript
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
},
|
|
130
222
|
})
|
|
223
|
+
```
|
|
131
224
|
|
|
225
|
+
Take a look at the SequelizeAdapter if you want to extend YAYSON to your ORM. Pull requests are welcome. :)
|
|
226
|
+
|
|
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
|
+
},
|
|
238
|
+
})
|
|
132
239
|
```
|
|
133
240
|
|
|
134
|
-
|
|
241
|
+
This would produce:
|
|
135
242
|
|
|
136
243
|
```javascript
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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: [...]
|
|
140
254
|
}
|
|
141
255
|
```
|
|
142
256
|
|
|
143
|
-
|
|
257
|
+
Both `meta` and `links` are optional and add top-level properties to the JSON API document.
|
|
144
258
|
|
|
145
|
-
|
|
259
|
+
## Parsing data
|
|
146
260
|
|
|
147
|
-
You can
|
|
261
|
+
You can use a `Store` like this:
|
|
148
262
|
|
|
149
|
-
```
|
|
150
|
-
|
|
263
|
+
```javascript
|
|
264
|
+
import yayson from 'yayson'
|
|
265
|
+
const { Store } = yayson()
|
|
266
|
+
const store = new Store()
|
|
267
|
+
|
|
268
|
+
const data = await adapter.get({ path: '/events/' + id })
|
|
269
|
+
const event = store.sync(data) // single resource → model directly
|
|
151
270
|
```
|
|
152
271
|
|
|
153
|
-
|
|
272
|
+
The `sync()` method returns a single model for single resources and an array for collections. Use `syncAll()` if you always want an array.
|
|
154
273
|
|
|
155
274
|
```javascript
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
275
|
+
const allSynced = store.syncAll(data) // always returns array
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Filtering by type with retrieveAll
|
|
279
|
+
|
|
280
|
+
Use `retrieveAll()` to sync data and return only models of a specific type:
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
const data = await adapter.get({ path: '/events/' })
|
|
284
|
+
|
|
285
|
+
// Sync and return only events (filters out included resources)
|
|
286
|
+
const events = store.retrieveAll('events', data)
|
|
287
|
+
```
|
|
288
|
+
|
|
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' } }],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Get only the events, not the included images
|
|
302
|
+
const events = store.retrieveAll('events', response)
|
|
303
|
+
// events.length === 2
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Finding synced data
|
|
307
|
+
|
|
308
|
+
You can also find in previously synced data:
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
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()
|
|
331
|
+
|
|
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({
|
|
160
347
|
data: {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
attributes: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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!
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Validation Modes
|
|
360
|
+
|
|
361
|
+
**Strict mode** (throws errors on validation failure):
|
|
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)
|
|
168
376
|
}
|
|
169
377
|
```
|
|
170
378
|
|
|
171
|
-
|
|
379
|
+
Schemas must be Zod-like objects with `parse()` and `safeParse()` methods. Any library that provides this interface will work.
|
|
172
380
|
|
|
173
|
-
|
|
381
|
+
### Utilities
|
|
174
382
|
|
|
175
|
-
|
|
176
|
-
{Store} = require('yayson')()
|
|
177
|
-
store = new Store()
|
|
383
|
+
YAYSON stores metadata on models using Symbol keys. The `yayson/utils` entry point provides helpers for reading them:
|
|
178
384
|
|
|
179
|
-
|
|
180
|
-
|
|
385
|
+
```javascript
|
|
386
|
+
import { getType, getLinks, getMeta, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
387
|
+
|
|
388
|
+
const events = store.syncAll(data)
|
|
389
|
+
const event = events[0]
|
|
390
|
+
|
|
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' }
|
|
181
396
|
```
|
|
182
397
|
|
|
183
|
-
|
|
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/
|
|
184
418
|
|
|
419
|
+
# Personal (available across all your projects)
|
|
420
|
+
cp -r node_modules/yayson/skill/yayson ~/.claude/skills/
|
|
421
|
+
```
|
|
185
422
|
|
|
186
423
|
## Use in the browser
|
|
187
424
|
|
|
188
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.
|
|
189
426
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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()
|
|
437
|
+
|
|
438
|
+
// CommonJS
|
|
439
|
+
const yayson = require('yayson/legacy')
|
|
440
|
+
const { Presenter, Store } = yayson()
|
|
193
441
|
```
|
|
194
|
-
Then you can `var yayson = window.yayson()` use the `yayson.Presenter` and `yayson.Store` as usual.
|
|
195
442
|
|
|
196
|
-
|
|
443
|
+
```javascript
|
|
444
|
+
const store = new Store({
|
|
445
|
+
types: {
|
|
446
|
+
events: 'event',
|
|
447
|
+
images: 'image',
|
|
448
|
+
},
|
|
449
|
+
})
|
|
197
450
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
451
|
+
const event = store.sync({
|
|
452
|
+
event: { id: '1', name: 'Demo Event' },
|
|
453
|
+
})
|
|
454
|
+
// single resource → returns model directly
|
|
455
|
+
|
|
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
|
|
461
|
+
|
|
462
|
+
const event = store.find('event', '1')
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Filtering by type with retrieveAll
|
|
466
|
+
|
|
467
|
+
Use `retrieveAll()` to sync data and return only models of a specific type:
|
|
468
|
+
|
|
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
|
+
```
|
|
479
|
+
|
|
480
|
+
Options can be passed when creating the store:
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
const store = new Store({
|
|
484
|
+
types: { events: 'event' },
|
|
485
|
+
})
|
|
486
|
+
```
|
|
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
|
+
```
|
|
203
576
|
|
|
204
|
-
|
|
205
|
-
- IE 9+
|
|
206
|
-
- Android
|
|
577
|
+
**Note**: Validation happens eagerly during `sync()` when schemas are configured. This allows you to check `store.validationErrors` immediately after syncing.
|