lumen-framework 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/cli.cjs +71 -38
  2. package/dist/cli.cjs.map +4 -4
  3. package/dist/index.js +978 -272
  4. package/dist/index.js.map +4 -4
  5. package/dist/index.mjs +969 -263
  6. package/dist/index.mjs.map +4 -4
  7. package/dist/types/errors/namespaced-serializer-missing-error.d.ts +15 -0
  8. package/dist/types/packages/application/utils/validate-namespaced-serializers.d.ts +15 -0
  9. package/dist/types/packages/controller/errors/index.d.ts +1 -0
  10. package/dist/types/packages/controller/errors/related-record-not-found-error.d.ts +14 -0
  11. package/dist/types/packages/controller/index.d.ts +65 -0
  12. package/dist/types/packages/controller/utils/find-many.d.ts +2 -1
  13. package/dist/types/packages/controller/utils/find-one.d.ts +2 -1
  14. package/dist/types/packages/controller/utils/params-to-query.d.ts +7 -2
  15. package/dist/types/packages/controller/utils/validate-relationships.d.ts +8 -0
  16. package/dist/types/packages/database/constants.d.ts +1 -0
  17. package/dist/types/packages/database/model/index.d.ts +11 -0
  18. package/dist/types/packages/database/model/utils/process-write-error.d.ts +9 -1
  19. package/dist/types/packages/database/validation/errors/validation-error.d.ts +11 -2
  20. package/dist/types/packages/jsonapi/errors/not-acceptable-error.d.ts +1 -1
  21. package/dist/types/packages/jsonapi/index.d.ts +3 -1
  22. package/dist/types/packages/jsonapi/interfaces.d.ts +1 -1
  23. package/dist/types/packages/jsonapi/utils/has-media-type-params.d.ts +6 -0
  24. package/dist/types/packages/jsonapi/utils/is-jsonapi.d.ts +2 -0
  25. package/dist/types/packages/jsonapi/utils/media-type.d.ts +19 -0
  26. package/dist/types/packages/router/route/params/errors/client-generated-id-error.d.ts +13 -0
  27. package/dist/types/packages/router/route/params/errors/forbidden-parameter-error.d.ts +14 -0
  28. package/dist/types/packages/router/route/params/errors/index.d.ts +2 -0
  29. package/dist/types/packages/router/route/params/errors/invalid-parameter-error.d.ts +2 -0
  30. package/dist/types/packages/router/route/params/errors/parameter-not-nullable-error.d.ts +2 -0
  31. package/dist/types/packages/router/route/params/errors/parameter-required-error.d.ts +2 -0
  32. package/dist/types/packages/router/route/params/errors/parameter-type-error.d.ts +2 -0
  33. package/dist/types/packages/router/route/params/errors/parameter-value-error.d.ts +2 -0
  34. package/dist/types/packages/router/route/params/errors/resource-mismatch-error.d.ts +2 -0
  35. package/dist/types/packages/router/route/params/index.d.ts +1 -0
  36. package/dist/types/packages/router/route/params/parameter/forbidden-parameter.d.ts +12 -0
  37. package/dist/types/packages/router/route/params/parameter/index.d.ts +8 -0
  38. package/dist/types/packages/router/route/params/utils/validate-client-id.d.ts +8 -0
  39. package/dist/types/packages/serializer/index.d.ts +111 -36
  40. package/dist/types/packages/serializer/utils/include-tree.d.ts +29 -0
  41. package/dist/types/packages/serializer/utils/load-linkage.d.ts +22 -0
  42. package/dist/types/packages/server/index.d.ts +2 -1
  43. package/dist/types/packages/server/interfaces.d.ts +5 -0
  44. package/dist/types/packages/server/utils/source-for.d.ts +10 -0
  45. package/dist/types/packages/server/utils/validate-accept.d.ts +5 -1
  46. package/dist/types/packages/server/utils/validate-content-type.d.ts +4 -0
  47. package/package.json +1 -1
  48. package/dist/types/packages/jsonapi/utils/has-media-type.d.ts +0 -4
