create-restforge-skills 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,388 @@
1
+ # Reference: Backend Pipeline and Decisions
2
+
3
+ Read this file when the intent router in SKILL.md points to the backend track
4
+ (schema, payload, endpoint, dashboard, processor, consumer, SDK, launcher) and
5
+ the next step is not obvious from the router row alone.
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Pipeline (canonical)](#pipeline-canonical)
10
+ 2. [Schema decisions](#schema-decisions)
11
+ 3. [RDF payload decisions](#rdf-payload-decisions)
12
+ 4. [Backend module type](#backend-module-type)
13
+ 5. [Soft-delete vs hard-delete](#soft-delete-vs-hard-delete)
14
+
15
+ ---
16
+
17
+ ## Pipeline (canonical)
18
+
19
+ This is the canonical (golden) path. For state-dependent choices see the decision
20
+ sections below; for failure handling see SKILL.md § Guardrails and
21
+ troubleshooting.md.
22
+
23
+ The sequence below applies to a **new project from scratch**. For an existing
24
+ project, start from the step that matches the current state — do not re-run
25
+ earlier steps that already succeeded.
26
+
27
+ ```
28
+ 1. npx create-restforge-app <name> ── PRIMARY ── human-run scaffolder
29
+ One shot: creates the project folder, runs
30
+ npm install @restforgejs/platform (local), and bundles the designer
31
+ binary. This is the dominant way to start a new project.
32
+ Granular alternative (agent scaffolds step by step):
33
+ setup_create_folder → create the project folder.
34
+
35
+ 2. setup_install_package (granular path only)
36
+ Install @restforgejs/platform into the folder. SKIP when the project
37
+ was created with create-restforge-app (already installed). Plain
38
+ 'npm install @restforgejs/platform' stays valid but is not the
39
+ primary entry point.
40
+
41
+ 3. setup_init_config
42
+ Write config/db-connection.env from the default template.
43
+ setup_get_init_template returns that same template WITHOUT writing a
44
+ file — use it to compare an edited config against the defaults.
45
+
46
+ 4. setup_write_env / setup_update_env
47
+ Set values: LICENSE, SERVER_ADDRESS, SERVER_PORT, DB_TYPE,
48
+ DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME (9 required parameters).
49
+ Use setup_read_env to read existing values before overwriting.
50
+ Grounding for the full parameter set: setup_get_config_schema.
51
+
52
+ 5. setup_validate_config
53
+ ── GATE ── Must pass before the first tool that takes a 'config'
54
+ parameter (it reads the .env or connects to the database). Validates
55
+ database connection and license. Running such a tool before this gate
56
+ passes produces uninformative errors. Steps 6-9 are file-only and may
57
+ run before the gate.
58
+ Read-only by default. Set autoCreateDb=true only when the target database
59
+ itself does not exist yet AND the user agreed to have it created: it runs
60
+ CREATE DATABASE on the server (postgres/mysql only, ignored for sqlite and
61
+ oracle), and validation has to be repeated afterwards.
62
+
63
+ 6. Decide where the table structure comes from (§ Schema decisions):
64
+ New table → the CLARIFY gate: the user names the fields and types, or
65
+ explicitly hands the design to the agent.
66
+ Existing DB → step 7b.
67
+
68
+ 7. codegen_get_dbschema_catalog
69
+ ── GROUNDING ── Source of truth for SDF syntax: field types,
70
+ constraints, shorthand syntax, relations, referential actions,
71
+ check operations, and the soft-delete contract. Ask only for the
72
+ sections the task needs ('section': fieldTypes, shorthandSyntax,
73
+ auditColumns, relationTypes, ...), once per session.
74
+ → references/dbschema-catalog.md
75
+
76
+ 7a. [author schema/<table>.js] (new table — PRIMARY path)
77
+ Write the complete SDF with the Write tool: every agreed field, the
78
+ RESTForge conventions (§ Schema decisions), indexes, uniques,
79
+ checks, and relations. One file per table.
80
+ codegen_dbschema_template (only when the user asks for a ready-made
81
+ template / reference example)
82
+ codegen_dbschema_init (only when the user explicitly asks for a
83
+ draft / initial / skeleton file)
84
+ 7b. codegen_list_tables (existing DB — what is actually in there)
85
+ → codegen_describe_table (columns, PK, FKs, indexes of one table)
86
+ → codegen_dbschema_introspect (generate SDF from the actual schema)
87
+ list/describe are read-only catalog reads: they answer "what does this
88
+ database already hold?" without writing an SDF file. Use them before
89
+ introspecting a subset, and whenever a question about an existing table
90
+ would otherwise be answered from memory.
91
+
92
+ 8. codegen_dbschema_validate
93
+ Validate SDF before any DDL is generated. Catch errors here, not at migrate.
94
+ File-only by default. With 'config' it also compares every model with the
95
+ database and gives one verdict per table: [OK], [DRIFT], or [ERROR] with
96
+ category table-missing or sdf-invalid. Exit code 1 in that mode is a
97
+ result (drift or error found), not a tool failure. SQLite is not
98
+ supported by the database mode.
99
+ codegen_dbschema_models (optional) lists the models already defined in the
100
+ SDF files with field count, primary key kind, indexes, uniques, relations.
101
+
102
+ 9. codegen_dbschema_generate_ddl (optional — preview DDL without executing)
103
+
104
+ 10a. codegen_dbschema_migrate (empty DB — apply DDL directly)
105
+ 10b. codegen_dbschema_diff (existing DB — review the differences first)
106
+ → codegen_dbschema_apply (apply only after confirming the drift)
107
+
108
+ 11. codegen_get_field_validation_catalog
109
+ ── GROUNDING ── before defining fieldValidation in a payload.
110
+ → references/field-validation.md
111
+
112
+ 12. codegen_get_query_declarative_catalog
113
+ ── GROUNDING ── when the payload contains datatablesQuery, viewQuery,
114
+ viewName, exportQuery, or detailQuery.
115
+ codegen_validate_sql
116
+ Check the SELECT / WITH statement against the live database (EXPLAIN, no
117
+ rows executed) BEFORE pasting it into the payload: syntax, column
118
+ references, function existence, type compatibility, JOIN resolution.
119
+
120
+ 13. codegen_generate_payload
121
+ Generate payload JSON from a table. Foundation for all subsequent
122
+ codegen operations. 'detail' (a detail table name) also writes the
123
+ masterDetail block, the detail query file, and the composite actions.
124
+ Re-running it on an existing payload keeps the customisations made to
125
+ generator-owned keys; commit payload/.meta/<name>.json together with the
126
+ payload (see § RDF payload decisions).
127
+
128
+ 14. codegen_validate_payload
129
+ Validate the payload before codegen. Catch errors here.
130
+
131
+ 15. codegen_diff_payload (when a payload exists and the DB schema has changed)
132
+ → codegen_sync_payload (apply the schema drift to the payload)
133
+ 'expandFk' (with 'table') also writes query/<table>-join.sql so
134
+ datatablesQuery (and viewQuery) show columns of referenced tables.
135
+
136
+ 16a. codegen_create_endpoint (standard CRUD module)
137
+ Leave 'database' UNSET unless the user named a database: the CLI then
138
+ auto-detects DB_TYPE from the active config (fallback postgres), so a
139
+ MySQL/Oracle/SQLite project generates for its own dialect. 'config'
140
+ selects that .env explicitly. createDemo (default true) writes the
141
+ curl / Postman / Insomnia examples. force defaults to true — an existing
142
+ module is overwritten and the previous files are moved to
143
+ .restforge/archive/<run>/<original relative path> (the 5 most recent runs
144
+ are kept); force=false stops without writing anything when the module
145
+ exists, which is the closest thing to a conflict dry run.
146
+ 16b. codegen_get_dashboard_catalog
147
+ → codegen_validate_dashboard_payload
148
+ ── GATE ── structural check of a dashboard payload; writes nothing.
149
+ On a platform without 'dashboard create --validate-only' the tool
150
+ answers with an upgrade suggestion instead of a validation result — then
151
+ let the generator itself validate, since it runs the same validator
152
+ before it writes.
153
+ → codegen_create_dashboard (analytic dashboard with SQL widgets)
154
+ No database auto-detection here: the dashboard command uses 'database'
155
+ when given and plain postgres otherwise, so pass it whenever the project
156
+ is not postgres. force defaults to true and then re-registers the project
157
+ under the database type carried by this call; force=false refuses cleanly
158
+ instead of writing.
159
+ 16c. codegen_create_processor (background processing)
160
+ 16d. codegen_create_kafka_consumer (Kafka event streaming)
161
+ → runtime_generate_consumer_launcher
162
+ Prepare a way to RUN that consumer: mode=host writes the fixed
163
+ consumer-start/consumer-stop pair in the project root, mode=pm2 produces
164
+ ecosystem.config.js + consumer-manager.sh in ./deploy/. 'config' is
165
+ required and must end with .env.
166
+ → (user runs the consumer) — the agent never starts it, same rule as the
167
+ server launcher below.
168
+
169
+ 17. codegen_generate_test (optional)
170
+ Jest + Supertest integration test for an endpoint that ALREADY exists.
171
+ Natural follow-up once the module is generated.
172
+
173
+ 18. project_sdk_generate (optional)
174
+ Write the JavaScript SDK source for the project (one resource file per
175
+ registered endpoint, plus client.auth when the backend auth extension is
176
+ installed) so a frontend calls client.<resource>.<verb>(payload). Source
177
+ only: the user runs install / build / deploy. Without force it refuses
178
+ when an SDK exists; with force=true it overwrites IN PLACE with no
179
+ archive, so local edits in the SDK folder are lost.
180
+
181
+ 19. runtime_check_launcher_exists → runtime_validate_preflight
182
+ → runtime_generate_launcher
183
+ Check what is already there (read-only), validate the runtime
184
+ prerequisites, then write the launcher script. The agent STOPS here. The
185
+ user executes the launcher — the server runs independently of the agent
186
+ session.
187
+
188
+ 20. (user executes the launcher)
189
+
190
+ 21. runtime_check_status
191
+ Verify the server is running and endpoints are reachable.
192
+ ```
193
+
194
+ Three hard checkpoints (SKILL.md § Guardrails): **step 5** is a gate — no tool that
195
+ takes a `config` parameter before `setup_validate_config` passes; **step 6** is
196
+ the clarify gate — no new table is written before its structure is settled;
197
+ **step 19** is where the agent stops (it generates the launcher, never runs the
198
+ server).
199
+
200
+ **Payload naming differs per generator** — the value is handed to the CLI and
201
+ each verb resolves it differently:
202
+
203
+ | Tool | Accepted form |
204
+ |---|---|
205
+ | `codegen_create_endpoint`, `codegen_create_processor` | bare name, with or without `.json`; lowercased by the CLI; path forms are rejected |
206
+ | `codegen_create_dashboard`, `codegen_validate_dashboard_payload` | name or relative path **with** the extension, used verbatim (`dash-sales.json`, `payload/dash-sales.json`) — nothing is appended |
207
+ | `codegen_create_kafka_consumer` | name or path |
208
+
209
+ **Config selection across calls.** Most backend tools take an optional `config`
210
+ and otherwise fall back to the default recorded per working directory in
211
+ `.restforge/defaults.json`. Manage that default instead of repeating the file
212
+ name on every call: `setup_list_configs` (which `.env` files exist),
213
+ `setup_set_default_config` (record one), `setup_get_default_config` (which one is
214
+ active), `setup_clear_default_config` (remove it — afterwards every call must
215
+ name its config explicitly).
216
+
217
+ ---
218
+
219
+ ## Schema decisions
220
+
221
+ Pick the branch from **what the user asked for**, not from whether the database
222
+ is empty. "Create a product table" is a request for a real table, never a request
223
+ for a skeleton.
224
+
225
+ - **New table, fields not stated** (e.g. "buatkan tabel product") → **CLARIFY
226
+ gate.** Ask once, in one short message, for the fields and their types
227
+ (optionally: required, unique, relations to other tables), and say the user may
228
+ leave the design to the agent. Write nothing and call no schema tool while
229
+ waiting: no `codegen_dbschema_init`, no `codegen_dbschema_template`, no file.
230
+ - **New table, fields stated** (with or without types) → author the SDF from
231
+ them. Missing types are chosen from the field names and the conventions below;
232
+ ask only when a type is genuinely ambiguous. Do not add business fields the user
233
+ did not list, apart from the conventional PK and audit columns.
234
+ - **New table, design handed to the agent** ("terserah", "tentukan sendiri",
235
+ "you decide", or no structure after the clarify question) → design the fields
236
+ from domain knowledge of the entity, then author the SDF with the conventions
237
+ below. Report the chosen structure briefly after the file is validated.
238
+ - **RESTForge table conventions** (apply to every authored table; syntax from the
239
+ catalog):
240
+ - table name snake_case, singular (`product`, `stock_inbound_item`);
241
+ - primary key `<table>_id` as `string:36 pk`, or the PK style the other SDF
242
+ files of the project already use;
243
+ - foreign key named after the target PK (`category_id` →
244
+ `fk:category.category_id`), only to a table that exists in the schema folder
245
+ (`codegen_dbschema_models`) or that the user asked for — otherwise ask;
246
+ - business code `string:<n> unique notnull`, display name `notnull`,
247
+ money/quantity `decimal:15,2 default:0` with a `gte: 0` check, active flag
248
+ `is_active` as `boolean default:true`, an index on columns used for search;
249
+ - the 4 audit columns exactly as the catalog `auditColumns` section gives
250
+ them (`created_at`, `created_by`, `updated_at`, `updated_by`) — the RDF
251
+ generator assumes they exist.
252
+ - **Explicit draft / initial / skeleton request** ("draft table", "inisial
253
+ table", "skeleton schema", "file awal untuk diisi sendiri") →
254
+ `codegen_dbschema_init`. It writes the generic `dummy` template (a sample
255
+ column per field type plus audit columns) for the user to edit. Never use it as
256
+ a first step before authoring a real table.
257
+ - **Explicit template request** ("pakai template", "contoh schema invoice",
258
+ "template apa saja untuk ERP") → `codegen_dbschema_template` (list, show, or
259
+ generate).
260
+ - **Table already in the database** → `codegen_dbschema_introspect` to generate SDF from the
261
+ actual schema. Do not hand-write a schema that a real DB can describe.
262
+ - **Question about what the DB already contains** → `codegen_list_tables` (tables
263
+ and views) and `codegen_describe_table` (columns, primary key, foreign keys,
264
+ indexes of one table). Both are read-only live introspection; they are the
265
+ safe way to answer such a question without generating anything.
266
+ - **Starting from a UI design** (HTML mockup, screenshot, image, Figma export)
267
+ → first confirm the design is an entity to model, not a dashboard/analytics
268
+ screen (those map to the Dashboard RDF branch below, not to a new SDF table).
269
+ Then classify visible elements into stored / derived / relation / audit, draft
270
+ the SDF, and run `codegen_dbschema_validate`. The design shows presentation,
271
+ not storage — do not turn every visible label into a column.
272
+ → references/design-to-sdf.md
273
+ - **"Is my schema valid and in sync with the database?"** →
274
+ `codegen_dbschema_validate` with `config` (optionally `table`): one verdict per
275
+ table, `[OK]`, `[DRIFT]`, or `[ERROR]` (`table-missing` → migrate/apply fixes
276
+ it; `sdf-invalid` → fix the file). Exit code 1 means drift or an error was
277
+ found, not that the tool failed. Not available for SQLite.
278
+ - **DB exists with drift** → `codegen_dbschema_diff` to review the per-column
279
+ differences, then `codegen_dbschema_apply`. Not `codegen_dbschema_migrate` —
280
+ that is for empty DBs only.
281
+
282
+ ---
283
+
284
+ ## RDF payload decisions
285
+
286
+ - **No payload yet** → `codegen_generate_payload`.
287
+ - **Payload exists, edit is API-layer only** (e.g. add/adjust `fieldValidation`,
288
+ messages, or a query — no column added or dropped) → ground via the matching
289
+ catalog, edit `payload/<name>.json`, run `codegen_validate_payload`, then regenerate
290
+ with `codegen_create_endpoint`. No DB migration: the schema (SDF) is unchanged.
291
+ - **Payload exists, DB schema changed** → `codegen_diff_payload` first, then
292
+ `codegen_sync_payload`. Turning the RDF into a frontend UDF is a separate
293
+ frontend step (Frontend Pipeline step 2a), not part of this branch.
294
+ - **Customisations survive regeneration.** `codegen_generate_payload` and
295
+ `codegen_sync_payload` keep edits made to generator-owned keys (`action`,
296
+ `fieldValidation`, inline SQL, `auditColumns`, the datatables query file).
297
+ A column deliberately removed from `fieldName` stays removed because generate
298
+ records the known columns in `payload/.meta/<name>.json`; commit that snapshot
299
+ with the payload and never edit it.
300
+ - **Show columns of a referenced table in the list** (e.g. `supplier_name` next
301
+ to `supplier_id`) → `codegen_sync_payload` with `table` and `expandFk`
302
+ (`"both"`, or `"datatables-only"` to leave a custom `viewQuery` alone). It
303
+ writes `query/<table>-join.sql` and points `datatablesQuery` (and `viewQuery`)
304
+ at it; `FK_AUTO_JOIN` picks LEFT or INNER JOIN. The display column per FK is
305
+ chosen automatically and is never the primary key. Use `fkColumns`
306
+ (`ref_table.column`, or `local_fk:ref_table.column`) to override it and
307
+ `expandFkSkip` to leave a relation out. When the CLI reports "No natural
308
+ display column found", **ask the user** which column to show or whether to
309
+ skip that relation — do not pick one yourself.
310
+ - **Custom query needed** → ground via `codegen_get_query_declarative_catalog`,
311
+ check the statement with `codegen_validate_sql` against the live database, then
312
+ set `datatablesQuery`, `viewQuery`, or `exportQuery` in the payload. External
313
+ SQL files use the `file:` prefix
314
+ (e.g. `"datatablesQuery": "file:sql/orders.sql"`). Validating first turns a
315
+ runtime 500 into an error message before the payload is even written.
316
+ A joined column must not reuse the name of a main table column
317
+ (`s.status_name AS status` when `status` is the FK column is rejected by
318
+ `codegen_validate_payload`, and `codegen_migrate_payload` then skips that
319
+ field). Select the main table column itself and give the joined column its
320
+ own name, e.g. `s.status_name`.
321
+
322
+ > **"Add an uppercase / case constraint" is ambiguous — disambiguate first.**
323
+ > In RESTForge `uppercase` is an RDF `fieldValidation` *normalization transform*
324
+ > (forces the stored value to upper case), not a reject-if-not-uppercase
325
+ > validator. If the user wants rejection, use `pattern`. If the user wants the
326
+ > database to enforce it, that is an SDF check constraint, not RDF. Confirm which
327
+ > one is meant before editing — see SKILL.md § Grounding-First Rules.
328
+
329
+ ---
330
+
331
+ ## Backend module type
332
+
333
+ - **Standard CRUD** → `codegen_create_endpoint`. One payload = one module.
334
+ - **Analytic dashboard** → `codegen_validate_dashboard_payload`, then
335
+ `codegen_create_dashboard`. Payload must have `widgets` (not `tableName`); page
336
+ name must be prefixed `dash-`; the payload argument keeps its `.json`
337
+ extension.
338
+ - **Background job** → `codegen_create_processor`.
339
+ - **Kafka event consumer** → `codegen_create_kafka_consumer`; requires
340
+ `KAFKA_ENABLED=true` in config. The consumer runtime is a separate process:
341
+ `runtime_generate_consumer_launcher` prepares it (host scripts or PM2 deploy
342
+ files) and the user starts it.
343
+ - **JavaScript client for a generated project** → `project_sdk_generate`. Run it
344
+ after the endpoints exist, and after `project_auth` when the project needs
345
+ auth, so `client.auth` is included.
346
+ - **Integration test for an existing endpoint** → `codegen_generate_test`.
347
+ - **Every feature endpoint needs its `action` flag.** A feature block without
348
+ the matching flag in `action` produces no endpoint.
349
+ → references/rdf-advanced.md § The `action` Block
350
+ - **Workflow (status transitions)** → set `action.workflow: true` and add a
351
+ `workflow` block (`statusField`, `transitions` as a map `status → [targets]`,
352
+ optional `hooks` keyed by target status) to the RDF; generates
353
+ `/change-status`. The buttons are UDF `workflowActions` — a frontend key that
354
+ never goes into the RDF.
355
+ → references/rdf-advanced.md § Workflow
356
+ - **Master-detail (composite CRUD)** → run `codegen_generate_payload` with
357
+ `detail: "<detail table>"`. It writes the `masterDetail` block, the detail
358
+ query file, and `action.createComposite` / `updateComposite` /
359
+ `readComposite`; then fill `headerCalculations` and `calculated` formulas by
360
+ hand. Generates `/create-composite`, `/update-composite`, `/read-composite`.
361
+ → references/rdf-advanced.md § Master-Detail
362
+ - **Summary numbers per resource** (count, sum, avg, min, max, optionally
363
+ grouped) → `action.aggregate: true`; the request body carries the operations,
364
+ and `aggregateConfig.joins` only whitelists JOINs. For charts or KPIs across
365
+ several tables, prefer a backend dashboard (`codegen_create_dashboard`).
366
+ → references/rdf-advanced.md § Aggregate Config
367
+ - **Excel export** → `/export` works by default (falls back to
368
+ `SELECT {fields} FROM tableName`); customise the columns/filter with
369
+ `exportQuery` in the RDF payload. Tune `EXPORT_FILE_EXPIRY` / `EXPORT_CHUNK_SIZE`
370
+ in config. Ground `exportQuery` via `codegen_get_query_declarative_catalog`.
371
+ → references/rdf-advanced.md § Data Source Resolution
372
+ - **Excel import (.xlsx)** → set `action.import: true` and add `importConfig`
373
+ with `enabled: true` (`upsertKeys`, `upsertStrategy`, `requiredFields`,
374
+ optional `lookupFields`) to the RDF payload; generates `/import-upload`,
375
+ `/import-preview`, `/import-commit`, and `/import-status`.
376
+ → references/rdf-advanced.md § Import Config
377
+ - Activate on an existing project: edit the payload to add `importConfig`
378
+ (and/or `exportQuery`) → `codegen_validate_payload` → `codegen_create_endpoint`.
379
+
380
+ ---
381
+
382
+ ## Soft-delete vs hard-delete
383
+
384
+ - Use soft-delete when deleted rows must be audited or recoverable. Declare
385
+ `softDelete: { enabled: true }` in SDF and add the three contract columns
386
+ (`is_deleted`, `deleted_at`, `deleted_by`).
387
+ - Soft-delete is supported on PostgreSQL only (Phase 1).
388
+ - Tables with composite UNIQUE constraints are incompatible with soft-delete.
@@ -0,0 +1,27 @@
1
+ # Reference: Data Seeding and Migration (rows)
2
+
3
+ Read this file when the user wants to export, import, seed, back up, or move
4
+ table rows. Structure changes go through the dbschema tools, not through these.
5
+
6
+ Move table **rows** through SDF-driven envelope files. This is for data, never
7
+ for schema — use the dbschema tools for structure.
8
+
9
+ **Default output location:** `data-storage/<schema>/<table>.json`, relative to the
10
+ project cwd. The `data-storage` folder is the default of the `storagePath` param
11
+ (CLI `--storage-path <folder>`); override it to write elsewhere. The SDF read from
12
+ is `schemaPath` (CLI `--schema-path`, default `schema`).
13
+
14
+ - **Export / dump / snapshot / back up rows** → `data_pull`. Scope is exactly one
15
+ of `table`, `schema`, or `allSchemas`. Only tables registered in the SDF can be
16
+ pulled. `force: true` overwrites existing envelope files. Optional `limit`,
17
+ `batchSize`, `config` (falls back to the default set via `config set-default`,
18
+ i.e. `.restforge/defaults.json`), `schemaPath`, `storagePath`.
19
+ - **Import / load / seed / restore rows** → `data_push`. Same file names as
20
+ `data_pull`, so pulled files push back directly. Scope is exactly one of `table`,
21
+ `schema`, or `allSchemas`; for `schema`/`allSchemas` tables load in FK
22
+ parent→child order.
23
+ - **Move data between databases** → `data_pull` from the source, then `data_push`
24
+ into the target (`config` selects the env per side).
25
+ - ⚠ `data_push` is **APPEND-ONLY** (batch INSERT, no upsert/replace). Running it
26
+ twice inserts the rows twice. Confirm with the user before pushing into a
27
+ database that may already hold those rows.
@@ -158,8 +158,8 @@ Oracle enforces the same way. `codegen_dbschema_diff` treats `noAction` and
158
158
 
159
159
  ## Audit Columns
160
160
 
161
- 4 standard columns for tables managed by RESTForge. Emitted automatically by
162
- `codegen_dbschema_init`. Lookup/system tables may remove them manually.
161
+ 4 standard columns for tables managed by RESTForge. Every authored table
162
+ declares them with the shorthand below; lookup/system tables may leave them out.
163
163
 
164
164
  | Column | SDF shorthand | Notes |
165
165
  |---|---|---|
@@ -60,10 +60,10 @@ Constraints not listed for a given type will be rejected.
60
60
 
61
61
  | Type | Database Types | Applicable Constraints |
62
62
  |---|---|---|
63
- | `string` | VARCHAR, TEXT, CHAR | required, unique, default, primaryKey, autoGenerate, nullable, minLength, maxLength, pattern, patternMessage, format, enum, trim, lowercase, uppercase |
64
- | `integer` | INTEGER, INT, BIGINT | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, format |
65
- | `decimal` | DECIMAL, NUMERIC | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, format |
66
- | `number` | NUMERIC | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, format |
63
+ | `string` | VARCHAR, TEXT, CHAR | required, unique, default, primaryKey, autoGenerate, nullable, minLength, maxLength, pattern, patternMessage, format, enum, notEqual, trim, lowercase, uppercase |
64
+ | `integer` | INTEGER, INT, BIGINT | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, notEqual, format |
65
+ | `decimal` | DECIMAL, NUMERIC | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, notEqual, format |
66
+ | `number` | NUMERIC | required, unique, default, primaryKey, nullable, min, max, precision, scale, positive, negative, integer, notEqual, format |
67
67
  | `boolean` | BOOLEAN | required, unique, default, primaryKey, nullable, strict |
68
68
  | `date` | DATE | required, unique, default, primaryKey, autoGenerate, nullable, format, min, max, before, after |
69
69
  | `datetime` | TIMESTAMP | required, unique, default, primaryKey, autoGenerate, nullable, format, min, max, before, after |
@@ -105,6 +105,7 @@ Constraints not listed for a given type will be rejected.
105
105
  | `patternMessage` | string | — | `"patternMessage": "Invalid format"` |
106
106
  | `format` | string | `formatMessage` | `"format": "email"` (see format presets) |
107
107
  | `enum` | array | `enumMessage` | `"enum": ["active", "inactive"]` |
108
+ | `notEqual` | string | `notEqualMessage` | `"notEqual": "none"` (derived from an SDF CHECK `neq`) |
108
109
  | `trim` | boolean | — | `"trim": true` |
109
110
  | `lowercase` | boolean | — | `"lowercase": true` |
110
111
  | `uppercase` | boolean | — | `"uppercase": true` |
@@ -126,6 +127,7 @@ Constraints not listed for a given type will be rejected.
126
127
  | `positive` | boolean | `positiveMessage` | `"positive": true` |
127
128
  | `negative` | boolean | `negativeMessage` | `"negative": true` |
128
129
  | `integer` | boolean | `integerMessage` | `"integer": true` |
130
+ | `notEqual` | number | `notEqualMessage` | `"notEqual": 0` (derived from an SDF CHECK `neq`) |
129
131
  | `format` | string | — | `"format": "currency"` (the only valid value) |
130
132
 
131
133
  `format: "currency"` is a display hint, not a validator. `codegen_migrate_payload`
@@ -0,0 +1,178 @@
1
+ # Reference: Frontend Pipeline and Decisions
2
+
3
+ Read this file when the intent router in SKILL.md points to the frontend track
4
+ (UDF, frontend page, plugin, designer generate).
5
+
6
+ ## Table of Contents
7
+
8
+ 1. [Pipeline (canonical)](#pipeline-canonical)
9
+ 2. [UDF decisions](#udf-decisions)
10
+ 3. [Regenerating an existing app](#regenerating-an-existing-app)
11
+ 4. [Page type](#page-type)
12
+ 5. [Plugin choice](#plugin-choice)
13
+
14
+ ---
15
+
16
+ ## Pipeline (canonical)
17
+
18
+ This is the canonical (golden) path for the frontend track. It runs
19
+ **independently** from the backend pipeline. The backend API must be running and
20
+ reachable at `apiBaseUrl` before the generated frontend is useful, but the
21
+ frontend can be defined and generated without the backend live.
22
+
23
+ ```
24
+ 1. designer_list_plugins
25
+ ── GROUNDING ── list available output plugins before creating the UDF.
26
+ Built-in: vanilla-js-basic (no auth), vanilla-js-auth and
27
+ vanilla-js-custom (JWT auth + RBAC).
28
+ → references/udf-catalog.md § Plugins
29
+
30
+ 2a. codegen_migrate_payload ── PRIMARY ── when a backend RDF payload exists
31
+ Convert the RDF into a split UDF set in the output folder (default
32
+ frontend/payload/): app-config.json, pages/<pageId>.json, the aggregator
33
+ <appCode>.json, and snapshots in .meta/pages/. Run it once per RDF with
34
+ the same output folder to add pages to one app. It derives fields, types,
35
+ lookups, details[], status filters, and the date patterns from the
36
+ backend, so start here instead of writing pages by hand.
37
+ Needs a license and the backend config (it takes `config`, so the
38
+ SKILL.md Guardrail 3 gate applies).
39
+ 2b. [hand-write the UDF] only when there is no RDF to migrate from
40
+ Ground every key with designer_get_udf_catalog first.
41
+ designer_init_project (optional) scaffold a project folder with the
42
+ assets of an auth-capable plugin (vanilla-js-auth / vanilla-js-custom).
43
+ It writes no UDF payload file.
44
+
45
+ 3. designer_get_udf_catalog
46
+ ── GROUNDING ── call before editing any UDF page.
47
+ Returns valid field types, enums, limits, and validation constants for
48
+ the installed designer version.
49
+ → references/udf-catalog.md
50
+
51
+ 4. [edit the UDF pages]
52
+ Edit pages/<pageId>.json (labels, layout, features, workflowActions) and
53
+ the aggregator (navigation, homepage). One page entry = one CRUD page or
54
+ one dashboard page. Re-running migrate later merges RDF changes into
55
+ these files without losing the edits (§ UDF decisions).
56
+
57
+ 5. designer_validate_payload
58
+ ── GATE ── validate the UDF (the aggregator file). Catches structural
59
+ errors before generation. Run before preview or generate, every time.
60
+
61
+ 6. designer_preview_files
62
+ Dry-run: list the files the generator produces, without writing to disk.
63
+ It does not read the output folder, so it cannot show which files a
64
+ re-generate will merge, keep, or skip.
65
+
66
+ 7. designer_generate
67
+ Generate frontend HTML/JS/CSS from the aggregator. On an existing app it
68
+ merges into the files on disk (§ Regenerating an existing app).
69
+ The agent STOPS here — the user opens the output in a browser.
70
+ ```
71
+
72
+ For plugin development (custom output plugins):
73
+
74
+ ```
75
+ designer_scaffold_plugin → [develop plugin templates]
76
+ → designer_inspect_plugin (verify plugin metadata and capabilities)
77
+ → designer_generate (test generation with the custom plugin)
78
+ ```
79
+
80
+ Two checkpoints (SKILL.md § Guardrails): **step 5** is a gate — never `designer_generate`
81
+ an unvalidated payload; **step 7** is where the agent stops (generates files, does
82
+ not serve or deploy).
83
+
84
+ Licensing note: the Designer tools (`designer_preview_files`, `designer_generate`,
85
+ etc.) do **not** require a RESTForge license, unlike the `codegen_*`, `runtime_*`,
86
+ and `setup_validate_config` tools. `codegen_migrate_payload` is a `codegen_*`
87
+ tool, so step 2a runs in the backend project folder.
88
+
89
+ ---
90
+
91
+ ## UDF decisions
92
+
93
+ - **A backend RDF exists** → `codegen_migrate_payload`; do not write the page by
94
+ hand. Run it once per RDF into the same output folder to build one app.
95
+ - **Re-running migrate** (RDF changed, or a page must pick up new columns) →
96
+ run it again **without** `overwrite`. Existing pages are merged: user edits
97
+ (labels, layout, removed fields, blocks) are kept and RDF changes to untouched
98
+ values are applied. Backend contract values (`apiPath`, `primaryKey`, `type`,
99
+ `required`, `maxlength`, `decimalPlaces`, `tableField`, lookup source, option
100
+ values) follow the RDF when both sides changed, with a warning. The merge uses
101
+ the snapshots in `<output>/.meta/pages/<pageId>.json`; commit them with the
102
+ pages and never edit them. The aggregator (`navigation`, `homepage`) and
103
+ `app-config.json` are merged too.
104
+ - **`overwrite: true`** recreates the page from the RDF and discards every
105
+ customisation (the old file goes to `.restforge/archive/`). Confirm with the
106
+ user before using it.
107
+ - **Date patterns** → `appConfig.dateFormat` / `dateTimeFormat` are rewritten
108
+ from the backend `DATEFORMAT` / `DATETIMEFORMAT` on every migrate. After the
109
+ backend changes them, re-run migrate and `designer_generate`; never edit the
110
+ frontend values to differ from the backend.
111
+
112
+ ---
113
+
114
+ ## Regenerating an existing app
115
+
116
+ `designer_generate` without `overwrite` is the normal way to bring UDF changes
117
+ into an app that already exists, for scope `app` and `form` alike. The user may
118
+ edit the generated app files (`<page>.html`, `js/<page>.js`, `js/common.js`,
119
+ `js/config.js`, `sidebar.html`, assets); the edits survive.
120
+
121
+ - **Merge** — each text file is merged three-way against the snapshot of the
122
+ last generate in `<output parent>/.meta/<output folder>/` (for
123
+ `frontend/apps/<project>` that is `frontend/apps/.meta/<project>/`). User
124
+ edits are kept and UDF or template changes on untouched lines are applied.
125
+ The result lists a status per file: `written`, `merged`, `unchanged`, `kept`,
126
+ `conflict`, or `skipped`. Commit the `.meta` folder with the app, except
127
+ `conflicts/` (it has its own `.gitignore`); never edit it.
128
+ - **Conflict** — a file whose edits clash with the new output is left untouched;
129
+ the version with conflict markers is in `.meta/<project>/conflicts/<path>`.
130
+ All other files are still processed and the command exits 1. That is a result
131
+ to relay, not a tool failure: resolve the file (apply the conflict version and
132
+ keep the user's intent), then run generate again. Do not use `overwrite` as
133
+ the fix.
134
+ - **Managed lines** — `BACKEND_DATE_FORMAT` and `BACKEND_DATETIME_FORMAT` in
135
+ `js/common.js` (marked `restforge:managed`) always take the new value.
136
+ - **Assets** — a modified asset (CSS, vendor bundle, image) is kept with a
137
+ warning; the new version is in `conflicts/<path>`.
138
+ - **App generated before snapshots existed** — files that differ from the new
139
+ output are kept with a warning and the new version goes to `conflicts/<path>`.
140
+ The user merges by hand, or recreates one page with `scope: "form"`, `page`,
141
+ and `overwrite`.
142
+ - **`index.html`** — merged like the shared files when it carries the
143
+ `RESTForge-Designer:LandingGenerated` marker. Without the marker it is never
144
+ replaced, even with `overwrite`, and is reported as `skipped`; a new page then
145
+ does not appear on the homepage. Remove the file or add the marker line back,
146
+ then generate with scope `app`.
147
+ - **`overwrite: true`** recreates every file from scratch and discards the
148
+ customisations in all of them (each old file that differs is archived to
149
+ `.restforge/archive/`). Confirm with the user before using it.
150
+ - **Not handled** — files of a page removed from the UDF are not deleted, and a
151
+ clean text merge is not proof that the logic still works; test the result in
152
+ the browser.
153
+
154
+ ---
155
+
156
+ ## Page type
157
+
158
+ - **Standard CRUD page** → `pageType: "crud"` (default) with `apiPath`,
159
+ `primaryKey`, `displayField`, and `fields[]`.
160
+ - **Dashboard page** → `pageType: "dashboard"` with a `dataSources` object
161
+ (`name → { url, method, body }`) and `rows[]` → `columns[]` → `widgets[]`.
162
+ → references/udf-catalog.md § Dashboard Page
163
+ - **Page with approval workflow** → add `workflow` (`statusField` and
164
+ `transitions`, identical to the RDF) and `workflowActions[]` whose `actionId`
165
+ equals the target status. Add `fieldStates` to lock rows in final statuses.
166
+ → references/udf-catalog.md § Workflow Actions
167
+
168
+ ---
169
+
170
+ ## Plugin choice
171
+
172
+ - **No auth** → `vanilla-js-basic`.
173
+ - **Auth WITH RBAC, built into the app** → `vanilla-js-auth` or
174
+ `vanilla-js-custom` at `designer_init_project`. Use `noAuth: true` (`--no-auth`)
175
+ to get the plugin's UI without its auth.
176
+ - **Custom branding / new plugin** → `designer_scaffold_plugin`.
177
+ - **Bolt-on auth WITHOUT RBAC** → not a plugin; see auth.md § Choosing the mechanism.
178
+ → references/udf-catalog.md § Plugins
@@ -26,7 +26,7 @@ before `codegen_create_endpoint` — a processor payload through
26
26
  `dateTimeFields`, `deleteReferences`, `softDelete`, and (for a table with an
27
27
  `is_active` column) `defaultScope`. Edit that file; do not write those keys by
28
28
  hand. Later `codegen_generate_payload` / `codegen_sync_payload` runs keep the
29
- customisations made to those keys (see SKILL.md § RDF Payload).
29
+ customisations made to those keys (see backend-pipeline.md § RDF payload decisions).
30
30
 
31
31
  ---
32
32