@prsm/arcdb
v1.7.3
Published
A lightweight, in-memory document database for smaller projects. You can think of this as MongoDB's little brother.
Downloads
2
Readme
arc
A lightweight, in-memory document database for smaller projects. You can think of this as MongoDB's little brother.
Installation
npm i @prsm/arcdb
API overview
For a more thorough API reference, please look at the tests in this repository.
Creating a collection
A collection is just a .json
file.
import { Collection } from "@prsm/arcdb";
type Planet = {
planet: string;
diameter: number;
population?: number;
temp: {
avg: number;
};
};
// from `./.data` load or create `planets.json`
const collection = new Collection<Planet>(".data", "planets");
Inserting
collection.insert({ planet: "Mercury", diameter: 4880, temp: { avg: 475 } });
collection.insert([
{ planet: "Venus", diameter: 12_104, temp: { avg: 737_000 } },
{ planet: "Earth", diameter: 12_742, temp: { avg: 288 } },
]);
Finding
// finds Earth document
collection.find({ avg: 288 });
collection.find({ planet: "Earth" });
// finds Venus and Earth documents
collection.find({ diameter: { $gt: 12_000 } });
// finds Mercury and Earth documents
collection.find({ temp: { avg: { $lt: 1_000 } } });
// finds Mercury and Earth documents
collection.find({ $and: [{ avg: { $gt: 100 } }, { avg: { $lt: 10_000 } }] });
Updating
Any queries that work with .find
work with .update
.
// increase population, creating the property if it doesn't exist.
collection.update({ planet: "Earth" }, { $inc: { population: 1 } });
Removing
Any queries that work with .find
work with .remove
.
// remove every planet except Earth
collection.remove({ $not: { planet: "Earth" } });
Query options
find
, update
and remove
accept a QueryOptions
object.
{
/** When true, attempts to deeply match the query against documents. */
deep: boolean;
/** Provide fallback values for null or undefined properties */
ifNull: Record<string, any>;
/** Provide fallback values for 'empty' properties ([], {}, "") */
ifEmpty: Record<string, any>;
/** Provide fallback values for null, undefined, or 'empty' properties. */
ifNullOrEmpty: Record<string, any>;
/**
* -1 || 0: descending
* 1: ascending
*/
sort: { [property: string]: -1 | 0 | 1 };
/**
* Particularly useful when sorting, `skip` defines the number of documents
* to ignore from the beginning of the result set.
*/
skip: number;
/** Determines the number of documents returned. */
take: number;
/**
* 1: property included in result document
* 0: property excluded from result document
*/
project: {
[property: string]: 0 | 1;
};
aggregate: {
[property: string]:
Record<"$floor", string> |
Record<"$ceil", string> |
Record<"$sub", (string|number)[]> |
Record<"$mult", (string|number)[]> |
Record<"$div", (string|number)[]> |
Record<"$add", (string|number)[]>;
};
join: Array<{
/** The collection to join on. */
collection: Collection<any>;
/** The property containing the foreign key(s). */
from: string;
/** The property on the joining collection that the foreign key should point to. */
on: string;
/** The name of the property to be created while will contain the joined documents. */
as: string;
/** QueryOptions that will be applied to the joined collection. */
options?: QueryOptions;
}>;
}
ifNull
// [
// { a: 1, b: 2, c: 3 },
// ];
collection.find({ a: 1 }, { ifNull: { d: 4 } });
// [
// { a: 1, b: 2, c: 3, d: 4 },
// ];
ifEmpty
// [
// { a: 1, b: 2, c: 3, d: " " },
// { a: 1, b: 2, c: 3, d: [] },
// { a: 1, b: 2, c: 3, d: {} },
// ];
collection.find({ a: 1 }, { ifEmpty: { d: 4 } });
// [
// { a: 1, b: 2, c: 3, d: 4 },
// { a: 1, b: 2, c: 3, d: 4 },
// { a: 1, b: 2, c: 3, d: 4 },
// ];
Sorting
// [
// { name: "Deanna Troi", age: 28 },
// { name: "Worf", age: 24 },
// { name: "Xorf", age: 24 },
// { name: "Zorf", age: 24 },
// { name: "Jean-Luc Picard", age: 59 },
// { name: "William Riker", age: 29 },
// ];
collection.find({ age: { $gt: 1 } }, { sort: { age: 1, name: -1 } });
// └─ asc └─ desc
// [
// { name: "Zorf", age: 24 },
// { name: "Xorf", age: 24 },
// { name: "Worf", age: 24 },
// { name: "Deanna Troi", age: 28 },
// { name: "William Riker", age: 29 },
// { name: "Jean-Luc Picard", age: 59 },
// ];
Skip & take (i.e. LIMIT)
Mostly useful when paired with sort
.
// [
// { a: 1, b: 1, c: 1 },
// { a: 2, b: 2, c: 2 },
// { a: 3, b: 3, c: 3 },
// ];
collection.find({ a: { $gt: 0 } }, { skip: 1, take: 1 });
// [
// { a: 2, b: 2, c: 2 },
// ];
Projection
The ID property of a document is always included unless explicitly excluded.
Implicit exclusion
When all projected properties have a value of 1
, this
is "implicit exclusion" mode.
In this mode, all document properties that are not defined in the projection are excluded from the result document.
// [
// { a: 1, b: 1, c: 1 },
// ];
collection.find({ a: 1 }, { project: { b: 1 } });
// [
// { _id: .., b: 1 },
// ];
Implicit inclusion
When all projected properties have a value of 0
, this
is "implicit inclusion" mode.
In this mode, all document properties that are not defined in the projection are included from the result document.
// [
// { a: 1, b: 1, c: 1 },
// ];
collection.find({ a: 1 }, { project: { b: 0 } });
// [
// { _id: .., a: 1, c: 1 },
// ];
Explicit
In the only remaining case, all document properties
are included unless explicitly removed with a 0
.
This is effectively the same behavior as implicit inclusion.
// [
// { a: 1, b: 1, c: 1 },
// ];
collection.find({ a: 1 }, { project: { b: 1, c: 0 } });
// [
// { _id: .., a: 1, b: 1 },
// ];
Aggregation
// [
// { math: 72, english: 82, science: 92 },
// { math: 60, english: 70, science: 80 },
// { math: 90, english: 72, science: 84 }
// ]
collection.find(
{},
{
aggregate: {
total: { $add: ["math", "english", "science"] },
average: { $div: ["total", 3] },
},
project: {
_id: 0,
total: 0,
},
}
);
// [
// { math: 72, english: 82, science: 92, average: 82 },
// { math: 60, english: 70, science: 80, average: 70 },
// { math: 90, english: 72, science: 84, average: 82 },
// ]
Joining
// "users" collection
// [
// { name: "Alice", purchasedTicketIds: [1, 2] },
// ];
// "tickets" collection
// [
// { _id: 0, seat: "A1" },
// { _id: 1, seat: "B1" },
// { _id: 2, seat: "C1" },
// { _id: 3, seat: "D1" },
// ];
users.find(
{ name: "Alice" },
{
join: [
{
collection: tickets,
from: "purchasedTicketIds",
on: "_id",
as: "tickets",
options: {
project: { _id: 0 },
},
},
],
}
);
// [
// {
// name: "Alice",
// purchasedTicketIds: [1, 2],
// tickets: [
// { seat: "B1" },
// { seat: "C1" },
// ],
// },
// ];
join
allows for QueryOptions
which in turn alows for join
.
This means that joins can be chained for more complex relationships
between collections.
users.find(
{ .. },
{
join: [{
collection: tickets,
options: {
join: [{
collection: seats,
options: {
join: [{
collection: auditoriums,
}]
}
}]
}
}]
}
);
Misc
Renaming builtin property names
The default property names for document ID (default _id
), "created at"
(default _created_at
) and "updated at" (default _updated_at
) timestamps can all be changed.
import { ID_KEY, CREATED_AT_KEY, UPDATED_AT_KEY } from "@prsm/arcdb";
ID_KEY = "id";
CREATED_AT_KEY = "createdAt";
UPDATED_AT_KEY = "updatedAt";
If you do this, make sure to do it at the beginning of collection creation.
Documents
The returned value from find
, update
and remove
is always an array, even when there
are no results.