@zeroxsolutions/jsonapi 0.13.2 → 0.13.3
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/CHANGELOG.md +45 -34
- package/README.md +33 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/find-options/find-options-of.d.ts +41 -0
- package/dist/lib/find-options/find-options-of.d.ts.map +1 -0
- package/dist/lib/find-options/find-options-of.js +86 -0
- package/dist/lib/find-options/index.d.ts +2 -0
- package/dist/lib/find-options/index.d.ts.map +1 -0
- package/dist/lib/find-options/index.js +1 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 0.13.3 (2026-10-18)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **jsonapi:** turn a guarded query into a finder's options ([9f5f895](https://github.com/zeroxsolutions/zeroxsolutions/commit/9f5f895))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Claude Opus 5.5
|
|
10
|
+
- Lương Văn Tú
|
|
11
|
+
|
|
1
12
|
## 0.13.2 (2026-10-06)
|
|
2
13
|
|
|
3
14
|
No code changes. It depends on @zeroxsolutions/response, so its version follows response 0.14.4.
|
|
@@ -17,11 +28,11 @@ No code changes. It depends on @zeroxsolutions/response, so its version follows
|
|
|
17
28
|
|
|
18
29
|
### 🩹 Fixes
|
|
19
30
|
|
|
20
|
-
- ⚠️
|
|
31
|
+
- ⚠️ **jsonapi:** negotiate by the four json:api server rules alone ([67f4529](https://github.com/zeroxsolutions/zeroxsolutions/commit/67f4529))
|
|
21
32
|
|
|
22
|
-
### ⚠️
|
|
33
|
+
### ⚠️ Breaking Changes
|
|
23
34
|
|
|
24
|
-
- **jsonapi:** negotiate by the four json:api server rules alone
|
|
35
|
+
- **jsonapi:** negotiate by the four json:api server rules alone ([67f4529](https://github.com/zeroxsolutions/zeroxsolutions/commit/67f4529))
|
|
25
36
|
negotiateJsonApi returns null for a Content-Type that
|
|
26
37
|
is not the JSON:API media type (or absent), and for an Accept that holds
|
|
27
38
|
no JSON:API instance. A route refuses a body it cannot read through its
|
|
@@ -41,12 +52,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
41
52
|
### 🩹 Fixes
|
|
42
53
|
|
|
43
54
|
- **jsonapi:** read every content-type parameter, q included ([07f81b9](https://github.com/zeroxsolutions/zeroxsolutions/commit/07f81b9))
|
|
44
|
-
- ⚠️
|
|
55
|
+
- ⚠️ **jsonapi:** negotiate ext, q, wildcards and content-type per spec ([2e2ffad](https://github.com/zeroxsolutions/zeroxsolutions/commit/2e2ffad))
|
|
45
56
|
- **jsonapi:** read q as a weight when parsing a media type ([5403c39](https://github.com/zeroxsolutions/zeroxsolutions/commit/5403c39))
|
|
46
57
|
|
|
47
|
-
### ⚠️
|
|
58
|
+
### ⚠️ Breaking Changes
|
|
48
59
|
|
|
49
|
-
- **jsonapi:** negotiate ext, q, wildcards and content-type per spec
|
|
60
|
+
- **jsonapi:** negotiate ext, q, wildcards and content-type per spec ([2e2ffad](https://github.com/zeroxsolutions/zeroxsolutions/commit/2e2ffad))
|
|
50
61
|
a Content-Type wildcard or a missing Content-Type is 415; an unsupported ext is 415 or 406; a wildcard no longer hides modified JSON:API Accept members; q=0 refuses a member.
|
|
51
62
|
|
|
52
63
|
### ❤️ Thank You
|
|
@@ -70,11 +81,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
70
81
|
|
|
71
82
|
### 💅 Refactors
|
|
72
83
|
|
|
73
|
-
- ⚠️
|
|
84
|
+
- ⚠️ **jsonapi:** list the names each entry publishes ([3746762](https://github.com/zeroxsolutions/zeroxsolutions/commit/3746762))
|
|
74
85
|
|
|
75
|
-
### ⚠️
|
|
86
|
+
### ⚠️ Breaking Changes
|
|
76
87
|
|
|
77
|
-
- **jsonapi:** list the names each entry publishes
|
|
88
|
+
- **jsonapi:** list the names each entry publishes ([3746762](https://github.com/zeroxsolutions/zeroxsolutions/commit/3746762))
|
|
78
89
|
the root no longer exports `parseMediaType`,
|
|
79
90
|
`isJsonApiWithDisallowedParams` or `ParsedMediaType`; `negotiateJsonApi`
|
|
80
91
|
is the negotiation decision. `./client` publishes the same names as
|
|
@@ -107,11 +118,11 @@ Version bump only, to take the repackaged `@zeroxsolutions/response`.
|
|
|
107
118
|
|
|
108
119
|
### 🚀 Features
|
|
109
120
|
|
|
110
|
-
- ⚠️
|
|
121
|
+
- ⚠️ **server,jsonapi:** one surface, one error-code vocabulary ([32b4aed](https://github.com/zeroxsolutions/zeroxsolutions/commit/32b4aed))
|
|
111
122
|
|
|
112
|
-
### ⚠️
|
|
123
|
+
### ⚠️ Breaking Changes
|
|
113
124
|
|
|
114
|
-
- **server,jsonapi:** one surface, one error-code vocabulary
|
|
125
|
+
- **server,jsonapi:** one surface, one error-code vocabulary ([32b4aed](https://github.com/zeroxsolutions/zeroxsolutions/commit/32b4aed))
|
|
115
126
|
createErrorHandler, createValidationHook and
|
|
116
127
|
jsonApiContentNegotiation take a required `namespace`; negotiateJsonApi takes it
|
|
117
128
|
as a third argument. Codes these mint are now `<namespace>.<name>`.
|
|
@@ -124,11 +135,11 @@ Version bump only, to take the repackaged `@zeroxsolutions/response`.
|
|
|
124
135
|
|
|
125
136
|
### 💅 Refactors
|
|
126
137
|
|
|
127
|
-
- ⚠️
|
|
138
|
+
- ⚠️ **query,response,server:** one wire spelling, flat exports ([5c57b3a](https://github.com/zeroxsolutions/zeroxsolutions/commit/5c57b3a))
|
|
128
139
|
|
|
129
|
-
### ⚠️
|
|
140
|
+
### ⚠️ Breaking Changes
|
|
130
141
|
|
|
131
|
-
- **query,response,server:** one wire spelling, flat exports
|
|
142
|
+
- **query,response,server:** one wire spelling, flat exports ([5c57b3a](https://github.com/zeroxsolutions/zeroxsolutions/commit/5c57b3a))
|
|
132
143
|
`@zeroxsolutions/response/http`, `@zeroxsolutions/query/http`
|
|
133
144
|
and `@zeroxsolutions/server/hono/http` are gone, and the surviving `/jsonapi`
|
|
134
145
|
and `/hono` subpaths move to the package root. Every import drops its subpath.
|
|
@@ -145,11 +156,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
145
156
|
|
|
146
157
|
### 💅 Refactors
|
|
147
158
|
|
|
148
|
-
- ⚠️
|
|
159
|
+
- ⚠️ **jsonapi:** widen negotiation, move its middleware out ([d28eb2b](https://github.com/zeroxsolutions/zeroxsolutions/commit/d28eb2b))
|
|
149
160
|
|
|
150
|
-
### ⚠️
|
|
161
|
+
### ⚠️ Breaking Changes
|
|
151
162
|
|
|
152
|
-
- **jsonapi:** widen negotiation, move its middleware out
|
|
163
|
+
- **jsonapi:** widen negotiation, move its middleware out ([d28eb2b](https://github.com/zeroxsolutions/zeroxsolutions/commit/d28eb2b))
|
|
153
164
|
jsonApiContentNegotiation is no longer exported here; it is
|
|
154
165
|
@zeroxsolutions/server. negotiateJsonApi answers 415 for any
|
|
155
166
|
Content-Type that is not the JSON:API media type, and 406 for an Accept naming
|
|
@@ -197,12 +208,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
197
208
|
|
|
198
209
|
### 🩹 Fixes
|
|
199
210
|
|
|
200
|
-
- ⚠️
|
|
211
|
+
- ⚠️ **jsonapi:** shape the collection model on the document, not the other spelling ([4ae8d26](https://github.com/zeroxsolutions/zeroxsolutions/commit/4ae8d26))
|
|
201
212
|
- **jsonapi:** add the reader source the previous commit left untracked ([7399114](https://github.com/zeroxsolutions/zeroxsolutions/commit/7399114))
|
|
202
213
|
|
|
203
|
-
### ⚠️
|
|
214
|
+
### ⚠️ Breaking Changes
|
|
204
215
|
|
|
205
|
-
- **jsonapi:** shape the collection model on the document, not the other spelling
|
|
216
|
+
- **jsonapi:** shape the collection model on the document, not the other spelling ([4ae8d26](https://github.com/zeroxsolutions/zeroxsolutions/commit/4ae8d26))
|
|
206
217
|
`Collection.items` is `Collection.data`, and `Collection.total`
|
|
207
218
|
is gone - read `meta` for it.
|
|
208
219
|
|
|
@@ -306,12 +317,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
306
317
|
|
|
307
318
|
### 🚀 Features
|
|
308
319
|
|
|
309
|
-
- ⚠️
|
|
320
|
+
- ⚠️ **jsonapi:** bind the permit-list to a row and an operator vocabulary, and pipe the schema through it ([5f824d0](https://github.com/zeroxsolutions/zeroxsolutions/commit/5f824d0))
|
|
310
321
|
- **jsonapi:** declare a list endpoint's query parameters from its permit-list ([bb3c3c8](https://github.com/zeroxsolutions/zeroxsolutions/commit/bb3c3c8))
|
|
311
322
|
|
|
312
|
-
### ⚠️
|
|
323
|
+
### ⚠️ Breaking Changes
|
|
313
324
|
|
|
314
|
-
- **jsonapi:** bind the permit-list to a row and an operator vocabulary, and pipe the schema through it
|
|
325
|
+
- **jsonapi:** bind the permit-list to a row and an operator vocabulary, and pipe the schema through it ([5f824d0](https://github.com/zeroxsolutions/zeroxsolutions/commit/5f824d0))
|
|
315
326
|
`jsonApiQuerySchema` now returns a piped schema whose output is
|
|
316
327
|
`GuardedJsonApiQuery`, not the raw query map. Unreleased, so no consumer is affected.
|
|
317
328
|
The two type-level checks are pinned by `@ts-expect-error` in a spec, watched failing
|
|
@@ -339,11 +350,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
339
350
|
|
|
340
351
|
### 🚀 Features
|
|
341
352
|
|
|
342
|
-
- ⚠️
|
|
353
|
+
- ⚠️ **jsonapi:** shape the document by the query, and close the escape hatch ([79da0f2](https://github.com/zeroxsolutions/zeroxsolutions/commit/79da0f2))
|
|
343
354
|
|
|
344
|
-
### ⚠️
|
|
355
|
+
### ⚠️ Breaking Changes
|
|
345
356
|
|
|
346
|
-
- **jsonapi:** shape the document by the query, and close the escape hatch
|
|
357
|
+
- **jsonapi:** shape the document by the query, and close the escape hatch ([79da0f2](https://github.com/zeroxsolutions/zeroxsolutions/commit/79da0f2))
|
|
347
358
|
`customFamilies` removed from `JsonApiQuerySpec`, its
|
|
348
359
|
argument removed from `unprocessableQueryParams`, and `isCustomFamilyBase` no
|
|
349
360
|
longer exported. No declaration can accept a parameter outside the families
|
|
@@ -358,13 +369,13 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
358
369
|
|
|
359
370
|
### 🚀 Features
|
|
360
371
|
|
|
361
|
-
- ⚠️
|
|
372
|
+
- ⚠️ **jsonapi:** bind the list query to a permit-list and parse the filter tree ([2c212b0](https://github.com/zeroxsolutions/zeroxsolutions/commit/2c212b0))
|
|
362
373
|
- **jsonapi:** screen query parameters against the standard's grammar ([e7bd26c](https://github.com/zeroxsolutions/zeroxsolutions/commit/e7bd26c))
|
|
363
374
|
- **jsonapi:** refuse a query the endpoint did not permit ([1dd1df6](https://github.com/zeroxsolutions/zeroxsolutions/commit/1dd1df6))
|
|
364
375
|
|
|
365
|
-
### ⚠️
|
|
376
|
+
### ⚠️ Breaking Changes
|
|
366
377
|
|
|
367
|
-
- **jsonapi:** bind the list query to a permit-list and parse the filter tree
|
|
378
|
+
- **jsonapi:** bind the list query to a permit-list and parse the filter tree ([2c212b0](https://github.com/zeroxsolutions/zeroxsolutions/commit/2c212b0))
|
|
368
379
|
`parseJsonApiQuery` and `parseFilter` are removed;
|
|
369
380
|
`filterable` entries are `{ operators, type }` rather than a bare operator
|
|
370
381
|
list; `filter` is a bracketed family, not a JSON-encoded array; queries
|
|
@@ -420,11 +431,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
420
431
|
|
|
421
432
|
### 🚀 Features
|
|
422
433
|
|
|
423
|
-
- ⚠️
|
|
434
|
+
- ⚠️ add JSON:API v1.1 toolkit and routing lib, dissolve common ([53545a6](https://github.com/zeroxsolutions/zeroxsolutions/commit/53545a6))
|
|
424
435
|
|
|
425
|
-
### ⚠️
|
|
436
|
+
### ⚠️ Breaking Changes
|
|
426
437
|
|
|
427
|
-
- add JSON:API v1.1 toolkit and routing lib, dissolve common
|
|
438
|
+
- add JSON:API v1.1 toolkit and routing lib, dissolve common ([53545a6](https://github.com/zeroxsolutions/zeroxsolutions/commit/53545a6))
|
|
428
439
|
@zeroxsolutions/common is removed. Its error/response
|
|
429
440
|
schemas move to @zeroxsolutions/jsonapi; its path/is-match helpers move to
|
|
430
441
|
@zeroxsolutions/routing.
|
|
@@ -432,4 +443,4 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
432
443
|
### ❤️ Thank You
|
|
433
444
|
|
|
434
445
|
- Claude Opus 4.8 (1M context)
|
|
435
|
-
- Lương Văn Tú
|
|
446
|
+
- Lương Văn Tú
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @zeroxsolutions/jsonapi
|
|
2
2
|
|
|
3
3
|
The [JSON:API v1.1](https://jsonapi.org/) halves that are **not** a response body: content negotiation
|
|
4
|
-
on the request, and document deserialization on the client.
|
|
4
|
+
on the request, the finder options a guarded query reads as, and document deserialization on the client.
|
|
5
5
|
|
|
6
6
|
Everything a server puts _in_ a response - the document schemas, the serializers, the `errors[]`
|
|
7
7
|
document - is `@zeroxsolutions/response`, and the central `onError` that renders it is
|
|
@@ -18,16 +18,16 @@ asks for, what the server answers, and what the caller may address.
|
|
|
18
18
|
pnpm add @zeroxsolutions/jsonapi @zeroxsolutions/response
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
No peer dep, and no framework: every export here is a plain function over strings and documents. The
|
|
21
|
+
No peer dep, and no framework: every export here is a plain function over strings, queries and documents. The
|
|
22
22
|
Hono middleware that mounts the negotiation decision is `@zeroxsolutions/server`, which is
|
|
23
23
|
what keeps `hono` out of the package an SPA also loads.
|
|
24
24
|
|
|
25
25
|
## Entry points
|
|
26
26
|
|
|
27
|
-
| Import | Use in | What it holds
|
|
28
|
-
| -------------------------------- | ----------------------- |
|
|
29
|
-
| `@zeroxsolutions/jsonapi` | services, gateway | `negotiateJsonApi` - the whole decision, from `Content-Type`, `Accept` and the `ext` URIs this surface supports (`supportedExtensions`), to the `JsonApiError` to reject with or `null`: 415 where `Content-Type` is the JSON:API media type carrying a parameter beyond `ext`/`profile` or an `ext` outside `supportedExtensions`; 406 where `Accept` holds JSON:API instances and every one carries such a parameter or `ext`; nothing else |
|
|
30
|
-
| `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; `toRequestDocument`, the JSON:API request document a write sends, each relationship wrapped in its relationship object; and `ApiError`, the non-2xx a caller throws once a response has come back
|
|
27
|
+
| Import | Use in | What it holds |
|
|
28
|
+
| -------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `@zeroxsolutions/jsonapi` | services, gateway | `negotiateJsonApi` - the whole decision, from `Content-Type`, `Accept` and the `ext` URIs this surface supports (`supportedExtensions`), to the `JsonApiError` to reject with or `null`: 415 where `Content-Type` is the JSON:API media type carrying a parameter beyond `ext`/`profile` or an `ext` outside `supportedExtensions`; 406 where `Accept` holds JSON:API instances and every one carries such a parameter or `ext`; nothing else. `findOptionsOf` - a guarded query to the options `@zeroxsolutions/db`'s finders take |
|
|
30
|
+
| `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; `toRequestDocument`, the JSON:API request document a write sends, each relationship wrapped in its relationship object; and `ApiError`, the non-2xx a caller throws once a response has come back |
|
|
31
31
|
|
|
32
32
|
A transport mounts the decision rather than calling it: `jsonApiContentNegotiation` is the Hono adapter
|
|
33
33
|
over it, and it lives with the other framework wiring. `negotiateJsonApi` itself takes the surface's
|
|
@@ -74,3 +74,30 @@ The decision is the four server rules of JSON:API v1.1 and nothing more:
|
|
|
74
74
|
names `Content-Type`, or one fewer usable member in `Accept`, which is a 406 where none is left.
|
|
75
75
|
`supportedExtensions` is `negotiateJsonApi`'s fourth parameter: the extension URIs this surface answers
|
|
76
76
|
`ext` requests for, empty by default, which refuses every `ext`.
|
|
77
|
+
|
|
78
|
+
## A guarded query as a finder's options
|
|
79
|
+
|
|
80
|
+
`findOptionsOf(query, permits)` turns the query `@zeroxsolutions/query`'s guard answered into the options a base
|
|
81
|
+
repository's finder takes, so no route writes a converter of its own. It is called from the controller, and the
|
|
82
|
+
finder hands the options to the base unchanged:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { findOptionsOf } from '@zeroxsolutions/jsonapi';
|
|
86
|
+
|
|
87
|
+
const options = findOptionsOf(c.req.valid('query'), placesQueryPermits); // { read, filter, orderBy, limit, offset }
|
|
88
|
+
const [rows, total] = await placesRepository.findAllAndCountByTenantId(tenantId, options);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A read-one query answers `{ read }` alone. The permits name the `type` the read starts from and the `relationships`
|
|
92
|
+
`relationsOf` answers in `@zeroxsolutions/db/utils`; it throws on permits naming no `type`. The `read` is built so:
|
|
93
|
+
|
|
94
|
+
| The guarded query holds | The read loads |
|
|
95
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
96
|
+
| a column in the type's fieldset | that column |
|
|
97
|
+
| a relationship in the fieldset, not included | the related row's primary key, which is the linkage its relationship publishes |
|
|
98
|
+
| a facet (`facets`) in the fieldset | that relation under its own type's fieldset, exactly |
|
|
99
|
+
| an `include` path | every relation on it, each under its type's fieldset and its primary key |
|
|
100
|
+
| a computed name given a `ComputedRead` | the columns and relations it names, whatever the fieldset asked |
|
|
101
|
+
|
|
102
|
+
The filter, the order and the page window pass through unchanged. The package depends on `@zeroxsolutions/query`
|
|
103
|
+
alone: the read names are strings, and `@zeroxsolutions/db` refuses one its table does not hold with `UnknownReadName`.
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,KAAK,mBAAmB,GACzB,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { FilterTree, PermittedCollectionQuery, PermittedResourceQuery, ResourceQueryPermits, SortField } from '@zeroxsolutions/query';
|
|
2
|
+
/**
|
|
3
|
+
* What one level of a read loads, in the shape a base finder of `@zeroxsolutions/db` takes: columns absent load every
|
|
4
|
+
* column, and a relation named under `relations` loads under its own read.
|
|
5
|
+
*/
|
|
6
|
+
export interface ReadOf {
|
|
7
|
+
columns?: string[];
|
|
8
|
+
relations?: Record<string, ReadOf>;
|
|
9
|
+
}
|
|
10
|
+
/** The finder options a guarded read-one query asks for. */
|
|
11
|
+
export interface ResourceFindOptions {
|
|
12
|
+
read: ReadOf;
|
|
13
|
+
}
|
|
14
|
+
/** The finder options a guarded collection query asks for: the read, and its filter, order and page window. */
|
|
15
|
+
export interface CollectionFindOptions<Leaf> extends ResourceFindOptions {
|
|
16
|
+
filter: FilterTree<Leaf>[];
|
|
17
|
+
orderBy: SortField[];
|
|
18
|
+
offset: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The finder options a guarded query asks for, built off the permits that guarded it: one function for every entity,
|
|
23
|
+
* so no context declares a converter of its own.
|
|
24
|
+
*
|
|
25
|
+
* | The guarded query holds | The options carry |
|
|
26
|
+
* | --- | --- |
|
|
27
|
+
* | `fields[<type>]` naming columns | those columns in `read` |
|
|
28
|
+
* | a relationship in a fieldset, not included | that relation in `read` with its primary key alone, the linkage it publishes |
|
|
29
|
+
* | an `include` path | every relation on it in `read`, each under its own type's fieldset and its primary key |
|
|
30
|
+
* | a facet in a fieldset | that relation in `read` with exactly the columns its own type's fieldset names |
|
|
31
|
+
* | `filter`, `sort`, `offset`, `limit` | `filter`, `orderBy`, `offset` and `limit`, unchanged |
|
|
32
|
+
*
|
|
33
|
+
* Each computed attribute the permits map to a read loads that read on every query, whatever fieldset was sent.
|
|
34
|
+
*
|
|
35
|
+
* @param permits - the permits the query was guarded under; its `type` names the root type and its `relationships`
|
|
36
|
+
* (from `relationsOf` in `@zeroxsolutions/db/utils`) what each relationship reaches
|
|
37
|
+
* @throws Error when the permits name no `type`, or an include path a relationship of theirs does not reach
|
|
38
|
+
*/
|
|
39
|
+
export declare function findOptionsOf<Leaf, P extends ResourceQueryPermits>(query: PermittedCollectionQuery<Leaf, P>, permits: P): CollectionFindOptions<Leaf>;
|
|
40
|
+
export declare function findOptionsOf<P extends ResourceQueryPermits>(query: PermittedResourceQuery<P>, permits: P): ResourceFindOptions;
|
|
41
|
+
//# sourceMappingURL=find-options-of.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-options-of.d.ts","sourceRoot":"","sources":["../../../src/lib/find-options/find-options-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,UAAU,EACV,wBAAwB,EACxB,sBAAsB,EAEtB,oBAAoB,EACpB,SAAS,EACV,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,+GAA+G;AAC/G,MAAM,WAAW,qBAAqB,CAAC,IAAI,CAAE,SAAQ,mBAAmB;IACtE,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AA4FD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,CAAC,SAAS,oBAAoB,EAChE,KAAK,EAAE,wBAAwB,CAAC,IAAI,EAAE,CAAC,CAAC,EACxC,OAAO,EAAE,CAAC,GACT,qBAAqB,CAAC,IAAI,CAAC,CAAC;AAC/B,wBAAgB,aAAa,CAAC,CAAC,SAAS,oBAAoB,EAC1D,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,EAChC,OAAO,EAAE,CAAC,GACT,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function includeTreeOf(paths) {
|
|
2
|
+
const root = {};
|
|
3
|
+
for (const path of paths ?? []) {
|
|
4
|
+
let level = root;
|
|
5
|
+
for (const segment of path.split('.'))
|
|
6
|
+
level = level[segment] ??= {};
|
|
7
|
+
}
|
|
8
|
+
return root;
|
|
9
|
+
}
|
|
10
|
+
/** `a` and `b` joined, each name once, in the order they were first named. */
|
|
11
|
+
const union = (a, b) => [...new Set([...a, ...b])];
|
|
12
|
+
/**
|
|
13
|
+
* Adds what a computed attribute is computed from to a level. Columns join where both name some; a level that names
|
|
14
|
+
* none already loads every column, and a computed read naming none adds nothing to a level that names some.
|
|
15
|
+
*/
|
|
16
|
+
function mergeComputed(level, computed) {
|
|
17
|
+
if (computed.columns !== undefined && level.columns !== undefined)
|
|
18
|
+
level.columns = union(level.columns, computed.columns);
|
|
19
|
+
for (const [name, nested] of Object.entries(computed.relations ?? {})) {
|
|
20
|
+
level.relations ??= {};
|
|
21
|
+
const existing = level.relations[name];
|
|
22
|
+
if (existing) {
|
|
23
|
+
mergeComputed(existing, nested);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const added = nested.columns === undefined ? {} : { columns: [...nested.columns] };
|
|
27
|
+
level.relations[name] = added;
|
|
28
|
+
mergeComputed(added, { relations: nested.relations });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One level of the read: its fieldset's columns, the linkage of each relationship it names, each facet under its own
|
|
33
|
+
* table's fieldset, each included relation under its own type's fieldset, and what its computed attributes need.
|
|
34
|
+
*/
|
|
35
|
+
function levelOf(walk, type, relations, include, path) {
|
|
36
|
+
const level = {};
|
|
37
|
+
const fieldset = walk.fields[type];
|
|
38
|
+
const facets = new Set(walk.permits.facets?.[type] ?? []);
|
|
39
|
+
if (fieldset !== undefined) {
|
|
40
|
+
level.columns = fieldset.filter((name) => !Object.hasOwn(relations, name));
|
|
41
|
+
for (const name of fieldset) {
|
|
42
|
+
const target = Object.hasOwn(relations, name) ? relations[name] : undefined;
|
|
43
|
+
if (!target)
|
|
44
|
+
continue;
|
|
45
|
+
level.relations ??= {};
|
|
46
|
+
const facetColumns = walk.fields[target.type];
|
|
47
|
+
level.relations[name] = facets.has(name)
|
|
48
|
+
? facetColumns === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { columns: [...facetColumns] }
|
|
51
|
+
: { columns: [...target.primaryKey] };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [name, below] of Object.entries(include)) {
|
|
55
|
+
const target = Object.hasOwn(relations, name) ? relations[name] : undefined;
|
|
56
|
+
if (!target)
|
|
57
|
+
throw new Error(`"${path}${name}" names no relationship the permits' relationships declare.`);
|
|
58
|
+
const included = levelOf(walk, target.type, target.relations, below, `${path}${name}.`);
|
|
59
|
+
// An included resource publishes its id, which its type's fieldset never names.
|
|
60
|
+
if (included.columns !== undefined)
|
|
61
|
+
included.columns = union(target.primaryKey, included.columns);
|
|
62
|
+
level.relations ??= {};
|
|
63
|
+
level.relations[name] = included;
|
|
64
|
+
}
|
|
65
|
+
const computed = walk.permits.computed?.[type];
|
|
66
|
+
if (computed !== undefined && !Array.isArray(computed)) {
|
|
67
|
+
for (const read of Object.values(computed))
|
|
68
|
+
mergeComputed(level, read);
|
|
69
|
+
}
|
|
70
|
+
return level;
|
|
71
|
+
}
|
|
72
|
+
export function findOptionsOf(query, permits) {
|
|
73
|
+
if (permits.type === undefined)
|
|
74
|
+
throw new Error('The permits name no `type` for the read to start from.');
|
|
75
|
+
const walk = { fields: query.fields, permits };
|
|
76
|
+
const read = levelOf(walk, permits.type, permits.relationships ?? {}, includeTreeOf(query.include), '');
|
|
77
|
+
if (query.limit === undefined)
|
|
78
|
+
return { read };
|
|
79
|
+
return {
|
|
80
|
+
read,
|
|
81
|
+
filter: query.filter ?? [],
|
|
82
|
+
orderBy: query.sort ?? [],
|
|
83
|
+
offset: query.offset ?? 0,
|
|
84
|
+
limit: query.limit,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/find-options/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './find-options-of.js';
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxsolutions/jsonapi",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
|
-
"description": "The JSON:API halves that are not the response body: v1.1 content negotiation (media type, 415/406/400) and the client-side document deserializer. The response document, its serializers and its error document live in @zeroxsolutions/response.",
|
|
7
|
+
"description": "The JSON:API halves that are not the response body: v1.1 content negotiation (media type, 415/406/400), findOptionsOf from a guarded query to a finder's options, and the client-side document deserializer. The response document, its serializers and its error document live in @zeroxsolutions/response.",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"module": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"jsona": "^1.12.0",
|
|
32
32
|
"tslib": "^2.3.0",
|
|
33
|
+
"@zeroxsolutions/query": "0.7.4",
|
|
33
34
|
"@zeroxsolutions/response": "0.14.4"
|
|
34
35
|
}
|
|
35
36
|
}
|