The q* stdlib docs
Dependency rules & architecture
This page explains how the stdlib crates are layered and why the boundaries are drawn where they are. The rules are short and strict: there is a zero-dependency policy (std plus first-party crates only, with cryptography behind a trait as the sole exception); the substrate (qexec + qvalue) sits at the bottom with no inter-dependency between its two halves; every area.* function package depends on exactly the substrate and nothing else; and products depend on the stdlib but the stdlib never depends on a product. The result is a small, acyclic dependency graph that compiles fast, has no third-party supply chain, and keeps the executor reusable independently of the value model.
The layers#
The qpkgs tree is the shared q* stdlib: a substrate plus one isolated function package per area.* namespace. There is no top-level workspace manifest — crates are wired by path dependency, and the de-facto member set is whatever a consumer declares. The integration point is the DMS (qdms), which path-deps all of them and is where cargo check runs green (it cannot run from qpkgs, which has no root manifest).
- Layer 0 — substrate
- qexec (executor + function runtime) and qvalue (value model + codec). Pure std, zero dependencies. These two are NOT dependent on each other.
- Layer 1 — encoding primitives
- qencoding (base64/hex codecs from scratch) depends only on the substrate. qcrypto depends on the substrate plus qencoding — the single intra-stdlib dependency beyond the substrate.
- Layer 1 — function packages
- qarray, qobject, qstring, qmath, qnumber, qconvert, qregex, qtime, quuid — each depends on exactly qexec + qvalue and nothing else.
- Layer 2 — products
- qdms (the DMS), qquill (the UI framework), the website, the cloud control plane. Products depend on the stdlib; the stdlib never depends on a product.
qvalue qexec <- Layer 0: substrate, std only, mutually independent
^ ^ ^
| | |
qencoding | |
^ | |
qcrypto | |
+------+------ qarray qobject qstring qmath
| qnumber qconvert qregex qtime quuid
v
(every area.* package -> qexec + qvalue)
products (qdms, qquill, site, cloud) -> q* stdlib
q* stdlib -X-> products (never)Rule 1 — zero third-party dependencies#
Every stdlib crate is built on std and first-party crates only. No serde, no tokio, no syn, no regex crate, no base64 crate — these are all implemented from scratch in-tree (qregex is a parser → bytecode → backtracking VM; qencoding hand-rolls base64/hex; qvalue hand-rolls its binary codec). Shipped JavaScript is hand-written with zero imports as well.
You can confirm the policy from the manifests: qexec's Cargo.toml has no [dependencies] section at all, and qvalue's likewise — both are pure std. A function package like qarray lists only qexec and qvalue; qcrypto additionally lists qencoding. There are no version constraints on external registries to audit.
Rule 2 — qexec and qvalue are independent#
The two halves of the substrate do not depend on each other. qexec works entirely in bytes — a job is FnOnce(&mut WorkerContext, &mut Vec<u8>) -> Result<(), JobError>, and the runtime moves &[u8] in and Vec<u8>/FunctionResponse out. It never references a Value. qvalue is pure data structures and a codec; it never spawns a thread or touches the executor.
This is deliberate. It means the executor is reusable on its own — you can schedule any byte-producing job on the bounded worker pool without buying into the value model — and the value model is reusable on its own, for storage or wire encoding, without dragging in a thread pool. The binding happens one layer up: a function package depends on BOTH and is the place where bytes are decoded into Values, computed on, and re-encoded. Keeping the seam there is what lets each half evolve and be tested in isolation.
Rule 3 — function packages depend only on the substrate#
Each area.* package depends on exactly qexec + qvalue (qcrypto additionally on qencoding). Crucially, packages do NOT depend on one another: qstring does not depend on qarray, qconvert does not depend on qnumber, and so on. They communicate only through the shared ABI — qvalue Values as arguments and results, dispatched through the qexec runtime — never by linking each other directly.
This flat fan-out keeps the graph wide and shallow: adding a new area.* package adds one node with two edges and zero coupling to its siblings. It also means a consumer can pick exactly the packages it needs; the de-facto member list is whatever that consumer's manifest declares, since there is no workspace forcing them all in.
- Why no database coupling
- The function runtime lives in qexec specifically so any isolated function package can register against it WITHOUT depending on the DB. The registry, execute/chain/all, FunctionContext, and helper pool were relocated out of the DMS into the executor crate precisely to break that coupling — a package needs the substrate, not the product.
Rule 4 — products depend on the stdlib, never the reverse#
The qroot superproject is six submodules, each its own repo: qdms (the DMS product, including the Studio admin app), qpkgs (the shared stdlib), qquill (the Quill UI/app framework product), qirava (the website + architecture/design SSOT), qbrand (the brand source), and qcloud (the managed-cloud control plane). The direction rule is absolute: products (the DMS, Quill, the site, the cloud) depend on the q* stdlib; the q* stdlib NEVER depends on a product.
- Do not put product code in qpkgs — it is the shared stdlib, not a home for product logic.
- Quill does not depend on q* (and q* does not depend on Quill); the qquill-* crates live in their own repo and must not be moved back into qpkgs.
- qdms is the integration point that path-deps the stdlib crates; that is the dependency flowing the correct way (product → stdlib).
- A change inside a submodule is committed in that submodule first, then its pointer is bumped in the qroot superproject — with tests green before any pointer bump.
qquill having been graduated out of qpkgs into its own product repo is a concrete application of this rule: a UI/app framework is a product, not shared functions, so it does not belong in the stdlib tree.
Rule 5 — no circular dependencies#
The combined effect of the layering is a strictly acyclic graph. Edges only ever point downward: products → function packages / substrate; qcrypto → qencoding → substrate; everything → qexec + qvalue; and the two substrate crates point at nothing inside the tree. There is no path back up, so there is no cycle anywhere in the stdlib.
This is what makes the tree compile cleanly without a workspace orchestrator and lets a consumer depend on an arbitrary subset. It also means each crate can be built and tested in isolation: qvalue's codec tests, qexec's executor/runtime tests, and each package's function tests run against only their own subtree.
Licensing#
Every stdlib crate is dual-licensed MIT OR Apache-2.0 (declared as license = "MIT OR Apache-2.0" in each Cargo.toml), the conventional permissive choice for the Rust ecosystem. The project name Qirava is a trademark; the business model is open-core with managed-cloud revenue (the qcloud control plane), and there are no patents involved. Because the stdlib carries no third-party runtime dependencies, there is no transitive license surface to reconcile — the only licenses in play are the project's own dual MIT/Apache terms (plus the trait-isolated crypto exception, which is itself implemented in-tree).