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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yayson
|
|
3
|
+
description: Serialize and parse JSON API data with yayson. Use when writing Presenters, setting up Stores (standard or legacy), or working with yayson relationships and schema validation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# YAYSON
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// Standard (JSON API 1.0)
|
|
12
|
+
import yayson from 'yayson'
|
|
13
|
+
const { Presenter, Store } = yayson()
|
|
14
|
+
|
|
15
|
+
// Legacy (pre-1.0 format)
|
|
16
|
+
import yayson from 'yayson/legacy'
|
|
17
|
+
const { Presenter, Store } = yayson()
|
|
18
|
+
|
|
19
|
+
// Sequelize adapter
|
|
20
|
+
const { Presenter } = yayson({ adapter: 'sequelize' })
|
|
21
|
+
|
|
22
|
+
// Utility helpers for reading symbols off store models
|
|
23
|
+
import { getType, getMeta, getLinks, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Presenters
|
|
27
|
+
|
|
28
|
+
Presenters serialize JS objects into JSON API documents. Subclass Presenter and set `static type`.
|
|
29
|
+
|
|
30
|
+
### Basic presenter
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
class UserPresenter extends Presenter {
|
|
34
|
+
static type = 'users'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
UserPresenter.render({ id: 1, name: 'Ada' })
|
|
38
|
+
// { data: { type: 'users', id: '1', attributes: { name: 'Ada' } } }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Field filtering
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
class UserPresenter extends Presenter {
|
|
45
|
+
static type = 'users'
|
|
46
|
+
static fields = ['name', 'email'] // whitelist, excludes everything else
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Relationships
|
|
51
|
+
|
|
52
|
+
Return a map of property name to Presenter class from `relationships()`. Related data goes into `included`.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
class WheelPresenter extends Presenter {
|
|
56
|
+
static type = 'wheels'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class BikePresenter extends Presenter {
|
|
60
|
+
static type = 'bikes'
|
|
61
|
+
relationships() {
|
|
62
|
+
return { wheels: WheelPresenter }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
BikePresenter.render({ id: 1, wheels: [{ id: 10 }, { id: 11 }] })
|
|
67
|
+
// data.relationships.wheels.data = [{ type: 'wheels', id: '10' }, ...]
|
|
68
|
+
// included = [{ type: 'wheels', id: '10', ... }, ...]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Custom attributes
|
|
72
|
+
|
|
73
|
+
Override `attributes()` to transform or compute attributes.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
class EventPresenter extends Presenter {
|
|
77
|
+
static type = 'events'
|
|
78
|
+
attributes(instance) {
|
|
79
|
+
const attrs = super.attributes(instance)
|
|
80
|
+
return { ...attrs, slug: attrs.name.toLowerCase().replace(/ /g, '-') }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Links
|
|
86
|
+
|
|
87
|
+
Override `selfLinks()` for resource links and `links()` for relationship links.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
class CarPresenter extends Presenter {
|
|
91
|
+
static type = 'cars'
|
|
92
|
+
relationships() {
|
|
93
|
+
return { motor: MotorPresenter }
|
|
94
|
+
}
|
|
95
|
+
selfLinks(instance) {
|
|
96
|
+
return '/cars/' + this.id(instance)
|
|
97
|
+
}
|
|
98
|
+
links(instance) {
|
|
99
|
+
return {
|
|
100
|
+
motor: {
|
|
101
|
+
self: this.selfLinks(instance) + '/relationships/motor',
|
|
102
|
+
related: this.selfLinks(instance) + '/motor',
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Render options
|
|
110
|
+
|
|
111
|
+
Pass `meta` and `links` as top-level document properties:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
ItemPresenter.render(items, {
|
|
115
|
+
meta: { total: 100, page: 1 },
|
|
116
|
+
links: { self: '/items?page=1', next: '/items?page=2' },
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Rendering `null` produces `{ data: null }`. Arrays produce `{ data: [...] }`.
|
|
121
|
+
|
|
122
|
+
## Store (Standard)
|
|
123
|
+
|
|
124
|
+
Parses JSON API documents, resolves relationships, and caches models.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const store = new Store()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Syncing data
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// sync: returns model for single resource, array for collection
|
|
134
|
+
const event = store.sync({ data: { type: 'events', id: '1', attributes: { name: 'Demo' } } })
|
|
135
|
+
|
|
136
|
+
// syncAll: always returns array
|
|
137
|
+
const events = store.syncAll(jsonApiDocument)
|
|
138
|
+
|
|
139
|
+
// retrieve: returns single model or null (type-filtered)
|
|
140
|
+
const event = store.retrieve('events', jsonApiDocument)
|
|
141
|
+
|
|
142
|
+
// retrieveAll: always returns array, filtered by type
|
|
143
|
+
const images = store.retrieveAll('images', jsonApiDocument)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Querying cached data
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
store.find('events', 1) // single model or null
|
|
150
|
+
store.findAll('events') // all cached events
|
|
151
|
+
store.remove('events', 1) // remove one
|
|
152
|
+
store.remove('events') // remove all of type
|
|
153
|
+
store.reset() // clear everything
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Relationships resolve automatically
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
store.sync({
|
|
160
|
+
data: { type: 'events', id: '1', relationships: { images: { data: [{ type: 'images', id: '2' }] } } },
|
|
161
|
+
included: [{ type: 'images', id: '2', attributes: { url: 'pic.jpg' } }],
|
|
162
|
+
})
|
|
163
|
+
const event = store.find('events', '1')
|
|
164
|
+
event.images[0].url // 'pic.jpg'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Schema validation
|
|
168
|
+
|
|
169
|
+
Accepts any Zod-like schema (must have `parse`/`safeParse` methods). Use `.passthrough()` on Zod objects so extra attributes aren't stripped.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { z } from 'zod'
|
|
173
|
+
|
|
174
|
+
const eventSchema = z
|
|
175
|
+
.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
name: z.string(),
|
|
178
|
+
})
|
|
179
|
+
.passthrough()
|
|
180
|
+
|
|
181
|
+
const store = new Store({
|
|
182
|
+
schemas: { events: eventSchema },
|
|
183
|
+
strict: true, // throws on validation error; false collects in store.validationErrors
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Accessing metadata via symbols
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { getType, getMeta, getLinks, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
191
|
+
|
|
192
|
+
const event = store.find('events', '1')
|
|
193
|
+
getType(event) // 'events'
|
|
194
|
+
getMeta(event) // resource meta
|
|
195
|
+
getLinks(event) // resource links
|
|
196
|
+
getRelationshipLinks(event.images) // relationship links
|
|
197
|
+
getRelationshipMeta(event.images) // relationship meta
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Store (Legacy)
|
|
201
|
+
|
|
202
|
+
For pre-JSON API 1.0 flat format. Import from `yayson/legacy`.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import yayson from 'yayson/legacy'
|
|
206
|
+
const { Store } = yayson()
|
|
207
|
+
|
|
208
|
+
const store = new Store({
|
|
209
|
+
types: { events: 'event', images: 'image' }, // plural -> singular mapping
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Legacy data format
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
store.sync({
|
|
217
|
+
links: {
|
|
218
|
+
'event.images': { type: 'images' },
|
|
219
|
+
'images.event': { type: 'event' },
|
|
220
|
+
},
|
|
221
|
+
event: { id: 1, name: 'Demo', images: [2] },
|
|
222
|
+
images: [{ id: 2, event: 1, url: 'pic.jpg' }],
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const event = store.find('event', 1)
|
|
226
|
+
event.images[0].url // 'pic.jpg'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The API methods (`sync`, `syncAll`, `retrieve`, `retrieveAll`, `find`, `findAll`, `remove`, `reset`) work the same as the standard Store. Schema validation is also supported.
|
|
230
|
+
|
|
231
|
+
## Quick Reference
|
|
232
|
+
|
|
233
|
+
For detailed API reference including all method signatures, return types, and edge cases, see [references/api.md](references/api.md).
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# YAYSON API Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Factory Function](#factory-function)
|
|
6
|
+
- [Presenter](#presenter)
|
|
7
|
+
- [Store (Standard)](#store-standard)
|
|
8
|
+
- [Store (Legacy)](#store-legacy)
|
|
9
|
+
- [Symbols & Utilities](#symbols--utilities)
|
|
10
|
+
- [Adapters](#adapters)
|
|
11
|
+
- [Type Inference](#type-inference)
|
|
12
|
+
|
|
13
|
+
## Factory Function
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import yayson from 'yayson'
|
|
17
|
+
// or: import yayson from 'yayson/legacy'
|
|
18
|
+
|
|
19
|
+
function yayson(options?: { adapter?: 'default' | 'sequelize' | AdapterClass }): {
|
|
20
|
+
Presenter: typeof Presenter
|
|
21
|
+
Store: typeof Store
|
|
22
|
+
Adapter: typeof Adapter
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Presenter
|
|
27
|
+
|
|
28
|
+
### Static Properties
|
|
29
|
+
|
|
30
|
+
| Property | Type | Default | Description |
|
|
31
|
+
| --------- | ---------------- | ----------- | ------------------------------------- |
|
|
32
|
+
| `type` | `string` | `'objects'` | JSON API resource type name |
|
|
33
|
+
| `fields` | `string[]` | `undefined` | Attribute whitelist (undefined = all) |
|
|
34
|
+
| `adapter` | `typeof Adapter` | injected | Data access adapter |
|
|
35
|
+
|
|
36
|
+
### Instance Methods
|
|
37
|
+
|
|
38
|
+
#### `relationships(): Record<string, typeof Presenter>`
|
|
39
|
+
|
|
40
|
+
Return map of property names to their Presenter classes. Relationship keys are automatically excluded from attributes.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
relationships() {
|
|
44
|
+
return { author: AuthorPresenter, comments: CommentPresenter }
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### `attributes(instance): Record<string, unknown>`
|
|
49
|
+
|
|
50
|
+
Return attributes for the resource. Default strips `id` and relationship keys, then applies `fields` filter. Override to customize.
|
|
51
|
+
|
|
52
|
+
#### `selfLinks(instance): string | { self: string, related?: string } | undefined`
|
|
53
|
+
|
|
54
|
+
Return links for the resource itself. String is normalized to `{ self: string }`.
|
|
55
|
+
|
|
56
|
+
#### `links(instance): Record<string, { self: string, related?: string }> | undefined`
|
|
57
|
+
|
|
58
|
+
Return links keyed by relationship name. Applied to each relationship in the output.
|
|
59
|
+
|
|
60
|
+
#### `id(instance): string | undefined`
|
|
61
|
+
|
|
62
|
+
Extract ID via adapter. Returns stringified ID.
|
|
63
|
+
|
|
64
|
+
### Static Methods
|
|
65
|
+
|
|
66
|
+
#### `render(instanceOrCollection, options?): JsonApiDocument`
|
|
67
|
+
|
|
68
|
+
#### `toJSON(instanceOrCollection, options?): JsonApiDocument`
|
|
69
|
+
|
|
70
|
+
Serialize one or many instances to a JSON API document. `render` and `toJSON` are identical.
|
|
71
|
+
|
|
72
|
+
**Options:**
|
|
73
|
+
|
|
74
|
+
| Option | Type | Description |
|
|
75
|
+
| ------- | ------------------------- | --------------------------------- |
|
|
76
|
+
| `meta` | `Record<string, unknown>` | Top-level `meta` in the document |
|
|
77
|
+
| `links` | `JsonApiLinks` | Top-level `links` in the document |
|
|
78
|
+
|
|
79
|
+
**Return value:** `{ data, included?, meta?, links? }`
|
|
80
|
+
|
|
81
|
+
- Single instance: `data` is a resource object
|
|
82
|
+
- Array: `data` is an array of resource objects
|
|
83
|
+
- `null`/`undefined`: `data` is `null`
|
|
84
|
+
- `included` only present when relationships exist, automatically deduplicated
|
|
85
|
+
|
|
86
|
+
### Circular Relationships
|
|
87
|
+
|
|
88
|
+
Presenters handle circular references. If `Car` has a `Motor` and `Motor` has a `Car`, the serializer tracks visited resources and avoids infinite recursion. Circular resources appear once in `included`.
|
|
89
|
+
|
|
90
|
+
## Store (Standard)
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
new Store<S extends SchemaRegistry>(options?: {
|
|
94
|
+
schemas?: S
|
|
95
|
+
strict?: boolean // default: false
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Methods
|
|
100
|
+
|
|
101
|
+
#### `sync(body: JsonApiDocument): StoreModel | StoreModel[]`
|
|
102
|
+
|
|
103
|
+
Sync a JSON API document. Returns a single model when `data` is a single resource, an array when `data` is an array.
|
|
104
|
+
|
|
105
|
+
#### `syncAll(body: JsonApiDocument): StoreModel[]`
|
|
106
|
+
|
|
107
|
+
Always returns an array. Processes `included` first, then `data`. Clears previous validation errors. On strict validation failure, rolls back store state.
|
|
108
|
+
|
|
109
|
+
#### `retrieve<T>(type: T, body: JsonApiDocument): InferModelType<S, T> | null`
|
|
110
|
+
|
|
111
|
+
Sync and return the first model matching `type`, or null.
|
|
112
|
+
|
|
113
|
+
#### `retrieveAll<T>(type: T, body: JsonApiDocument): InferModelType<S, T>[]`
|
|
114
|
+
|
|
115
|
+
Sync and return only models of specified type, preserving order from `data`.
|
|
116
|
+
|
|
117
|
+
#### `find<T>(type: T, id: string | number): InferModelType<S, T> | null`
|
|
118
|
+
|
|
119
|
+
Look up a previously synced model by type and ID. Accepts numeric or string ID.
|
|
120
|
+
|
|
121
|
+
#### `findAll<T>(type: T): InferModelType<S, T>[]`
|
|
122
|
+
|
|
123
|
+
Return all synced models of a given type.
|
|
124
|
+
|
|
125
|
+
#### `remove(type: string, id?: string | number): void`
|
|
126
|
+
|
|
127
|
+
Remove a specific model or all models of a type.
|
|
128
|
+
|
|
129
|
+
#### `reset(): void`
|
|
130
|
+
|
|
131
|
+
Clear all cached data and validation errors.
|
|
132
|
+
|
|
133
|
+
### Properties
|
|
134
|
+
|
|
135
|
+
| Property | Type | Description |
|
|
136
|
+
| ------------------ | ------------------- | ----------------------------------------------------------------- |
|
|
137
|
+
| `validationErrors` | `ValidationError[]` | Errors from non-strict validation. Each has `type`, `id`, `error` |
|
|
138
|
+
|
|
139
|
+
### Validation Modes
|
|
140
|
+
|
|
141
|
+
**strict: true** - Throws on first validation error. Store state rolls back (unchanged).
|
|
142
|
+
|
|
143
|
+
**strict: false** (default) - Collects errors in `store.validationErrors`. Models are still created. Each error contains:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
{ type: string, id: string, error: ZodError | Error }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### ID Handling
|
|
150
|
+
|
|
151
|
+
- IDs are stored and matched as strings internally
|
|
152
|
+
- Numeric IDs in source data are preserved on the model (e.g. `event.id` stays `1` not `'1'`)
|
|
153
|
+
- `find('events', 1)` and `find('events', '1')` both work
|
|
154
|
+
- Numeric IDs are preserved through relationship resolution
|
|
155
|
+
|
|
156
|
+
### Duplicate Handling
|
|
157
|
+
|
|
158
|
+
When the same resource (same type + id) appears multiple times, the store deduplicates by keeping one copy.
|
|
159
|
+
|
|
160
|
+
## Store (Legacy)
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
new LegacyStore<S extends SchemaRegistry>(options?: {
|
|
164
|
+
types?: Record<string, string> // plural -> singular type mapping
|
|
165
|
+
schemas?: S
|
|
166
|
+
strict?: boolean
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Key Differences from Standard Store
|
|
171
|
+
|
|
172
|
+
1. **Data format**: Flat objects keyed by type name, not JSON API envelope
|
|
173
|
+
2. **Type mapping**: `types` option maps plural keys to singular type names
|
|
174
|
+
3. **Relationship links**: Defined in a `links` object within the data
|
|
175
|
+
|
|
176
|
+
### Legacy Data Structure
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
{
|
|
180
|
+
links: {
|
|
181
|
+
'event.images': { type: 'images' },
|
|
182
|
+
'images.event': { type: 'event' }
|
|
183
|
+
},
|
|
184
|
+
event: { id: 1, name: 'Demo', images: [2] }, // single
|
|
185
|
+
images: [{ id: 2, event: 1, url: 'pic.jpg' }] // array
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Methods
|
|
190
|
+
|
|
191
|
+
Same as standard Store: `sync`, `syncAll`, `retrieve`, `retrieveAll`, `find`, `findAll`, `remove`, `reset`.
|
|
192
|
+
|
|
193
|
+
Note: `find`/`findAll` use the **singular** (mapped) type name, not the plural key.
|
|
194
|
+
|
|
195
|
+
## Symbols & Utilities
|
|
196
|
+
|
|
197
|
+
Import from `yayson/utils`:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { TYPE, LINKS, META, REL_LINKS, REL_META } from 'yayson/utils'
|
|
201
|
+
import { getType, getLinks, getMeta, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| Symbol | Helper | Stored On | Contains |
|
|
205
|
+
| ----------- | --------------------------- | ------------------ | -------------------- |
|
|
206
|
+
| `TYPE` | `getType(model)` | Any model | Resource type string |
|
|
207
|
+
| `META` | `getMeta(model)` | Any model | Resource-level meta |
|
|
208
|
+
| `LINKS` | `getLinks(model)` | Any model | Resource-level links |
|
|
209
|
+
| `REL_LINKS` | `getRelationshipLinks(rel)` | Relationship value | Relationship links |
|
|
210
|
+
| `REL_META` | `getRelationshipMeta(rel)` | Relationship value | Relationship meta |
|
|
211
|
+
|
|
212
|
+
Symbols survive schema validation (explicitly copied after `parse()`).
|
|
213
|
+
|
|
214
|
+
## Adapters
|
|
215
|
+
|
|
216
|
+
### Default Adapter
|
|
217
|
+
|
|
218
|
+
Works with plain JS objects.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
Adapter.get(model) // returns shallow copy of all properties
|
|
222
|
+
Adapter.get(model, 'key') // returns single property value
|
|
223
|
+
Adapter.id(model) // returns model.id as string
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Sequelize Adapter
|
|
227
|
+
|
|
228
|
+
Works with Sequelize model instances by calling `model.get()`.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const { Presenter } = yayson({ adapter: 'sequelize' })
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Custom Adapter
|
|
235
|
+
|
|
236
|
+
Any object with static `id(model)` and `get(model, key?)` methods:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const { Presenter } = yayson({
|
|
240
|
+
adapter: {
|
|
241
|
+
id: (model) => String(model.pk),
|
|
242
|
+
get: (model, key) => (key ? model.attrs[key] : model.attrs),
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Type Inference
|
|
248
|
+
|
|
249
|
+
With schema registry, store methods infer TypeScript types automatically:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const schemas = {
|
|
253
|
+
events: z.object({ id: z.string(), name: z.string() }).passthrough(),
|
|
254
|
+
images: z.object({ id: z.string(), url: z.string() }).passthrough(),
|
|
255
|
+
} as const
|
|
256
|
+
|
|
257
|
+
const store = new Store({ schemas })
|
|
258
|
+
|
|
259
|
+
const event = store.find('events', '1')
|
|
260
|
+
// TypeScript infers: { id: string, name: string, [key: string]: unknown } | null
|
|
261
|
+
|
|
262
|
+
const images = store.findAll('images')
|
|
263
|
+
// TypeScript infers: { id: string, url: string, [key: string]: unknown }[]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The `as const` on the schemas object is required for type inference to work.
|
package/.eslintrc.json
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"commonjs": true,
|
|
4
|
-
"es6": true,
|
|
5
|
-
"mocha": true,
|
|
6
|
-
"node": true
|
|
7
|
-
},
|
|
8
|
-
"parser": "@babel/eslint-parser",
|
|
9
|
-
"parserOptions": {
|
|
10
|
-
"ecmaVersion": 2020
|
|
11
|
-
},
|
|
12
|
-
"ignorePatterns": ["node_modules", "dist"],
|
|
13
|
-
"rules": {
|
|
14
|
-
"eqeqeq": ["error", "smart"],
|
|
15
|
-
"semi": ["error", "never", { "beforeStatementContinuationChars": "always" }],
|
|
16
|
-
"no-extra-semi": "off"
|
|
17
|
-
},
|
|
18
|
-
"plugins": ["mocha"],
|
|
19
|
-
"extends": ["eslint:recommended", "plugin:mocha/recommended"],
|
|
20
|
-
"overrides": [
|
|
21
|
-
{
|
|
22
|
-
"files": ["test/**/*.js"],
|
|
23
|
-
"rules": {
|
|
24
|
-
"no-unused-vars": "off"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
-
|
|
4
|
-
name: Node.js CI
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [ master ]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [ master ]
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
strategy:
|
|
18
|
-
matrix:
|
|
19
|
-
node-version: [14.x, 16.x, 18.x]
|
|
20
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
21
|
-
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v2
|
|
24
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
-
uses: actions/setup-node@v2
|
|
26
|
-
with:
|
|
27
|
-
node-version: ${{ matrix.node-version }}
|
|
28
|
-
- run: npm ci
|
|
29
|
-
- run: npm run build --if-present
|
|
30
|
-
- run: npm test
|
package/.mocharc.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
recursive: true,
|
|
5
|
-
require: ['test/node.js'],
|
|
6
|
-
diff: true,
|
|
7
|
-
extension: ['js'],
|
|
8
|
-
package: './package.json',
|
|
9
|
-
reporter: 'spec',
|
|
10
|
-
slow: 75,
|
|
11
|
-
timeout: 10000,
|
|
12
|
-
ui: 'bdd',
|
|
13
|
-
spec: ['test/yayson/**/*.js'],
|
|
14
|
-
'watch-files': ['src/**/*.js', 'test/**/*.js'],
|
|
15
|
-
}
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v18.13.0
|
package/RELEASE.md
DELETED
package/babel.config.json
DELETED
package/legacy.js
DELETED
package/prettier.config.js
DELETED
package/src/legacy.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
const yayson = require('./yayson')
|
|
2
|
-
const legacyPresenter = require('./yayson/legacy-presenter')
|
|
3
|
-
const legacyStore = require('./yayson/legacy-store')
|
|
4
|
-
|
|
5
|
-
module.exports = function (options = {}) {
|
|
6
|
-
const { Store, Presenter, Adapter } = yayson(options)
|
|
7
|
-
return {
|
|
8
|
-
Store: legacyStore(Store),
|
|
9
|
-
Presenter: legacyPresenter(Presenter),
|
|
10
|
-
Adapter,
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
package/src/yayson/adapter.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
class Adapter {
|
|
2
|
-
static get(model, key) {
|
|
3
|
-
if (key) {
|
|
4
|
-
return model[key]
|
|
5
|
-
}
|
|
6
|
-
return model
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
static id(model) {
|
|
10
|
-
const id = this.get(model, 'id')
|
|
11
|
-
if (id === undefined) {
|
|
12
|
-
return id
|
|
13
|
-
}
|
|
14
|
-
return `${id}`
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = Adapter
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = { sequelize: require('./sequelize') }
|