caspian-utils 0.1.1 → 0.1.3
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/dist/docs/ai-validation-checklist.md +4 -1
- package/dist/docs/commands.md +6 -3
- package/dist/docs/core-runtime-map.md +5 -2
- package/dist/docs/database.md +11 -7
- package/dist/docs/fetch-data.md +10 -2
- package/dist/docs/file-conventions.md +15 -10
- package/dist/docs/index.md +21 -15
- package/dist/docs/project-structure.md +18 -12
- package/dist/docs/pulsepoint.md +83 -0
- package/dist/docs/routing.md +9 -5
- package/dist/docs/websockets.md +165 -0
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ Use prompts like these to check whether AI lands on the correct docs and files.
|
|
|
56
56
|
| Make a grouped shell keep sidebar scroll while resetting page content on child-route navigation. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [core-runtime-map.md](./core-runtime-map.md) | `src/app/**/layout.html`, `public/js/pp-reactive-v2.js`, `main.py` | `pp-reset-scroll` placement, push-vs-history scroll behavior, and shared-shell ownership |
|
|
57
57
|
| Create a new contact page with interactive form behavior. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `src/app/**/index.html`, `main.py`, `.venv/Lib/site-packages/casp/components_compiler.py`, `.venv/Lib/site-packages/casp/scripts_type.py` | single-root template shape, script-inside-root authoring, and authored-vs-runtime boundaries |
|
|
58
58
|
| Add a button, filter, or form interaction to a route template. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) | `src/app/**/index.html`, `public/js/pp-reactive-v2.js`, `main.py` | PulsePoint `on*` event attributes, `pp.state`, directives, `Object.fromEntries(new FormData(...).entries())` for simple form submits, and avoiding id-driven `querySelector` or `addEventListener` wiring |
|
|
59
|
-
| Add a file manager page with upload progress and persisted metadata. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md), [database.md](./database.md) | `src/app/**/index.html`, `src/app/**/index.py`, `src/lib/**`, `prisma/schema.prisma` when Prisma applies | route-owned upload actions, persisted metadata flow, and Prisma
|
|
59
|
+
| Add a file manager page with upload progress and persisted metadata. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md), [database.md](./database.md) | `src/app/**/index.html`, `src/app/**/index.py`, `src/lib/**`, `prisma/schema.prisma` when Prisma applies | route-owned upload actions, persisted metadata flow, and mandatory Prisma Python ORM use for database reads and writes when Prisma is enabled |
|
|
60
60
|
| Explain why authored Caspian templates use a plain `<script>` instead of `type="text/pp"`. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [routing.md](./routing.md) | `main.py`, `.venv/Lib/site-packages/casp/scripts_type.py`, `.venv/Lib/site-packages/casp/components_compiler.py` | script rewriting, `pp-component` injection, and authored-vs-runtime boundaries |
|
|
61
61
|
| Debug why `StateManager` does not persist across a full redirect. | [index.md](./index.md), [state.md](./state.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/state_manager.py` | wire vs non-wire reset behavior and `request.state.session` dependency |
|
|
62
62
|
| Trace the auth cookie and CSRF cookie names used in development. | [index.md](./index.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/auth.py` | development cookie scoping and CSRF or session naming ownership |
|
|
@@ -64,6 +64,7 @@ Use prompts like these to check whether AI lands on the correct docs and files.
|
|
|
64
64
|
| Decide whether a dashboard child route should use page caching. | [index.md](./index.md), [cache.md](./cache.md), [routing.md](./routing.md), [fetch-data.md](./fetch-data.md) | `main.py`, `.venv/Lib/site-packages/casp/cache_handler.py`, the route's `index.py` | route-level cache ownership, cacheable HTML scope, and invalidation points |
|
|
65
65
|
| Add a PulsePoint context provider to a reusable component. | [index.md](./index.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `ts/TemplateCompiler.ts`, `public/js/pp-reactive-v2.js`, component `.html`, `.venv/Lib/site-packages/casp/components_compiler.py` | lowercase HTML-first `*.provider` usage, logical ancestry, single-root template shape |
|
|
66
66
|
| Add upload progress to an existing file manager. | [index.md](./index.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md) | `src/app/**/index.py`, `src/app/**/index.html`, `src/lib/**`, `public/js/pp-reactive-v2.js` | `pp.rpc` upload options, state replacement, persisted metadata, BrowserSync ignore rules |
|
|
67
|
+
| Add or debug a live WebSocket channel. | [index.md](./index.md), [websockets.md](./websockets.md), [core-runtime-map.md](./core-runtime-map.md), [pulsepoint.md](./pulsepoint.md), [auth.md](./auth.md) | `caspian.config.json`, `main.py`, `src/lib/websocket/**`, `src/app/**`, `settings/bs-config.json` | `websocket` feature gate, endpoint registration, origin checks, auth/session handling, native `WebSocket` client lifecycle, and BrowserSync URL selection |
|
|
67
68
|
| Decide whether MCP files should be created. | [index.md](./index.md), [mcp.md](./mcp.md), [commands.md](./commands.md), [project-structure.md](./project-structure.md) | `caspian.config.json`, `settings/restart-mcp.ts`, `package.json`, `src/lib/mcp/**` when enabled | `mcp` feature gate, update workflow, nested FastMCP config ownership |
|
|
68
69
|
|
|
69
70
|
Treat the table as a prompt pack for spot checks, not as a full validation matrix.
|
|
@@ -76,6 +77,8 @@ Treat the table as a prompt pack for spot checks, not as a full validation matri
|
|
|
76
77
|
- AI treats runtime HTML examples as authored template examples.
|
|
77
78
|
- AI generates a route or component template with a valid-looking root element but leaves a sibling top-level `<script>` or second top-level element after it.
|
|
78
79
|
- AI starts first-party interactivity with ids, `data-*` attributes, `querySelector`, `addEventListener`, manual `fetch`, manual `innerHTML`, or per-input `pp-ref` payload collection instead of PulsePoint `on*` attributes, form submit events, state, directives, and `pp.rpc()`.
|
|
80
|
+
- AI bypasses the generated Prisma Python ORM in a Prisma-enabled project with direct database drivers, custom fetch helpers, JSON active stores, browser-side database fetches, or a second app-owned database abstraction.
|
|
81
|
+
- AI extracts one-route backend logic into `src/lib/**` even though the logic is not shared; route-specific first-render queries, RPC actions, validation, and response shaping belong in the owning `src/app/**/index.py`.
|
|
79
82
|
- AI puts `pp-reset-scroll="true"` on the whole shell or `body` when only the page-content pane should reset, causing persistent sidebars or rails to lose their scroll position.
|
|
80
83
|
- AI decides behavior from memory without checking the owning implementation details.
|
|
81
84
|
|
package/dist/docs/commands.md
CHANGED
|
@@ -179,9 +179,12 @@ In skip-prompt mode, the default feature values are:
|
|
|
179
179
|
|
|
180
180
|
- `backendOnly: false`
|
|
181
181
|
- `tailwindcss: false`
|
|
182
|
-
- `typescript: false`
|
|
183
|
-
- `mcp: false`
|
|
184
|
-
- `prisma: false`
|
|
182
|
+
- `typescript: false`
|
|
183
|
+
- `mcp: false`
|
|
184
|
+
- `prisma: false`
|
|
185
|
+
- `websocket: false`
|
|
186
|
+
|
|
187
|
+
If a project needs WebSockets after creation, set `websocket: true` in `caspian.config.json`, then run the project update workflow so framework-managed files and any scaffolded socket surfaces stay aligned before assuming `@app.websocket(...)` routes or `src/lib/websocket/**` exist.
|
|
185
188
|
|
|
186
189
|
### 4.2 Backend-only combinations
|
|
187
190
|
|
|
@@ -7,8 +7,9 @@ related:
|
|
|
7
7
|
links:
|
|
8
8
|
- /docs/project-structure
|
|
9
9
|
- /docs/routing
|
|
10
|
-
- /docs/auth
|
|
10
|
+
- /docs/auth
|
|
11
11
|
- /docs/fetch-data
|
|
12
|
+
- /docs/websockets
|
|
12
13
|
- /docs/pulsepoint
|
|
13
14
|
- /docs/pulsepoint-runtime-map
|
|
14
15
|
- /docs/index
|
|
@@ -59,6 +60,7 @@ Use this table when the task names a framework feature but the owning file is no
|
|
|
59
60
|
| Baseline response headers and safe public-file serving | `main.py` imports from `casp.runtime_security` | `.venv/Lib/site-packages/casp/runtime_security.py` | [auth.md](./auth.md), [project-structure.md](./project-structure.md) |
|
|
60
61
|
| RPC and server actions | route or component Python modules with `@rpc()` | `casp.rpc`, `main.py` middleware | [fetch-data.md](./fetch-data.md), [file-uploads.md](./file-uploads.md) |
|
|
61
62
|
| Streaming | route `page()` generators, RPC generators | `casp.streaming`, `casp.rpc`, `main.py` | [fetch-data.md](./fetch-data.md) |
|
|
63
|
+
| WebSockets | `main.py` `@app.websocket(...)` endpoints when `caspian.config.json` has `websocket: true`, `src/lib/**` socket helpers, route-owned browser clients in `src/app/**` | FastAPI app-owned ASGI endpoints in `main.py` | [websockets.md](./websockets.md), [auth.md](./auth.md), [pulsepoint.md](./pulsepoint.md) |
|
|
62
64
|
| Server state | request handlers and RPC actions | `casp.state_manager`, `main.py` middleware | [state.md](./state.md) |
|
|
63
65
|
| Page caching | route-level `Cache(...)` declarations | `casp.cache_handler`, `main.py` cache check/save | [cache.md](./cache.md) |
|
|
64
66
|
| Validation | route and RPC input boundaries | `casp.validate` | [validation.md](./validation.md) |
|
|
@@ -111,7 +113,8 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
111
113
|
| Runtime area | Verify these behaviors |
|
|
112
114
|
| --- | --- |
|
|
113
115
|
| `main.py` routing and request flow | route registration, path and query injection, static asset handling, session-middleware wiring, response-header middleware, and exception rendering |
|
|
114
|
-
| `
|
|
116
|
+
| `main.py` WebSockets | `cfg.websocket` route-registration gate, `@app.websocket(...)` paths, origin checks before `accept()`, auth/session extraction, message-size and idle-timeout handling, close codes, and connection cleanup |
|
|
117
|
+
| `public/js/pp-reactive-v2.js` browser runtime | SPA interception, history-vs-push scroll behavior, `pp-reset-scroll` semantics, and browser-side `pp.rpc()` behavior |
|
|
115
118
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
|
116
119
|
| `casp.rpc` and streamed RPC responses | middleware interception, CSRF and session expectations, registry behavior, and helper-level RPC contracts |
|
|
117
120
|
| `casp.layout` | layout discovery, metadata merge, root handling, and layout rendering rules |
|
package/dist/docs/database.md
CHANGED
|
@@ -14,7 +14,9 @@ related:
|
|
|
14
14
|
|
|
15
15
|
This page documents the Prisma workflow for Caspian projects where `caspian.config.json` enables Prisma.
|
|
16
16
|
|
|
17
|
-
When a project enables Prisma, use `prisma/schema.prisma` for schema management, `prisma.config.ts` for Prisma config and seed wiring, and reuse an app-owned `src/lib/prisma/` package when the project includes one.
|
|
17
|
+
When a project enables Prisma, use `prisma/schema.prisma` for schema management, `prisma.config.ts` for Prisma config and seed wiring, and reuse an app-owned `src/lib/prisma/` package when the project includes one.
|
|
18
|
+
|
|
19
|
+
High-priority rule: when `caspian.config.json` has `prisma: true`, Python-side database access must use the generated Prisma Python ORM. Route `page()` functions, route-owned `@rpc()` actions, auth handlers, upload flows, and shared helpers should import the generated client from `src/lib/prisma/**` instead of inventing direct driver calls, hand-written SQL wrappers, JSON manifests as active stores, app-specific HTTP fetches, or browser-side database fetches. Use raw SQL only through Prisma as a narrow fallback when the generated ORM cannot express a query clearly.
|
|
18
20
|
|
|
19
21
|
Treat `caspian.config.json` as the single source of truth for whether Prisma is enabled in a workspace. If `prisma` is false and the user wants Prisma, ask first, then update `caspian.config.json` and run `npx casp update project` before assuming Prisma-managed files exist.
|
|
20
22
|
|
|
@@ -29,7 +31,7 @@ The standard Prisma flow in Caspian is:
|
|
|
29
31
|
5. Run `npx ppy generate` so the Python ORM classes stay aligned with the updated schema.
|
|
30
32
|
6. Reuse the shared Python database layer in `src/lib/prisma/` when Python route or RPC code needs database access, and never hand-edit generated ORM classes.
|
|
31
33
|
|
|
32
|
-
Use this workflow instead of writing raw SQL first. Drop to raw SQL only when a query cannot be expressed clearly with the generated client.
|
|
34
|
+
Use this workflow instead of writing raw SQL first. Drop to raw SQL only through Prisma when a query cannot be expressed clearly with the generated client.
|
|
33
35
|
|
|
34
36
|
## Environment Setup
|
|
35
37
|
|
|
@@ -157,7 +159,7 @@ If Python route or RPC code needs database access, import from `src.lib.prisma`
|
|
|
157
159
|
|
|
158
160
|
## Python Route Usage
|
|
159
161
|
|
|
160
|
-
If the project ships an app-owned Python Prisma-style layer under `src/lib/prisma/`, treat it as async-first and reuse it from route and RPC code.
|
|
162
|
+
If the project ships an app-owned Python Prisma-style layer under `src/lib/prisma/`, treat it as async-first and reuse it from route and RPC code. In a Prisma-enabled project, this is the required data-access path for Python code.
|
|
161
163
|
|
|
162
164
|
Example:
|
|
163
165
|
|
|
@@ -178,7 +180,7 @@ Prisma calls fit naturally in:
|
|
|
178
180
|
- `async def page()` for first-render data
|
|
179
181
|
- `@rpc()` actions for browser-triggered reads and writes
|
|
180
182
|
|
|
181
|
-
Keep route-specific Prisma I/O in `page()` or `@rpc()` actions. The installed layout engine supports synchronous and async `layout()` results, but layout work should stay focused on shared subtree props or metadata.
|
|
183
|
+
Keep route-specific Prisma I/O in that route's `page()` or `@rpc()` actions in `src/app/**/index.py`. The installed layout engine supports synchronous and async `layout()` results, but layout work should stay focused on shared subtree props or metadata. Move Prisma helpers into `src/lib/**` only when the same behavior is reused by multiple routes, features, or integrations.
|
|
182
184
|
|
|
183
185
|
See `fetch-data.md` for the recommended route-render versus RPC split.
|
|
184
186
|
|
|
@@ -325,12 +327,13 @@ users = await prisma.query_raw(
|
|
|
325
327
|
)
|
|
326
328
|
```
|
|
327
329
|
|
|
328
|
-
Use raw SQL sparingly. Prefer the generated Prisma API when the query can be expressed clearly there.
|
|
330
|
+
Use raw SQL sparingly and only through Prisma. Prefer the generated Prisma API when the query can be expressed clearly there.
|
|
329
331
|
|
|
330
332
|
## Recommended Project Rules
|
|
331
333
|
|
|
332
334
|
- Keep the schema in `prisma/schema.prisma` and follow the required regeneration order after changes: `npx prisma migrate dev`, optional seed flow, then `npx ppy generate`.
|
|
333
|
-
- Reuse `src/lib/prisma/` for Python-side database access instead of creating a second bridge.
|
|
335
|
+
- Reuse `src/lib/prisma/` for Python-side database access instead of creating a second bridge.
|
|
336
|
+
- When Prisma is enabled, do not bypass the Prisma Python ORM with direct database drivers, custom fetch functions, JSON files as active stores, or browser-side database access.
|
|
334
337
|
- Never hand-edit generated Prisma or Python ORM classes.
|
|
335
338
|
- Keep reusable database helpers in `src/lib/`, and keep route-specific orchestration in `src/app/`.
|
|
336
339
|
- Use `await` with Prisma operations.
|
|
@@ -349,7 +352,8 @@ If an AI agent is working on a Caspian app with Prisma enabled, apply these rule
|
|
|
349
352
|
- Run `npx ppy generate` after schema changes to refresh the Python ORM classes.
|
|
350
353
|
- Never hand-edit generated Prisma or Python ORM classes.
|
|
351
354
|
- Read `prisma.config.ts` and `prisma/seed.ts` when you need the current project's Prisma tooling examples.
|
|
352
|
-
- Reuse the existing `src/lib/prisma/` package when the Python app needs database access.
|
|
355
|
+
- Reuse the existing `src/lib/prisma/` package when the Python app needs database access.
|
|
356
|
+
- Treat use of the Prisma Python ORM as mandatory for Python-side database reads and writes in Prisma-enabled projects.
|
|
353
357
|
- For file managers and uploads, persist metadata in Prisma and keep blob storage separate. See [file-uploads.md](./file-uploads.md).
|
|
354
358
|
- Put reusable database helpers in `src/lib/`; keep route and RPC orchestration in `src/app/`.
|
|
355
359
|
- Use `async def page()` for route-specific first-render reads. Use `layout()` only for shared subtree props or metadata, and use `@rpc()` plus `pp.rpc()` for browser-triggered reads and writes.
|
package/dist/docs/fetch-data.md
CHANGED
|
@@ -23,6 +23,10 @@ This page explains how data fetching works in Caspian. Use route functions for i
|
|
|
23
23
|
|
|
24
24
|
Treat RPC as the default way for browser code to talk to Python in Caspian. For CRUD operations and any browser-initiated backend reads after first render, default to `@rpc()` on the server and `pp.rpc()` in PulsePoint code. Do not reach for ad hoc fetch calls to custom JSON endpoints, alternate transport layers, or older helper names unless the task explicitly requires that shape.
|
|
25
25
|
|
|
26
|
+
WebSockets are the main exception for long-lived bidirectional live channels when `caspian.config.json` has `websocket: true`. If the task needs a native browser `WebSocket`, persistent broadcast channel, presence, live chat, or socket origin/auth handling, confirm that flag and read [websockets.md](./websockets.md) before adding endpoint or client code. Do not use WebSockets as a replacement for ordinary RPC form submits or CRUD work.
|
|
27
|
+
|
|
28
|
+
When `caspian.config.json` has `prisma: true`, the Python side of those route and RPC actions must use the generated Prisma Python ORM from `src/lib/prisma/**` for database reads and writes. The browser asks Python through `pp.rpc(...)`; Python asks the database through Prisma. Do not replace either side with a custom browser fetch, direct database driver, hand-written SQL helper, JSON active store, or second app-owned data-fetch abstraction.
|
|
29
|
+
|
|
26
30
|
Browser-triggered data work should still be PulsePoint-first at the event layer. Bind the initiating click, submit, input, upload, refresh, filter, or pagination control in authored HTML with `onclick`, `onsubmit`, `oninput`, `onchange`, or another native `on*` attribute handled by PulsePoint. For normal form submissions, read named controls with `Object.fromEntries(new FormData(event.currentTarget).entries())` in the submit handler and pass that object directly to `pp.rpc(...)`; the route's Python `@rpc()` action should validate, normalize, and decide what to persist. Do not set up first-party data actions by assigning ids and then wiring `querySelector(...)`, `addEventListener(...)`, manual `fetch(...)`, manual DOM repainting, or per-input `pp-ref` payload collection.
|
|
27
31
|
|
|
28
32
|
MCP is a separate integration surface. Do not place app-owned FastMCP tools in route `index.py` files or treat `@rpc()` actions as a replacement for MCP tools. Use `mcp.md` and `src/lib/mcp/` only when `caspian.config.json` has `mcp: true`. If `mcp` is false, do not assume those files exist.
|
|
@@ -49,6 +53,7 @@ When a page belongs to a grouped subtree such as a dashboard, account area, admi
|
|
|
49
53
|
- Use `page()` for route-level data required before HTML renders, and use `layout()` only for shared subtree props or metadata.
|
|
50
54
|
- When a route renders UI and also needs backend work, keep the HTML in the sibling `index.html`; `index.py` should prepare data and call `render_page(__file__, ...)`, not inline the route markup.
|
|
51
55
|
- Use `@rpc()` on the server and `pp.rpc()` in PulsePoint code for all browser-triggered data work after first render, including CRUD operations and follow-up reads.
|
|
56
|
+
- When Prisma is enabled, route all Python database I/O inside `page()`, `layout()` when truly shared, `@rpc()` actions, and reusable helpers through the generated Prisma Python ORM.
|
|
52
57
|
- Trigger those browser actions through PulsePoint event attributes in the HTML, not through a separate DOM listener layer.
|
|
53
58
|
- For simple form submits, prefer `onsubmit="{submitForm(event)}"` plus `Object.fromEntries(new FormData(event.currentTarget).entries())` over `pp-ref` fields and effect-managed submit listeners.
|
|
54
59
|
- Keep custom REST or other endpoint patterns as explicit exceptions, not the baseline Caspian approach.
|
|
@@ -86,7 +91,7 @@ Notes:
|
|
|
86
91
|
- Use a normal parent folder such as `dashboard/` when the section name should appear in the URL. Use a route-group folder such as `(reports)/` only when the shared wrapper should organize child routes without adding a URL segment.
|
|
87
92
|
- Keep reusable database or API clients under `src/lib/`; keep route-specific orchestration in `src/app/`.
|
|
88
93
|
|
|
89
|
-
If the data source is Prisma, confirm `caspian.config.json` has `prisma: true`, then see `database.md` for the project's schema, migration, and generation workflow. If the project includes an app-owned Python database layer under `src/lib/prisma/`, reuse it instead of creating another helper.
|
|
94
|
+
If the data source is Prisma, confirm `caspian.config.json` has `prisma: true`, then see `database.md` for the project's schema, migration, and generation workflow. If the project includes an app-owned Python database layer under `src/lib/prisma/`, reuse it instead of creating another helper. In Prisma-enabled projects, direct driver access and custom fetch helpers are exceptions to avoid, not alternate defaults.
|
|
90
95
|
|
|
91
96
|
## Interactive Data With RPC
|
|
92
97
|
|
|
@@ -135,6 +140,8 @@ Call it from the client with `pp.rpc()`:
|
|
|
135
140
|
</script>
|
|
136
141
|
```
|
|
137
142
|
|
|
143
|
+
Do not call `fetch("/api/todos")` from the browser for normal Caspian CRUD when the route can expose an `@rpc()` action. Do not use `fetch()` or a database driver in Python to work around Prisma when `src/lib/prisma/**` already provides the generated ORM client.
|
|
144
|
+
|
|
138
145
|
For form submissions, let the form event provide the submitted values. The inputs' `name` attributes define the RPC payload keys, and the Python action owns trimming, validation, coercion, and persistence decisions.
|
|
139
146
|
|
|
140
147
|
```html
|
|
@@ -179,7 +186,8 @@ Important:
|
|
|
179
186
|
|
|
180
187
|
- `pp.rpc()` posts to the current route RPC bridge.
|
|
181
188
|
- Older docs may refer to `pp.fetchFunction()`. In this repo's current runtime, the supported helper is `pp.rpc()`.
|
|
182
|
-
- Use [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) when debugging browser-side RPC options such as streaming, upload progress, abort behavior, redirects, or SPA interaction.
|
|
189
|
+
- Use [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) when debugging browser-side RPC options such as streaming, upload progress, abort behavior, redirects, or SPA interaction.
|
|
190
|
+
- Use [websockets.md](./websockets.md) when `caspian.config.json` has `websocket: true` and the browser and server need a persistent bidirectional channel instead of request/response RPC or one-way SSE streaming.
|
|
183
191
|
|
|
184
192
|
## Streaming Responses
|
|
185
193
|
|
|
@@ -83,7 +83,7 @@ Use `index.html` by itself when the route is UI-only.
|
|
|
83
83
|
|
|
84
84
|
## `index.py`
|
|
85
85
|
|
|
86
|
-
`index.py` is the backend companion for a route.
|
|
86
|
+
`index.py` is the backend companion for a route.
|
|
87
87
|
|
|
88
88
|
Add it when the route needs:
|
|
89
89
|
|
|
@@ -92,9 +92,13 @@ Add it when the route needs:
|
|
|
92
92
|
- auth checks or redirects
|
|
93
93
|
- route-level `Cache(...)`
|
|
94
94
|
- route-owned `@rpc()` actions
|
|
95
|
-
- server-side context before rendering the sibling template
|
|
96
|
-
|
|
97
|
-
For UI routes, `index.py` does not replace `index.html`. It prepares data and calls `render_page(__file__, ...)` so Caspian renders the sibling template.
|
|
95
|
+
- server-side context before rendering the sibling template
|
|
96
|
+
|
|
97
|
+
For UI routes, `index.py` does not replace `index.html`. It prepares data and calls `render_page(__file__, ...)` so Caspian renders the sibling template.
|
|
98
|
+
|
|
99
|
+
Keep logic here when it belongs only to this route. That includes route-specific first-render queries, `@rpc()` actions, auth checks, redirects, filters, validation, upload orchestration, and response shaping. Move logic to `src/lib/**` only when it is shared across routes, components, integrations, or features. Do not extract one-route logic into a library just because it is written in Python.
|
|
100
|
+
|
|
101
|
+
When `caspian.config.json` has `prisma: true`, route-specific database access in `index.py` should use the generated Prisma Python ORM from `src/lib/prisma/**`. Shared database helpers may live in `src/lib/**`, but they should still call the generated Prisma Python ORM rather than a separate fetch or driver layer.
|
|
98
102
|
|
|
99
103
|
Example:
|
|
100
104
|
|
|
@@ -251,11 +255,12 @@ If rendering `error.html` fails, the runtime falls back to a minimal plain HTML
|
|
|
251
255
|
Use this order when deciding where a concern belongs:
|
|
252
256
|
|
|
253
257
|
1. Put visible route markup in `index.html`.
|
|
254
|
-
2. Add `index.py` only when the same route needs backend work.
|
|
255
|
-
3.
|
|
256
|
-
4.
|
|
257
|
-
5. Add `
|
|
258
|
-
6.
|
|
259
|
-
7. Use root `
|
|
258
|
+
2. Add `index.py` only when the same route needs backend work.
|
|
259
|
+
3. Keep route-specific logic in `index.py`; move logic into `src/lib/**` only when it is actually shared.
|
|
260
|
+
4. Put shared subtree wrapper markup in `layout.html`.
|
|
261
|
+
5. Add `layout.py` only when that shared shell needs synchronous Python props or metadata.
|
|
262
|
+
6. Add `loading.html` when SPA navigation needs an immediate scoped loading state.
|
|
263
|
+
7. Use root `not-found.html` for unmatched URLs.
|
|
264
|
+
8. Use root `error.html` for unhandled exceptions.
|
|
260
265
|
|
|
261
266
|
Use [routing.md](./routing.md) for the broader file-based routing model, [metadata.md](./metadata.md) for page and layout metadata, [cache.md](./cache.md) for route caching in `index.py`, and [pulsepoint.md](./pulsepoint.md) for the authored template contract and browser runtime behavior.
|
package/dist/docs/index.md
CHANGED
|
@@ -11,8 +11,9 @@ related:
|
|
|
11
11
|
- /docs/core-runtime-map
|
|
12
12
|
- /docs/pulsepoint-runtime-map
|
|
13
13
|
- /docs/mcp
|
|
14
|
-
- /docs/file-uploads
|
|
15
|
-
- /docs/
|
|
14
|
+
- /docs/file-uploads
|
|
15
|
+
- /docs/websockets
|
|
16
|
+
- /docs/file-conventions
|
|
16
17
|
- /docs/project-structure
|
|
17
18
|
- /docs/components
|
|
18
19
|
---
|
|
@@ -23,7 +24,7 @@ Treat these docs as reusable Caspian feature guidance. Treat `./caspian.config.j
|
|
|
23
24
|
|
|
24
25
|
The docs can mention optional features even when those features are disabled in a project. Their job is to explain how a feature works, when a doc applies, and which files to inspect next once the feature is confirmed as relevant.
|
|
25
26
|
|
|
26
|
-
Before making feature, tooling, or file-placement decisions in a Caspian project, read `./caspian.config.json` almost immediately. That file tells you which optional capabilities are enabled, such as Prisma, MCP, TypeScript, Tailwind, backend-only mode, and component scan directories.
|
|
27
|
+
Before making feature, tooling, or file-placement decisions in a Caspian project, read `./caspian.config.json` almost immediately. That file tells you which optional capabilities are enabled, such as Prisma, MCP, WebSockets, TypeScript, Tailwind, backend-only mode, and component scan directories.
|
|
27
28
|
|
|
28
29
|
## Default Stack
|
|
29
30
|
|
|
@@ -33,8 +34,10 @@ When generating or editing a Caspian app, treat these as the default choices unl
|
|
|
33
34
|
- For first-party HTML events and reactivity, use PulsePoint `on*` attributes, state, refs, effects, directives, and `pp.rpc()` instead of ordinary DOM wiring with ids, `data-*` state, `querySelector`, `addEventListener`, or manual `innerHTML`.
|
|
34
35
|
- Treat every authored route, layout, and component HTML file like a React component return value: exactly one top-level parent HTML element or one imported `x-*` root, with any owned plain `<script>` kept inside that same root.
|
|
35
36
|
- When `caspian.config.json` has `tailwindcss: true`, use Python `merge_classes(...)` plus browser `twMerge(...)` as the only supported Tailwind class-merging path.
|
|
36
|
-
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
37
|
-
-
|
|
37
|
+
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
38
|
+
- When `caspian.config.json` has `websocket: true`, use app-owned FastAPI WebSocket endpoints only for long-lived bidirectional live channels; keep normal browser-triggered data work on RPC.
|
|
39
|
+
- When `caspian.config.json` has `prisma: true`, use the generated Prisma Python ORM from `src/lib/prisma/**` for Python-side database reads and writes. Do not invent a second fetch layer, raw driver wrapper, JSON active store, or browser-side data path that bypasses Prisma.
|
|
40
|
+
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
38
41
|
|
|
39
42
|
## AI Doc Shape
|
|
40
43
|
|
|
@@ -82,8 +85,9 @@ The packaged Caspian docs referenced by this index live here:
|
|
|
82
85
|
- `file-conventions.md` - quick decision guide for `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`, plus the owning runtime files to verify
|
|
83
86
|
- `components.md` - Create reusable Python components, template-backed UI, HTML-first `x-*` component tags, the single-parent authored-root rule for component HTML files, and the Python-side `merge_classes(...)` contract when Tailwind CSS is enabled
|
|
84
87
|
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, first-party `on*` events, state, effects, directives, SPA navigation scroll restoration, `pp-reset-scroll`, and direct browser `twMerge(...)` usage when Tailwind CSS is enabled
|
|
85
|
-
- `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
|
|
86
|
-
- `
|
|
88
|
+
- `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
|
|
89
|
+
- `websockets.md` - WebSocket feature-gate guidance for projects where `caspian.config.json` has `websocket: true`, app-owned FastAPI endpoint placement, browser `WebSocket` clients inside PulsePoint templates, origin checks, auth/session handling, and when to choose sockets over RPC or SSE
|
|
90
|
+
- `file-uploads.md` - Route-local file uploads and file-manager flows with `@rpc()`, `pp.rpc()`, Prisma metadata, public asset storage, and BrowserSync ignore rules
|
|
87
91
|
- `state.md` - Request-scoped server state with `StateManager`, session-backed JSON persistence, and listener callbacks for transient flows
|
|
88
92
|
- `cache.md` - Route-level HTML caching with `Cache`, `CacheHandler`, TTL behavior, file-system storage, and invalidation patterns
|
|
89
93
|
- `validation.md` - Input validation and sanitization with `Validate`, `Rule`, direct field checks, and multi-rule workflows for routes and RPC actions
|
|
@@ -101,14 +105,16 @@ Preferred lookup order:
|
|
|
101
105
|
2. Read `./caspian.config.json` before making any feature assumption. A doc existing in the package does not mean that feature is enabled in the current project.
|
|
102
106
|
3. Before authoring or editing any `src/app/**` or component HTML template, apply the single-root invariant: one authored root only, any owned `<script>` inside that root, and no handwritten `pp-component` or `type="text/pp"`.
|
|
103
107
|
4. Before inventing browser JavaScript, check whether the interaction is first-party UI behavior. If it is, use PulsePoint `on*` attributes, `pp.state`, refs, effects, directives, and `pp.rpc()` rather than id-driven DOM scripting.
|
|
104
|
-
5.
|
|
105
|
-
6. If the task
|
|
106
|
-
7.
|
|
107
|
-
8.
|
|
108
|
-
9.
|
|
109
|
-
10.
|
|
110
|
-
11. Use `
|
|
111
|
-
12.
|
|
108
|
+
5. When Prisma is enabled, route all Python database work through the generated Prisma Python ORM. Use `database.md` before schema, migration, seed, or ORM decisions, and use `fetch-data.md` before wiring route data or RPC flows.
|
|
109
|
+
6. If the task asks for WebSockets, live bidirectional channels, socket origin checks, or native browser `WebSocket` clients, confirm `caspian.config.json` has `websocket: true`, read `websockets.md`, then inspect `main.py`, `src/lib/**`, the owning `src/app/**` route, and `settings/bs-config.json`. If `websocket` is false and the user wants it, ask first, then enable the config flag and follow the update workflow.
|
|
110
|
+
7. Treat `caspian.config.json` as the single source of truth for optional features. Use feature-specific docs only when the matching flag is enabled. If a feature is disabled and the user wants it, ask first, then update `caspian.config.json` and follow the update workflow in `commands.md`.
|
|
111
|
+
8. If the task touches `main.py` or `.venv/Lib/site-packages/casp/**`, read `core-runtime-map.md` to jump to the controlling runtime file and the matching feature doc.
|
|
112
|
+
9. If the task names a PulsePoint feature or directive, read `pulsepoint-runtime-map.md` for the fastest feature-to-runtime lookup, then read `pulsepoint.md` for authoring rules.
|
|
113
|
+
10. After the feature is confirmed, inspect the actual project files that decide behavior, such as `package.json`, `main.py`, `src/app/**`, `src/lib/**`, `settings/**`, `prisma/**`, and the installed `casp` runtime.
|
|
114
|
+
11. Use `file-conventions.md` for quick decisions about `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`; use `commands.md` for scaffold and update workflows, `project-structure.md` for placement decisions, and the feature docs such as `mcp.md`, `database.md`, `auth.md`, `fetch-data.md`, `websockets.md`, and `file-uploads.md` for task-specific guidance.
|
|
115
|
+
12. Prefer packaged Caspian docs before upstream documentation when generating code, commands, or migration guidance.
|
|
116
|
+
13. Use `ai-validation-checklist.md` when you want to verify that the docs lead AI to the correct files and behavior checkpoints.
|
|
117
|
+
14. Keep `index.md` and cross-links aligned so AI can quickly discover the right doc.
|
|
112
118
|
|
|
113
119
|
## Maintenance
|
|
114
120
|
|
|
@@ -31,7 +31,7 @@ In that layout, the default stack is Python components for reusable UI, PulsePoi
|
|
|
31
31
|
|
|
32
32
|
For public pages that can safely reuse rendered HTML, Caspian also supports route-level page caching through `casp.cache_handler`.
|
|
33
33
|
|
|
34
|
-
Before an AI agent decides which Caspian features are available in a workspace, it should read `./caspian.config.json` almost immediately. That file is the feature gate for project capabilities such as `backendOnly`, `tailwindcss`, `mcp`, `prisma`, `typescript`, and `componentScanDirs`.
|
|
34
|
+
Before an AI agent decides which Caspian features are available in a workspace, it should read `./caspian.config.json` almost immediately. That file is the feature gate for project capabilities such as `backendOnly`, `tailwindcss`, `mcp`, `prisma`, `typescript`, `websocket`, and `componentScanDirs`.
|
|
35
35
|
|
|
36
36
|
Treat `caspian.config.json` as the single source of truth for optional feature enablement. Use feature-specific files and docs only after the matching flag is confirmed as enabled. If a feature is disabled and the user wants it, ask first, then update `caspian.config.json` and follow the update workflow in `commands.md`.
|
|
37
37
|
|
|
@@ -40,9 +40,10 @@ Treat `caspian.config.json` as the single source of truth for optional feature e
|
|
|
40
40
|
- `src/` contains routes, page templates, styles, reusable components, and shared libraries.
|
|
41
41
|
- `src/components/` contains reusable application UI components and optional same-name HTML templates.
|
|
42
42
|
- `src/lib/` contains reusable non-UI code such as helpers, services, validators, adapters, and shared support modules.
|
|
43
|
-
- `src/lib/auth/auth_config.py` contains auth-specific configuration for the app.
|
|
44
|
-
- `src/lib/mcp/` contains the app-owned FastMCP server and nested FastMCP config when MCP is enabled.
|
|
45
|
-
- `
|
|
43
|
+
- `src/lib/auth/auth_config.py` contains auth-specific configuration for the app.
|
|
44
|
+
- `src/lib/mcp/` contains the app-owned FastMCP server and nested FastMCP config when MCP is enabled.
|
|
45
|
+
- `src/lib/websocket/` contains reusable socket helpers when WebSockets are enabled and the project includes shared session, auth, connection, or broadcast utilities.
|
|
46
|
+
- `prisma/` contains the Prisma schema and seed scripts.
|
|
46
47
|
- `public/` contains static assets served directly.
|
|
47
48
|
- `settings/` contains BrowserSync, build, restart, and generated-project helper files.
|
|
48
49
|
- `main.py` is the application entry point.
|
|
@@ -77,9 +78,11 @@ my-app/
|
|
|
77
78
|
lib/
|
|
78
79
|
auth/
|
|
79
80
|
auth_config.py
|
|
80
|
-
mcp/
|
|
81
|
-
fastmcp.json
|
|
82
|
-
mcp_server.py
|
|
81
|
+
mcp/
|
|
82
|
+
fastmcp.json
|
|
83
|
+
mcp_server.py
|
|
84
|
+
websocket/
|
|
85
|
+
websocket_security.py
|
|
83
86
|
prisma/
|
|
84
87
|
__init__.py
|
|
85
88
|
db.py
|
|
@@ -94,7 +97,7 @@ my-app/
|
|
|
94
97
|
docs/
|
|
95
98
|
```
|
|
96
99
|
|
|
97
|
-
Optional directories such as `src/lib/mcp/` appear only when the relevant feature flag is enabled in `caspian.config.json
|
|
100
|
+
Optional directories such as `src/lib/mcp/` and `src/lib/websocket/` appear only when the relevant feature flag is enabled in `caspian.config.json` and the project needs that app-owned surface.
|
|
98
101
|
|
|
99
102
|
## Directory Breakdown
|
|
100
103
|
|
|
@@ -104,9 +107,11 @@ This is the main application area. It contains route files, templates, styles, a
|
|
|
104
107
|
|
|
105
108
|
### `src/app/`
|
|
106
109
|
|
|
107
|
-
This directory handles file-based routing. Route templates and route-specific backend logic live here.
|
|
108
|
-
|
|
110
|
+
This directory handles file-based routing. Route templates and route-specific backend logic live here.
|
|
111
|
+
|
|
109
112
|
For any route that renders UI, keep that markup in `src/app/**/index.html`. If the route is UI-only, `index.html` alone is enough. Add `src/app/**/index.py` only as a companion when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side behavior. Keep shared wrappers in `layout.html` and use `layout.py` only for shared props or metadata. Use a lone `index.py` only for non-visual routes such as redirect-only or action-only handlers.
|
|
113
|
+
|
|
114
|
+
Keep backend logic in the owning route when it is route-specific. Move Python code into `src/lib/**` only when it is shared across routes, components, integrations, or features.
|
|
110
115
|
|
|
111
116
|
When a folder represents a section with child routes, such as `dashboard`, `account`, `settings`, or `docs`, create `layout.html` in that folder and let the child routes live beneath it. See [routing.md](./routing.md) for the canonical section layout pattern.
|
|
112
117
|
|
|
@@ -148,7 +153,7 @@ Do not add `src/lib/security/runtime_security.py` for normal app work. Runtime s
|
|
|
148
153
|
|
|
149
154
|
If your Python routes or RPC actions need reusable database access code, keep that helper layer under `src/lib/` and extend the existing `src/lib/prisma/` package.
|
|
150
155
|
|
|
151
|
-
In a Prisma-enabled Caspian project, schema and seed files typically live under `prisma/`, while the Python-side adapter lives in application-owned code under `src/lib/prisma/` when that layer exists.
|
|
156
|
+
In a Prisma-enabled Caspian project, schema and seed files typically live under `prisma/`, while the Python-side adapter lives in application-owned code under `src/lib/prisma/` when that layer exists. When `caspian.config.json` has `prisma: true`, Python-side database reads and writes should use that generated Prisma Python ORM instead of a custom fetch, raw driver, JSON active store, or second app-owned database abstraction.
|
|
152
157
|
|
|
153
158
|
### `src/lib/auth/`
|
|
154
159
|
|
|
@@ -323,7 +328,8 @@ If an AI agent is deciding where to make changes, use these rules first.
|
|
|
323
328
|
- Treat `package.json` scripts as opt-in operations. Do not run `npm run dev` or `npm run build` unless the user explicitly asks, the task genuinely requires that exact script, or deployment prep needs `npm run build`.
|
|
324
329
|
- Treat `__pycache__/` directories, `.pyc` files, `public/css/styles.css`, `settings/component-map.json`, and `settings/files-list.json` as generated artifacts when the local stack is intentionally running. They are not authored source files.
|
|
325
330
|
- Inspect `settings/component-map.json` and `settings/files-list.json` when you need the generated component or route inventory, but do not hand-edit them. The workspace regenerates them from `settings/component-map.ts` and `settings/files-list.ts`.
|
|
326
|
-
- Put route templates and route-specific backend logic in `src/app/`.
|
|
331
|
+
- Put route templates and route-specific backend logic in `src/app/`.
|
|
332
|
+
- Put only genuinely shared helpers, services, adapters, and validation logic in `src/lib/`.
|
|
327
333
|
- As the app grows, keep route-owned code in `src/app/`, reusable rendered UI in `src/components/`, and reusable non-UI support code in `src/lib/`.
|
|
328
334
|
- When the user asks for a dashboard, admin area, account area, or any grouped set of child routes, create a parent folder in `src/app/` with `layout.html` and place the child routes beneath it. Use `(group)/layout.html` only when that parent should not appear in the URL.
|
|
329
335
|
- Read [file-uploads.md](./file-uploads.md) when the task involves upload widgets, media libraries, or file-manager flows.
|
package/dist/docs/pulsepoint.md
CHANGED
|
@@ -81,6 +81,7 @@ Avoid building a parallel JavaScript layer for normal UI behavior:
|
|
|
81
81
|
|
|
82
82
|
- Do not add ids only so a script can find elements with `document.querySelector(...)` or `document.getElementById(...)`.
|
|
83
83
|
- Do not use `data-*` attributes as a private client state system when PulsePoint state or props should own the data.
|
|
84
|
+
- Do not create click-in buttons by placing intent in `data-*` attributes and then scanning for those attributes. Put the action directly on the element with `onclick`, `oninput`, `onchange`, `onsubmit`, or another native `on*` event attribute.
|
|
84
85
|
- Do not bind normal first-party clicks, input changes, submits, filters, menus, or toggles with `addEventListener(...)`.
|
|
85
86
|
- Do not use form refs plus input refs plus `pp.effect(...)` solely to construct an RPC payload from normal submitted fields.
|
|
86
87
|
- Do not repaint first-party lists or panels with manual `innerHTML` writes when `pp.state(...)` plus `pp-for` can express the same UI.
|
|
@@ -206,6 +207,86 @@ Avoid this for a normal form:
|
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
Refs are still useful when the feature actually requires imperative access. They are not the first choice for reading standard form fields that the browser already exposes through the submit event. Keep client code minimal and put reviewable data normalization in the route's Python `@rpc()` action unless the client must transform values for UX before submitting.
|
|
210
|
+
|
|
211
|
+
Basic two-way state pattern:
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<section>
|
|
215
|
+
<label>
|
|
216
|
+
Name
|
|
217
|
+
<input name="name" value="{name}" oninput="setName(event.target.value)" />
|
|
218
|
+
</label>
|
|
219
|
+
|
|
220
|
+
<p>Hello, {name || "friend"}.</p>
|
|
221
|
+
<button onclick="setName('')">Clear</button>
|
|
222
|
+
|
|
223
|
+
<script>
|
|
224
|
+
const [name, setName] = pp.state("");
|
|
225
|
+
</script>
|
|
226
|
+
</section>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Basic RPC-backed form pattern:
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<section>
|
|
233
|
+
<form onsubmit="{saveContact(event)}" novalidate>
|
|
234
|
+
<input name="name" value="{name}" oninput="setName(event.target.value)" required />
|
|
235
|
+
<input name="email" type="email" required />
|
|
236
|
+
|
|
237
|
+
<button type="submit" disabled="{isSaving}">
|
|
238
|
+
{isSaving ? "Saving..." : "Save"}
|
|
239
|
+
</button>
|
|
240
|
+
</form>
|
|
241
|
+
|
|
242
|
+
<script>
|
|
243
|
+
const [name, setName] = pp.state("");
|
|
244
|
+
const [isSaving, setIsSaving] = pp.state(false);
|
|
245
|
+
|
|
246
|
+
async function saveContact(event) {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
if (isSaving) return;
|
|
249
|
+
|
|
250
|
+
const data = Object.fromEntries(new FormData(event.currentTarget).entries());
|
|
251
|
+
|
|
252
|
+
setIsSaving(true);
|
|
253
|
+
try {
|
|
254
|
+
await pp.rpc("save_contact", data, { abortPrevious: true });
|
|
255
|
+
} finally {
|
|
256
|
+
setIsSaving(false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
</script>
|
|
260
|
+
</section>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Back that form with a route-owned `@rpc()` action in the sibling `index.py`. If Prisma is enabled, the action should use the generated Prisma Python ORM for persistence.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from casp.rpc import rpc
|
|
267
|
+
from casp.validate import Validate
|
|
268
|
+
from src.lib.prisma import prisma
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@rpc()
|
|
272
|
+
async def save_contact(name: str, email: str):
|
|
273
|
+
name = name.strip()
|
|
274
|
+
email = email.strip().lower()
|
|
275
|
+
|
|
276
|
+
if not name:
|
|
277
|
+
raise ValueError("Name is required.")
|
|
278
|
+
if Validate.with_rules(email, "required|email") is not True:
|
|
279
|
+
raise ValueError("A valid email address is required.")
|
|
280
|
+
|
|
281
|
+
contact = await prisma.contact.create(
|
|
282
|
+
data={
|
|
283
|
+
"name": name,
|
|
284
|
+
"email": email,
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return contact.to_dict()
|
|
289
|
+
```
|
|
209
290
|
|
|
210
291
|
## Authoring Model
|
|
211
292
|
|
|
@@ -549,6 +630,7 @@ Example:
|
|
|
549
630
|
- Event attributes are removed from the live DOM after binding and rebound after DOM morphing.
|
|
550
631
|
- Owned template/event-owner internals are runtime-managed. Do not author them directly.
|
|
551
632
|
- Do not replace normal PulsePoint event attributes with id-driven `querySelector(...)` plus `addEventListener(...)` wiring. If an imperative listener is unavoidable for an integration, attach and clean it up from `pp.effect(...)`.
|
|
633
|
+
- Do not replace a normal `onclick` with a `data-action`, `data-target`, or similar attribute plus a script that scans the DOM. PulsePoint event attributes are the event contract for first-party Caspian UI.
|
|
552
634
|
|
|
553
635
|
## SPA, loading, and navigation helpers
|
|
554
636
|
|
|
@@ -614,6 +696,7 @@ Use these rules when generating or editing PulsePoint runtime code:
|
|
|
614
696
|
- For grouped shells with independent shell and content scrolling, put `pp-reset-scroll="true"` on the content pane rather than the whole shell when only the page content should reset between child-route navigations.
|
|
615
697
|
- Prefer PulsePoint state and template directives over manual DOM mutation for reactive updates.
|
|
616
698
|
- Avoid generating ids, `data-*` state, `querySelector`, `getElementById`, `addEventListener`, manual `innerHTML`, or custom event buses for normal Caspian UI behavior.
|
|
699
|
+
- Avoid data-attribute click wiring such as `data-action="save"` plus a delegated listener. Use `onclick="save()"` or `onsubmit="{save(event)}"` in authored HTML and keep reactive state in `pp.state(...)`.
|
|
617
700
|
- If you are explicitly editing raw runtime HTML or internals, keep `pp-component` unique per live instance.
|
|
618
701
|
- In authored templates, use a plain `<script>` inside the root. In runtime HTML, the owned script appears as `script[type="text/pp"]`.
|
|
619
702
|
- Keep template-facing variables at top level.
|
package/dist/docs/routing.md
CHANGED
|
@@ -29,8 +29,9 @@ Start with these rules:
|
|
|
29
29
|
- Put application routes in `src/app/`.
|
|
30
30
|
- For any route that renders a page, put the markup in `index.html`.
|
|
31
31
|
- If a route is UI-only, `index.html` by itself is enough.
|
|
32
|
-
- Add `index.py` only when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side logic.
|
|
33
|
-
-
|
|
32
|
+
- Add `index.py` only when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side logic.
|
|
33
|
+
- Keep route-specific server logic in that route's `index.py`. Move logic into `src/lib/**` only when it is shared across routes, components, integrations, or features.
|
|
34
|
+
- Use a standalone `index.py` only for non-visual routes such as redirects or action-only handlers.
|
|
34
35
|
- When a folder owns child routes, use `layout.html` to wrap them. This is the default pattern for dashboards, admin sections, account areas, settings trees, and route groups.
|
|
35
36
|
- In grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane in `layout.html` when that pane should reset on child-route navigation while the shell sidebar or rail keeps its own scroll.
|
|
36
37
|
- Use `layout.py` when a layout needs shared props or metadata before rendering. The `layout()` function may be synchronous or async.
|
|
@@ -235,7 +236,9 @@ Use [pulsepoint.md](./pulsepoint.md) when you need the full authored-vs-rendered
|
|
|
235
236
|
|
|
236
237
|
### `index.py`
|
|
237
238
|
|
|
238
|
-
Use `index.py` as the backend companion when a route needs metadata or async server-side logic. For routes that render UI, keep the markup in the sibling `index.html` and let `page()` call `render_page(__file__, ...)`. Do not inline route HTML inside `index.py`.
|
|
239
|
+
Use `index.py` as the backend companion when a route needs metadata or async server-side logic. For routes that render UI, keep the markup in the sibling `index.html` and let `page()` call `render_page(__file__, ...)`. Do not inline route HTML inside `index.py`.
|
|
240
|
+
|
|
241
|
+
If the logic belongs only to this route, keep it here. Examples include this page's first-render query, route-owned RPC actions, redirect decisions, route-specific validation, route-specific filters, and route-specific response shaping. Move code into `src/lib/**` when the same logic is reused or intentionally shared; do not extract one-route orchestration into a library just because it is Python code.
|
|
239
242
|
|
|
240
243
|
Use `index.py` by itself only for non-visual routes such as redirects or action-only handlers. Because Caspian runs on FastAPI, the page entry should be async when it performs async work.
|
|
241
244
|
|
|
@@ -288,7 +291,7 @@ The key name is arbitrary, but it must match exactly between the dict returned f
|
|
|
288
291
|
|
|
289
292
|
Use distinct names for those layout props. In the current router, the second dict is merged into the full layout context after path params and `request`, so a key such as `slug` or `request` can shadow an existing value.
|
|
290
293
|
|
|
291
|
-
When a route owns a file manager or upload UI, keep the owning upload and delete `@rpc()` actions in that same `index.py` and move reusable filesystem or Prisma helpers into `src/lib/`. Do not move ordinary upload behavior into `main.py`. See [file-uploads.md](./file-uploads.md).
|
|
294
|
+
When a route owns a file manager or upload UI, keep the owning upload and delete `@rpc()` actions in that same `index.py` and move reusable filesystem or Prisma helpers into `src/lib/`. Do not move ordinary upload behavior into `main.py`. See [file-uploads.md](./file-uploads.md). When Prisma is enabled, database reads and writes in this route logic or its shared helpers should use the generated Prisma Python ORM.
|
|
292
295
|
|
|
293
296
|
For static and dynamic metadata rules, inheritance order, and social card fields, see [metadata.md](./metadata.md).
|
|
294
297
|
|
|
@@ -505,7 +508,8 @@ If an AI agent is choosing where to add or update route code, apply these rules
|
|
|
505
508
|
- Treat `src/app/` as the routing source of truth.
|
|
506
509
|
- Use folder names to model URL segments.
|
|
507
510
|
- If a route renders UI, create or update `index.html` for the markup.
|
|
508
|
-
- Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
|
|
511
|
+
- Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
|
|
512
|
+
- Keep route-specific backend logic in the route's own `index.py`; extract to `src/lib/**` only for genuinely shared logic.
|
|
509
513
|
- Keep visible page markup in `index.html` and shared subtree shells in `layout.html`; do not place route HTML in `index.py` or layout HTML in `layout.py`.
|
|
510
514
|
- Use PulsePoint as the first-party interaction model for route and layout HTML. Avoid custom DOM wiring for normal events and reactivity.
|
|
511
515
|
- When the user asks for a dashboard, admin area, account section, or any grouped subtree of child routes, create a parent folder with `layout.html` and place the child routes beneath it. Follow the same mental model as the Next.js App Router.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WebSockets
|
|
3
|
+
description: Use this page when the task mentions FastAPI WebSocket endpoints, live channels, broadcast managers, browser `WebSocket`, origin checks, or authenticated socket sessions in Caspian.
|
|
4
|
+
related:
|
|
5
|
+
title: Related docs
|
|
6
|
+
description: Use routing and PulsePoint for client placement, auth for session policy, and the core runtime map for endpoint ownership in `main.py`.
|
|
7
|
+
links:
|
|
8
|
+
- /docs/routing
|
|
9
|
+
- /docs/pulsepoint
|
|
10
|
+
- /docs/auth
|
|
11
|
+
- /docs/fetch-data
|
|
12
|
+
- /docs/core-runtime-map
|
|
13
|
+
- /docs/index
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
WebSockets in Caspian are gated by `caspian.config.json` and implemented as app-owned FastAPI endpoints, not the default RPC data path.
|
|
17
|
+
|
|
18
|
+
Use `@rpc()` plus `pp.rpc()` for ordinary browser-triggered reads, writes, uploads, and Server-Sent Event streams. Use WebSockets only when the task needs a bidirectional, long-lived channel such as live chat, collaboration, multiplayer state, server push that is not a good SSE fit, or presence.
|
|
19
|
+
|
|
20
|
+
Treat `caspian.config.json` as the single source of truth for whether the WebSocket feature is enabled. If `websocket` is false, this page is reference material only; do not assume `@app.websocket(...)` endpoints, `src/lib/websocket/**`, or socket demo routes exist until the user chooses to enable the feature and the update workflow has run.
|
|
21
|
+
|
|
22
|
+
## Source Of Truth
|
|
23
|
+
|
|
24
|
+
Before adding, debugging, or documenting a WebSocket feature:
|
|
25
|
+
|
|
26
|
+
- Read `caspian.config.json` first and confirm `websocket: true`.
|
|
27
|
+
- If `websocket` is false and the user wants live channels, ask for confirmation before enabling it, then update the config and follow the project update workflow.
|
|
28
|
+
- Inspect `main.py` for `@app.websocket(...)` routes, origin checks, close codes, message limits, idle timeout, and middleware interactions.
|
|
29
|
+
- Inspect `src/lib/**` for app-owned socket helpers such as session extraction, auth payload checks, connection managers, or broadcast utilities.
|
|
30
|
+
- Inspect the owning route under `src/app/**` for the browser client, first-render URL selection, and PulsePoint state around the native `WebSocket`.
|
|
31
|
+
- Check `settings/bs-config.json` before testing development URLs through BrowserSync or a proxy.
|
|
32
|
+
|
|
33
|
+
Packaged docs should describe this workflow generally. Keep endpoint names, route names, ports, and demo inventory project-owned; do not treat one app's route tree as the framework convention.
|
|
34
|
+
|
|
35
|
+
## Recommended Shape
|
|
36
|
+
|
|
37
|
+
When `caspian.config.json` has `websocket: true`, put socket endpoints in `main.py` when they are application-level ASGI routes:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from fastapi import WebSocket, WebSocketDisconnect, status
|
|
41
|
+
|
|
42
|
+
WEBSOCKET_PATH = "/ws/channel"
|
|
43
|
+
|
|
44
|
+
@app.websocket(WEBSOCKET_PATH)
|
|
45
|
+
async def websocket_live_endpoint(websocket: WebSocket):
|
|
46
|
+
if not is_origin_allowed(websocket):
|
|
47
|
+
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
await manager.connect(websocket)
|
|
51
|
+
try:
|
|
52
|
+
while True:
|
|
53
|
+
raw_message = await websocket.receive_text()
|
|
54
|
+
...
|
|
55
|
+
except WebSocketDisconnect:
|
|
56
|
+
return
|
|
57
|
+
finally:
|
|
58
|
+
manager.disconnect(websocket)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Keep reusable socket helpers in `src/lib/**` when they are shared by more than one endpoint or route. Good candidates include connection managers, session helpers, auth-payload extraction, payload normalization, and shared broadcast code.
|
|
62
|
+
|
|
63
|
+
Keep route-specific browser UI in `src/app/**/index.html` and route-specific first-render values in the matching `index.py`. For example, an `index.py` can pass `websocket_path` and a development-only BrowserSync-aware `websocket_url` into the template with `render_page(__file__, {...})`.
|
|
64
|
+
|
|
65
|
+
## Browser Client Pattern
|
|
66
|
+
|
|
67
|
+
Use PulsePoint for the component state and lifecycle, while using the native browser `WebSocket` object for the socket itself.
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<main>
|
|
71
|
+
<button onclick="{connectSocket()}" disabled="{isConnected}">Connect</button>
|
|
72
|
+
<button onclick="{disconnectSocket()}" disabled="{!isConnected}">Disconnect</button>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
const websocketPath = "{{ websocket_path }}";
|
|
76
|
+
const socketRef = pp.ref(null);
|
|
77
|
+
const [status, setStatus] = pp.state("idle");
|
|
78
|
+
const isConnected = status === "connected";
|
|
79
|
+
|
|
80
|
+
function buildWebSocketUrl(path) {
|
|
81
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
82
|
+
return new URL(`${protocol}//${window.location.host}${path}`, window.location.href).toString();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function connectSocket() {
|
|
86
|
+
const socket = new WebSocket(buildWebSocketUrl(websocketPath));
|
|
87
|
+
socketRef.current = socket;
|
|
88
|
+
|
|
89
|
+
socket.addEventListener("message", (event) => {
|
|
90
|
+
const payload = JSON.parse(event.data);
|
|
91
|
+
...
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function disconnectSocket() {
|
|
96
|
+
socketRef.current?.close(1000, "Client disconnect");
|
|
97
|
+
socketRef.current = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pp.effect(() => {
|
|
101
|
+
return () => socketRef.current?.close(1000, "Page disposed");
|
|
102
|
+
}, []);
|
|
103
|
+
</script>
|
|
104
|
+
</main>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This is one of the narrow cases where direct browser APIs and event listeners are expected. Keep them inside the owning PulsePoint component script, store the socket in `pp.ref(...)`, and keep rendered UI state in `pp.state(...)`.
|
|
108
|
+
|
|
109
|
+
## Auth And Sessions
|
|
110
|
+
|
|
111
|
+
HTTP route privacy does not automatically make a WebSocket endpoint private. If a socket is authenticated, the endpoint must explicitly inspect the session or auth payload available on the WebSocket scope and close unauthenticated clients.
|
|
112
|
+
|
|
113
|
+
Use close code `1008` for policy violations such as failed origin checks or missing authentication. If the endpoint accepts before rejecting, send a small JSON error payload first so the browser UI can show a useful message.
|
|
114
|
+
|
|
115
|
+
If the app refreshes auth sessions, verify whether the WebSocket helper refreshes session expiry and whether that session mutation is persisted in the current middleware stack. Do not assume HTTP `AuthMiddleware` behavior applies to `scope["type"] == "websocket"`.
|
|
116
|
+
|
|
117
|
+
## Origin And Proxy Checks
|
|
118
|
+
|
|
119
|
+
WebSocket endpoints should validate the `Origin` header before accepting connections, especially in production.
|
|
120
|
+
|
|
121
|
+
Common allowed origins:
|
|
122
|
+
|
|
123
|
+
- same-origin derived from the socket URL
|
|
124
|
+
- explicit values from environment variables such as `WEBSOCKET_ALLOWED_ORIGINS`, `CORS_ALLOWED_ORIGINS`, `APP_BASE_URL`, `PUBLIC_BASE_URL`, or `SITE_URL`
|
|
125
|
+
- localhost HTTP origins in development when the app intentionally permits local BrowserSync or proxy testing
|
|
126
|
+
|
|
127
|
+
When testing locally, read `settings/bs-config.json` first and build `ws://` or `wss://` URLs from its active `local` value. Do not assume the BrowserSync port is the default.
|
|
128
|
+
|
|
129
|
+
## Message Contract
|
|
130
|
+
|
|
131
|
+
Document the JSON message contract near the endpoint and client that own it.
|
|
132
|
+
|
|
133
|
+
Useful baseline conventions:
|
|
134
|
+
|
|
135
|
+
- Clients send JSON objects, usually `{ "type": "message", "text": "..." }` or `{ "type": "ping" }`.
|
|
136
|
+
- Servers send `ready`, `message`, `error`, and `pong` payloads as JSON.
|
|
137
|
+
- Enforce a maximum incoming message size before parsing or broadcasting.
|
|
138
|
+
- Close idle sockets after a configured timeout when the app does not need long silent connections.
|
|
139
|
+
- Sanitize or trim outbound text so one client cannot broadcast unbounded data.
|
|
140
|
+
- Disconnect stale sockets when sends fail.
|
|
141
|
+
|
|
142
|
+
## WebSockets Vs RPC/SSE
|
|
143
|
+
|
|
144
|
+
Choose the transport deliberately:
|
|
145
|
+
|
|
146
|
+
| Need | Preferred transport |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| CRUD, form submits, button actions, upload progress | `@rpc()` plus `pp.rpc()` |
|
|
149
|
+
| Server sends one-way progress chunks for a request | RPC streaming / SSE |
|
|
150
|
+
| Browser and server both send messages over a persistent channel | WebSocket |
|
|
151
|
+
| Presence, collaboration, live chat, multiplayer state | WebSocket |
|
|
152
|
+
|
|
153
|
+
Do not replace normal Caspian RPC with WebSockets just because a page is interactive.
|
|
154
|
+
|
|
155
|
+
## AI Retrieval Notes
|
|
156
|
+
|
|
157
|
+
If an AI agent is working on WebSockets in a Caspian app:
|
|
158
|
+
|
|
159
|
+
- Start with `caspian.config.json`. Continue only after confirming `websocket: true`, unless the task is specifically to enable the feature.
|
|
160
|
+
- Use [core-runtime-map.md](./core-runtime-map.md) to connect `main.py` behavior back to this doc.
|
|
161
|
+
- Use [routing.md](./routing.md) before changing the route files that render the client.
|
|
162
|
+
- Use [pulsepoint.md](./pulsepoint.md) for authored template rules and component lifecycle cleanup.
|
|
163
|
+
- Use [auth.md](./auth.md) before changing authenticated socket behavior.
|
|
164
|
+
- Keep reusable connection/session helpers in `src/lib/websocket/**` when they are shared; keep route UI in whichever `src/app/**` route owns the live experience; keep app-level ASGI endpoints in `main.py` unless the runtime intentionally provides another registration layer.
|
|
165
|
+
- Verify local test URLs from `settings/bs-config.json`.
|