@zeroxsolutions/jsonapi 0.13.1 → 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 +49 -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 +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.13.2 (2026-10-06)
|
|
13
|
+
|
|
14
|
+
No code changes. It depends on @zeroxsolutions/response, so its version follows response 0.14.4.
|
|
15
|
+
|
|
1
16
|
## 0.13.1 (2026-09-30)
|
|
2
17
|
|
|
3
18
|
### 🚀 Features
|
|
@@ -13,11 +28,11 @@
|
|
|
13
28
|
|
|
14
29
|
### 🩹 Fixes
|
|
15
30
|
|
|
16
|
-
- ⚠️
|
|
31
|
+
- ⚠️ **jsonapi:** negotiate by the four json:api server rules alone ([67f4529](https://github.com/zeroxsolutions/zeroxsolutions/commit/67f4529))
|
|
17
32
|
|
|
18
|
-
### ⚠️
|
|
33
|
+
### ⚠️ Breaking Changes
|
|
19
34
|
|
|
20
|
-
- **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))
|
|
21
36
|
negotiateJsonApi returns null for a Content-Type that
|
|
22
37
|
is not the JSON:API media type (or absent), and for an Accept that holds
|
|
23
38
|
no JSON:API instance. A route refuses a body it cannot read through its
|
|
@@ -37,12 +52,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
37
52
|
### 🩹 Fixes
|
|
38
53
|
|
|
39
54
|
- **jsonapi:** read every content-type parameter, q included ([07f81b9](https://github.com/zeroxsolutions/zeroxsolutions/commit/07f81b9))
|
|
40
|
-
- ⚠️
|
|
55
|
+
- ⚠️ **jsonapi:** negotiate ext, q, wildcards and content-type per spec ([2e2ffad](https://github.com/zeroxsolutions/zeroxsolutions/commit/2e2ffad))
|
|
41
56
|
- **jsonapi:** read q as a weight when parsing a media type ([5403c39](https://github.com/zeroxsolutions/zeroxsolutions/commit/5403c39))
|
|
42
57
|
|
|
43
|
-
### ⚠️
|
|
58
|
+
### ⚠️ Breaking Changes
|
|
44
59
|
|
|
45
|
-
- **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))
|
|
46
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.
|
|
47
62
|
|
|
48
63
|
### ❤️ Thank You
|
|
@@ -66,11 +81,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
66
81
|
|
|
67
82
|
### 💅 Refactors
|
|
68
83
|
|
|
69
|
-
- ⚠️
|
|
84
|
+
- ⚠️ **jsonapi:** list the names each entry publishes ([3746762](https://github.com/zeroxsolutions/zeroxsolutions/commit/3746762))
|
|
70
85
|
|
|
71
|
-
### ⚠️
|
|
86
|
+
### ⚠️ Breaking Changes
|
|
72
87
|
|
|
73
|
-
- **jsonapi:** list the names each entry publishes
|
|
88
|
+
- **jsonapi:** list the names each entry publishes ([3746762](https://github.com/zeroxsolutions/zeroxsolutions/commit/3746762))
|
|
74
89
|
the root no longer exports `parseMediaType`,
|
|
75
90
|
`isJsonApiWithDisallowedParams` or `ParsedMediaType`; `negotiateJsonApi`
|
|
76
91
|
is the negotiation decision. `./client` publishes the same names as
|
|
@@ -103,11 +118,11 @@ Version bump only, to take the repackaged `@zeroxsolutions/response`.
|
|
|
103
118
|
|
|
104
119
|
### 🚀 Features
|
|
105
120
|
|
|
106
|
-
- ⚠️
|
|
121
|
+
- ⚠️ **server,jsonapi:** one surface, one error-code vocabulary ([32b4aed](https://github.com/zeroxsolutions/zeroxsolutions/commit/32b4aed))
|
|
107
122
|
|
|
108
|
-
### ⚠️
|
|
123
|
+
### ⚠️ Breaking Changes
|
|
109
124
|
|
|
110
|
-
- **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))
|
|
111
126
|
createErrorHandler, createValidationHook and
|
|
112
127
|
jsonApiContentNegotiation take a required `namespace`; negotiateJsonApi takes it
|
|
113
128
|
as a third argument. Codes these mint are now `<namespace>.<name>`.
|
|
@@ -120,11 +135,11 @@ Version bump only, to take the repackaged `@zeroxsolutions/response`.
|
|
|
120
135
|
|
|
121
136
|
### 💅 Refactors
|
|
122
137
|
|
|
123
|
-
- ⚠️
|
|
138
|
+
- ⚠️ **query,response,server:** one wire spelling, flat exports ([5c57b3a](https://github.com/zeroxsolutions/zeroxsolutions/commit/5c57b3a))
|
|
124
139
|
|
|
125
|
-
### ⚠️
|
|
140
|
+
### ⚠️ Breaking Changes
|
|
126
141
|
|
|
127
|
-
- **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))
|
|
128
143
|
`@zeroxsolutions/response/http`, `@zeroxsolutions/query/http`
|
|
129
144
|
and `@zeroxsolutions/server/hono/http` are gone, and the surviving `/jsonapi`
|
|
130
145
|
and `/hono` subpaths move to the package root. Every import drops its subpath.
|
|
@@ -141,11 +156,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
141
156
|
|
|
142
157
|
### 💅 Refactors
|
|
143
158
|
|
|
144
|
-
- ⚠️
|
|
159
|
+
- ⚠️ **jsonapi:** widen negotiation, move its middleware out ([d28eb2b](https://github.com/zeroxsolutions/zeroxsolutions/commit/d28eb2b))
|
|
145
160
|
|
|
146
|
-
### ⚠️
|
|
161
|
+
### ⚠️ Breaking Changes
|
|
147
162
|
|
|
148
|
-
- **jsonapi:** widen negotiation, move its middleware out
|
|
163
|
+
- **jsonapi:** widen negotiation, move its middleware out ([d28eb2b](https://github.com/zeroxsolutions/zeroxsolutions/commit/d28eb2b))
|
|
149
164
|
jsonApiContentNegotiation is no longer exported here; it is
|
|
150
165
|
@zeroxsolutions/server. negotiateJsonApi answers 415 for any
|
|
151
166
|
Content-Type that is not the JSON:API media type, and 406 for an Accept naming
|
|
@@ -193,12 +208,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
193
208
|
|
|
194
209
|
### 🩹 Fixes
|
|
195
210
|
|
|
196
|
-
- ⚠️
|
|
211
|
+
- ⚠️ **jsonapi:** shape the collection model on the document, not the other spelling ([4ae8d26](https://github.com/zeroxsolutions/zeroxsolutions/commit/4ae8d26))
|
|
197
212
|
- **jsonapi:** add the reader source the previous commit left untracked ([7399114](https://github.com/zeroxsolutions/zeroxsolutions/commit/7399114))
|
|
198
213
|
|
|
199
|
-
### ⚠️
|
|
214
|
+
### ⚠️ Breaking Changes
|
|
200
215
|
|
|
201
|
-
- **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))
|
|
202
217
|
`Collection.items` is `Collection.data`, and `Collection.total`
|
|
203
218
|
is gone - read `meta` for it.
|
|
204
219
|
|
|
@@ -302,12 +317,12 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
302
317
|
|
|
303
318
|
### 🚀 Features
|
|
304
319
|
|
|
305
|
-
- ⚠️
|
|
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))
|
|
306
321
|
- **jsonapi:** declare a list endpoint's query parameters from its permit-list ([bb3c3c8](https://github.com/zeroxsolutions/zeroxsolutions/commit/bb3c3c8))
|
|
307
322
|
|
|
308
|
-
### ⚠️
|
|
323
|
+
### ⚠️ Breaking Changes
|
|
309
324
|
|
|
310
|
-
- **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))
|
|
311
326
|
`jsonApiQuerySchema` now returns a piped schema whose output is
|
|
312
327
|
`GuardedJsonApiQuery`, not the raw query map. Unreleased, so no consumer is affected.
|
|
313
328
|
The two type-level checks are pinned by `@ts-expect-error` in a spec, watched failing
|
|
@@ -335,11 +350,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
335
350
|
|
|
336
351
|
### 🚀 Features
|
|
337
352
|
|
|
338
|
-
- ⚠️
|
|
353
|
+
- ⚠️ **jsonapi:** shape the document by the query, and close the escape hatch ([79da0f2](https://github.com/zeroxsolutions/zeroxsolutions/commit/79da0f2))
|
|
339
354
|
|
|
340
|
-
### ⚠️
|
|
355
|
+
### ⚠️ Breaking Changes
|
|
341
356
|
|
|
342
|
-
- **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))
|
|
343
358
|
`customFamilies` removed from `JsonApiQuerySpec`, its
|
|
344
359
|
argument removed from `unprocessableQueryParams`, and `isCustomFamilyBase` no
|
|
345
360
|
longer exported. No declaration can accept a parameter outside the families
|
|
@@ -354,13 +369,13 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
354
369
|
|
|
355
370
|
### 🚀 Features
|
|
356
371
|
|
|
357
|
-
- ⚠️
|
|
372
|
+
- ⚠️ **jsonapi:** bind the list query to a permit-list and parse the filter tree ([2c212b0](https://github.com/zeroxsolutions/zeroxsolutions/commit/2c212b0))
|
|
358
373
|
- **jsonapi:** screen query parameters against the standard's grammar ([e7bd26c](https://github.com/zeroxsolutions/zeroxsolutions/commit/e7bd26c))
|
|
359
374
|
- **jsonapi:** refuse a query the endpoint did not permit ([1dd1df6](https://github.com/zeroxsolutions/zeroxsolutions/commit/1dd1df6))
|
|
360
375
|
|
|
361
|
-
### ⚠️
|
|
376
|
+
### ⚠️ Breaking Changes
|
|
362
377
|
|
|
363
|
-
- **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))
|
|
364
379
|
`parseJsonApiQuery` and `parseFilter` are removed;
|
|
365
380
|
`filterable` entries are `{ operators, type }` rather than a bare operator
|
|
366
381
|
list; `filter` is a bracketed family, not a JSON-encoded array; queries
|
|
@@ -416,11 +431,11 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
416
431
|
|
|
417
432
|
### 🚀 Features
|
|
418
433
|
|
|
419
|
-
- ⚠️
|
|
434
|
+
- ⚠️ add JSON:API v1.1 toolkit and routing lib, dissolve common ([53545a6](https://github.com/zeroxsolutions/zeroxsolutions/commit/53545a6))
|
|
420
435
|
|
|
421
|
-
### ⚠️
|
|
436
|
+
### ⚠️ Breaking Changes
|
|
422
437
|
|
|
423
|
-
- 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))
|
|
424
439
|
@zeroxsolutions/common is removed. Its error/response
|
|
425
440
|
schemas move to @zeroxsolutions/jsonapi; its path/is-match helpers move to
|
|
426
441
|
@zeroxsolutions/routing.
|
|
@@ -428,4 +443,4 @@ This was a version bump only for @zeroxsolutions/jsonapi to align it with other
|
|
|
428
443
|
### ❤️ Thank You
|
|
429
444
|
|
|
430
445
|
- Claude Opus 4.8 (1M context)
|
|
431
|
-
- 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/
|
|
33
|
+
"@zeroxsolutions/query": "0.7.4",
|
|
34
|
+
"@zeroxsolutions/response": "0.14.4"
|
|
34
35
|
}
|
|
35
36
|
}
|