@loj-lang/cli 0.5.0
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.
- package/README.md +87 -0
- package/agent-assets/loj-authoring/SKILL.md +179 -0
- package/agent-assets/loj-authoring/agents/openai.yaml +3 -0
- package/agent-assets/loj-authoring/metadata.json +6 -0
- package/agent-assets/loj-authoring/references/backend-family.md +340 -0
- package/agent-assets/loj-authoring/references/backend-targets.md +171 -0
- package/agent-assets/loj-authoring/references/frontend-family.md +794 -0
- package/agent-assets/loj-authoring/references/frontend-runtime-trace.md +204 -0
- package/agent-assets/loj-authoring/references/policy-rules-proof.md +178 -0
- package/agent-assets/loj-authoring/references/project-and-transport.md +454 -0
- package/agent-assets/loj-authoring/references/workflow-flow-proof.md +263 -0
- package/dist/database-native-sql.d.ts +4 -0
- package/dist/database-native-sql.d.ts.map +1 -0
- package/dist/database-native-sql.js +266 -0
- package/dist/database-native-sql.js.map +1 -0
- package/dist/env.d.ts +31 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +229 -0
- package/dist/env.js.map +1 -0
- package/dist/fastapi-dev-runner.d.ts +3 -0
- package/dist/fastapi-dev-runner.d.ts.map +1 -0
- package/dist/fastapi-dev-runner.js +263 -0
- package/dist/fastapi-dev-runner.js.map +1 -0
- package/dist/flow-proof.d.ts +3 -0
- package/dist/flow-proof.d.ts.map +1 -0
- package/dist/flow-proof.js +2 -0
- package/dist/flow-proof.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5353 -0
- package/dist/index.js.map +1 -0
- package/dist/rules-proof.d.ts +3 -0
- package/dist/rules-proof.d.ts.map +1 -0
- package/dist/rules-proof.js +2 -0
- package/dist/rules-proof.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Project File And Transport
|
|
2
|
+
|
|
3
|
+
Use this reference for `loj.project.yaml`, full-stack orchestration, target-level database/runtime
|
|
4
|
+
profiles, and the shared frontend↔backend HTTP/JSON baseline.
|
|
5
|
+
|
|
6
|
+
## `loj.project.yaml` Purpose
|
|
7
|
+
|
|
8
|
+
`loj.project.yaml` is orchestration only.
|
|
9
|
+
|
|
10
|
+
It answers:
|
|
11
|
+
|
|
12
|
+
- what targets belong to the app
|
|
13
|
+
- where each target entry file lives
|
|
14
|
+
- where generated output should go
|
|
15
|
+
- which dev host/server topology should run together
|
|
16
|
+
- which project-shell database/runtime profile should be prepared for generated targets
|
|
17
|
+
|
|
18
|
+
It must not contain business semantics such as:
|
|
19
|
+
|
|
20
|
+
- `model`
|
|
21
|
+
- `resource`
|
|
22
|
+
- `page`
|
|
23
|
+
- controllers/entities/services
|
|
24
|
+
- frontend layout details
|
|
25
|
+
|
|
26
|
+
## Recommended Directory Structure
|
|
27
|
+
|
|
28
|
+
For new projects, default to this shape unless the task explicitly needs something else:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
my-app/
|
|
32
|
+
loj.project.yaml
|
|
33
|
+
|
|
34
|
+
frontend/
|
|
35
|
+
app.web.loj
|
|
36
|
+
models/
|
|
37
|
+
resources/
|
|
38
|
+
read-models/
|
|
39
|
+
pages/
|
|
40
|
+
rules/
|
|
41
|
+
workflows/
|
|
42
|
+
styles/
|
|
43
|
+
components/
|
|
44
|
+
logic/
|
|
45
|
+
assets/
|
|
46
|
+
|
|
47
|
+
backend/
|
|
48
|
+
app.api.loj
|
|
49
|
+
models/
|
|
50
|
+
resources/
|
|
51
|
+
read-models/
|
|
52
|
+
rules/
|
|
53
|
+
workflows/
|
|
54
|
+
handlers/
|
|
55
|
+
policies/
|
|
56
|
+
queries/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use this as a recommendation, not a parser requirement.
|
|
60
|
+
|
|
61
|
+
Intent:
|
|
62
|
+
|
|
63
|
+
- `frontend/` keeps `.web.loj`, `.style.loj`, frontend rules/workflows, host components, and assets together
|
|
64
|
+
- `backend/` keeps `.api.loj`, backend rules/workflows, `@fn(...)` handlers/policies, and `@sql(...)` queries together
|
|
65
|
+
- `loj.project.yaml` stays at the app root
|
|
66
|
+
|
|
67
|
+
## Minimal Shape
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
app:
|
|
71
|
+
name: user-admin
|
|
72
|
+
|
|
73
|
+
targets:
|
|
74
|
+
frontend:
|
|
75
|
+
type: web
|
|
76
|
+
entry: frontend/app.web.loj
|
|
77
|
+
backend:
|
|
78
|
+
type: api
|
|
79
|
+
entry: backend/app.api.loj
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Rules:
|
|
83
|
+
|
|
84
|
+
- `app.name` is required
|
|
85
|
+
- `targets` is required
|
|
86
|
+
- each target has:
|
|
87
|
+
- logical alias key
|
|
88
|
+
- `type`
|
|
89
|
+
- `entry`
|
|
90
|
+
- `entry` is relative to the project file
|
|
91
|
+
|
|
92
|
+
## Target Types
|
|
93
|
+
|
|
94
|
+
Preferred:
|
|
95
|
+
|
|
96
|
+
- `web`
|
|
97
|
+
- `api`
|
|
98
|
+
|
|
99
|
+
Legacy aliases still accepted during the beta window:
|
|
100
|
+
|
|
101
|
+
- `rdsl`
|
|
102
|
+
- `sdsl`
|
|
103
|
+
|
|
104
|
+
Use `web/api` for new project files.
|
|
105
|
+
|
|
106
|
+
## Current Expanded Shape
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
app:
|
|
110
|
+
name: flight-booking-proof
|
|
111
|
+
|
|
112
|
+
targets:
|
|
113
|
+
frontend:
|
|
114
|
+
type: web
|
|
115
|
+
entry: frontend/app.web.loj
|
|
116
|
+
outDir: generated/frontend
|
|
117
|
+
runtime:
|
|
118
|
+
basePath: /console
|
|
119
|
+
backend:
|
|
120
|
+
type: api
|
|
121
|
+
entry: backend/app.api.loj
|
|
122
|
+
outDir: generated/backend
|
|
123
|
+
database:
|
|
124
|
+
vendor: postgres
|
|
125
|
+
mode: docker-compose
|
|
126
|
+
name: booking_app
|
|
127
|
+
username: loj
|
|
128
|
+
password: loj
|
|
129
|
+
autoProvision: true
|
|
130
|
+
migrations: native-sql
|
|
131
|
+
runtime:
|
|
132
|
+
basePath: /internal-api
|
|
133
|
+
shutdown:
|
|
134
|
+
mode: graceful
|
|
135
|
+
timeout: 30s
|
|
136
|
+
health:
|
|
137
|
+
path: /health
|
|
138
|
+
readiness:
|
|
139
|
+
path: /ready
|
|
140
|
+
drain:
|
|
141
|
+
path: /drain
|
|
142
|
+
cors:
|
|
143
|
+
origins: ["http://127.0.0.1:5173"]
|
|
144
|
+
forwardedHeaders:
|
|
145
|
+
mode: standard
|
|
146
|
+
trustedProxy:
|
|
147
|
+
mode: local
|
|
148
|
+
requestSizeLimit: 10mb
|
|
149
|
+
|
|
150
|
+
dev:
|
|
151
|
+
host:
|
|
152
|
+
type: react-vite
|
|
153
|
+
target: frontend
|
|
154
|
+
dir: ./generated/frontend
|
|
155
|
+
apiBase: /api
|
|
156
|
+
proxyTarget: backend
|
|
157
|
+
server:
|
|
158
|
+
target: backend
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Rules:
|
|
162
|
+
|
|
163
|
+
- the project file stays declarative
|
|
164
|
+
- do not put shell commands in it
|
|
165
|
+
- do not turn it into a generic env bag
|
|
166
|
+
- database/runtime slices live here, not in `.web.loj` / `.api.loj`
|
|
167
|
+
|
|
168
|
+
Complete Spring-style example:
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
app:
|
|
172
|
+
name: flight-booking-proof
|
|
173
|
+
|
|
174
|
+
targets:
|
|
175
|
+
frontend:
|
|
176
|
+
type: web
|
|
177
|
+
entry: frontend/app.web.loj
|
|
178
|
+
outDir: generated/frontend
|
|
179
|
+
runtime:
|
|
180
|
+
basePath: /console
|
|
181
|
+
backend:
|
|
182
|
+
type: api
|
|
183
|
+
entry: backend/app.api.loj
|
|
184
|
+
outDir: generated/backend-postgres
|
|
185
|
+
database:
|
|
186
|
+
vendor: postgres
|
|
187
|
+
mode: docker-compose
|
|
188
|
+
name: flight_booking_proof
|
|
189
|
+
username: loj
|
|
190
|
+
password: loj
|
|
191
|
+
autoProvision: true
|
|
192
|
+
migrations: flyway
|
|
193
|
+
runtime:
|
|
194
|
+
basePath: /internal-api
|
|
195
|
+
shutdown:
|
|
196
|
+
mode: graceful
|
|
197
|
+
timeout: 30s
|
|
198
|
+
cors:
|
|
199
|
+
origins: [http://127.0.0.1:5173]
|
|
200
|
+
methods: [GET, POST, PUT, PATCH, DELETE, OPTIONS]
|
|
201
|
+
headers: [Authorization, Content-Type]
|
|
202
|
+
credentials: true
|
|
203
|
+
forwardedHeaders:
|
|
204
|
+
mode: standard
|
|
205
|
+
trustedProxy:
|
|
206
|
+
mode: local
|
|
207
|
+
health:
|
|
208
|
+
path: /healthz
|
|
209
|
+
readiness:
|
|
210
|
+
path: /readyz
|
|
211
|
+
drain:
|
|
212
|
+
path: /drainz
|
|
213
|
+
requestSizeLimit: 10mb
|
|
214
|
+
|
|
215
|
+
dev:
|
|
216
|
+
host:
|
|
217
|
+
type: react-vite
|
|
218
|
+
target: frontend
|
|
219
|
+
dir: ../../subprojects/rdsl/examples/user-admin/host
|
|
220
|
+
apiBase: /api
|
|
221
|
+
proxyTarget: backend
|
|
222
|
+
server:
|
|
223
|
+
target: backend
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Current CLI Mapping
|
|
227
|
+
|
|
228
|
+
Project-shell commands are the preferred default when `loj.project.yaml` exists:
|
|
229
|
+
|
|
230
|
+
- `loj validate loj.project.yaml`
|
|
231
|
+
- `loj build loj.project.yaml`
|
|
232
|
+
- `loj dev loj.project.yaml`
|
|
233
|
+
- `loj dev loj.project.yaml --debug`
|
|
234
|
+
- `loj rebuild loj.project.yaml --target frontend`
|
|
235
|
+
- `loj restart loj.project.yaml --service host`
|
|
236
|
+
- `loj status loj.project.yaml`
|
|
237
|
+
- `loj stop loj.project.yaml`
|
|
238
|
+
- `loj doctor loj.project.yaml`
|
|
239
|
+
|
|
240
|
+
Single-target project-shell flows keep database/runtime profiles active:
|
|
241
|
+
|
|
242
|
+
- `loj validate loj.project.yaml --target backend`
|
|
243
|
+
- `loj build loj.project.yaml --target backend`
|
|
244
|
+
- `loj dev loj.project.yaml --target backend`
|
|
245
|
+
- `loj status loj.project.yaml --target backend`
|
|
246
|
+
- `loj doctor loj.project.yaml --target backend`
|
|
247
|
+
|
|
248
|
+
Target-local CLIs remain available as secondary tools:
|
|
249
|
+
|
|
250
|
+
- `rdsl validate/build`
|
|
251
|
+
- `sdsl validate/build/dev`
|
|
252
|
+
|
|
253
|
+
Use `loj.project.yaml` whenever you need project-shell database/runtime/dev orchestration, even if
|
|
254
|
+
you are only generating one target.
|
|
255
|
+
|
|
256
|
+
Command stance:
|
|
257
|
+
|
|
258
|
+
- default: `loj ...`
|
|
259
|
+
- secondary/high-signal local tooling: `rdsl ...` / `sdsl ...`
|
|
260
|
+
- do not recommend `rdsl build` / `sdsl build` as the default path for new user-facing full-stack
|
|
261
|
+
examples
|
|
262
|
+
|
|
263
|
+
## Required, Optional, Defaults
|
|
264
|
+
|
|
265
|
+
Core field status:
|
|
266
|
+
|
|
267
|
+
| Path | Status | Current default / note |
|
|
268
|
+
| --- | --- | --- |
|
|
269
|
+
| `app.name` | required | non-empty string |
|
|
270
|
+
| `targets` | required | must contain at least one target |
|
|
271
|
+
| `targets.<alias>.type` | required | `web | api` preferred; `rdsl | sdsl` still accepted as legacy aliases |
|
|
272
|
+
| `targets.<alias>.entry` | required | non-empty relative path |
|
|
273
|
+
| `targets.<alias>.outDir` | optional | build falls back to target-local defaults when omitted |
|
|
274
|
+
| `targets.<alias>.database` | optional | `api` targets only |
|
|
275
|
+
| `targets.<alias>.runtime` | optional | `api` targets may use the narrow runtime slice; `web` currently only uses `runtime.basePath` |
|
|
276
|
+
| `dev` | optional | no managed dev processes when omitted |
|
|
277
|
+
| `dev.server` | optional | starts no backend process when omitted |
|
|
278
|
+
| `dev.server.target` | required when `dev.server` exists | must reference an `api` target |
|
|
279
|
+
| `dev.server.host` | optional | `127.0.0.1` |
|
|
280
|
+
| `dev.server.port` | optional | `3001` |
|
|
281
|
+
| `dev.host` | optional | starts no frontend host when omitted |
|
|
282
|
+
| `dev.host.type` | optional | `react-vite` |
|
|
283
|
+
| `dev.host.target` | required when `dev.host` exists | must reference a `web` target |
|
|
284
|
+
| `dev.host.dir` | required when `dev.host` exists | non-empty relative path |
|
|
285
|
+
| `dev.host.host` | optional | `127.0.0.1` |
|
|
286
|
+
| `dev.host.port` | optional | `5173` |
|
|
287
|
+
| `dev.host.previewPort` | optional | `4173` |
|
|
288
|
+
| `dev.host.apiBase` | optional | `/api` |
|
|
289
|
+
| `dev.host.proxyTarget` | optional | defaults to `dev.server.target` when a local backend is also configured |
|
|
290
|
+
| `dev.host.proxyAuth` | optional | no default |
|
|
291
|
+
|
|
292
|
+
Database defaults:
|
|
293
|
+
|
|
294
|
+
| Path | Status | Current default / note |
|
|
295
|
+
| --- | --- | --- |
|
|
296
|
+
| `database.vendor` | required when `database` exists | `h2 | sqlite | postgres | mysql | mariadb | sqlserver | oracle` |
|
|
297
|
+
| `database.mode` | optional | `embedded` for `h2/sqlite`, otherwise `external` |
|
|
298
|
+
| `database.name` | optional | derived from `app.name`; `sqlite` becomes `<app>.db`, `oracle` becomes `FREEPDB1` |
|
|
299
|
+
| `database.host` | optional | external vendors default to `127.0.0.1` |
|
|
300
|
+
| `database.port` | optional | vendor default where applicable |
|
|
301
|
+
| `database.username` | optional | vendor default |
|
|
302
|
+
| `database.password` | optional | vendor default |
|
|
303
|
+
| `database.autoProvision` | optional | `false` |
|
|
304
|
+
| `database.migrations` | optional | `none` |
|
|
305
|
+
|
|
306
|
+
Runtime defaults:
|
|
307
|
+
|
|
308
|
+
| Path | Status | Current default / note |
|
|
309
|
+
| --- | --- | --- |
|
|
310
|
+
| `runtime.basePath` | optional | no default; supported on `api` and `web` targets |
|
|
311
|
+
| `runtime.shutdown` | optional | `api` targets only |
|
|
312
|
+
| `runtime.shutdown.mode` | optional inside `shutdown` | `graceful` |
|
|
313
|
+
| `runtime.shutdown.timeout` | optional inside `shutdown` | `30s` |
|
|
314
|
+
| `runtime.health.path` | optional | no default helper emitted unless declared |
|
|
315
|
+
| `runtime.readiness.path` | optional | no default helper emitted unless declared |
|
|
316
|
+
| `runtime.drain.path` | optional | no default helper emitted unless declared |
|
|
317
|
+
| `runtime.cors` | optional | `api` targets only |
|
|
318
|
+
| `runtime.cors.origins` | required when `cors` exists | non-empty string array |
|
|
319
|
+
| `runtime.cors.methods` | optional | target defaults when omitted |
|
|
320
|
+
| `runtime.cors.headers` | optional | target defaults when omitted |
|
|
321
|
+
| `runtime.cors.credentials` | optional | `false` |
|
|
322
|
+
| `runtime.forwardedHeaders` | optional | `api` targets only |
|
|
323
|
+
| `runtime.forwardedHeaders.mode` | optional inside `forwardedHeaders` | `standard` |
|
|
324
|
+
| `runtime.trustedProxy` | optional | `api` targets only |
|
|
325
|
+
| `runtime.trustedProxy.mode` | optional inside `trustedProxy` | `local` |
|
|
326
|
+
| `runtime.trustedProxy.cidrs` | optional | required only when `mode: cidrs` |
|
|
327
|
+
| `runtime.requestSizeLimit` | optional | no default |
|
|
328
|
+
|
|
329
|
+
Current auto-fill behavior:
|
|
330
|
+
|
|
331
|
+
- if `runtime.trustedProxy` is declared without `runtime.forwardedHeaders`, the project shell
|
|
332
|
+
currently auto-enables `forwardedHeaders.mode: standard`
|
|
333
|
+
|
|
334
|
+
## Env Story
|
|
335
|
+
|
|
336
|
+
Current conventional env files:
|
|
337
|
+
|
|
338
|
+
- `.env`
|
|
339
|
+
- `.env.local`
|
|
340
|
+
- `.env.<target-alias>`
|
|
341
|
+
- `.env.<target-alias>.local`
|
|
342
|
+
|
|
343
|
+
`loj validate`, `loj build`, and `loj dev` load these conventionally.
|
|
344
|
+
|
|
345
|
+
`loj dev` also watches them and reloads managed target sessions when they change.
|
|
346
|
+
|
|
347
|
+
## Database Slice
|
|
348
|
+
|
|
349
|
+
`targets.<alias>.database` is currently supported on `api` targets.
|
|
350
|
+
|
|
351
|
+
Supported fields:
|
|
352
|
+
|
|
353
|
+
- `vendor`: `h2 | sqlite | postgres | mysql | mariadb | sqlserver | oracle`
|
|
354
|
+
- `mode`: `embedded | external | docker-compose`
|
|
355
|
+
- `name`
|
|
356
|
+
- `host`
|
|
357
|
+
- `port`
|
|
358
|
+
- `username`
|
|
359
|
+
- `password`
|
|
360
|
+
- `autoProvision`
|
|
361
|
+
- `migrations`: `none | native-sql | flyway`
|
|
362
|
+
|
|
363
|
+
Current intent:
|
|
364
|
+
|
|
365
|
+
- database choice stays in project/runtime orchestration
|
|
366
|
+
- generated targets may emit runtime-specific config plus native `db/schema.sql`
|
|
367
|
+
- `loj dev` may auto-start/stop generated `docker-compose.database.yaml` when
|
|
368
|
+
`mode: docker-compose` and `autoProvision: true`
|
|
369
|
+
|
|
370
|
+
## Runtime Slice
|
|
371
|
+
|
|
372
|
+
`targets.<alias>.runtime` currently supports:
|
|
373
|
+
|
|
374
|
+
- on `api` targets:
|
|
375
|
+
- `basePath`
|
|
376
|
+
- `shutdown.mode`
|
|
377
|
+
- `shutdown.timeout`
|
|
378
|
+
- `health.path`
|
|
379
|
+
- `readiness.path`
|
|
380
|
+
- `drain.path`
|
|
381
|
+
- `cors.origins`
|
|
382
|
+
- `cors.methods`
|
|
383
|
+
- `cors.headers`
|
|
384
|
+
- `cors.credentials`
|
|
385
|
+
- `forwardedHeaders.mode`
|
|
386
|
+
- `trustedProxy.mode`
|
|
387
|
+
- `trustedProxy.cidrs`
|
|
388
|
+
- `requestSizeLimit`
|
|
389
|
+
- on `web` targets:
|
|
390
|
+
- `basePath`
|
|
391
|
+
|
|
392
|
+
Keep this narrow. It expresses generated runtime/deploy intent, not business semantics.
|
|
393
|
+
|
|
394
|
+
## Dev Story
|
|
395
|
+
|
|
396
|
+
Current `loj dev` scope:
|
|
397
|
+
|
|
398
|
+
- starts target-local rebuild loops
|
|
399
|
+
- can run a React/Vite host for the configured web target
|
|
400
|
+
- can run the configured backend server
|
|
401
|
+
- can auto-provision supported Docker Compose databases
|
|
402
|
+
- persists dev-session state for `status`, `stop`, and editor reuse
|
|
403
|
+
- accepts `loj rebuild` to queue a manual rebuild for all or selected active targets
|
|
404
|
+
- accepts `loj restart` to restart managed `host` and/or `server` processes without restarting the whole loop
|
|
405
|
+
- may expose attachable debugger endpoints through `loj dev --debug`
|
|
406
|
+
|
|
407
|
+
Keep this conventional. Do not invent arbitrary process orchestration syntax.
|
|
408
|
+
|
|
409
|
+
## Neutral Transport Contract
|
|
410
|
+
|
|
411
|
+
Frontend and backend targets align to a shared HTTP/JSON contract, not directly to each other's
|
|
412
|
+
frameworks.
|
|
413
|
+
|
|
414
|
+
### Lists
|
|
415
|
+
|
|
416
|
+
Accepted list responses:
|
|
417
|
+
|
|
418
|
+
- raw JSON array
|
|
419
|
+
- `{ items: [...] }`
|
|
420
|
+
- `{ data: [...] }`
|
|
421
|
+
|
|
422
|
+
### Single records
|
|
423
|
+
|
|
424
|
+
Accepted single-record responses:
|
|
425
|
+
|
|
426
|
+
- raw JSON object
|
|
427
|
+
- `{ item: {...} }`
|
|
428
|
+
- `{ data: {...} }`
|
|
429
|
+
|
|
430
|
+
### Errors
|
|
431
|
+
|
|
432
|
+
Errors should be non-2xx JSON objects with string `message`.
|
|
433
|
+
|
|
434
|
+
Current message rule:
|
|
435
|
+
|
|
436
|
+
- backend targets should return a stable human-readable `message`
|
|
437
|
+
- do not invent target-private message templating in source DSL
|
|
438
|
+
|
|
439
|
+
### IDs
|
|
440
|
+
|
|
441
|
+
- every resource record includes `id`
|
|
442
|
+
- frontend may coerce it to string
|
|
443
|
+
|
|
444
|
+
### Pagination
|
|
445
|
+
|
|
446
|
+
- server pagination metadata is still optional
|
|
447
|
+
- generated list pagination remains client-side unless the shared transport contract widens later
|
|
448
|
+
|
|
449
|
+
## Full-Stack Authoring Guardrails
|
|
450
|
+
|
|
451
|
+
- Keep frontend-family and backend-family semantics in their own files.
|
|
452
|
+
- Use `loj.project.yaml` only to compose them.
|
|
453
|
+
- If frontend and backend need a richer shared transport shape, evolve the transport contract first.
|
|
454
|
+
- Do not hide target mismatches inside project orchestration.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Workflow / Flow (`.flow.loj`)
|
|
2
|
+
|
|
3
|
+
Use this reference for the current first-slice workflow/state-machine surface.
|
|
4
|
+
|
|
5
|
+
It still has standalone CLI entry points, but it is no longer standalone-only:
|
|
6
|
+
|
|
7
|
+
- `.api.loj` may link it through `resource workflow: '@flow("./workflows/x")'`
|
|
8
|
+
- `.web.loj` may link it through either:
|
|
9
|
+
- `resource workflow: '@flow("./workflows/x")'`
|
|
10
|
+
- `resource workflow: { source: '@flow("./workflows/x")', style: workflowShell }`
|
|
11
|
+
- `loj.project.yaml` still does not orchestrate `.flow.loj` directly
|
|
12
|
+
|
|
13
|
+
## Current Scope
|
|
14
|
+
|
|
15
|
+
Implemented today:
|
|
16
|
+
|
|
17
|
+
- `.flow.loj` file suffix
|
|
18
|
+
- one named workflow per file
|
|
19
|
+
- parser + validator + shared workflow-manifest generation
|
|
20
|
+
- repo CLI entry:
|
|
21
|
+
- `loj flow validate <file.flow.loj>`
|
|
22
|
+
- `loj flow build <file.flow.loj> --out-dir <dir>`
|
|
23
|
+
- narrow `.api.loj` linkage through `resource workflow: '@flow("./workflows/x")'`
|
|
24
|
+
- narrow `.web.loj` linkage through scalar or mapping `resource workflow`
|
|
25
|
+
- generated backend transition-enforcement surface for Spring Boot + FastAPI
|
|
26
|
+
- generated frontend create/edit/read workflow summaries, step-aware create/edit submit CTA labels, and next-step-prioritized read-surface transition actions
|
|
27
|
+
- fixed generated frontend workflow route/page at `/:id/workflow` for workflow-linked resources
|
|
28
|
+
|
|
29
|
+
Not implemented yet:
|
|
30
|
+
|
|
31
|
+
- project-shell orchestration for flow targets
|
|
32
|
+
- broader custom page-level wizard routing or long-transaction semantics beyond the fixed generated workflow page
|
|
33
|
+
- non-resource workflow consumers
|
|
34
|
+
|
|
35
|
+
## File Shape
|
|
36
|
+
|
|
37
|
+
Use one top-level block:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
workflow booking-process:
|
|
41
|
+
model: Booking
|
|
42
|
+
field: status
|
|
43
|
+
|
|
44
|
+
states:
|
|
45
|
+
DRAFT:
|
|
46
|
+
label: "Draft"
|
|
47
|
+
color: gray
|
|
48
|
+
READY:
|
|
49
|
+
label: "Ready"
|
|
50
|
+
color: blue
|
|
51
|
+
CONFIRMED:
|
|
52
|
+
label: "Confirmed"
|
|
53
|
+
color: green
|
|
54
|
+
|
|
55
|
+
wizard:
|
|
56
|
+
steps:
|
|
57
|
+
- name: select-flight
|
|
58
|
+
completesWith: DRAFT
|
|
59
|
+
surface: form
|
|
60
|
+
- name: enter-passengers
|
|
61
|
+
completesWith: READY
|
|
62
|
+
surface: read
|
|
63
|
+
allow: currentUser.role in [ADMIN, AGENT]
|
|
64
|
+
|
|
65
|
+
transitions:
|
|
66
|
+
confirm:
|
|
67
|
+
from: READY
|
|
68
|
+
to: CONFIRMED
|
|
69
|
+
allow: currentUser.role == ADMIN
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Rules:
|
|
73
|
+
|
|
74
|
+
- exactly one top-level `workflow <name>:` block per file
|
|
75
|
+
- `model` is required
|
|
76
|
+
- `field` is required
|
|
77
|
+
- `states` is required
|
|
78
|
+
- `transitions` is required
|
|
79
|
+
- `wizard` is optional
|
|
80
|
+
|
|
81
|
+
Richer booking-style example:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
workflow booking-process:
|
|
85
|
+
model: Booking
|
|
86
|
+
field: status
|
|
87
|
+
|
|
88
|
+
states:
|
|
89
|
+
DRAFT:
|
|
90
|
+
label: "Draft"
|
|
91
|
+
color: gray
|
|
92
|
+
READY:
|
|
93
|
+
label: "Ready"
|
|
94
|
+
color: blue
|
|
95
|
+
CONFIRMED:
|
|
96
|
+
label: "Confirmed"
|
|
97
|
+
color: green
|
|
98
|
+
FAILED:
|
|
99
|
+
label: "Failed"
|
|
100
|
+
color: red
|
|
101
|
+
|
|
102
|
+
wizard:
|
|
103
|
+
steps:
|
|
104
|
+
- name: select-itinerary
|
|
105
|
+
completesWith: DRAFT
|
|
106
|
+
surface: form
|
|
107
|
+
- name: review-booking
|
|
108
|
+
completesWith: READY
|
|
109
|
+
surface: read
|
|
110
|
+
- name: complete-ticketing
|
|
111
|
+
completesWith: CONFIRMED
|
|
112
|
+
surface: workflow
|
|
113
|
+
|
|
114
|
+
transitions:
|
|
115
|
+
confirm:
|
|
116
|
+
from: READY
|
|
117
|
+
to: CONFIRMED
|
|
118
|
+
allow: currentUser.role in [ADMIN, AGENT]
|
|
119
|
+
fail_ticketing:
|
|
120
|
+
from: READY
|
|
121
|
+
to: FAILED
|
|
122
|
+
allow: currentUser.role in [ADMIN, AGENT]
|
|
123
|
+
reopen:
|
|
124
|
+
from: FAILED
|
|
125
|
+
to: READY
|
|
126
|
+
allow: currentUser.role == ADMIN
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use this kind of thicker example when the task is booking/approval/recovery oriented. It is still
|
|
130
|
+
within the current first-slice workflow surface.
|
|
131
|
+
|
|
132
|
+
## Supported Keys
|
|
133
|
+
|
|
134
|
+
Top-level workflow keys:
|
|
135
|
+
|
|
136
|
+
- `model`
|
|
137
|
+
- `field`
|
|
138
|
+
- `states`
|
|
139
|
+
- `wizard`
|
|
140
|
+
- `transitions`
|
|
141
|
+
|
|
142
|
+
Inside each `states:` entry:
|
|
143
|
+
|
|
144
|
+
- optional `label`
|
|
145
|
+
- optional `color`
|
|
146
|
+
|
|
147
|
+
Inside each `wizard.steps:` item:
|
|
148
|
+
|
|
149
|
+
- required `name`
|
|
150
|
+
- required `completesWith`
|
|
151
|
+
- optional `surface`
|
|
152
|
+
- optional `allow`
|
|
153
|
+
|
|
154
|
+
Current `surface` values:
|
|
155
|
+
|
|
156
|
+
- `form`
|
|
157
|
+
- `read`
|
|
158
|
+
- `workflow`
|
|
159
|
+
|
|
160
|
+
Current defaulting:
|
|
161
|
+
|
|
162
|
+
- first wizard step defaults to `form`
|
|
163
|
+
- later wizard steps default to `workflow`
|
|
164
|
+
|
|
165
|
+
Inside each `transitions:` entry:
|
|
166
|
+
|
|
167
|
+
- required `from`
|
|
168
|
+
- required `to`
|
|
169
|
+
- optional `allow`
|
|
170
|
+
|
|
171
|
+
## Expression Language
|
|
172
|
+
|
|
173
|
+
The current flow proof reuses the same constrained expression language used by `.web.loj`,
|
|
174
|
+
`.api.loj`, and `.rules.loj`.
|
|
175
|
+
|
|
176
|
+
Use:
|
|
177
|
+
|
|
178
|
+
- comparisons like `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
179
|
+
- logical `&&`, `||`, `not`
|
|
180
|
+
- arithmetic like `+`, `-`, `*`, `/`
|
|
181
|
+
- dotted paths like `currentUser.role`, `record.status`
|
|
182
|
+
- membership like `currentUser.role in [ADMIN, AGENT]`
|
|
183
|
+
|
|
184
|
+
Do not use:
|
|
185
|
+
|
|
186
|
+
- raw JavaScript, Java, or Python
|
|
187
|
+
- statements, loops, imports, closures
|
|
188
|
+
- router/state-machine/transaction-framework internals
|
|
189
|
+
|
|
190
|
+
## Current Command Path
|
|
191
|
+
|
|
192
|
+
Validate:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
loj flow validate ./workflows/booking-process.flow.loj
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Build manifest:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
loj flow build ./workflows/booking-process.flow.loj --out-dir ./generated/flow
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Current build output is a standalone workflow manifest JSON file. Treat it as the first proof
|
|
205
|
+
surface, even though that same manifest is now also consumed by narrow `.api.loj` / `.web.loj`
|
|
206
|
+
resource linkage.
|
|
207
|
+
|
|
208
|
+
## Current Linked Resource Surfaces
|
|
209
|
+
|
|
210
|
+
Backend:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
resource bookings:
|
|
214
|
+
model: Booking
|
|
215
|
+
api: /api/bookings
|
|
216
|
+
workflow: '@flow("./workflows/booking-process")'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- Spring Boot + FastAPI generate workflow-aware create/update wrappers
|
|
220
|
+
- create seeds the initial workflow state from the first wizard step's `completesWith` when present, otherwise the first declared workflow state
|
|
221
|
+
- update preserves the current workflow state rather than accepting direct state mutation through the normal update payload
|
|
222
|
+
- workflow-linked resources also gain `POST /.../{id}/transitions/{transition}` for transition enforcement
|
|
223
|
+
|
|
224
|
+
Frontend:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
resource bookings:
|
|
228
|
+
model: Booking
|
|
229
|
+
api: /api/bookings
|
|
230
|
+
workflow:
|
|
231
|
+
source: '@flow("./workflows/booking-process")'
|
|
232
|
+
style: workflowShell
|
|
233
|
+
read:
|
|
234
|
+
fields: [reference, status]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- generated create/edit/read surfaces reuse the linked workflow manifest
|
|
238
|
+
- on `.web.loj`, `workflow:` may be a scalar `@flow("./workflows/x")` or a mapping with
|
|
239
|
+
`source:` plus optional shell-level `style:`
|
|
240
|
+
- `wizard.steps[].surface` now controls narrow generated handoff across existing surfaces: `form` reuses generated edit after record creation, `read` reuses the generated read surface, and `workflow` reuses the fixed generated workflow page
|
|
241
|
+
- create shows workflow state plus visible wizard steps, derives narrow current/next-step summaries, upgrades the primary submit CTA to `Create and continue to <next step>` when a visible next step exists, and defaults successful submit into the next step's declared surface when no explicit redirect or app-local `returnTo` exists
|
|
242
|
+
- edit shows workflow state plus visible wizard steps, derives the same narrow current/next-step summaries, upgrades the primary submit CTA to `Save and continue to <next step>` when a visible next step exists, surfaces a narrow `Workflow` link into the fixed workflow page, and defaults successful submit into that same next-step surface resolution when no explicit redirect or app-local `returnTo` exists
|
|
243
|
+
- read also shows next-step-prioritized allowed transitions, narrow current/next-step summaries, a generated `workflowStep` handoff when later-step review is requested, and a narrow `Redo <previous step>` link when a previous visible step exists; it posts to the generated backend transition route, and successful transitions now also hand off into the next visible wizard step's declared surface when that surface changes
|
|
244
|
+
- generated routing also adds a fixed `/:id/workflow` page that reuses the same manifest for current-state summary, narrow current/next-step summaries, wizard-step progress, next-step-prioritized transition actions, narrow `workflowStep` review handoff, `Redo <previous step>` navigation, post-transition next-step handoff, a narrow related-surface summary derived from existing `read.related` anchors when a read view exists, generated `read.fields` record-context details plus generated `read.related` panel context when a read view exists, preserves workflow links and workflow state labels across label-list related fallbacks when the target resource has no generated read/edit surface, and narrow `View` / `Edit` / `Back` links with sanitized app-local `returnTo`
|
|
245
|
+
- record-scoped relation pages that already exist for the same parent resource also pass a narrow `parentWorkflow` summary into adjacent generated custom blocks when that parent resource is workflow-linked
|
|
246
|
+
- the workflow-controlled enum state field may not be listed as a plain generated `create.fields` or `edit.fields` entry
|
|
247
|
+
|
|
248
|
+
Current expression boundary for linked workflow usage:
|
|
249
|
+
|
|
250
|
+
- wizard step `allow` stays inside the shared expression language
|
|
251
|
+
- web wizard-step visibility currently supports `currentUser`, `record`, `formData`, and bare enum-like literals
|
|
252
|
+
- backend transition enforcement currently supports `currentUser`, `record`, and bare enum-like literals
|
|
253
|
+
|
|
254
|
+
## Hard Guardrails
|
|
255
|
+
|
|
256
|
+
- Do not invent any `.flow.loj` linkage beyond:
|
|
257
|
+
- `resource workflow: '@flow("./workflows/x")'` in `.api.loj`
|
|
258
|
+
- `resource workflow: '@flow("./workflows/x")'` in `.web.loj`
|
|
259
|
+
- `resource workflow: { source: '@flow("./workflows/x")', style: workflowShell }` in `.web.loj`
|
|
260
|
+
- Do not add policy/rules syntax to `.flow.loj`.
|
|
261
|
+
- Do not add target-specific router or transaction vocabulary to `.flow.loj`.
|
|
262
|
+
- Do not invent `loj.project.yaml` workflow target types yet.
|
|
263
|
+
- Keep workflow authoring declarative and target-neutral.
|