@zeroxsolutions/response 0.13.0 → 0.14.1

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether a string is a legal JSON:API member name.
3
+ *
4
+ * Copied rather than imported at `packages/query/src/bracket-params.ts`'s own `isMemberName`: this
5
+ * package may not import `@zeroxsolutions/query` (nor the reverse), so each side of that boundary
6
+ * keeps its own copy of the one rule instead.
7
+ */
8
+ export declare function isMemberName(name: string): boolean;
9
+ //# sourceMappingURL=member-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member-name.d.ts","sourceRoot":"","sources":["../src/member-name.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD"}
@@ -0,0 +1,15 @@
1
+ /** Characters a member name may start or end with: the standard's globally allowed characters. */
2
+ const GLOBAL = 'a-zA-Z0-9\\u0080-\\u{10FFFF}';
3
+ /** Additionally allowed between the first and last character: hyphen, low line and space. */
4
+ const MIDDLE = '\\-_ ';
5
+ const MEMBER_NAME = new RegExp(`^[${GLOBAL}](?:[${GLOBAL}${MIDDLE}]*[${GLOBAL}])?$`, 'u');
6
+ /**
7
+ * Whether a string is a legal JSON:API member name.
8
+ *
9
+ * Copied rather than imported at `packages/query/src/bracket-params.ts`'s own `isMemberName`: this
10
+ * package may not import `@zeroxsolutions/query` (nor the reverse), so each side of that boundary
11
+ * keeps its own copy of the one rule instead.
12
+ */
13
+ export function isMemberName(name) {
14
+ return MEMBER_NAME.test(name);
15
+ }
@@ -59,7 +59,60 @@ export declare const resourceLinksSchema: z.ZodObject<{
59
59
  }, z.core.$strip>;
60
60
  /** A resource object's links - inferred from {@link resourceLinksSchema}. */
61
61
  export type ResourceLinks = z.infer<typeof resourceLinksSchema>;
62
- /** Collection pagination links: `self` plus the `first`/`prev`/`next`/`last` navigation, each null at the page that has none. */
62
+ /** A document's own top-level `self`/`related`, plus the description of the document itself. */
63
+ export declare const topLevelLinksSchema: z.ZodObject<{
64
+ self: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
65
+ rel: z.ZodOptional<z.ZodString>;
66
+ title: z.ZodOptional<z.ZodString>;
67
+ type: z.ZodOptional<z.ZodString>;
68
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
69
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
70
+ href: z.ZodString;
71
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
72
+ rel: z.ZodOptional<z.ZodString>;
73
+ title: z.ZodOptional<z.ZodString>;
74
+ type: z.ZodOptional<z.ZodString>;
75
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
76
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
77
+ href: z.ZodString;
78
+ }, z.core.$strip>, z.ZodNull]>>;
79
+ }, z.core.$strip>, z.ZodNull]>>;
80
+ related: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
81
+ rel: z.ZodOptional<z.ZodString>;
82
+ title: z.ZodOptional<z.ZodString>;
83
+ type: z.ZodOptional<z.ZodString>;
84
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
85
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
86
+ href: z.ZodString;
87
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
88
+ rel: z.ZodOptional<z.ZodString>;
89
+ title: z.ZodOptional<z.ZodString>;
90
+ type: z.ZodOptional<z.ZodString>;
91
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
92
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
93
+ href: z.ZodString;
94
+ }, z.core.$strip>, z.ZodNull]>>;
95
+ }, z.core.$strip>, z.ZodNull]>>;
96
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
97
+ rel: z.ZodOptional<z.ZodString>;
98
+ title: z.ZodOptional<z.ZodString>;
99
+ type: z.ZodOptional<z.ZodString>;
100
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
101
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
102
+ href: z.ZodString;
103
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
104
+ rel: z.ZodOptional<z.ZodString>;
105
+ title: z.ZodOptional<z.ZodString>;
106
+ type: z.ZodOptional<z.ZodString>;
107
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
108
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
109
+ href: z.ZodString;
110
+ }, z.core.$strip>, z.ZodNull]>>;
111
+ }, z.core.$strip>, z.ZodNull]>>;
112
+ }, z.core.$strip>;
113
+ /** A single-resource document's top-level links - inferred from {@link topLevelLinksSchema}. */
114
+ export type TopLevelLinks = z.infer<typeof topLevelLinksSchema>;
115
+ /** Collection pagination links, plus the description of the document itself. */
63
116
  export declare const paginationLinksSchema: z.ZodObject<{
64
117
  self: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
65
118
  rel: z.ZodOptional<z.ZodString>;
@@ -141,9 +194,146 @@ export declare const paginationLinksSchema: z.ZodObject<{
141
194
  href: z.ZodString;
142
195
  }, z.core.$strip>, z.ZodNull]>>;
