Architect SDK
Configuration-driven REST backend library for Rust with PostgreSQL. Define your entire data model — schemas, tables, columns, indexes, relationships, API endpoints — in JSON. No entity-specific business logic required.
Features
- Config-driven schema: All DB structure defined in JSON; no hardcoded migrations
- Auto CRUD API: List, read, create, update, delete, bulk ops — all generated from config
- Multi-tenancy: Database-per-tenant or Row-Level Security (RLS), configured per tenant
- Package system: Deploy config as versioned ZIP packages with install/uninstall/upgrade
- Migration planning: Diff-based upgrades with preview before execution
- Asset storage: File uploads with S3, Azure, GCS, or local filesystem backends
- Request validation: Per-column rules (required, format, length, pattern, allowed, min/max)
- Audit logging: Optional per-table audit trail with row snapshots and change deltas
- Event publishing: Optional async event publishing to Decision Hub after CRUD ops
- Authorization: Optional permission checks via Authrs integration
- OpenAPI spec: Dynamically generated from config at
GET /spec - Safe SQL: All identifiers from validated config; values always use
$Nplaceholders
Quick Start
Add the SDK to your Cargo.toml:
[]
= "0.1"
# With cloud storage support
= { = "0.1", = ["storage-s3"] }
# Other storage features: storage-azure, storage-gcs, storage-all
The crate is published as foundry-rs on crates.io. The Rust library name is architect_sdk — import it as use architect_sdk::....
Run the bundled example server:
# Optional: set PACKAGE_PATH=sample to auto-load config from sample/
# Starts on http://0.0.0.0:3000
Usage
1. Minimal Server
The full startup sequence: create the DB if missing, ensure system tables exist, load config, resolve the model, build the router.
use ;
use ;
use TcpListener;
async
2. Loading Config from a Package Directory
Instead of loading from the database, you can load config from a local directory containing a manifest.json and config JSON files. This is useful during development or for seeding a fresh server.
Set PACKAGE_PATH=sample in your .env (or call the loader directly):
use ;
// Minimal in-code config (no JSON files needed)
let config = FullConfig ;
// Apply DDL (CREATE SCHEMA / TABLE / INDEX / FK) — idempotent for schemas and enums
apply_migrations.await?;
let model = resolve?.with_package_id;
For a directory-based package (the same format used by PACKAGE_PATH), see examples/server.rs — it reads manifest.json plus per-kind JSON files (tables.json, columns.json, etc.) and builds a FullConfig from them.
3. Multi-Tenancy
Tenants are stored in _sys_tenants. Register tenants by inserting rows directly (or via your own admin flow):
-- Database-per-tenant
INSERT INTO architect._sys_tenants (id, strategy, database_url)
VALUES ('acme', 'database', 'postgres://localhost/acme_db');
-- RLS tenant (shared DB, row-level isolation)
INSERT INTO architect._sys_tenants (id, strategy, database_url)
VALUES ('beta', 'rls', NULL);
All entity, config, and KV routes require the X-Tenant-ID header:
GET /api/v1/users HTTP/1.1
X-Tenant-ID: acme
The SDK resolves the tenant from the registry, connects to the right database (Database strategy) or sets app.tenant_id in the session (RLS strategy), then executes the query. Tenant pools are created lazily on first use.
For RLS, apply migrations with the rls_tenant_column parameter so the SDK adds the tenant_id column and RLS policies automatically:
apply_migrations.await?;
After registering new Database-strategy tenants post-install, call the bootstrap endpoint to create their schema:
POST /api/v1/config/package/my-package/bootstrap
X-Tenant-ID: acme
4. Packages
A package is a versioned ZIP containing manifest.json + config JSONs for one domain. This is the recommended way to deploy config changes to a running server.
Install:
POST /api/v1/config/package
X-Tenant-ID: acme
Content-Type: multipart/form-data
file=@my-package.zip
The SDK extracts the ZIP, validates all config, writes to _sys_* tables, runs DDL against every tenant database, and reloads the in-memory model — all atomically in one request.
Preview an upgrade (diff old config against new ZIP before touching any DB):
POST /api/v1/config/package/migration/preview
X-Tenant-ID: acme
Content-Type: multipart/form-data
file=@my-package-v2.zip
Response includes a migration_id and an ordered list of DDL steps, each annotated with safety and risk:
Apply after reviewing:
POST /api/v1/config/package/migration/apply/abc123
X-Tenant-ID: acme
Uninstall:
DELETE /api/v1/config/package/my-package
X-Tenant-ID: acme
You can also drive migrations programmatically:
use ;
let plan = compute_migration_plan?;
// Inspect plan.steps — check safety/risk before proceeding
let summary = execute_migration_plan.await?;
println!;
5. Asset Uploads
Columns of type asset or asset[] accept file uploads via multipart/form-data. Enable the relevant storage backend in Cargo.toml and set env vars:
# Cargo.toml
= { = "0.1", = ["storage-s3"] }
STORAGE_PROVIDER=s3
STORAGE_BUCKET=my-bucket
AWS_REGION=us-east-1
Upload a file alongside JSON fields:
POST /api/v1/products
X-Tenant-ID: acme
Content-Type: multipart/form-data
name=Widget
price=9.99
image=@widget.jpg
The SDK uploads the file to the configured backend and stores the object key in the image column. To generate a signed download URL:
GET /api/v1/assets/sign?key=products/2024/06/01/abc.jpg
X-Tenant-ID: acme
6. Validation
Validation rules are declared per column in config and are enforced automatically — no handler code required.
On a POST (full validation), all required fields must be present. On a PATCH (partial validation), only the fields included in the request body are validated. Failures return HTTP 422:
7. Audit Logging
Set audit_log: true on any table config. The SDK creates a companion {table}_audit table and a trigger that records every INSERT, UPDATE, and DELETE automatically — no application code needed.
Each audit row contains:
audit_id— UUID primary keyaudit_action—INSERT,UPDATE, orDELETEaudit_at— timestamp of the changeaudit_by— value ofX-User-IDheader at request timechanged_fields— JSONB delta (only the columns that changed)- Full nullable copy of every row column for point-in-time snapshots
8. Related Entity Includes
Define a relationship in config, then use ?include= on list or read requests to embed related rows in a single query (no N+1):
GET /api/v1/users?include=orders
X-Tenant-ID: acme
Include multiple relationships by comma-separating path segments: ?include=orders,payments.
9. Event Publishing (Decision Hub)
Set DECISION_HUB_URL in the environment. The SDK automatically publishes async events after every create, update, delete, and archive operation — no code changes required.
DECISION_HUB_URL=http://decision-hub:8080
DECISION_HUB_TIMEOUT_SECS=5
Events are fire-and-forget: failures are logged but never surface to the API caller.
10. Authorization (Authrs)
Set AUTHRS_URL and SERVICE_NAME. The SDK checks permissions before every entity operation using the X-User-ID header.
AUTHRS_URL=http://authrs:8080
SERVICE_NAME=my-service
Every request to /api/v1/:entity must include:
X-Tenant-ID: acme
X-User-ID: user-abc
The SDK checks service:my-service/package:default/table:users with action getUsers (for GET) before executing the query. A missing or unauthorized user receives 401 Unauthorized.
Environment Variables
| Variable | Purpose | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string for the central architect DB | required |
ARCHITECT_SCHEMA |
Schema for _sys_* tables |
architect |
PACKAGE_PATH |
Load config from this directory instead of DB | — |
RUST_LOG |
Log level filter (e.g. architect_sdk=debug) |
— |
STORAGE_PROVIDER |
Storage backend: s3, azure, gcs, rustfs |
— |
STORAGE_BUCKET |
Bucket/container name (S3, GCS) | — |
STORAGE_ENDPOINT |
Filesystem path prefix (RustFS) | — |
AWS_REGION |
AWS region (S3) | — |
AZURE_STORAGE_ACCOUNT |
Azure storage account name | — |
AZURE_STORAGE_CONTAINER |
Azure container name | — |
AZURE_STORAGE_ACCESS_KEY |
Azure storage key | — |
GCS_SERVICE_ACCOUNT_JSON |
GCS service account JSON path | — |
DECISION_HUB_URL |
Event publishing endpoint; events disabled if unset | — |
DECISION_HUB_TIMEOUT_SECS |
Event publish timeout | 5 |
AUTHRS_URL |
Permission check endpoint; auth disabled if unset | — |
SERVICE_NAME |
Service identifier for Authrs resources | — |
API Reference
All data routes require X-Tenant-ID header. When Authrs is enabled, X-User-ID is also required.
Common Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/ready |
Readiness probe (checks DB connectivity) |
GET |
/version |
Package name and version |
GET |
/info |
Alias for /version |
GET |
/spec |
OpenAPI 3.0 specification |
Package Management
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/config/packages |
List installed packages |
GET |
/api/v1/config/packages/:package_id |
Get package details |
POST |
/api/v1/config/package |
Install package (multipart ZIP) |
DELETE |
/api/v1/config/package/:package_id |
Uninstall package |
POST |
/api/v1/config/package/migration/preview |
Preview migration diff |
POST |
/api/v1/config/package/migration/apply/:migration_id |
Apply migration plan |
POST |
/api/v1/config/package/:package_id/bootstrap |
Bootstrap tenant DB (Database strategy) |
Config Ingestion
Each config kind has a POST (create/replace) and GET (retrieve) endpoint:
| Path | Kind |
|---|---|
/api/v1/config/schemas |
Schema definitions |
/api/v1/config/enums |
PostgreSQL ENUM types |
/api/v1/config/tables |
Table definitions |
/api/v1/config/columns |
Column definitions |
/api/v1/config/indexes |
Index definitions |
/api/v1/config/relationships |
Foreign key relationships |
/api/v1/config/api_entities |
API entity mappings |
/api/v1/config/kv_stores |
KV namespace definitions |
Entity CRUD
Replace :entity with the entity's path_segment from config (e.g. users).
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/:entity |
List with filtering, sorting, pagination |
POST |
/api/v1/:entity |
Create (JSON or multipart for assets) |
GET |
/api/v1/:entity/:id |
Read single record |
PATCH |
/api/v1/:entity/:id |
Partial update |
DELETE |
/api/v1/:entity/:id |
Hard delete |
POST |
/api/v1/:entity/:id/archive |
Soft delete (sets archived_at) |
POST |
/api/v1/:entity/:id/unarchive |
Restore soft delete |
POST |
/api/v1/:entity/bulk |
Bulk create |
PATCH |
/api/v1/:entity/bulk |
Bulk update |
Package-scoped routes follow the same pattern under /api/v1/package/:package_id/:entity.
List Query Parameters
| Param | Description | Example |
|---|---|---|
filter |
RSQL/FIQL filter expression | status==active;created_at>2024-01-01 |
sort |
Comma-separated columns; + asc, - desc |
+created_at,-status |
limit |
Page size (default 10) | 50 |
offset |
Skip N records (default 0) | 100 |
include |
Comma-separated related entity path segments | orders,payments |
Response Envelope
// List
// Single
// Error
// Bulk with partial failure (207)
HTTP status codes: 200, 201, 207, 401, 404, 409, 422, 500.
Key-Value Store
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/package/:package_id/kv/:namespace |
List all keys |
GET |
/api/v1/package/:package_id/kv/:namespace/:key |
Get value |
PUT |
/api/v1/package/:package_id/kv/:namespace/:key |
Set value (upsert) |
DELETE |
/api/v1/package/:package_id/kv/:namespace/:key |
Delete entry |
Assets
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/assets/sign |
Get signed download URL |
Configuration Reference
Schema
Enum
Table
Setting audit_log: true creates a companion orders_audit table recording every INSERT, UPDATE, and DELETE with a full row snapshot and changed_fields JSONB delta.
Every table automatically gets: id (UUID PK), created_at, updated_at, archived_at, created_by, updated_by.
Column
Supported types: TEXT, VARCHAR(n), INTEGER, BIGINT, SMALLINT, BOOLEAN, NUMERIC(p,s), DECIMAL, REAL, DOUBLE PRECISION, UUID, DATE, TIME, TIMESTAMP, TIMESTAMPTZ, JSON, JSONB, BYTEA, custom enums, asset, asset[].
Validation rules: required, min_length, max_length, pattern (regex), allowed (enum list), minimum, maximum, format (email | uuid).
API Entity
Relationship
Package Manifest
Multi-Tenancy
Tenants are registered in _sys_tenants. Two isolation strategies:
Database — each tenant has its own PostgreSQL database. DDL is broadcast to all tenant DBs on package install/uninstall.
RLS — tenants share a database. The SDK sets app.tenant_id via SET LOCAL before each query. PostgreSQL RLS policies enforce isolation. All tables get a tenant_id column automatically.
All data routes require X-Tenant-ID: <tenant_id> header.
Packages
A package is a ZIP containing manifest.json plus config JSON files:
my-package.zip
├── manifest.json
├── enums.json
├── tables.json
├── columns.json
├── indexes.json
├── relationships.json
└── api_entities.json
Install: POST /api/v1/config/package (multipart)
Upgrade: POST /api/v1/config/package/migration/preview → review → POST /api/v1/config/package/migration/apply/:id
Uninstall: DELETE /api/v1/config/package/:package_id
Migration steps carry safety (Safe | BestEffort | WarnOnly) and risk (None | MayFail | ExistingNullsMustBeAbsent | DataWillBeModified | ManualActionRequired) metadata so you know exactly what will happen before applying.
Asset Storage
Columns of type asset or asset[] enable file uploads via multipart requests. Configure the backend via env:
| Provider | STORAGE_PROVIDER value |
|---|---|
| Local filesystem | rustfs |
| AWS S3 | s3 |
| Azure Blob Storage | azure |
| Google Cloud Storage | gcs |
Enable S3, Azure, or GCS support via Cargo features: storage-s3, storage-azure, storage-gcs, or storage-all.
Optional Integrations
Decision Hub (Event Publishing)
Set DECISION_HUB_URL to enable async event publishing after CRUD operations. Events are fire-and-forget and do not block API responses.
Authrs (Authorization)
Set AUTHRS_URL and SERVICE_NAME to enable per-request permission checks. The SDK calls Authrs before each entity operation; requests without the required permission receive 401 Unauthorized.
System Tables
All stored in the architect schema (configurable via ARCHITECT_SCHEMA):
| Table | Contents |
|---|---|
_sys_packages |
Installed package manifests |
_sys_schemas |
Schema definitions |
_sys_enums |
Enum type definitions |
_sys_tables |
Table definitions |
_sys_columns |
Column definitions and validation rules |
_sys_indexes |
Index definitions |
_sys_relationships |
FK relationship definitions |
_sys_api_entities |
API endpoint definitions |
_sys_kv_stores |
KV namespace definitions |
_sys_tenants |
Tenant registry (strategy, database_url) |
_sys_kv_data |
KV store data |
Build & Test
CI (GitHub Actions) runs on push/PR to main: fmt check, build, test, clippy.
Sample Packages
| Directory | Description |
|---|---|
sample/ |
Minimal: 2 tables (users, orders), FK, email unique constraint |
sample_ecommerce/ |
Full e-commerce: 12 tables, 4 enums, 18 relationships, multi-tenant ready |
Private GitHub and CI
For a private repo, authenticate Cargo:
Local (HTTPS):
CI (GitHub Actions):
- run: |
git config --global url."https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
License
MIT