@@ -1,6 +1,9 @@
1
+ import type { Bundle$Namespace } from '../loader';
1
2
  import type { Model, ModelClass } from '../database';
2
3
  import type { JSONAPI$Document, JSONAPI$DocumentLinks, JSONAPI$ResourceObject, JSONAPI$RelationshipObject } from '../jsonapi';
3
4
  import type { Serializer$opts } from './interfaces';
5
+ import type { Linkage } from './utils/load-linkage';
6
+ import type { IncludeTree } from './utils/include-tree';
4
7
  /**
5
8
  * ## Overview
6
9
  *
@@ -155,6 +158,17 @@ import type { Serializer$opts } from './interfaces';
155
158
  * If we request that the `posts` association is included from the `/users`
156
159
  * endpoint, we will only get the `attributes` that the `PostsSerializer` has
157
160
  * defined even though the response is processed by the `UsersSerializer`.
161
+ * The same goes for relationships: each included resource carries the
162
+ * `relationships` its own Serializer declares in `hasOne` and `hasMany`.
163
+ *
164
+ * Included resources follow the request's namespace: from `/admin/posts`,
165
+ * included comments are serialized by `AdminCommentsSerializer` when it
166
+ * exists and by `CommentsSerializer` otherwise (the same fallback a
167
+ * namespaced Controller uses), and their links point into `/admin`.
168
+ *
169
+ * Relationship paths may be nested, e.g. `/posts?include=comments.user`, up
170
+ * to the controller's `maxIncludeDepth` (3 by default). The intermediate
171
+ * resources (the comments) are included along with the leaves (their users).
158
172
  *
159
173
  * #### Sparse Fieldsets
160
174
  *
@@ -401,6 +415,17 @@ declare class Serializer<T extends Model> {
401
415
  * @private
402
416
  */
403
417
  namespace: string;
418
+ /**
419
+ * Every Serializer of the application, keyed by namespaced path (`posts`,
420
+ * `admin/posts`). Attached once all of them are built, and used by
421
+ * `serializerFor()` to serialize related resources in this Serializer's
422
+ * namespace.
423
+ *
424
+ * @property serializers
425
+ * @type {Map}
426
+ * @private
427
+ */
428
+ serializers?: Bundle$Namespace<Serializer<Model>>;
404
429
  constructor({ model, parent, namespace }: Serializer$opts<T>);
405
430
  /**
406
431
  * Transform an array of Model instances or a single Model instance into a
@@ -423,25 +448,40 @@ declare class Serializer<T extends Model> {
423
448
  * the resource and relationship objects in the returned [JSON API](
424
449
  * http://jsonapi.org) document object.
425
450
  *
426
- * @param {Array} options.include - An array of strings containing the
427
- * relationship keys that should be added to the top level included object of
428
- * the returned [JSON API](http://jsonapi.org) document object.
451
+ * @param {Array} options.include - An array of relationship paths (e.g.
452
+ * `'comments'` or `'comments.user'`) whose resources should be added to the
453
+ * top level included object of the returned [JSON API](http://jsonapi.org)
454
+ * document object. Intermediate resources of a nested path are included too.
455
+ *
456
+ * @param {String} options.namespace - The namespace of the request, i.e. of
457
+ * the Controller handling it. Every link in the document is built in it, and
458
+ * included resources are serialized by their Serializer in it (falling back
459
+ * to the root). Defaults to this Serializer's namespace — which is the root
460
+ * one when a namespaced Controller has no Serializer of its own, so the
461
+ * Controller passes its namespace explicitly.
429
462
  *
430
463
  * @return {Promise} Resolves with a [JSON API](http://jsonapi.org) document
431
464
  * object.
432
465
  *
433
466
  * @private
434
467
  */
