|
| 1 | +# Your database, store or framework is not built in — work it out |
| 2 | + |
| 3 | +> Part of the `metaobjects-codegen` skill. Read it when the app's database, data store, HTTP |
| 4 | +> framework or UI framework is not one a reference generator targets. That is a normal |
| 5 | +> situation with a known procedure, not a dead end and not a reason to hand-write the app. |
| 6 | +
|
| 7 | +MetaObjects is a **core** (the model, the loader, `verify`, prompt render and reply extract, |
| 8 | +and `meta migrate` for Postgres/SQLite/D1) plus **generators you own**. The reference |
| 9 | +generators cover some stacks. Every other stack is reached the same way: keep the model as the |
| 10 | +source of truth, generate what is built in, and write or retarget a generator for the rest. |
| 11 | +Nothing below needs a new MetaObjects release, a new metamodel attribute, or permission. |
| 12 | + |
| 13 | +## 1. Find out what is built in — do not guess |
| 14 | + |
| 15 | +```bash |
| 16 | +npx meta gen --list --format json --probe # every generator: layer, framework, what it emits for YOUR model |
| 17 | +npx meta types --type source --kind subtype # the registered sources: only source.rdb |
| 18 | +``` |
| 19 | + |
| 20 | +The dialects are `postgres`, `sqlite`, `d1` (TypeScript only) and `mysql` (codegen and runtime |
| 21 | +only). The HTTP reference generators are `routes` (Fastify) and `routes-hono` (Hono). The UI |
| 22 | +reference generators target React and TanStack. Anything else is this page. |
| 23 | + |
| 24 | +## 2. Split the app into layers and decide each one |
| 25 | + |
| 26 | +| Layer | Always available | When your target is not built in | |
| 27 | +|---|---|---| |
| 28 | +| **model** — types, create/PATCH validation, filter and sort allowlists | yes, in every port, for every store | — | |
| 29 | +| **persistence** — how rows are read and written | Postgres, SQLite, D1; MySQL (TypeScript, Java) | §3 | |
| 30 | +| **api** — the HTTP surface | Fastify, Hono | §4 | |
| 31 | +| **client** — hooks, forms, grids | React, TanStack | §5 | |
| 32 | + |
| 33 | +The model layer never depends on the store or the framework. Generate it first |
| 34 | +(`meta eject entity`, wire `entityFile()`); everything you write later imports it. |
| 35 | + |
| 36 | +## 3. Persistence |
| 37 | + |
| 38 | +**MySQL (TypeScript).** Set `dialect: "mysql"`. The generated tables, queries and routes |
| 39 | +target `drizzle-orm/mysql-core`, and the ObjectManager runs over `kyselyDriver` or |
| 40 | +`drizzleDriver`. `meta migrate` does not own a MySQL schema, so you write the DDL. Setup, |
| 41 | +the column-type table and the behaviour differences are in `typescript-mysql.md`. On Java, |
| 42 | +OMDB's `MySQLDriver` reads and writes MySQL; the generated DTOs and controllers are |
| 43 | +dialect-neutral. |
| 44 | + |
| 45 | +**A SQL database that speaks another dialect's protocol.** Use that dialect: CockroachDB, |
| 46 | +Neon, Supabase, AlloyDB and Aurora PostgreSQL are `postgres`; Turso and libSQL are `sqlite` |
| 47 | +(or `d1` on Cloudflare); MariaDB and PlanetScale are `mysql`. Test the generated queries |
| 48 | +against the real server before relying on them. |
| 49 | + |
| 50 | +**Any other store** — MongoDB, Cassandra, DynamoDB, Firestore, Neo4j, Redis, Elasticsearch, |
| 51 | +a SQL database no dialect covers, or a remote API. Model each stored record as a |
| 52 | +**sourceless entity** and generate its data access with a generator you own: |
| 53 | + |
| 54 | +1. An `object.entity` with an `identity.primary` and **no `source.*` child**. With no source, |
| 55 | + nothing generates a table, queries or routes for it, and `meta migrate` ignores it. It still |
| 56 | + gets its whole wire contract in every port: the type, the create and PATCH schemas, and the |
| 57 | + filter allowlist. |
| 58 | +2. Store-specific settings go in a property bag of your own: any `@name` whose value is a JSON |
| 59 | + object is accepted, for example `"@mongo": { "collection": "orders" }` or |
| 60 | + `"@dynamo": { "table": "orders", "partitionKey": "customerId" }`. Your generator reads it. |
| 61 | + **Do not invent a document or graph source subtype, or a `@collection` attribute** — the loader |
| 62 | + refuses unregistered vocabulary (`ERR_UNKNOWN_ATTR`), and a bag needs no registration. |
| 63 | +3. Embedded documents are value objects: `field.object` + `@objectRef` to an `object.value`, |
| 64 | + `isArray: true` for a list. They validate as part of the parent. |
| 65 | +4. Write a repository generator: select objects with the port's sourceless predicate, and emit |
| 66 | + create / findById / list / patch / delete over the store's driver, validating with the |
| 67 | + generated schemas and checking filters against the generated allowlist. A complete, |
| 68 | + tested MongoDB generator for TypeScript is in `typescript-document-store.md`; for |
| 69 | + Cassandra, DynamoDB or an HTTP API only the driver calls change. |
| 70 | + |
| 71 | +| Port | Sourceless predicate | Build the repository on | |
| 72 | +|---|---|---| |
| 73 | +| TypeScript | `isSourcelessEntity` (`@metaobjectsdev/codegen-ts`) | `<E>InsertSchema`, `<E>UpdateSchema`, `<E>FilterAllowlist` | |
| 74 | +| Python | `is_sourceless_entity` | `<E>Create`, `<E>Patch`, `<e>_filter_allowlist` | |
| 75 | +| Java / Kotlin | `RestSurfaceGate.isSourcelessEntity` | `<E>Dto`, `<E>Patch`, `<E>FilterAllowlist` | |
| 76 | +| C# | `InstanceArtifacts.IsSourcelessEntity` | the entity POCO, `<E>FilterAllowlist` | |
| 77 | + |
| 78 | +What a non-relational store does not get: schema management (create collections and indexes |
| 79 | +the store's own way — or write a generator for its optional schema, such as a MongoDB |
| 80 | +`$jsonSchema`), generated routes and client hooks (your repository is the seam; see §4), and |
| 81 | +join traversal (`identity.reference` loads and documents, but nothing follows it across stores). |
| 82 | + |
| 83 | +## 4. An HTTP framework that is not built in |
| 84 | + |
| 85 | +Try these in order: |
| 86 | + |
| 87 | +1. **Config first.** Several apparent framework mismatches are one setting: `apiPrefix`, |
| 88 | + `extStyle` (`"none"` for Turbopack and other bundlers that do not rewrite `.js` |
| 89 | + specifiers), `clientDirective` (React Server Components), `outDir`, per-target output. |
| 90 | +2. **Hono reaches most hosts.** `routes-hono` emits Hono handlers that take the database as an |
| 91 | + injected dependency, and Hono runs on Node, Bun, Deno, Cloudflare Workers and inside a |
| 92 | + Next.js route handler (`hono/vercel`). Mount the generated Hono app inside your framework |
| 93 | + rather than rewriting routes. |
| 94 | +3. **Eject the nearest reference and retarget it.** `meta eject routes` (or `routes-hono`) |
| 95 | + copies the generator and its HTTP adapter into your repo; change the emit to your |
| 96 | + framework's handler shape (Express, Koa, NestJS, Elysia, tRPC, …). The handlers call |
| 97 | + the generated queries, which carry no framework coupling. The TypeScript procedure is in |
| 98 | + `typescript-retargeting.md`. |
| 99 | +4. **Or write a routes generator** from scratch over the generated queries or your |
| 100 | + repository — the same ~20-line generator shape as any other (the top of this skill). |
| 101 | + |
| 102 | +Never hand-write per-entity routes the model describes: a generator gives every entity the |
| 103 | +same surface, and `meta verify --codegen` keeps it in step with the model. |
| 104 | + |
| 105 | +## 5. A UI framework that is not built in |
| 106 | + |
| 107 | +`@metaobjectsdev/runtime-web` is framework-free: the fetch contract, filter query strings, |
| 108 | +currency and grid config. Write a generator that emits your framework's hooks or components |
| 109 | +(Vue composables, Svelte stores, Solid resources, Angular services) over it and over the |
| 110 | +generated types. Angular has source-only reference packages in the MetaObjects repository. |
| 111 | + |
| 112 | +## 6. Prove it works |
| 113 | + |
| 114 | +- `meta verify --codegen` fails when committed output is stale — it re-runs your generator. |
| 115 | +- Typecheck the generator (`npx tsc -p tsconfig.codegen.json`); `meta gen` loads it untyped. |
| 116 | +- Write a test that **executes the generated code against the real store or framework** — |
| 117 | + create, read back, patch, a refused filter, a validation failure. A generator that emits |
| 118 | + plausible text is not proven until its output runs. |
0 commit comments