143
196
  }, z.core.$strip>, z.ZodNull]>>;
197
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
198
+ rel: z.ZodOptional<z.ZodString>;
199
+ title: z.ZodOptional<z.ZodString>;
200
+ type: z.ZodOptional<z.ZodString>;
201
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
202
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
203
+ href: z.ZodString;
204
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
205
+ rel: z.ZodOptional<z.ZodString>;
206
+ title: z.ZodOptional<z.ZodString>;
207
+ type: z.ZodOptional<z.ZodString>;
208
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
209
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
210
+ href: z.ZodString;
211
+ }, z.core.$strip>, z.ZodNull]>>;
212
+ }, z.core.$strip>, z.ZodNull]>>;
144
213
  }, z.core.$strip>;
145
214
  /** Collection pagination links - inferred from {@link paginationLinksSchema}. */
146
215
  export type PaginationLinks = z.infer<typeof paginationLinksSchema>;
216
+ /**
217
+ * The `meta` of a collection that counts its rows: `total` is how many match the read, across every page.
218
+ *
219
+ * This is house vocabulary, not the cursor-pagination profile's `meta.page.total`. That profile
220
+ * describes cursor paging, and this house pages by `page[offset]` and `page[limit]`.
221
+ */
222
+ export declare const countedCollectionMetaSchema: z.ZodObject<{
223
+ total: z.ZodNumber;
224
+ }, z.core.$strip>;
225
+ /**
226
+ * The links an offset-paged collection always carries. `first` and `last` always exist under offset
227
+ * paging, so they are strings; `prev` and `next` are null where there is no such page.
228
+ */
229
+ export declare const offsetPaginationLinksSchema: z.ZodObject<{
230
+ self: z.ZodString;
231
+ first: z.ZodString;
232
+ last: z.ZodString;
233
+ prev: z.ZodNullable<z.ZodString>;
234
+ next: z.ZodNullable<z.ZodString>;
235
+ }, z.core.$strip>;
236
+ /** A relationship's links: its own `self`/`related`, and the pages of a to-many linkage. */
237
+ export declare const relationshipLinksSchema: z.ZodObject<{
238
+ self: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
239
+ rel: z.ZodOptional<z.ZodString>;
240
+ title: z.ZodOptional<z.ZodString>;
241
+ type: z.ZodOptional<z.ZodString>;
242
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
243
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
244
+ href: z.ZodString;
245
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
246
+ rel: z.ZodOptional<z.ZodString>;
247
+ title: z.ZodOptional<z.ZodString>;
248
+ type: z.ZodOptional<z.ZodString>;
249
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
250
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
251
+ href: z.ZodString;
252
+ }, z.core.$strip>, z.ZodNull]>>;
253
+ }, z.core.$strip>, z.ZodNull]>>;
254
+ related: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
255
+ rel: z.ZodOptional<z.ZodString>;
256
+ title: z.ZodOptional<z.ZodString>;
257
+ type: z.ZodOptional<z.ZodString>;
258
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
259
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
260
+ href: z.ZodString;
261
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
262
+ rel: z.ZodOptional<z.ZodString>;
263
+ title: z.ZodOptional<z.ZodString>;
264
+ type: z.ZodOptional<z.ZodString>;
265
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
266
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
267
+ href: z.ZodString;
268
+ }, z.core.$strip>, z.ZodNull]>>;
269
+ }, z.core.$strip>, z.ZodNull]>>;
270
+ first: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
271
+ rel: z.ZodOptional<z.ZodString>;
272
+ title: z.ZodOptional<z.ZodString>;
273
+ type: z.ZodOptional<z.ZodString>;
274
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
275
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
276
+ href: z.ZodString;
277
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
278
+ rel: z.ZodOptional<z.ZodString>;
279
+ title: z.ZodOptional<z.ZodString>;
280
+ type: z.ZodOptional<z.ZodString>;
281
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
282
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
283
+ href: z.ZodString;
284
+ }, z.core.$strip>, z.ZodNull]>>;
285
+ }, z.core.$strip>, z.ZodNull]>>;
286
+ last: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
287
+ rel: z.ZodOptional<z.ZodString>;
288
+ title: z.ZodOptional<z.ZodString>;
289
+ type: z.ZodOptional<z.ZodString>;
290
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
291
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
292
+ href: z.ZodString;
293
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
294
+ rel: z.ZodOptional<z.ZodString>;
295
+ title: z.ZodOptional<z.ZodString>;
296
+ type: z.ZodOptional<z.ZodString>;
297
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
298
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
299
+ href: z.ZodString;
300
+ }, z.core.$strip>, z.ZodNull]>>;
301
+ }, z.core.$strip>, z.ZodNull]>>;
302
+ prev: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
303
+ rel: z.ZodOptional<z.ZodString>;
304
+ title: z.ZodOptional<z.ZodString>;
305
+ type: z.ZodOptional<z.ZodString>;
306
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
307
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
308
+ href: z.ZodString;
309
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
310
+ rel: z.ZodOptional<z.ZodString>;
311
+ title: z.ZodOptional<z.ZodString>;
312
+ type: z.ZodOptional<z.ZodString>;
313
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
314
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
315
+ href: z.ZodString;
316
+ }, z.core.$strip>, z.ZodNull]>>;
317
+ }, z.core.$strip>, z.ZodNull]>>;
318
+ next: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
319
+ rel: z.ZodOptional<z.ZodString>;
320
+ title: z.ZodOptional<z.ZodString>;
321
+ type: z.ZodOptional<z.ZodString>;
322
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
323
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
324
+ href: z.ZodString;
325
+ describedby: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
326
+ rel: z.ZodOptional<z.ZodString>;
327
+ title: z.ZodOptional<z.ZodString>;
328
+ type: z.ZodOptional<z.ZodString>;
329
+ hreflang: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
330
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
331
+ href: z.ZodString;
332
+ }, z.core.$strip>, z.ZodNull]>>;
333
+ }, z.core.$strip>, z.ZodNull]>>;
334
+ }, z.core.$strip>;
335
+ /** A relationship's links - inferred from {@link relationshipLinksSchema}. */
336
+ export type RelationshipLinks = z.infer<typeof relationshipLinksSchema>;
147
337
  /** A resource identifier object - the `{ type, id }` linkage that points at another resource. */