435
- format({ data, links, domain, include }: {
468
+ format({ data, links, domain, include, namespace }: {
436
469
  data: T | Array<T>;
437
470
  links: JSONAPI$DocumentLinks;
438
471
  domain: string;
439
472
  include: Array<string>;
473
+ namespace?: string;
440
474
  }): Promise<JSONAPI$Document>;
441
475
  /**
442
476
  * Transform a single Model instance into a [JSON API](http://jsonapi.org)
443
477
  * resource object.
444
478
  *
479
+ * Relationships are serialized in one of two ways. By default each one is
480
+ * read from the Model instance (for primary data the query has already
481
+ * loaded them). When `linkage` is given — as it is for included resources,
482
+ * whose relationships are batch-loaded by `addIncluded()` — the resource
483
+ * linkage is built from it instead, without touching the database.
484
+ *
445
485
  * @method formatOne
446
486
  *
447
487
  * @param {Object} options - An options object used for building the returned
@@ -458,30 +498,33 @@ declare class Serializer<T extends Model> {
458
498
  * the top level links object or relationship links objects in the returned
459
499
  * [JSON API](http://jsonapi.org) resource object.
460
500
  *
461
- * @param {Array} options.include - An array of strings containing the
462
- * relationship keys that should be added to the top level included object of
463
- * a [JSON API](http://jsonapi.org) document object.
501
+ * @param {Array} options.include - An array of the relationship keys whose
502
+ * related records should be collected into `options.related`.
464
503
  *
465
- * @param {Array} options.included - An array of [JSON API](
466
- * http://jsonapi.org) resource objects that will be added to the top level
467
- * included array of a [JSON API](http://jsonapi.org) document object.
504
+ * @param {Map} options.related - Collects, per relationship key in
505
+ * `options.include`, the related Model instances that belong in the top
506
+ * level included object of a [JSON API](http://jsonapi.org) document object.
468
507
  *
469
- * @param {Boolean} options.formatRelationships - Wether or not
470
- * relationships should be formatted and included in the returned
471
- * [JSON API](http://jsonapi.org) resource object.
508
+ * @param {Object} options.linkage - Pre-loaded resource linkage (related
509
+ * primary keys per relationship key) to serialize relationships from.
510
+ *
511
+ * @param {String} options.namespace - The namespace to build links in.
512
+ * Defaults to this Serializer's; included resources pass the namespace of
513
+ * the request, so every link in a document points into the same namespace.
472
514
  *
473
515
  * @return {Promise} Resolves with a [JSON API](http://jsonapi.org) resource
474
516
  * object.
475
517
  *
476
518
  * @private
477
519
  */
478
- formatOne({ item, links, domain, include, included, formatRelationships }: {
520
+ formatOne({ item, links, domain, include, related, linkage, namespace }: {
479
521
  item: T;
480
522
  links?: boolean;
481
523
  domain: string;
482
- include: Array<string>;
483
- included: Array<JSONAPI$ResourceObject>;
484
- formatRelationships?: boolean;
524
+ include?: Array<string>;
525
+ related?: Map<string, Array<Model>>;
526
+ linkage?: Linkage;
527
+ namespace?: string;
485
528
  }): Promise<JSONAPI$ResourceObject>;
486
529
  /**
487
530
  * Transform a single Model instance into a [JSON API](http://jsonapi.org)
@@ -489,33 +532,65 @@ declare class Serializer<T extends Model> {
489
532
  *
490
533
  * @method formatRelationship
491
534
  *
492
- * @param {Object} options - An options object used for building the returned
535
+ * @param {Model} item - The Model instance to transform into the returned
493
536
  * [JSON API](http://jsonapi.org) relationship object.
494
537
  *
495
- * @param {Model} options.item - The Model instance to transform into the
538
+ * @param {String} domain - A string used to build links included in the
496
539
  * returned [JSON API](http://jsonapi.org) relationship object.
497
540
  *
498
- * @param {String} options.domain - A string used to build links included in
499
- * the returned [JSON API](http://jsonapi.org) relationship object.
500
- *
501
- * @param {Array} options.include - An array of strings containing the
502
- * relationship keys that should be added to the top level included object of
503
- * a [JSON API](http://jsonapi.org) document object.
541
+ * @return {Object} A [JSON API](http://jsonapi.org) relationship object.
504
542
  *
505
- * @param {Array} options.included - An array of [JSON API](
506
- * http://jsonapi.org) resource objects that will be added to the top level
507
- * included array of a [JSON API](http://jsonapi.org) document object.
508
- *
509
- * @return {Promise} Resolves with a [JSON API](http://jsonapi.org)
510
- * relationship object.
543
+ * @private
544
+ */
545
+ formatRelationship(item: Model, domain: string, namespace?: string): JSONAPI$RelationshipObject;
546
+ /**
547
+ * Build a [JSON API](http://jsonapi.org) relationship object from resource
548
+ * linkage, in the same shape `formatRelationship()` produces from Model
549
+ * instances: to-one relationships carry a `links` object, to-many ones only
550
+ * `data`, and a missing to-one relationship is `{ data: null }`.
511
551
  *
552
+ * @method formatLinkage
553
+ * @private
554
+ */
555
+ formatLinkage(domain: string, type: string | undefined, linkage: Array<string> | string | null | undefined, namespace?: string): JSONAPI$RelationshipObject;
556
+ /**
557
+ * Add `records` (instances of `model`) to `included` as resource objects,
558
+ * then recurse into the relationships named in `tree`. Each is serialized by
559
+ * `model`'s Serializer in this Serializer's namespace (`serializerFor()`), so
560
+ * `/admin/posts?include=comments` uses `AdminCommentsSerializer` when there
561
+ * is one and `CommentsSerializer` otherwise. The relationships of every level
562
+ * are batch-loaded with one query per relationship, not one per record.
563
+ *
564
+ * @method addIncluded
512
565
  * @private
513
566
  */
514
- formatRelationship({ item, domain, include, included }: {
515
- item: Model;
567
+ addIncluded({ model, records, tree, domain, included, namespace }: {
568
+ model: ModelClass;
569
+ records: Array<Model>;
570
+ tree: IncludeTree;
516
571
  domain: string;
517
- include: boolean;
518
- included: Array<JSONAPI$ResourceObject>;
519
- }): Promise<JSONAPI$RelationshipObject>;
572
+ included: Map<string, JSONAPI$ResourceObject>;
573
+ namespace: string;
574
+ }): Promise<void>;
575
+ /**
576
+ * Resolve the Serializer for `model` in `namespace` (this Serializer's by
577
+ * default), the way a namespaced Controller resolves its own:
578
+ * `admin/comments` if it exists, otherwise the closest ancestor namespace's,
579
+ * down to the root `comments` Serializer. Falls back to `model.serializer`
580
+ * when this Serializer was not created by an application (e.g. in
581
+ * isolation).
582
+ *
583
+ * Pass the request's namespace when there is one: a Serializer's own
584
+ * namespace is the root one whenever it is a namespaced Controller's
585
+ * fallback.
586
+ *
587
+ * @method serializerFor
588
+ * @private
589
+ */
590
+ serializerFor(model: ModelClass, namespace?: string): Serializer<Model>;
591
+ /**
592
+ * @private
593
+ */
594
+ linkFor(domain: string, type: string, id: string, namespace?: string): string;
520
595
  }
521
596
  export default Serializer;
@@ -0,0 +1,29 @@
1
+ import type { Model, ModelClass } from '../../database';
2
+ import type Serializer from '../index';
3
+ /**
4
+ * A parsed `include` parameter: each relationship name maps to the tree of
5
+ * relationships to include from the records it points to.
6
+ *
7
+ * @private
8
+ */
9
+ export type IncludeTree = Map<string, IncludeTree>;
10
+ /**
11
+ * Parse relationship paths (`['comments', 'comments.user', 'user']`) into an
12
+ * include tree. Every prefix of a path is part of the tree, because JSON:API
13
+ * requires the intermediate resources of a multi-part path to be included
14
+ * along with its leaves (`comments.user` includes the comments too).
15
+ *
16
+ * @private
17
+ */
18
+ export declare function createIncludeTree(paths?: Array<string>): IncludeTree;
19
+ /**
20
+ * Enumerate every include path available from `names` (relationships of
21
+ * `model`), down to `depth` levels. Each level continues with the relationships
22
+ * declared by the related model's serializer as `serializerFor` resolves it —
23
+ * the same one its included resources are serialized with (namespaced, with a
24
+ * fallback to the root). Used to build the allowed values of the `include`
25
+ * parameter.
26
+ *
27
+ * @private
28
+ */
29
+ export declare function enumerateIncludePaths(model: ModelClass, names: Array<string>, depth: number, serializerFor?: (model: ModelClass) => Serializer<Model> | undefined): Array<string>;
@@ -0,0 +1,22 @@
1
+ import type { Model, ModelClass } from '../../database';
2
+ /**
3
+ * The resource linkage of one record: the primary key(s) of the records each
4
+ * named relationship points to — an array for has-many, otherwise a single id
5
+ * or `null`.
6
+ *
7
+ * @private
8
+ */
9
+ export type Linkage = Record<string, Array<string> | string | null>;
10
+ /**
11
+ * Batch-load the resource linkage of the relationships `names` for every one of
12
+ * `records` (all instances of `model`), keyed by each record's primary key.
13
+ *
14
+ * This is what lets included resources carry `relationships` without an N+1:
15
+ * it issues at most one query per relationship — and a single query for all
16
+ * belongs-to relationships, whose foreign keys live on `model` itself — however
17
+ * many records there are. The queries mirror the lazy relationship getters in
18
+ * `database/relationship/utils/getters.ts`, which remain the source of truth.
19
+ *
20
+ * @private
21
+ */
22
+ export default function loadLinkage(model: ModelClass, records: Array<Model>, names: Array<string>): Promise<Map<string, Linkage>>;
@@ -22,6 +22,7 @@ declare class Server {
22
22
  export default Server;
23
23
  export { REQUEST_METHODS, getDomain } from './request';
24
24
  export { default as createServerError } from './utils/create-server-error';
25
- export type { Server$config } from './interfaces';
25
+ export { default as sourceFor } from './utils/source-for';
26
+ export type { Server$config, Server$ErrorSource } from './interfaces';
26
27
  export type { Request, Request$params, Request$method } from './request/interfaces';
27
28
  export type { Response } from './response/interfaces';
@@ -13,6 +13,11 @@ export type Server$opts = Server$config & {
13
13
  logger: Logger;
14
14
  router: Router;
15
15
  };
16
+ export type Server$ErrorSource = {
17
+ pointer?: string;
18
+ parameter?: string;
19
+ };
16
20
  export interface Server$Error extends Error {
17
21
  statusCode: number;
22
+ source?: Server$ErrorSource;
18
23
  }
@@ -0,0 +1,10 @@
1
+ import type { Server$ErrorSource } from '../interfaces';
2
+ /**
3
+ * Map an internal parameter path to a JSON:API error `source`. Paths under
4
+ * `data` are request document members and become a JSON Pointer
5
+ * (`data.attributes.isPublic` -> `/data/attributes/is-public`); anything else
6
+ * is a query parameter (`page.size` -> `page[size]`).
7
+ *
8
+ * @private
9
+ */
10
+ export default function sourceFor(path: string): Server$ErrorSource;
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * JSON:API 1.0: respond 406 if the Accept header contains the JSON:API media
3
+ * type and *all* instances of it are modified with media type parameters.
4
+ * An Accept header without the JSON:API media type is left alone.
5
+ *
2
6
  * @private
3
7
  */
4
- export default function validateAccept(contentType?: string): true;
8
+ export default function validateAccept(accept?: string): true;
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * JSON:API 1.0: respond 415 if the Content-Type is the JSON:API media type
3
+ * with any media type parameters. A missing or different Content-Type is
4
+ * also answered with 415, since that is the only type the server accepts.
5
+ *
2
6
  * @private
3
7
  */
4
8
  export default function validateContentType(contentType?: string): true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumen-framework",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Build scalable, Node.js-powered REST APIs with almost no code.",
5
5
  "keywords": [
6
6
  "mvc",
@@ -1,4 +0,0 @@
1
- /**
2
- * @private
3
- */
4
- export default function hasMediaType(value: string): boolean;