-
Notifications
You must be signed in to change notification settings - Fork 11
feat: database with configuration #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,18 @@ | ||
| workers: | ||
| - name: database | ||
| config: | ||
| databases: | ||
| primary: | ||
| url: sqlite:./data/iii.db | ||
| pool: | ||
| max: 10 | ||
| idle_timeout_ms: 30000 | ||
| acquire_timeout_ms: 5000 | ||
| # Optional seed file for first-time registration (--config ./config.yaml.example). | ||
| # When omitted, the worker seeds the built-in default: | ||
| # | ||
| # databases: | ||
| # primary: | ||
| # url: sqlite:./data/iii.db | ||
| # pool: { max: 10, idle_timeout_ms: 30000, acquire_timeout_ms: 5000 } | ||
| # | ||
| # After the first boot, the runtime source of truth is the `configuration` | ||
| # worker entry `database`, persisted at ./data/configuration/database.yaml. | ||
|
|
||
| databases: | ||
| primary: | ||
| url: sqlite:./data/iii.db | ||
| pool: | ||
| max: 10 | ||
| idle_timeout_ms: 30000 | ||
| acquire_timeout_ms: 5000 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| --- | ||
| name: database | ||
| description: >- | ||
| Run SQL against PostgreSQL, MySQL, or SQLite from the iii engine — reads, | ||
| writes, transactions, and prepared statements over managed connection pools. | ||
| --- | ||
|
|
||
| # database | ||
|
|
||
| The database worker connects to PostgreSQL, MySQL, and SQLite through a | ||
| managed per-database connection pool. Every callable surface lives under | ||
| the `database::*` namespace. The driver is chosen from each database URL | ||
| scheme (`sqlite:`, `postgres://`, `postgresql://`, `mysql://`). | ||
|
|
||
| Runtime settings live in the `configuration` worker under id `database`; | ||
| pools hot-reload when the value changes. SQLite is the recommended starting | ||
| point. Placeholder syntax: `?` for SQLite and MySQL, `$1`/`$2`/… for Postgres. | ||
|
|
||
| ## When to Use | ||
|
|
||
| - You need to read rows from a configured database (`database::query`). | ||
| - You need to insert, update, delete, or run DDL and read affected-row | ||
| counts or autoincrement ids (`database::execute`). | ||
| - Several statements must commit or roll back together as one unit | ||
| (`database::transaction` or the interactive transaction surface). | ||
| - The same parameterized SQL will run many times and you want to skip | ||
| per-call parse/plan cost (`database::prepareStatement` + | ||
| `database::runStatement`). | ||
| - You need read-your-writes across round-trips with logic between steps | ||
| (`database::beginTransaction` … `commitTransaction` / `rollbackTransaction`). | ||
| - You want to react to Postgres row-level changes once logical replication | ||
| streaming ships (`database::row-change` trigger — see below). | ||
|
|
||
| ## Boundaries | ||
|
|
||
| - Not a migration tool, ORM, or schema designer — pass raw SQL only. | ||
| - Not a general pub/sub bus — use `database::row-change` only for Postgres | ||
| table change feeds, not for application events. | ||
| - `database::query` is read-oriented; use `database::execute` for writes. | ||
| Running a SELECT through `execute` discards rows. | ||
| - Prepared handles pin a pool connection until TTL expiry — not transactions. | ||
| Batch `database::transaction` needs every statement up front; use the | ||
| interactive surface when code must branch between steps. | ||
| - MySQL ignores the `returning` option on `execute` (warn-once). SQLite | ||
| degrades `read_committed` / `repeatable_read` isolation to serializable. | ||
| - For filesystem or shell operations, use the `shell` worker instead. | ||
|
|
||
| ## Functions | ||
|
|
||
| - `database::query` — run read-only SQL and return rows, row count, and | ||
| column metadata. | ||
| - `database::execute` — run write SQL (INSERT/UPDATE/DELETE/DDL) and | ||
| return affected rows, optional last insert id, and optional RETURNING rows. | ||
| - `database::prepareStatement` — parse and plan SQL once; return a handle | ||
| that pins a pool connection until TTL expiry. | ||
| - `database::runStatement` — re-execute a prepared handle with new bind | ||
| params; response shape matches `query`. | ||
| - `database::transaction` — run an ordered batch of statements atomically; | ||
| rolls back on first failure and reports `failed_index`. | ||
| - `database::beginTransaction` — open an interactive transaction and | ||
| return an id plus expiry deadline. | ||
| - `database::transactionQuery` — read SQL inside an open interactive | ||
| transaction; same envelope as `query`. | ||
| - `database::transactionExecute` — write SQL inside an open interactive | ||
| transaction; same envelope as `execute`. Rejects bare transaction-control | ||
| SQL — finalize via `commitTransaction` or `rollbackTransaction`. | ||
| - `database::commitTransaction` — commit and finalize an interactive | ||
| transaction. | ||
| - `database::rollbackTransaction` — roll back and finalize an interactive | ||
| transaction. | ||
|
|
||
| Interactive transactions auto-roll back when `timeout_ms` elapses (default | ||
| 30 s, max 5 min). Prepared handles default to a 1 h TTL (max 24 h) with no | ||
| explicit release call — let them expire or stop using them when done. | ||
|
|
||
| ## Reactive triggers | ||
|
|
||
| Register a `database::row-change` trigger when a function should run | ||
| automatically on Postgres INSERT/UPDATE/DELETE for specific tables — without | ||
| polling with `database::query`. | ||
|
|
||
| Reach for it when: | ||
|
|
||
| - A downstream worker or workflow must react to row mutations in near real | ||
| time on Postgres. | ||
| - You need decoded row payloads (old/new values) from logical replication | ||
| rather than polling an outbox table. | ||
|
|
||
| Do not bind when: | ||
|
|
||
| - The writer already has the new row in its `execute` or `transactionExecute` | ||
| return payload. | ||
| - You are on SQLite or MySQL — this trigger type is Postgres-only. | ||
| - You need events today — v1.0.0 returns `UNSUPPORTED` on `registerTrigger` | ||
| pending an upstream `tokio-postgres` replication API release. | ||
|
Comment on lines
+94
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conflicting Line 94 states 🤖 Prompt for AI Agents |
||
|
|
||
| ### How to bind | ||
|
|
||
| 1. Register a handler: `registerFunction('stream::on-row-change', handler)`. | ||
| 2. Register the trigger: | ||
|
|
||
| ```typescript | ||
| iii.registerTrigger({ | ||
| type: 'database::row-change', | ||
| function_id: 'stream::on-row-change', | ||
| config: { | ||
| db: 'primary', | ||
| schema: 'public', | ||
| tables: ['orders', 'payments'], | ||
| // optional: slot_name, publication_name — see get function info | ||
| }, | ||
| }) | ||
| ``` | ||
|
|
||
| Config: `db`, `schema` (default `public`), `tables`. Slot/publication names | ||
| derive from `trigger_id` unless overridden. For event payload shape, call | ||
| `get function info` on the trigger type or handler function id. | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
What installation flags does https://install.iii.dev/iii/main/install.sh support for pinning a specific iii engine version, and what is the recommended CI strategy for reproducible installs (exact version vs channel)?💡 Result:
The iii engine installer script (https://install.iii.dev/iii/main/install.sh) supports these installation “pinning” arguments: 1) Pin an exact engine version - Usage: sh -s -- v0.9.0 (the docs show passing the version argument as vX.Y.Z) [1] - Behavior: when a specific VERSION is requested, the installer rejects prerelease versions (it checks the GitHub release JSON’s prerelease field, and errors if prerelease == true) [2] 2) Install “latest stable” (channel behavior) - Default behavior (no -v / version argument): the script “installing latest version” [2] - Under the hood: for latest-version selection it probes GitHub’s /releases/latest first, and only accepts a tag that matches the expected prefix (iii/v*) and is non-draft/non-prerelease; if that fails, it falls back to listing releases and filtering by prerelease==false and tag_name prefix iii/v* [2] So, for pinning reproducibly, prefer exact version pinning (e.g., v0.9.0) over “channel/latest” resolution [1][2]. Recommended CI strategy for reproducible installs (exact version vs channel) - Recommended: pin an exact version (e.g., use -v vX.Y.Z / pass vX.Y.Z as the version argument) in CI, not the moving “latest”/channel behavior, because “latest” is resolved at install time by querying GitHub releases/latest and filtering [2]. - Additionally, do not use prerelease channels in CI: the engine installer explicitly rejects prereleases even when a specific version is requested (“version $VERSION is a prerelease — use a stable release”) [2]. If you want stronger reproducibility than “install a single binary version,” use the project’s lockfile workflow for registry-managed workers: commit iii.lock with config.yaml and replay with iii worker sync so CI/dev/deploy environments install/repair the same pinned set (and the sync step verifies binaries by SHA-256 before activation) [3].
Citations:
🌐 Web query:
What does the iii engine installer script https://install.iii.dev/iii/main/install.sh interpret the--nextflag as (prerelease/next channel vs something else), and how should CI handle it for reproducible installs?💡 Result:
The --next flag in the iii engine installer script (https://install.iii.dev/iii/main/install.sh) directs the script to fetch and install the latest pre-release version of the iii engine [1]. Specifically, it triggers logic that queries GitHub releases for tags matching the -next. pattern (such as iii/v*-next.*) [1][2], rather than defaulting to the latest stable release [1][3]. How CI Should Handle It for Reproducible Installs: Because --next fetches the latest available pre-release dynamically, it is inherently non-deterministic and unsuitable for reproducible CI environments [1]. To ensure reproducibility in CI/CD pipelines, you should pin the engine to a specific version instead of using the --next flag [4][5]. 1. Pin to a Specific Version: Use the script's ability to accept a version argument directly. By providing a specific tag, you ensure the installer fetches a known, immutable release [4][5]. Example: curl -fsSL https://install.iii.dev/iii/main/install.sh | sh -s -- v0.12.0 2. Avoid --next in Production CI: The --next flag is intended for "work-in-progress" (WIP) testing and development environments where tracking the cutting edge is desired [1]. If you must test pre-release builds, pin to a specific pre-release tag (e.g., v0.12.0-next.1) rather than using the generic --next flag to avoid unexpected breaking changes between CI runs [1][2]. 3. Verify Dependencies: Ensure your CI environment validates the checksum or version of the installed binary to prevent supply chain drift [6][3]. Since the installer script itself can evolve [7][2], pinning the version and potentially caching the installer script or the downloaded binary are best practices for maintaining a locked environment [4].
Citations:
iii/v...notv...) iii#1652Pin iii engine to a specific release in CI (avoid
--next)In
.github/workflows/database-e2e.yml,--nextinstalls the latest pre-release dynamically (e.g., matching*-next.*), making E2E runs non-reproducible and prone to unexpected breakage. Pin an exact stable engine version (e.g.,vX.Y.Z), or if you need WIP, pin a specific pre-release tag (e.g.,vX.Y.Z-next.1) instead of using--next.🤖 Prompt for AI Agents