148
338
  export declare const resourceIdentifierSchema: z.ZodObject<{
149
339
  type: z.ZodString;
@@ -151,9 +341,11 @@ export declare const resourceIdentifierSchema: z.ZodObject<{
151
341
  }, z.core.$strip>;
152
342
  /** A type-and-id resource identifier - inferred from {@link resourceIdentifierSchema}. */
153
343
  export type ResourceIdentifier = z.infer<typeof resourceIdentifierSchema>;
154
- /** The top-level `jsonapi` member - the spec version this document is written to, plus meta. */
344
+ /** The top-level `jsonapi` member: the version this document is written to, the extensions and profiles it applies. */
155
345
  export declare const jsonApiObjectSchema: z.ZodObject<{
156
346
  version: z.ZodOptional<z.ZodString>;
347
+ ext: z.ZodOptional<z.ZodArray<z.ZodString>>;
348
+ profile: z.ZodOptional<z.ZodArray<z.ZodString>>;
157
349
  meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
158
350
  }, z.core.$strip>;
159
351
  /** The top-level `jsonapi` member - inferred from {@link jsonApiObjectSchema}. */
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8DAA8D;AAC9D,eAAO,MAAM,UAAU,wCAAoC,CAAC;AAC5D,oEAAoE;AACpE,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAsB9C,iGAAiG;AACjG,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;8BAIrB,CAAC;AACH,gEAAgE;AAChE,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,iFAAiF;AACjF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,iIAAiI;AACjI,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMhC,CAAC;AACH,iFAAiF;AACjF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,iGAAiG;AACjG,eAAO,MAAM,wBAAwB;;;iBAAiD,CAAC;AACvF,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,gGAAgG;AAChG,eAAO,MAAM,mBAAmB;;;iBAG9B,CAAC;AACH,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8DAA8D;AAC9D,eAAO,MAAM,UAAU,wCAAoC,CAAC;AAC5D,oEAAoE;AACpE,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAsB9C,iGAAiG;AACjG,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;8BAIrB,CAAC;AACH,gEAAgE;AAChE,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,iFAAiF;AACjF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,gGAAgG;AAChG,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqE,CAAC;AACtG,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,gFAAgF;AAChF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOhC,CAAC;AACH,iFAAiF;AACjF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;;iBAAsD,CAAC;AAE/F;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;;;;iBAMtC,CAAC;AAEH,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKlC,CAAC;AACH,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,iGAAiG;AACjG,eAAO,MAAM,wBAAwB;;;iBAAiD,CAAC;AACvF,0FAA0F;AAC1F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,uHAAuH;AACvH,eAAO,MAAM,mBAAmB;;;;;iBAK9B,CAAC;AACH,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -30,18 +30,48 @@ export const resourceLinksSchema = z.object({
30
30
  self: linkSchema.optional(),
31
31
  related: linkSchema.optional(),
32
32
  });
33
- /** Collection pagination links: `self` plus the `first`/`prev`/`next`/`last` navigation, each null at the page that has none. */
33
+ /** A document's own top-level `self`/`related`, plus the description of the document itself. */
34
+ export const topLevelLinksSchema = resourceLinksSchema.extend({ describedby: linkSchema.optional() });
35
+ /** Collection pagination links, plus the description of the document itself. */
34
36
  export const paginationLinksSchema = z.object({
35
37
  self: linkSchema.optional(),
36
38
  first: linkSchema.optional(),
37
39
  prev: linkSchema.optional(),
38
40
  next: linkSchema.optional(),
39
41
  last: linkSchema.optional(),
42
+ describedby: linkSchema.optional(),
43
+ });
44
+ /**
45
+ * The `meta` of a collection that counts its rows: `total` is how many match the read, across every page.
46
+ *
47
+ * This is house vocabulary, not the cursor-pagination profile's `meta.page.total`. That profile
48
+ * describes cursor paging, and this house pages by `page[offset]` and `page[limit]`.
49
+ */
50
+ export const countedCollectionMetaSchema = z.object({ total: z.number().int().nonnegative() });
51
+ /**
52
+ * The links an offset-paged collection always carries. `first` and `last` always exist under offset
53
+ * paging, so they are strings; `prev` and `next` are null where there is no such page.
54
+ */
55
+ export const offsetPaginationLinksSchema = z.object({
56
+ self: z.string(),
57
+ first: z.string(),
58
+ last: z.string(),
59
+ prev: z.string().nullable(),
60
+ next: z.string().nullable(),
61
+ });
62
+ /** A relationship's links: its own `self`/`related`, and the pages of a to-many linkage. */
63
+ export const relationshipLinksSchema = resourceLinksSchema.extend({
64
+ first: linkSchema.optional(),
65
+ last: linkSchema.optional(),
66
+ prev: linkSchema.optional(),
67
+ next: linkSchema.optional(),
40
68
  });
41
69
  /** A resource identifier object - the `{ type, id }` linkage that points at another resource. */
42
70
  export const resourceIdentifierSchema = z.object({ type: z.string(), id: z.string() });
43
- /** The top-level `jsonapi` member - the spec version this document is written to, plus meta. */
71
+ /** The top-level `jsonapi` member: the version this document is written to, the extensions and profiles it applies. */
44
72
  export const jsonApiObjectSchema = z.object({
45
73
  version: z.string().optional(),
74
+ ext: z.array(z.string()).optional(),
75
+ profile: z.array(z.string()).optional(),
46
76
  meta: metaSchema.optional(),
47
77
  });
@@ -1 +1 @@
1
- {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../src/relationship.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../src/relationship.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { metaSchema, resourceIdentifierSchema, resourceLinksSchema } from './primitives.js';
2
+ import { metaSchema, relationshipLinksSchema, resourceIdentifierSchema } from './primitives.js';
3
3
  /** The context a serialize call was handed, for the load pass to give the loaders. */
4
4
  export function loadContext(contextArguments) {
5
5
  // A conditional tuple's element does not narrow back to `C`, and this is the one place that reads it.
@@ -21,10 +21,14 @@ export function toMany(related, read, options) {
21
21
  /** The linkage schema for one relationship, pinned to the related `type` so a wrong type fails the parse. */
22
22
  export function relationshipSchema(def) {
23
23
  const identifier = resourceIdentifierSchema.extend({ type: z.literal(def.related().type) });
24
- return z.object({
24
+ return z
25
+ .object({
25
26
  data: (def.cardinality === 'one' ? identifier.nullable() : z.array(identifier)).optional(),
26
- links: resourceLinksSchema.optional(),
27
+ links: relationshipLinksSchema.optional(),
27
28
  meta: metaSchema.optional(),
29
+ })
30
+ .refine((relationship) => Object.keys(relationship).length > 0, {
31
+ message: 'A relationship object carries links, data or meta.',
28
32
  });
29
33
  }
30
34
  /** The `relationships` member for a whole resource - one key per declaration, absent when it declares none. */
@@ -1,5 +1,5 @@
1
1
  import type { output, ZodObject, ZodType } from 'zod';
2
- import { type CollectionDocument, type ResourceDocument } from './document.js';
2
+ import { type CollectionDocument, type CollectionDocumentFor, type ResourceDocument } from './document.js';
3
3
  import { type LoadContextArguments, type RelatedResource, type RelationshipDef } from './relationship.js';
4
4
  import { type CollectionExtras, type RequestedDocumentShape, type Row } from './serialize.js';
5
5
  /** The context every loader in a relationship map runs under. */
@@ -36,10 +36,25 @@ export interface JsonApiResource<A extends ZodObject, C = unknown, T extends str
36
36
  * @param context - what the loaders are given; the call takes it only where a loader is reachable
37
37
  */
38
38
  serialize(row: Row, requested?: RequestedDocumentShape, ...context: LoadContextArguments<C>): Promise<ResourceDocument<output<A>>>;
39
- /** Serializes rows into a collection document, merging in pagination `links` and `meta`. */
40
- serializeMany(rows: Row[], extras?: CollectionExtras, requested?: RequestedDocumentShape, ...context: LoadContextArguments<C>): Promise<CollectionDocument<output<A>>>;
39
+ /**
40
+ * Serializes rows into a collection document, merging in pagination `links` and `meta`.
41
+ *
42
+ * Typed off `extras`: a member passed is a member present, so the result checks against what the route
43
+ * declared with `listResponse`. A key that is neither `links` nor `meta` is refused, since the
44
+ * serializer drops it unsent.
45
+ */
46
+ serializeMany<X extends CollectionExtras = Record<never, never>>(rows: Row[], extras?: X & Record<Exclude<keyof X, keyof CollectionExtras>, never>, requested?: RequestedDocumentShape, ...context: LoadContextArguments<C>): Promise<CollectionDocumentFor<output<A>, X>>;
41
47
  }
48
+ /** A relationship map's own names, and none where the map is the empty default. */
49
+ type RelationshipName<R> = string extends keyof R ? never : keyof R & string;
50
+ /**
51
+ * `never` where a name breaks the standard's one field namespace (an attribute or relationship named `type` or `id`,
52
+ * or a name that is both), which turns the declaring call into a compile error.
53
+ */
54
+ type FieldNamespace<A extends ZodObject, R> = [
55
+ (keyof A['shape'] & string & ('type' | 'id' | RelationshipName<R>)) | (RelationshipName<R> & ('type' | 'id'))
56
+ ] extends [never] ? unknown : never;
42
57
  /** Declares a JSON:API resource type - its `type`, the schema of its `attributes`, and its relationships. */
43
- export declare function jsonApiResource<const T extends string, A extends ZodObject, const R extends Readonly<Record<string, RelationshipDef<any, any>>> = Readonly<Record<string, never>>, C = ContextOfRelationships<R>>(type: T, attributes: A, options?: ResourceOptions<R>): JsonApiResource<A, C, T, R>;
58
+ export declare function jsonApiResource<const T extends string, A extends ZodObject, const R extends Readonly<Record<string, RelationshipDef<any, any>>> = Readonly<Record<string, never>>, C = ContextOfRelationships<R>>(type: T, attributes: A & FieldNamespace<A, R>, options?: ResourceOptions<R>): JsonApiResource<A, C, T, R>;
44
59
  export {};
45
60
  //# sourceMappingURL=resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACtD,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAe,KAAK,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACvH,OAAO,EAIL,KAAK,gBAAgB,EAErB,KAAK,sBAAsB,EAE3B,KAAK,GAAG,EACT,MAAM,gBAAgB,CAAC;AAExB,iEAAiE;AAEjE,KAAK,sBAAsB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;AAEhG,uDAAuD;AACvD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,mGAAmG;IACnG,aAAa,CAAC,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,SAAS,EACnB,CAAC,GAAG,OAAO,EACX,CAAC,SAAS,MAAM,GAAG,MAAM,EAEzB,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAC5G,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,8EAA8E;IAC9E,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,SAAS,CACP,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,4FAA4F;IAC5F,aAAa,CACX,IAAI,EAAE,GAAG,EAAE,EACX,MAAM,CAAC,EAAE,gBAAgB,EACzB,SAAS,CAAC,EAAE,sBAAsB,EAClC,GAAG,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C;AAgBD,6GAA6G;AAC7G,wBAAgB,eAAe,CAC7B,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,CAAC,SAAS,SAAS,EAEnB,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,EACrG,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,EAC7B,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,GAAE,eAAe,CAAC,CAAC,CAAM,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAwEvF"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
package/dist/resource.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Relator } from 'ts-japi';
2
+ import { z } from 'zod';
2
3
  import { collectionDocumentSchema, resourceDocumentSchema, resourceObjectSchema, } from './document.js';
3
4
  import { loadedRelated, loadIncludedRows } from './document-load.js';
5
+ import { isMemberName } from './member-name.js';
4
6
  import { loadContext } from './relationship.js';
5
- import { createSerializer, serializeCollection, serializeResource, } from './serialize.js';
7
+ import { assertRowId, createSerializer, serializeCollection, serializeResource, } from './serialize.js';
6
8
  /**
7
9
  * How many requested shapes one resource keeps a serializer for. The shape is spelled by the caller's
8
10
  * query, so past this bound a serializer is built for the call and not kept.
@@ -15,11 +17,67 @@ const MAX_KEPT_SERIALIZERS = 64;
15
17
  function shapeKey(requested) {
16
18
  return JSON.stringify([requested?.fields ?? null, requested?.include ?? null]);
17
19
  }
20
+ /**
21
+ * Steps past the wrappers that change presence or transport, not shape, as a declaration's schemas nest:
22
+ * optional/nullable/default, an array's element, a union's members, and a pipe's `in` - which is what a
23
+ * `.transform()` or `.pipe()` compiles to, so a nested object behind either is still checked.
24
+ *
25
+ * A `z.record()` and a `z.lazy()` schema are left unwalked on purpose: a record's keys are the caller's
26
+ * own data, not members this declaration names, and a lazy schema resolves its inner type only on use,
27
+ * which is past the point this walk runs.
28
+ */
29
+ function membersOf(schema) {
30
+ const def = schema.def;
31
+ if (def.innerType)
32
+ return membersOf(def.innerType);
33
+ if (def.type === 'array' && def.element)
34
+ return membersOf(def.element);
35
+ if (def.type === 'union' && def.options)
36
+ return def.options.flatMap(membersOf);
37
+ if (def.type === 'pipe' && def.in)
38
+ return membersOf(def.in);
39
+ return [schema];
40
+ }
41
+ /** Every member name an attribute schema declares, at every depth. */
42
+ function memberNames(schema) {
43
+ return membersOf(schema).flatMap((member) => member instanceof z.ZodObject
44
+ ? Object.entries(member.shape).flatMap(([name, inner]) => [name, ...memberNames(inner)])
45
+ : []);
46
+ }
47
+ /**
48
+ * @throws Error on a type or member name the standard does not allow, a name outside the one field namespace, or an
49
+ * attribute that republishes a relationship's own reference as a foreign key
50
+ */
51
+ function assertDeclaration(type, attributes,
52
+ // Only `cardinality` is read here; the full `RelationshipDef<C, R>` is contravariant in `C` through its
53
+ // optional `load`, which makes a map typed with concrete C/R fail against one typed `any`/`any`.
54
+ relationships) {
55
+ if (!isMemberName(type))
56
+ throw new Error(`"${type}" is not a legal JSON:API type.`);
57
+ const attributeNames = Object.keys(attributes.shape);
58
+ for (const name of [...memberNames(attributes), ...Object.keys(relationships)]) {
59
+ if (!isMemberName(name))
60
+ throw new Error(`"${name}" is not a legal JSON:API member name on "${type}".`);
61
+ }
62
+ for (const name of [...attributeNames, ...Object.keys(relationships)]) {
63
+ if (name === 'type' || name === 'id')
64
+ throw new Error(`"${type}" cannot declare a field named "${name}".`);
65
+ }
66
+ for (const [name, def] of Object.entries(relationships)) {
67
+ if (attributeNames.includes(name))
68
+ throw new Error(`"${type}" declares "${name}" as an attribute and a relationship.`);
69
+ const keys = def.cardinality === 'one' ? [`${name}Id`, `${name}_id`] : [`${name}Ids`, `${name}_ids`];
70
+ const foreignKey = keys.find((key) => attributeNames.includes(key));
71
+ if (foreignKey)
72
+ throw new Error(`"${type}" publishes "${foreignKey}"; the "${name}" relationship already carries it.`);
73
+ }
74
+ }
18
75
  /** Declares a JSON:API resource type - its `type`, the schema of its `attributes`, and its relationships. */
19
76
  export function jsonApiResource(type, attributes, options = {}) {
20
77
  // Safe: when `relationships` is omitted, R is inferred as its own empty-relationships default above,
21
78
  // which `{}` already satisfies.
22
79
  const relationships = (options.relationships ?? {});
80
+ assertDeclaration(type, attributes, relationships);
23
81
  // The attribute keys ARE the projection, so a row member the schema does not declare never reaches the wire.
24
82
  const projection = Object.fromEntries(Object.keys(attributes.shape).map((name) => [name, 1]));
25
83
  const serializers = new Map();
@@ -46,7 +104,14 @@ export function jsonApiResource(type, attributes, options = {}) {
46
104
  return built;
47
105
  const relators = Object.fromEntries(Object.entries(relationships).map(([name, def]) => [
48
106
  name,
49
- new Relator(async (row) => def.load === undefined ? def.read(row) : loadedRelated(def, row, loaded),
107
+ new Relator(async (row) => {
108
+ const related = def.load === undefined ? def.read(row) : loadedRelated(def, row, loaded);
109
+ // A relationship absent on the row reads back `undefined`, not the `null` its own type promises.
110
+ for (const one of Array.isArray(related) ? related : related == null ? [] : [related]) {
111
+ assertRowId(def.related().type, one);
112
+ }
113
+ return related;
114
+ },
50
115
  // The lazy overload, so a pair of resources naming each other resolves on use, not on construction.
51
116
  () => def.related().serializerFor(requested, loaded), { relatedName: name }),
52
117
  ]));
@@ -57,11 +122,14 @@ export function jsonApiResource(type, attributes, options = {}) {
57
122
  return serializer;
58
123
  },
59
124
  async serialize(row, requested, ...context) {
125
+ assertRowId(type, row);
60
126
  const loaded = await loadIncludedRows(resource, [row], requested, loadContext(context));
61
127
  // ts-japi hands back an untyped object; the declared attributes are this resource's own schema.
62
128
  return serializeResource(resource.serializerFor(requested, loaded), row, loaded);
63
129
  },
64
130
  async serializeMany(rows, extras, requested, ...context) {
131
+ for (const row of rows)
132
+ assertRowId(type, row);
65
133
  const loaded = await loadIncludedRows(resource, rows, requested, loadContext(context));
66
134
  return serializeCollection(resource.serializerFor(requested, loaded), rows, extras, loaded);
67
135
  },
@@ -1,7 +1,17 @@
1
1
  import { Serializer, type SerializerOptions } from 'ts-japi';
2
2
  import type { PaginationLinks } from './primitives.js';
3
- /** One row as a serializer hands it over - the shape ts-japi works in. */
4
- export type Row = Record<string, unknown>;
3
+ /** One row as a serializer hands it over. Its `id` is the resource id, which the standard makes a string. */
4
+ export interface Row {
5
+ id: string;
6
+ [member: string]: unknown;
7
+ }
8
+ /**
9
+ * Refuses a row whose id is not a string at runtime. The type is the defence that matters: on a create this fires
10
+ * after the write committed, so it only stops a wrong document from going out.
11
+ *
12
+ * @throws TypeError naming the resource type
13
+ */
14
+ export declare function assertRowId(type: string, row: unknown): void;
5
15
  /**
6
16
  * The rows one document's relationship loaders answered, keyed by related type and then by id.
7
17
  *
@@ -46,8 +56,8 @@ export interface CollectionExtras {
46
56
  * @param data - the row to serialize; its `id` member becomes the resource id
47
57
  * @param loaded - what this document's relationship loaders answered, which decides its `included`
48
58
  */
49
- export declare function serializeResource(serializer: ResourceSerializer, data: Row, loaded?: LoadedRows): Promise<Row>;
59
+ export declare function serializeResource(serializer: ResourceSerializer, data: Row, loaded?: LoadedRows): Promise<Record<string, unknown>>;
50
60
  /** Serializes a list of rows into a collection document, merging in the pagination `links` and `meta`. */
51
- export declare function serializeCollection(serializer: ResourceSerializer, items: Row[], extras?: CollectionExtras, loaded?: LoadedRows): Promise<Row>;
61
+ export declare function serializeCollection(serializer: ResourceSerializer, items: Row[], extras?: CollectionExtras, loaded?: LoadedRows): Promise<Record<string, unknown>>;
52
62
  export {};
53
63
  //# sourceMappingURL=serialize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,UAAU,EAAqB,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEzF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,0EAA0E;AAC1E,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;AAEvE,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;AAEjD,KAAK,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,mFAAmF;IACnF,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACrD,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAgCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,gBAAgB,EAC1B,SAAS,CAAC,EAAE,sBAAsB,GACjC,kBAAkB,CAgBpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AA0BD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAEd;AAED,0GAA0G;AAC1G,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,kBAAkB,EAC9B,KAAK,EAAE,GAAG,EAAE,EACZ,MAAM,GAAE,gBAAqB,EAC7B,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAMd"}
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,UAAU,EAAqB,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEzF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,6GAA6G;AAC7G,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAK5D;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;AAEvE,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;AAEjD,KAAK,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,mFAAmF;IACnF,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACrD,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAgCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,gBAAgB,EAC1B,SAAS,CAAC,EAAE,sBAAsB,GACjC,kBAAkB,CAgBpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AA8CD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,kBAAkB,EAC9B,IAAI,EAAE,GAAG,EACT,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAElC;AAED,0GAA0G;AAC1G,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,kBAAkB,EAC9B,KAAK,EAAE,GAAG,EAAE,EACZ,MAAM,GAAE,gBAAqB,EAC7B,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMlC"}
package/dist/serialize.js CHANGED
@@ -1,5 +1,17 @@
1
1
  import { Relator, Serializer } from 'ts-japi';
2
2
  import { JSON_API_VERSION } from './media-type.js';
3
+ /**
4
+ * Refuses a row whose id is not a string at runtime. The type is the defence that matters: on a create this fires
5
+ * after the write committed, so it only stops a wrong document from going out.
6
+ *
7
+ * @throws TypeError naming the resource type
8
+ */
9
+ export function assertRowId(type, row) {
10
+ const id = row?.id;
11
+ if (typeof id !== 'string') {
12
+ throw new TypeError(`A "${type}" row carries a ${typeof id} id; a JSON:API id is a string.`);
13
+ }
14
+ }
3
15
  /**
4
16
  * Whether the static projection publishes `name`, read so that a `0` counts as hidden under either mode.
5
17
  *
@@ -54,7 +66,11 @@ export function createSerializer(type, options, requested) {
54
66
  }),
55
67
  });
56
68
  }
57
- /** Raises the `jsonapi` member to {@link JSON_API_VERSION}, keeping whatever else ts-japi put there. */
69
+ /**
70
+ * Raises the `jsonapi` member to {@link JSON_API_VERSION}, keeping whatever else ts-japi put there.
71
+ *
72
+ * Typed over a document, not a {@link Row}: a document carries no `id` of its own, unlike the row serialized into it.
73
+ */
58
74
  function stamped(doc) {
59
75
  const existing = (doc.jsonapi ?? {});
60
76
  return { ...doc, jsonapi: { ...existing, version: JSON_API_VERSION } };
@@ -74,6 +90,19 @@ function includedAsLoaded(document, loaded) {
74
90
  included: document.included.filter((resource) => loaded.get(resource.type)?.has(resource.id) ?? true),
75
91
  };
76
92
  }
93
+ /** The `type` and `id` pair a resource object is identified by. */
94
+ const identity = (resource) => JSON.stringify([resource.type, resource.id]);
95
+ /**
96
+ * Drops from `included` every resource already present as primary data. A compound document carries one resource
97
+ * object per type and id, and a client indexing both members by that pair reads the second as a conflict.
98
+ */
99
+ function withoutPrimary(document) {
100
+ if (document.included === undefined || document.data == null)
101
+ return document;
102
+ const data = Array.isArray(document.data) ? document.data : [document.data];
103
+ const primary = new Set(data.map(identity));
104
+ return { ...document, included: document.included.filter((resource) => !primary.has(identity(resource))) };
105
+ }
77
106
  /**
78
107
  * Serializes one row into a single-resource document.
79
108
  *
@@ -81,12 +110,12 @@ function includedAsLoaded(document, loaded) {
81
110
  * @param loaded - what this document's relationship loaders answered, which decides its `included`
82
111
  */
83
112
  export async function serializeResource(serializer, data, loaded) {
84
- return stamped(includedAsLoaded(await serializer.serialize(data), loaded));
113
+ return stamped(withoutPrimary(includedAsLoaded(await serializer.serialize(data), loaded)));
85
114
  }
86
115
  /** Serializes a list of rows into a collection document, merging in the pagination `links` and `meta`. */
87
116
  export async function serializeCollection(serializer, items, extras = {}, loaded) {
88
117
  return {
89
- ...stamped(includedAsLoaded(await serializer.serialize(items), loaded)),
118
+ ...stamped(withoutPrimary(includedAsLoaded(await serializer.serialize(items), loaded))),
90
119
  ...(extras.links ? { links: extras.links } : {}),
91
120
  ...(extras.meta ? { meta: extras.meta } : {}),
92
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxsolutions/response",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,