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.
Files changed (57) hide show
  1. package/README.md +440 -68
  2. package/build/legacy.cjs +278 -0
  3. package/build/legacy.d.cts +105 -0
  4. package/build/legacy.d.cts.map +1 -0
  5. package/build/legacy.d.mts +105 -0
  6. package/build/legacy.d.mts.map +1 -0
  7. package/build/legacy.mjs +279 -0
  8. package/build/legacy.mjs.map +1 -0
  9. package/build/symbols-DSjKJ8vh.mjs +26 -0
  10. package/build/symbols-DSjKJ8vh.mjs.map +1 -0
  11. package/build/symbols-nFs99aEX.cjs +61 -0
  12. package/build/types-BsfPiPEw.d.mts +122 -0
  13. package/build/types-BsfPiPEw.d.mts.map +1 -0
  14. package/build/types-DbMIe8E2.d.cts +122 -0
  15. package/build/types-DbMIe8E2.d.cts.map +1 -0
  16. package/build/utils.cjs +31 -0
  17. package/build/utils.d.cts +11 -0
  18. package/build/utils.d.cts.map +1 -0
  19. package/build/utils.d.mts +11 -0
  20. package/build/utils.d.mts.map +1 -0
  21. package/build/utils.mjs +22 -0
  22. package/build/utils.mjs.map +1 -0
  23. package/build/yayson-BJv2Z0Z6.mjs +391 -0
  24. package/build/yayson-BJv2Z0Z6.mjs.map +1 -0
  25. package/build/yayson-BKEyEcGk.d.cts +69 -0
  26. package/build/yayson-BKEyEcGk.d.cts.map +1 -0
  27. package/build/yayson-CRbukIqr.d.mts +69 -0
  28. package/build/yayson-CRbukIqr.d.mts.map +1 -0
  29. package/build/yayson-CUfrxK9d.cjs +407 -0
  30. package/build/yayson.cjs +3 -0
  31. package/build/yayson.d.cts +3 -0
  32. package/build/yayson.d.mts +3 -0
  33. package/build/yayson.mjs +3 -0
  34. package/package.json +72 -28
  35. package/skill/yayson/SKILL.md +233 -0
  36. package/skill/yayson/references/api.md +266 -0
  37. package/.eslintrc.json +0 -28
  38. package/.github/workflows/node.js.yml +0 -30
  39. package/.mocharc.js +0 -15
  40. package/.nvmrc +0 -1
  41. package/RELEASE.md +0 -3
  42. package/babel.config.json +0 -4
  43. package/legacy.js +0 -2
  44. package/prettier.config.js +0 -5
  45. package/src/legacy.js +0 -14
  46. package/src/yayson/adapter.js +0 -18
  47. package/src/yayson/adapters/index.js +0 -1
  48. package/src/yayson/adapters/sequelize.js +0 -11
  49. package/src/yayson/legacy-presenter.js +0 -121
  50. package/src/yayson/legacy-store.js +0 -156
  51. package/src/yayson/presenter.js +0 -198
  52. package/src/yayson/store.js +0 -194
  53. package/src/yayson.js +0 -23
  54. package/tags +0 -59
  55. package/webpack.browser.js +0 -27
  56. package/webpack.common.js +0 -39
  57. 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
- From version 3 we now support native JavaScript classes. YAYSON has zero dependencies and works in the browser and in node 14 and up.
5
+ YAYSON supports both ESM and CommonJS, has zero dependencies, and works in the browser and in Node.js 20+.
6
6
 
7
7
  [![NPM](https://nodei.co/npm/yayson.png?downloads=true)](https://nodei.co/npm/yayson/)
8
8
 
9
-
10
9
  ## Installing
11
10
 
12
- Install yayson by running:
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
- $ npm i yayson
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 yayson = require('yayson')
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
- const yayson = require('yayson')
106
- {Presenter} = yayson(adapter: {
107
- id: function(model){ return 'omg' + model.id},
108
- get: function(model, key){ return model[key] }
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
- Take a look at the SequelizeAdapter if you want to extend YAYSON to your ORM. Pull requests are welcome. :)
259
+ ## Parsing data
115
260
 
116
- ### Metadata
261
+ You can use a `Store` like this:
117
262
 
118
- You can add metadata to the top level object.
263
+ ```javascript
264
+ import yayson from 'yayson'
265
+ const { Store } = yayson()
266
+ const store = new Store()
119
267
 
120
- ``` javascript
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
- ItemsPresenter.render(items, {meta: count: 10})
274
+ ```javascript
275
+ const allSynced = store.syncAll(data) // always returns array
124
276
  ```
125
277
 
126
- This would produce:
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
- meta: {
133
- count: 10
134
- }
135
- data: {
136
- id: 5,
137
- type: 'items',
138
- attributes: {
139
- id: 5,
140
- name: 'First'
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
- ## Parsing data
306
+ ### Finding synced data
147
307
 
148
- You can use a `Store` can like this:
308
+ You can also find in previously synced data:
149
309
 
150
310
  ```javascript
151
- const {Store} = require('yayson')();
152
- const store = new Store();
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
- const data = await adapter.get({path: '/events/' + id});
155
- const event = store.sync(data);
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
- This will give you the parsed event with all its relationships.
359
+ #### Validation Modes
159
360
 
160
- Its also possible to find in the synched data:
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
- const event = this.store.find('events', id)
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
- const images = this.store.findAll('images')
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
- If you just want to try it out, copy the file `dist/yayson.js` to your project. Then simply include it:
175
- ```html
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
- <script src="./lib/yayson.js"></script>
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
- ### Browser support
443
+ ```javascript
444
+ const store = new Store({
445
+ types: {
446
+ events: 'event',
447
+ images: 'image',
448
+ },
449
+ })
182
450
 
183
- #### Tested
184
- - Chrome
185
- - Firefox
186
- - Safari
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
- #### Untested, but should work
190
- - IE 11
191
- - Android
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
- ## Legacy support
465
+ #### Filtering by type with retrieveAll
195
466
 
196
- 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. Its used similar to the standard presenters:
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
- const yayson = require('yayson/legacy')
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.