Core concepts
DocStack treats logic as data. Where a typical stack separates the database from the code that validates and transforms what goes into it, DocStack stores both in the same place, and both replicate.
1. Everything is a document
- Data is a document. A task, a user, an invoice.
- Schema is a document. The definition of what a
Tasklooks like is aclassdocument:{ _id: 'Task', '~class': 'class', schema: { … }, triggers: [ … ] }. - Logic is a document. Validation rules and triggers live on the class document; background jobs are
~Jobdocuments; migrations arepatchdocuments; the scope a document belongs to is a label on the document and a scope document beside it.
Two things follow. You can change a validation rule or a business process by writing a document, with no redeploy. And your entire application, structure, logic and data, moves as one database: replicate it and the other side has all three.
2. The authoring pipeline
Every write through the class API or stack.db goes through the same sequence:
- Run
beforetriggers on the incoming document. They may derive fields or refuse the write. - Validate against the class's hydrated schema (Zod, built from the class document). Validation applies defaults for absent attributes and checks that foreign keys point at existing documents; relation documents are checked against their domain's cardinality.
- Encrypt flagged attributes under the stack's document key.
- Store the batch.
- Run
aftertriggers on the stored document, revision included.
The pipeline is what makes the guarantees real. It is also why replication writes bypass it by design: a document arriving from another device was validated and triggered where it was written, and re-running the pipeline would reject documents authored by a device one patch ahead. The write guard on stack.db makes sure application code cannot take the replication path by accident.
A class flagged simple skips the pipeline entirely, on purpose.
3. Class-based modelling
A class document is a blueprint. Documents "belong" to a class through their ~class field and inherit its validation rules, defaults, triggers, encrypted-field selection and default scope. Classes relate through domains, named relationships with a cardinality whose relation documents are judged by their endpoints.
The workbench reads the same class documents your application uses, which is why it can show any DocStack database's model and data without configuration.
4. Logic injection: triggers and jobs
- Triggers are small pieces of JavaScript attached to a class. They run before or after a document is saved, synchronously in the pipeline. "Before saving an invoice, compute the total." "After creating an order, start the confirmation job."
- Jobs are background tasks defined in
~Jobdocuments. They run when asked, from a trigger, or unattended under a scheduler that the application explicitly starts with an allow-list. "Every day, sweep orders due for a review request."
Both are hydrated at runtime with new Function. That is not a sandbox: trigger and job code runs with the application's authority, and it replicates with the class or job document. DocStack treats write access to those documents as administrative access, and so should you.
5. Access is a property of the ciphertext
Security is not an afterthought, and it is not a rule that runs. Access is a property of the ciphertext: content belongs to a scope, the scope's key is sealed under a monotone attribute policy (("role:manager" and "dept:sales") or "clearance:secret"), and a session's attribute key either satisfies the formula mathematically or the data stays sealed.
- Enforced by decryption: denial is the math failing. There is no client-side check to bypass, and the guarantee holds against the device owner too.
- Conditions are labelling: "published means public" is the write path choosing the document's scope, once; the ciphertext enforces it after.
See Access control for the full model.
6. Field-level encryption under a key the application owns
Mark an attribute encrypted: true and it is AES-GCM ciphertext before it reaches storage, and on every remote it replicates to. The document key is supplied by the application, from its own server, a cloud grant or a passphrase-derived vault; DocStack never invents it, because a key generated per session could not outlive it and a second device would generate a different one. A stack opened without its key is locked: readable where no key is needed, refusing writes to encrypted classes, deferring migrations that need the key, and honest at the sync gate about the schema it has not installed.
The schema says which fields encrypt; a scope says under which key. See Crypto engine.
7. Evolution by patches
Schema changes are declarative patch documents with a semver ledger. A chain of patches applies on open, in order, exactly once per device, through a single internal transaction, so a chain is all-or-nothing and a refused patch names the class and attribute at fault. A patch can carry class models, seed data, massages of existing documents and one-shot migration jobs. The sync layer compares patch versions on both sides before replicating, so a device that trails cannot pull documents its schema cannot describe. See Patches and migrations.
8. The local database is the database
Sync is convergence between two databases, not fetching from one. Every stack is complete on its own; when a remote is reachable, the two replicate with PouchDB's protocol and converge on the same winning revisions. That is why offline is the normal operating mode rather than a degraded one, and why the remote can be anything from a CouchDB endpoint to a folder in the user's own Google Drive. See Offline-first and the sync model.