@tulipes/cli 0.1.0-rc.1
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/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/app-core.d.ts +33 -0
- package/dist/app-core.js +58 -0
- package/dist/app-core.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/dev.d.ts +8 -0
- package/dist/dev.js +22 -0
- package/dist/dev.js.map +1 -0
- package/dist/env-check.d.ts +6 -0
- package/dist/env-check.js +28 -0
- package/dist/env-check.js.map +1 -0
- package/dist/init.d.ts +25 -0
- package/dist/init.js +6416 -0
- package/dist/init.js.map +1 -0
- package/dist/inspection.d.ts +8 -0
- package/dist/inspection.js +15 -0
- package/dist/inspection.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +166 -0
- package/dist/main.js.map +1 -0
- package/dist/minimal.d.ts +2 -0
- package/dist/minimal.js +2200 -0
- package/dist/minimal.js.map +1 -0
- package/dist/new-module.d.ts +19 -0
- package/dist/new-module.js +348 -0
- package/dist/new-module.js.map +1 -0
- package/dist/package-info.d.ts +18 -0
- package/dist/package-info.js +53 -0
- package/dist/package-info.js.map +1 -0
- package/dist/routes.d.ts +8 -0
- package/dist/routes.js +54 -0
- package/dist/routes.js.map +1 -0
- package/dist/spec.d.ts +19 -0
- package/dist/spec.js +101 -0
- package/dist/spec.js.map +1 -0
- package/dist/sync.d.ts +12 -0
- package/dist/sync.js +117 -0
- package/dist/sync.js.map +1 -0
- package/dist/update.d.ts +20 -0
- package/dist/update.js +240 -0
- package/dist/update.js.map +1 -0
- package/package.json +59 -0
- package/templates/CLAUDE.md +120 -0
- package/templates/browser-auth.md +120 -0
- package/templates/claude/skills/tulipes-boot-errors/SKILL.md +74 -0
- package/templates/claude/skills/tulipes-endpoint/SKILL.md +132 -0
- package/templates/claude/skills/tulipes-env-variable/SKILL.md +78 -0
- package/templates/claude/skills/tulipes-i18n/SKILL.md +98 -0
- package/templates/claude/skills/tulipes-model/SKILL.md +107 -0
- package/templates/claude/skills/tulipes-module/SKILL.md +67 -0
- package/templates/claude/skills/tulipes-permissions/SKILL.md +105 -0
- package/templates/claude/skills/tulipes-queue/SKILL.md +68 -0
- package/templates/claude/skills/tulipes-response/SKILL.md +118 -0
- package/templates/claude/skills/tulipes-settings/SKILL.md +111 -0
- package/templates/claude/skills/tulipes-socket/SKILL.md +60 -0
- package/templates/claude/skills/tulipes-spec/SKILL.md +101 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tulipes-spec
|
|
3
|
+
description: Use when validating request input in a Tulipes app, adding a zod schema to a route, generating OpenAPI or a Postman collection, or grouping endpoints in the generated documents. Covers rai() schemas, the folder property and `tulipes spec`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schemas, validation and generated specs
|
|
7
|
+
|
|
8
|
+
A route's schemas are declared once, in its `rai()` call, and used twice: the
|
|
9
|
+
framework validates the request against them, and `tulipes spec` describes
|
|
10
|
+
them. That is the whole point — documentation cannot claim a shape the
|
|
11
|
+
endpoint would reject.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { z } from "zod/v4"; // v4, not the classic import
|
|
15
|
+
|
|
16
|
+
router.post("/users", rai({
|
|
17
|
+
id: "users:create",
|
|
18
|
+
name: "Create a user",
|
|
19
|
+
body: z.object({ email: z.email("emailInvalid") }),
|
|
20
|
+
returns: z.object({ id: z.string(), email: z.string() }),
|
|
21
|
+
}), users(value => value.create())); // users is a runtime() binder
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`params`, `query` and `body` are validated. `returns` never is — a handler
|
|
26
|
+
is trusted with its own output — it exists so the document can describe the
|
|
27
|
+
response.
|
|
28
|
+
|
|
29
|
+
**Use `zod/v4`.** `z.toJSONSchema` is what the generator calls, and it only
|
|
30
|
+
understands v4 schemas. The classic `from "zod"` import still validates but
|
|
31
|
+
produces no shape in the document, and `tulipes spec` will say so.
|
|
32
|
+
|
|
33
|
+
Keep schemas in `modules/<name>/schemas.ts` when a route file starts to
|
|
34
|
+
sprawl; both the example's users and auth modules do.
|
|
35
|
+
|
|
36
|
+
## What validation gives you
|
|
37
|
+
|
|
38
|
+
Failures become the envelope's `errors[]`, with no handler code:
|
|
39
|
+
|
|
40
|
+
```jsonc
|
|
41
|
+
{ "success": false, "data": null,
|
|
42
|
+
"errors": [{ "field": "email", "message": "Must be a valid email address",
|
|
43
|
+
"code": "INVALID_FORMAT" }],
|
|
44
|
+
"meta": {} }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- **The zod message doubles as an i18n key.** `z.email("emailInvalid")`
|
|
48
|
+
resolves in this module's namespace, so a rejected field speaks the
|
|
49
|
+
caller's language. A plain sentence passes through unchanged.
|
|
50
|
+
- `code` comes from zod's issue code, uppercased — `INVALID_FORMAT`,
|
|
51
|
+
`TOO_SMALL`. Clients branch on it; it is never translated.
|
|
52
|
+
- All three segments are checked before answering, so a request wrong in
|
|
53
|
+
the query *and* the body says so once.
|
|
54
|
+
- Coerced values are written back: `z.coerce.number()` means `req.query.page`
|
|
55
|
+
is a number by the time the handler reads it.
|
|
56
|
+
- **Validation runs after the access check.** A caller who may not reach a
|
|
57
|
+
route never learns which of its fields are wrong.
|
|
58
|
+
|
|
59
|
+
## Grouping: `folder`
|
|
60
|
+
|
|
61
|
+
`folder` decides the Postman folder and the OpenAPI tag. It defaults to the
|
|
62
|
+
declaring module, so grouping is right without saying anything — set it to
|
|
63
|
+
split one module's routes apart, or to gather several modules under one
|
|
64
|
+
heading:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
rai({ id: "auth:sessions", name: "List sessions", folder: "sessions" })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Generating
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
yarn spec # tulipes spec --offline --openapi docs/openapi.json --postman docs/collection.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Offline generation builds native router factories wrapped in defineRoutes().
|
|
77
|
+
Every module opts in with tulipes.offline: true; no extra declaration file is
|
|
78
|
+
required. Keep schema imports pure and acquire services/controllers through
|
|
79
|
+
runtime(). Use --runtime for factories requiring live configuration.
|
|
80
|
+
--url sets the advertised base URL. Offline defaults to http://localhost:3000
|
|
81
|
+
and package.json title/version; runtime uses PORT/PUBLIC_DOMAIN and config.app.
|
|
82
|
+
Install custom access checkers inside defineRoutes using routes.setAccessChecker;
|
|
83
|
+
offline tooling sees the checker and makes no static role/public-access claim.
|
|
84
|
+
|
|
85
|
+
The generator lives in `@tulipes/spec`, a dev dependency. Core does not
|
|
86
|
+
depend on it, so an app that never generates a spec never installs it.
|
|
87
|
+
|
|
88
|
+
The Postman collection carries what an OpenAPI import cannot: one folder
|
|
89
|
+
per `folder`, `{{baseUrl}}` and `{{accessToken}}` variables, `noauth` on
|
|
90
|
+
public routes, and a script on the login request that captures the token
|
|
91
|
+
pair — sign in once and the rest of the collection is authenticated.
|
|
92
|
+
|
|
93
|
+
## Rules
|
|
94
|
+
|
|
95
|
+
- **Commit the generated documents.** They are artifacts a reviewer can
|
|
96
|
+
diff; a spec that only exists on someone's laptop is not documentation.
|
|
97
|
+
- Regenerate after changing any route, and check the diff — an unexpected
|
|
98
|
+
change there usually means an unexpected change to the API.
|
|
99
|
+
- A schema is not a substitute for authorization. `rai({ id })` gates the
|
|
100
|
+
route; the schema only shapes the input.
|
|
101
|
+
- Never hand-edit the generated files.
|