|
| 1 | +# Oracle Notes |
| 2 | + |
| 3 | +This is a deliberately small notes app running on Oracle Database 23. The app itself is basic: create notes, edit them, and delete them. The interesting part is how it is implemented. |
| 4 | + |
| 5 | +## The data model is the API |
| 6 | + |
| 7 | +In Typegres, the backend data model is also the application API. The model in [`server/api.ts`](server/api.ts) has only two tables: `Users` and `Notes`. Members are selectively exposed as capabilities. A note exposes the fields the browser needs, while a user never exposes `password_hash`. |
| 8 | + |
| 9 | +The browser starts with a root `Api` capability. Each HTTP batch logs in and pipelines the resulting `Users` capability through the rest of the operation. From that user, the browser can follow `Users.notes()`, a `Relation.has` edge containing only that user's notes. |
| 10 | + |
| 11 | +The browser then composes ordinary typed query operations over the relation it can reach. For example, the notes list adds `orderBy()` and `select()` on the frontend. That query expression travels over Cap'n Web RPC, Typegres compiles it on the server, and Oracle executes the resulting SQL. |
| 12 | + |
| 13 | +This means a feature such as title search belongs in the frontend query: add a search input and conditionally add a `where()` clause to the existing notes relation. Oracle's operators and built-in functions remain available with TypeScript types, and the filter is pushed down to Oracle. The client can author a new database query without adding a server endpoint or changing the authority boundary. |
| 14 | + |
| 15 | +Mutations follow the same graph. The user capability exposes note creation and supplies `this.id` as the new note's `user_id`. Once the frontend reaches one note through the user's relation, that note grants the ability to update or delete itself. |
| 16 | + |
| 17 | +Three properties of this model are particularly useful for agent-authored applications: |
| 18 | + |
| 19 | +1. Clients retain much of SQL's compositional power within the boundaries they have been given. |
| 20 | +2. Reads and mutations follow the same authority graph instead of reproducing it across CRUD endpoints and a separate policy layer. |
| 21 | +3. The main review surface is one file describing the data, relationships, allowed queries, and mutations—and that file is also the API. |
| 22 | + |
| 23 | +That is the durable core: the application and its authority model remain separate from the many clients or agent-authored interfaces that may use it. |
| 24 | + |
| 25 | +## Local development |
| 26 | + |
| 27 | +From the repository root: |
| 28 | + |
| 29 | +```bash |
| 30 | +npm install |
| 31 | +npm run build |
| 32 | +bin/startora |
| 33 | +npm install --prefix examples/oracle-notes |
| 34 | +``` |
| 35 | + |
| 36 | +Then start the API server and Vite together: |
| 37 | + |
| 38 | +```bash |
| 39 | +npm run dev --prefix examples/oracle-notes |
| 40 | +``` |
| 41 | + |
| 42 | +Development defaults to `oracle://typegres:typegres@localhost:1521/FREEPDB1`. Set `ORACLE_URL` before running the command to override it. |
| 43 | + |
| 44 | +Open <http://localhost:5173>. A new username creates an account; later logins must provide the same password. |
| 45 | + |
| 46 | +## Fly deployment |
| 47 | + |
| 48 | +The demo uses two private-networked Fly apps: |
| 49 | + |
| 50 | +- Node application: standard multi-stage Docker image, HTTP exposed through Fly Proxy. |
| 51 | +- Oracle: `gvenzl/oracle-free:23-slim`, private port 1521, one persistent volume. The non-faststart image initializes its database files on the mounted volume. |
| 52 | + |
| 53 | +Copy and edit the deployment configuration: |
| 54 | + |
| 55 | +```bash |
| 56 | +cp examples/oracle-notes/.env.example examples/oracle-notes/.env |
| 57 | +$EDITOR examples/oracle-notes/.env |
| 58 | +examples/oracle-notes/deploy.sh |
| 59 | +``` |
| 60 | + |
| 61 | +`NOTES_DOMAIN` is intentionally deployment configuration. After deployment, `flyctl certs show` prints the A/AAAA or CNAME records to add at the domain's current DNS provider; Fly provisions and renews TLS. |
| 62 | + |
| 63 | +The Oracle Machine remains running because database cold starts are expensive. The Node Machine also keeps one instance running so the demo does not pay an application cold-start penalty. |
| 64 | + |
| 65 | +### Useful commands |
| 66 | + |
| 67 | +```bash |
| 68 | +flyctl logs --app "$FLY_APP" |
| 69 | +flyctl logs --app "$FLY_ORACLE_APP" |
| 70 | +flyctl proxy 1521:1521 --app "$FLY_ORACLE_APP" |
| 71 | +``` |
| 72 | + |
| 73 | +The Oracle volume is tied to its region and is not replicated. This deployment is a demo, not a production topology. |
| 74 | + |
| 75 | +## Security scope |
| 76 | + |
| 77 | +The example demonstrates the same login scheme as the chat sample: PBKDF2 claims a username on first login. Each HTTP RPC operation logs in and hydrates the user before following its note relation. Notes are always filtered and mutated by the authenticated user ID on the server. It intentionally omits password reset, rate limiting, lockout, CSRF hardening for cross-origin hosting, and production database operations. |
0 commit comments