Document store
Scripts keep documents in collections: ctx.db.<collection>, made the first time a script names
it. Writes are kept in memory for the run (see --db-file), shared by every script and shown by
the admin API’s /__apigenie/db.
export default {
onRequest(req, ctx) {
if (req.method === 'POST' && req.path === '/notes') {
return { status: 201, body: ctx.db.notes.insert(req.body) };
}
if (req.path === '/notes') {
return { body: ctx.db.notes.find({ tags: { $contains: 'home' } }, { sort: '-id', limit: 10 }) };
}
},
};
| Method | Returns |
|---|---|
insert(document) |
The document as stored, with its id. |
get(id) |
The document, or undefined. "3" finds 3. |
find(where, {limit, offset, sort}) |
An array of documents. |
count(where) |
A number. |
all() |
Every document, in the order they were inserted. |
update(id, patch) |
The document after a JSON merge patch (null removes a property), or undefined for an unknown id. The id stays. |
replace(id, document) |
The new document with the old id, or undefined. |
delete(id) |
true when there was such a document. |
clear() |
How many documents went. |
ctx.db.collections() lists the collections and ctx.db.reset() (reset('notes') for one) puts
them back to their seeds.
- Queries.
whereis an object of fields and values, all of which must match:{status: 'open'},{'address.city': 'Kraków'}(a path), or operators:$eq,$ne,$in(an array of values),$gt,$gte,$lt,$lte(numbers and strings),$contains(a substring, or an item of an array):{weight: {$gte: 5, $lt: 20}}. Numbers and strings are never equal.sortis'field','-field'(descending),'a,-b', an array of them or[{field, descending}]; missing values sort last. Anything fancier is a JavaScript filter on a limited result. - Ids. A document keeps its own
id(a string or an integer), else gets the next one: 1, 2, 3… ("1","2"… for a resource whose id is a string, a UUID made from the run’s--seedforformat: uuid). An id in use is refused, and the script’s call throws. - Schemas. The collection of a resource is bound to its schema: a new
document gets the
readOnlyproperties it lacks (generated, likecreatedAt), and every write is checked. A document that doesn’t match is kept and reported in the log, or refused (422 for a request, a throw for a script) whenvalidation.responseisenforce(--strict). - Seeds.
db.seednames a file of documents (a JSON or YAML array) per collection, or a directory of them (seed/shipments.jsonseedsshipments). A resource’s collection without one is seeded from its list operation’s example, else withdb.generate(3) documents generated with the run’s seed.ctx.db.reset()andPOST /__apigenie/db/resetgo back to the seeds, not to nothing. A seed that doesn’t match the schema is kept and reported at the start; a file that can’t be read stops the server with exit status 2. - Tests. A request with an
X-Apigenie-Test: <id>header reads and writes a namespace of its own, which starts as a copy of the seeds: parallel test runs don’t see each other’s documents. The admin API’s/dbendpoints take the same header (or?test=<id>). - Snapshots.
PUT /__apigenie/db/snapshots/{name}copies the documents of every collection,POST /__apigenie/db/snapshots/{name}/restoreputs them back. - Persistence. In memory by default, so a restart is a clean slate. With
--db-file(ordb.file), the store is read from that JSON file at the start (a file that can’t be read is reported, and the store starts empty) and written there when the server stops or onPOST /__apigenie/db/save. The file holdsformatVersion1, the collections’ seeds, the documents of each namespace and the snapshots. Seed files don’t replace what the file holds. - Log. Every write is a
dbevent: the collection,insert 3,update 3,delete 3,clear 2 documents,reset,restore <name>,seed 3 documents from file, who wrote (script:mock.mjs,admin) and the test’s namespace (test).onMessageandonGraphQLuse the default namespace. - After a reload. The documents stay; collections are bound again, new ones seeded, and documents that no longer match their schema are reported.
- Several services. Each service has a store of its own, which its
scripts’
ctx.dband its resources use;db.seedseeds every service’s collections, and the admin API’s/dbendpoints take?service=when several could be meant.--db-file(anddb.file) keep the store of a server of one document; with several services it exits with status 2. - Limits. A collection keeps up to
db.max_documents(10,000) documents per namespace. The store is not a database: no transactions, relations or durability promises.
CRUD resources
With db.crud: auto (or --crud), a path and the path of its items, such as /shipments and
/shipments/{id}, make a resource whose collection (shipments) its operations read and write;
no script is needed. Without it (db.crud: explicit, the default), only the paths an
x-apigenie-mock: {resource: ...} extension names are resources; db.crud: off serves none.
| Operation | Answer |
|---|---|
GET /shipments |
The documents, paged and filtered by the operation’s query parameters. |
POST /shipments |
The document stored, with its id and readOnly properties; 201 when the operation declares it. |
GET /shipments/{id} |
The document, or 404. |
PUT /shipments/{id} |
The document replaced (the id and readOnly properties stay), or 404. |
PATCH /shipments/{id} |
The document after a JSON merge patch, or 404. |
DELETE /shipments/{id} |
204 when the operation declares it, else the document that went; 404 for an unknown id. |
- The schema is the list response’s items, or the items of its object’s array property (an
envelope such as
{data: [...], total: 3}: one array property, or the one calleddata,items,resultsor like the collection), else the item response’s or the request body’s. The id is the schema’sidproperty, else the path parameter when it is a property; integers count, strings withformat: uuidare UUIDs. Answers leavewriteOnlyproperties out. - Lists page by the list operation’s parameters:
offsetandlimit,page(from 1) andper_page(page_size,pageSize,size,limit), orcursor(after,page_token) and a size, the size’sdefaultwhen not given (20 without one). An envelope gets the page in its array property, andtotal(totalCount,total_count…),hasMore,nextCursor,page,offsetandlimitfilled in when it declares them; its other members are generated. Other query parameters named like a property of the documents filter by it (?status=open), andsort(order_by) sorts:-createdAt,status:desc,id. - What comes first. Requests are validated before the store sees them (422 under
validation.request: enforce). A request’sPrefer(or__parameters), a scenario’s or an override’s rule, an extension that sets the status, an example or a body, and stubs answer instead of the store. The store’s answers are validated like any other, and sayX-Apigenie-Mock-Source: db. - The extension, on a path item or an operation of either path:
resource: shipments(the collection’s name),resource: false(not a resource, also withdb.crud: auto) orresource: {name: parcels, id: code, schema: Parcel, list: entries}(the id property, the schema undercomponents/schemasor as a#/reference, the envelope’s array property). - Proxies. A document served with
--proxy-tohas no resources: the server’s state is the upstream’s.
paths:
/shipments:
x-apigenie-mock: {resource: {name: shipments, schema: Shipment}}
get: ...
/shipments/{id}:
get: ...