Layer 2 — Components
Stateless, in-process modules — one per domain — sitting between the database and the L3 workflows. Each component is a thin DB-access API for the tables it owns.
Principles
- One component per domain. Thin DB-access only. No constraints, no cross-component calls, no business rules. Constraints belong in L3.
- Every op accepts a connection. Components don't import a pool. The conn enters as a parameter.
- Stateless. No module-level caches, no singletons, no memoization. Same
(conn, args)→ same SQL → same result. - One table → one owner. A component can own many tables; a table belongs to exactly one component. No other component reads or writes it directly.
Tenant context
The request boundary sets SET LOCAL app.org_id = $1 on the conn before passing it down. L2 SQL reads the tenant via current_setting('app.org_id')::uuid and still carries an explicit WHERE org_id = $1 clause. No RLS.
Connection handling
L2 ops accept a Queryable (just .query() — Pool, PoolClient, or a test fake all satisfy it). L2 never opens transactions; L3 owns the unit of work.
The request boundary uses two helpers:
withConnection(pool, orgId, fn)— reads. Acquires a conn, appliesSET LOCAL app.org_id, releases.withTransaction(pool, orgId, fn)— writes. Same +BEGIN/COMMIT/ROLLBACK.
Response shape
L2 maps SQL rows to idiomatic TS at the boundary:
- Keys are
camelCase. NULL→null, neverundefined.BIGINTcolumns → strings (precision-safe).DATEcolumns →'YYYY-MM-DD'strings.TIMESTAMPTZ→ JSDate.JSON/JSONB→ parsed.
Cross-domain reads
Components don't call each other. When a workflow needs data spanning two components, L3 calls both L2 ops and merges in memory — two batched queries instead of one cross-domain JOIN.
Ledger ownership
Materials and finished-goods ledger writes are exclusively the ledger components' API. PO receipts, WO receipts, and transfers call into MaterialsInventory / FinishedGoodsInventory rather than inserting directly.
Components
| Component | Schema | Owns |
|---|---|---|
| Platform | platform | users, organization_users, organizations, user_invitations, api_tokens |
| OrganizationSettings | org | organization_settings |
| Customers | org | customers, customer_types, customer_segments, brokers |
| Vendors | org | vendors |
| Products | org | skus, sku_types |
| Materials | org | materials, material_types |
| Locations | org | locations |
| Contacts | org | contacts, entity_contacts |
| PurchaseOrders | org | purchase_orders, po_items, po_receipts, po_receipt_items |
| WorkOrders | org | work_orders, wo_items, work_order_inputs, wo_receipts, wo_receipt_items |
| SalesOrders | org | sales_orders, so_items, so_timeline_notes, order_channels |
| BOM | org | boms, bom_items |
| MaterialsInventory | org | materials_inventory_ledger |
| FinishedGoodsInventory | org | finished_goods_inventory_ledger |
| Documents | org | documents |