Generated contracts
Generate and use Ridu’s manifest, OpenAPI schema, Go models, TypeScript client, and admin registry.
ridu generate resolves the application config into a manifest and generated contracts. It does
not touch the database, and it installs the output set atomically.
Commit generated contracts. They are reviewable application interfaces, not disposable caches.
Why Ridu executes config
A Go configuration can call functions, compose packages, and register compiled plugins. Parsing Go source would see syntax, not the values the program actually produces, so the CLI runs the project’s configured command to resolve it.
The project command resolves ridu.Config, applies plugin transforms, validates the graph, and
returns a deterministic snapshot. Generation does not connect to the database, initialize runtime
storage, start the HTTP server, or make application network requests.
Protocol or framework version mismatches fail with an upgrade diagnostic instead of guessing at a schema. Executable access callbacks, hooks, task handlers, storage credentials, and secrets remain runtime-only; they are never serialized into the manifest.
Files in one generation
The default generated project writes:
| Path | What it contains | Commit it? |
|---|---|---|
generated/ridu.schema.json |
Canonical, versioned manifest with stable IDs, resources, fields, capabilities, plugin metadata, and digests | Yes |
generated/ridu.openapi.json |
REST paths, request/response schemas, auth, locale parameters, and stable error envelopes | Yes |
generated/ridu.generated.go |
Output/create/update structs and typed local collection/global handles | Yes |
generated/ridu.generated.ts |
Exact document/input/query/select/populate types and the generated client factory | Yes |
admin/src/ridu.plugins.generated.ts |
Validated static imports for official or packaged admin plugins | Yes |
.ridu/… |
Project-command builds, staging files, caches, and intermediate admin output | No |
ridu.toml can change the structural paths, but it cannot contain database credentials, access
rules, or schema definitions. See Project structure for the directory
map.
Keep @riducms/sdk and packages referenced by generated TypeScript field types in the root
package.json. Keep an admin plugin in admin/package.json as well when the generated admin
registry imports its runtime module.
Canonical manifest
The manifest is the contract shared by migrations, runtime plugins, generators, the admin, and tooling. Its serialization is deterministic: equivalent resolved configuration produces identical bytes and digests regardless of map iteration order.
Stable resource and field identities let a migration distinguish a rename from an unrelated drop and create. Human-facing slugs and labels can change while committed identity records continuity. Plugin contributions are namespaced and versioned.
A built-in field may carry admin.component with a paired plugin key, exact component key, and
deterministic object configuration. This is public presentation metadata, not a new value type:
storage, validation, migrations, OpenAPI, and generated Go/TypeScript values continue to follow the
built-in field kind. Executable callbacks and credentials remain outside the manifest.
The manifest describes that an access rule exists and which operation it protects, but never its function body or decision. The admin can use this metadata to hide an unavailable action only as a presentation; every operation re-evaluates authorization on the server.
Generated Go API
The Go file contains distinct types for stored output, create input, and partial update input. A generated collection handle binds those types to the same dynamic local operation engine:
posts := generated.PostsCollection.With(app.Local())
created, err := posts.Create(ctx, generated.PostCreate{
Title: "Hello, Ridu",
Status: "draft",
}, actor)
if err != nil {
return err
}
page, err := posts.List(ctx, core.TypedListOptions{Page: 1, Limit: 20, Actor: actor})Generated handles do not bypass access, hooks, validation, transactions, localization, or
redaction. Dynamic filters keep using the shared query package; generated
document and mutation shapes remove field-name maps from ordinary application code.
Nullable create/update fields use *core.Input[T]: leave the pointer nil to omit the field, call
core.Set(value) to send a concrete value (including false, 0, or an empty slice), and call
core.Null[T]() to send explicit JSON null. A non-null slice, map, or fallback
json.RawMessage cannot safely use its raw Go type because a nil value would encode as null. The
same conservative rule applies to plugin-owned named Go types, whose underlying type and custom
marshaling cannot be proven from the manifest. Required fields of those shapes therefore use
core.NonNullInput[T] and core.NonNull(value); their omittable default/update forms use
*core.NonNullInput[T] and core.SetNonNull(value). JSON encoding rejects a nil or otherwise
null-encoding wrapped value. Other non-null fields that are omittable in an update or because they
have a server default use *T.
Both wrappers are write-only typed local-API arguments: core.Input and core.NonNullInput are not
general-purpose JSON-unmarshal contracts. The non-null
wrapper prevents JSON null; ordinary required/minimum-length validation still decides whether an
empty concrete collection is valid. Typed list handles accept core.TypedListOptions; population
and all-locale reads remain on the dynamic local API because they change field value shapes at
runtime.
Generated TypeScript API
The generated module extends the neutral @riducms/sdk runtime with application-specific types:
- output, create, and update shapes for every collection and global;
- collection/global/auth/upload/version-capability slug unions;
- field-aware
where,select, andpopulateinputs; - one
RiduConfigmap connecting slugs to all those types; - a
createClientwrapper that supplies the exact config without repeated generics; - type-only module augmentation for raw SDK inference in programs with one generated config.
Conditional admin presentation does not narrow generated TypeScript inputs. Create and update types remain ordinary flat field contracts, including each field’s required, default, and null behavior. Hidden fields can still be submitted, and conditions do not perform runtime validation or authorization; enforce active-shape rules in application validation when needed.
import { createClient } from '~/generated/ridu.generated';
const client = createClient({ baseURL: 'https://cms.example.com' });
const post = await client.create('posts', {
title: 'Generated contracts',
status: 'draft'
});A TypeScript program that contains zero generated configs—or more than one—does not select an
automatic default. Import the intended generated wrapper, or pass its RiduConfig explicitly to
the raw SDK. This avoids whichever package happened to load first silently choosing your API.
Selection and population inputs are typed, but they do not yet transform the method’s output type. Sort terms also remain strings. Treat the runtime projection as authoritative and see Querying data for that current boundary.
OpenAPI and other consumers
The OpenAPI document is generated from the same snapshot as the TypeScript module. Use it for API
inspection, non-TypeScript client generation, request fixtures, or gateway tooling. Do not
hand-edit it: the next generation replaces it. When generated.graphql.schema is
configured, the compiled GraphQL plugin also emits its exact runtime SDL for GraphQL clients,
editor tooling, and API review.
The admin loads the runtime schema and generated SDK contract, while its plugin registry imports static code selected at build time. GraphQL derives its SDL from the same manifest and executable plugin options.
Atomic writes and drift checks
ridu generate writes the outputs as one set. If generation or installation fails, the previous
contract set remains intact. Byte-identical output keeps its timestamp.
Use the non-writing mode in CI:
npm run ridu -- generate --checkIt fails when an expected file is missing or byte-different. During ordinary local development, keep the complete loop running:
npm run devIt regenerates contracts on config changes and synchronizes safe additive development schema changes. After the behavior works, review the generated files:
git diff -- generated admin/src/ridu.plugins.generated.tsIf the change affects stored schema, create the immutable artifact and run the project checks before deployment:
npm run ridu -- migrate create --name describe-the-change
npm run ridu -- checkGeneration describes the target API; migration creation records the reviewed, immutable database transition from the previous manifest. A migration is not required for ordinary local development. Read Migrations before changing an existing schema.
Common failures
| Symptom | Likely cause | Next step |
|---|---|---|
| Project command cannot compile | Invalid Go config or incompatible package versions | Run go test ./...; fix the first compiler error |
| Handshake version mismatch | CLI and application framework do not share the project-command contract | Use the CLI release pinned to the project |
| Manifest validation fails | Duplicate slug/name, invalid option combination, unknown relationship target, localization cycle, or plugin conflict | Follow the structured path in the error |
--check reports drift |
Config or generator version changed without committed outputs | Run generation locally and review all artifacts together |
| Raw SDK types fall back to defaults | No generated config, or multiple generated configs, are in the TypeScript program | Import/use the generated wrapper explicitly |
| Generated plugin import fails | Registry metadata and installed Go/admin packages disagree | Use ridu plugin commands; do not patch the generated registry |
The Troubleshooting guide covers recovery in more detail. Exact generator, manifest, and project-handshake types are in the Go API reference and CLI reference.