projection-utils

1.1.0 • Public • Published

projection-utils

A set of utilities for working with MongoDB-style projections.

Notably, this project exposes a ProjectionFieldSet class that tracks, merges, and intersects multi-level projections.

We do not support symmetric or asymmetric diffing of field sets, as the semantics are not well-defined on mongo projections. A field set that contains users minus a field set that contains users.accessToken would need new syntax to represent the fields under users that aren't accessToken, or would need knowledge of all existant fields under the users subdocument. It's better to handle this yourself, using intersect, and a whitelist of permitted fields.

ProjectionFieldSet

Basic usage:

const permittedFields = ProjectionFieldSet.fromDotted(
  ['users.id', 'users.email', 'share', 'content']);
 
const desiredFields = ProjectionFieldSet.fromDotted(
  ['users', 'users.accessToken', 'share', 'invalid']);
 
// The fields we want, where they're permitted.
const selectedFields = permittedFields.intersect(desiredFields);
 
// Add fields that we need for server-side business logic.
const mandatoryFields = ProjectionFieldSet.fromDotted(
  ['internalVersion']);
 
const queryFields = selectedFields.union(mandatoryFields);
const projection = queryFields.toMongo();
// => {'users.id': 1, 'users.email': 1, share: 1, internalVersion: 1}

Constructor usage:

// Equivalent to the first fromDotted invocation in the previous example.
const permittedFields = new ProjectionFieldSet([
  ['users', 'id'],
  ['users', 'email'],
  ['share'],
  ['content'],
]);

Iterate over paths:

for (const path of permittedFields) {
  // path is the array containing the parts of the path, e.g.:
  // ['users', 'email']
}
 
// Or just convert to an Array:
const fields = Array.from(permittedFields);

Enumerate dot-joined paths:

const dotJoined = Array.from(queryFields.toDotted());
// => ['users.id', 'users.email', 'share', 'internalVersion']

Check for field containment, and partial field containment:

queryFields.contains(['users']);
// => false, because only some of the fields in users are included
 
// equivalent to the above
queryFields.containsDotted('users');
 
// produces the set of fields that are included under the users field
Array.from(queryFields.get(['users']));
// => [['users', 'id'], ['users', 'email']]
 
Array.from(queryFields.getDotted('users'));
// => ['users.id', 'users.email']
 
// both produce no items
Array.from(queryFields.get(['invalid']));
Array.from(queryFields.getDotted('invalid'));
// => []
 
// exclude the users prefix
Array.from(queryFields.get('users', false));
// => [['id'], ['email']]
 
Array.from(queryFields.getDotted('users', false));
// => ['id', 'email']

Explicitly expand the set of fields:

// Add users.name to queryFields. Unlike intersect and union, this mutates the
// ProjectionFieldSet instead of making a new instance.
queryFields.widen(['users', 'name']);
queryFields.toMongo();
// => {'users.id': 1, 'users.email': 1, 'users.name': 1, share: 1, internalVersion: 1}
 
// Expand queryFields to include all fields of users (even accessToken - take
// care when ordering operations on ProjectionFieldSets, as an intersect won't
// forbid a set of fields being added to the produced ProjectionFieldSet.
queryFields.widen(['users']);
queryFields.toMongo();
// => {users: 1, share: 1, internalVersion: 1}

Note that field sets can be singular. Unioning with a singular value yields a singular value, and intersecting with a singular value yields the non-singular value. For example:

// This is distinct from new ProjectionFieldSet([]) (and
// new ProjectionFieldSet()), which yield an empty fieldset, rather than a
// singular fieldset.
const singular = new ProjectionFieldSet([[]]);
 
singular.union(singular);
// => copy of singular
 
singular.intersect(singular);
// => copy of singular
 
singular.union(mandatoryFields);
// => copy of singular
 
singular.intersect(mandatoryFields);
// => copy of mandatoryFields
 
const empty = new ProjectionFieldSet([]);
 
empty.union(empty);
// => copy of empty
 
empty.intersect(empty);
// => copy of empty
 
empty.union(mandatoryFields);
// => copy of mandatoryFields
 
empty.intersect(mandatoryFields);
// => copy of empty
 
 
singular.union(empty);
// => copy of singular
 
singular.intersect(empty);
// => copy of empty

Package Sidebar

Install

npm i projection-utils

Weekly Downloads

7,818

Version

1.1.0

License

MIT

Unpacked Size

26.3 kB

Total Files

6

Last publish

Collaborators

  • skeggse