@shaferllc/keel 0.66.0 → 0.74.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/AGENTS.md +169 -0
- package/README.md +44 -6
- package/bin/keel-mcp.mjs +9 -0
- package/dist/api/api.config.stub +9 -0
- package/dist/api/config.d.ts +13 -0
- package/dist/api/config.js +14 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.js +13 -0
- package/dist/api/provider.d.ts +10 -0
- package/dist/api/provider.js +17 -0
- package/dist/api/query.d.ts +35 -0
- package/dist/api/query.js +42 -0
- package/dist/api/resource.d.ts +91 -0
- package/dist/api/resource.js +188 -0
- package/dist/core/application.d.ts +5 -5
- package/dist/core/application.js +8 -2
- package/dist/core/cache.d.ts +81 -5
- package/dist/core/cache.js +188 -23
- package/dist/core/cli/stubs.d.ts +26 -0
- package/dist/core/cli/stubs.js +225 -0
- package/dist/core/console-prompt.d.ts +79 -0
- package/dist/core/console-prompt.js +239 -0
- package/dist/core/console-ui.d.ts +96 -0
- package/dist/core/console-ui.js +187 -0
- package/dist/core/console.d.ts +188 -0
- package/dist/core/console.js +395 -0
- package/dist/core/database.d.ts +70 -1
- package/dist/core/database.js +174 -15
- package/dist/core/env.d.ts +96 -0
- package/dist/core/env.js +140 -0
- package/dist/core/events.d.ts +129 -5
- package/dist/core/events.js +165 -7
- package/dist/core/health.d.ts +141 -0
- package/dist/core/health.js +226 -0
- package/dist/core/helpers.d.ts +9 -3
- package/dist/core/helpers.js +11 -3
- package/dist/core/http/kernel.d.ts +2 -0
- package/dist/core/http/kernel.js +48 -0
- package/dist/core/http/router.d.ts +5 -5
- package/dist/core/http/router.js +5 -5
- package/dist/core/i18n.d.ts +162 -0
- package/dist/core/i18n.js +472 -0
- package/dist/core/index.d.ts +39 -13
- package/dist/core/index.js +19 -6
- package/dist/core/instrumentation.d.ts +113 -0
- package/dist/core/instrumentation.js +52 -0
- package/dist/core/lock.d.ts +139 -0
- package/dist/core/lock.js +215 -0
- package/dist/core/logger.d.ts +89 -4
- package/dist/core/logger.js +167 -22
- package/dist/core/mail.d.ts +128 -7
- package/dist/core/mail.js +264 -16
- package/dist/core/notification.js +10 -1
- package/dist/core/package.d.ts +120 -0
- package/dist/core/package.js +169 -0
- package/dist/core/pages.d.ts +108 -0
- package/dist/core/pages.js +199 -0
- package/dist/core/queue.d.ts +134 -9
- package/dist/core/queue.js +325 -14
- package/dist/core/repl.d.ts +33 -0
- package/dist/core/repl.js +88 -0
- package/dist/core/scheduler.js +6 -0
- package/dist/core/social.d.ts +4 -4
- package/dist/core/social.js +4 -4
- package/dist/core/storage.d.ts +159 -6
- package/dist/core/storage.js +299 -7
- package/dist/core/telemetry.d.ts +208 -0
- package/dist/core/telemetry.js +383 -0
- package/dist/core/template.d.ts +2 -3
- package/dist/core/template.js +2 -3
- package/dist/core/testing.d.ts +170 -1
- package/dist/core/testing.js +504 -2
- package/dist/db/d1.js +13 -0
- package/dist/db/pg.d.ts +13 -0
- package/dist/db/pg.js +46 -4
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +355 -0
- package/dist/openapi/config.d.ts +28 -0
- package/dist/openapi/config.js +25 -0
- package/dist/openapi/doc.d.ts +40 -0
- package/dist/openapi/doc.js +20 -0
- package/dist/openapi/export.d.ts +8 -0
- package/dist/openapi/export.js +19 -0
- package/dist/openapi/gate.d.ts +15 -0
- package/dist/openapi/gate.js +27 -0
- package/dist/openapi/index.d.ts +19 -0
- package/dist/openapi/index.js +15 -0
- package/dist/openapi/openapi.config.stub +29 -0
- package/dist/openapi/provider.d.ts +18 -0
- package/dist/openapi/provider.js +35 -0
- package/dist/openapi/routes.d.ts +9 -0
- package/dist/openapi/routes.js +23 -0
- package/dist/openapi/spec.d.ts +23 -0
- package/dist/openapi/spec.js +132 -0
- package/dist/openapi/ui.d.ts +8 -0
- package/dist/openapi/ui.js +31 -0
- package/dist/openapi/zod.d.ts +12 -0
- package/dist/openapi/zod.js +46 -0
- package/dist/watch/config.d.ts +33 -0
- package/dist/watch/config.js +38 -0
- package/dist/watch/entry.d.ts +53 -0
- package/dist/watch/entry.js +105 -0
- package/dist/watch/gate.d.ts +20 -0
- package/dist/watch/gate.js +32 -0
- package/dist/watch/index.d.ts +21 -0
- package/dist/watch/index.js +17 -0
- package/dist/watch/migration.d.ts +7 -0
- package/dist/watch/migration.js +20 -0
- package/dist/watch/provider.d.ts +22 -0
- package/dist/watch/provider.js +58 -0
- package/dist/watch/prune.d.ts +11 -0
- package/dist/watch/prune.js +20 -0
- package/dist/watch/recorder.d.ts +24 -0
- package/dist/watch/recorder.js +39 -0
- package/dist/watch/routes.d.ts +13 -0
- package/dist/watch/routes.js +55 -0
- package/dist/watch/store.d.ts +54 -0
- package/dist/watch/store.js +158 -0
- package/dist/watch/ui/dist/watch.css +1 -0
- package/dist/watch/ui/dist/watch.js +555 -0
- package/dist/watch/ui-shell.d.ts +12 -0
- package/dist/watch/ui-shell.js +24 -0
- package/dist/watch/watch.config.stub +47 -0
- package/dist/watch/watchers.d.ts +12 -0
- package/dist/watch/watchers.js +156 -0
- package/docs/ai-manifest.json +3206 -0
- package/docs/ai.md +128 -0
- package/docs/api-resources.md +118 -0
- package/docs/architecture.md +331 -0
- package/docs/authentication.md +453 -0
- package/docs/authorization.md +167 -0
- package/docs/broadcasting.md +137 -0
- package/docs/broker.md +500 -0
- package/docs/cache.md +558 -0
- package/docs/configuration.md +385 -0
- package/docs/console.md +536 -0
- package/docs/container.md +467 -0
- package/docs/controllers.md +265 -0
- package/docs/cors.md +51 -0
- package/docs/database.md +631 -0
- package/docs/debugging.md +129 -0
- package/docs/decorators.md +127 -0
- package/docs/errors.md +395 -0
- package/docs/events.md +496 -0
- package/docs/examples/architecture-app.ts +27 -0
- package/docs/examples/authentication.ts +61 -0
- package/docs/examples/authorization.ts +79 -0
- package/docs/examples/broadcasting.ts +60 -0
- package/docs/examples/broker-cache-validate.ts +34 -0
- package/docs/examples/broker-fault-tolerance.ts +29 -0
- package/docs/examples/broker-middleware.ts +27 -0
- package/docs/examples/broker.ts +203 -0
- package/docs/examples/cache.ts +222 -0
- package/docs/examples/configuration.ts +121 -0
- package/docs/examples/console.ts +134 -0
- package/docs/examples/container.ts +134 -0
- package/docs/examples/controllers.ts +86 -0
- package/docs/examples/database.ts +208 -0
- package/docs/examples/debugging.ts +41 -0
- package/docs/examples/decorators.ts +40 -0
- package/docs/examples/errors.ts +121 -0
- package/docs/examples/events.ts +204 -0
- package/docs/examples/factories.ts +84 -0
- package/docs/examples/hashing.ts +71 -0
- package/docs/examples/health.ts +94 -0
- package/docs/examples/helpers.ts +171 -0
- package/docs/examples/hooks.ts +54 -0
- package/docs/examples/i18n.ts +117 -0
- package/docs/examples/inertia.ts +81 -0
- package/docs/examples/locks.ts +120 -0
- package/docs/examples/logger.ts +166 -0
- package/docs/examples/mail.ts +263 -0
- package/docs/examples/middleware.ts +119 -0
- package/docs/examples/migrations.ts +126 -0
- package/docs/examples/models.ts +239 -0
- package/docs/examples/notification.ts +124 -0
- package/docs/examples/pages.ts +82 -0
- package/docs/examples/providers.ts +123 -0
- package/docs/examples/queues.ts +254 -0
- package/docs/examples/rate-limiting.ts +42 -0
- package/docs/examples/redis.ts +99 -0
- package/docs/examples/request-response.ts +197 -0
- package/docs/examples/routing.ts +186 -0
- package/docs/examples/scheduling.ts +62 -0
- package/docs/examples/sessions.ts +102 -0
- package/docs/examples/static-files.ts +63 -0
- package/docs/examples/storage.ts +132 -0
- package/docs/examples/telemetry.ts +127 -0
- package/docs/examples/templates.ts +58 -0
- package/docs/examples/testing.ts +215 -0
- package/docs/examples/transformer.ts +141 -0
- package/docs/examples/transformers.ts +49 -0
- package/docs/examples/url-builder.ts +86 -0
- package/docs/examples/validation.ts +102 -0
- package/docs/examples/views.tsx +62 -0
- package/docs/examples/vite.ts +106 -0
- package/docs/factories.md +166 -0
- package/docs/getting-started.md +290 -0
- package/docs/hashing.md +259 -0
- package/docs/health.md +225 -0
- package/docs/helpers.md +347 -0
- package/docs/hono.md +186 -0
- package/docs/hooks.md +118 -0
- package/docs/i18n.md +302 -0
- package/docs/inertia.md +241 -0
- package/docs/locks.md +323 -0
- package/docs/logger.md +436 -0
- package/docs/mail.md +751 -0
- package/docs/middleware.md +425 -0
- package/docs/migrations.md +476 -0
- package/docs/models.md +810 -0
- package/docs/notifications.md +474 -0
- package/docs/openapi.md +111 -0
- package/docs/packages.md +118 -0
- package/docs/pages.md +217 -0
- package/docs/providers.md +363 -0
- package/docs/queues.md +679 -0
- package/docs/rate-limiting.md +155 -0
- package/docs/redis.md +178 -0
- package/docs/request-response.md +953 -0
- package/docs/routing.md +804 -0
- package/docs/scheduling.md +110 -0
- package/docs/security.md +85 -0
- package/docs/sessions.md +354 -0
- package/docs/social-auth.md +174 -0
- package/docs/static-files.md +211 -0
- package/docs/storage.md +452 -0
- package/docs/telemetry.md +263 -0
- package/docs/templates.md +314 -0
- package/docs/testing.md +376 -0
- package/docs/transformers.md +381 -0
- package/docs/url-builder.md +295 -0
- package/docs/validation.md +288 -0
- package/docs/views.md +267 -0
- package/docs/vite.md +434 -0
- package/docs/watch.md +118 -0
- package/llms-full.txt +19816 -0
- package/llms.txt +127 -0
- package/package.json +42 -7
package/docs/vite.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Vite
|
|
2
|
+
|
|
3
|
+
Wire a modern frontend build — bundling, hashed filenames, hot module reload —
|
|
4
|
+
to Keel's server-rendered HTML, the way modern full-stack frameworks do. There are two
|
|
5
|
+
halves:
|
|
6
|
+
|
|
7
|
+
- **`keelVite()`** — a plugin for `vite.config.ts` (from `@shaferllc/keel/vite`).
|
|
8
|
+
It configures the build and, while the dev server runs, writes a `public/hot`
|
|
9
|
+
marker file.
|
|
10
|
+
- **`Vite`** — a server service (from `@shaferllc/keel/core`) that renders the
|
|
11
|
+
`<script>`/`<link>` tags for your entrypoints and resolves asset URLs.
|
|
12
|
+
|
|
13
|
+
The service switches modes automatically. When the dev server is running the
|
|
14
|
+
`hot` file exists, so tags point at it with HMR; otherwise it reads the build
|
|
15
|
+
manifest and emits hashed, split, preloaded production tags. Tag generation is
|
|
16
|
+
pure string work, so it runs on Node and on the edge alike — only reading the
|
|
17
|
+
manifest from disk touches the filesystem.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
Install Vite (it's an optional peer dependency):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -D vite
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add `vite.config.ts` at your project root and register the plugin with your
|
|
28
|
+
entrypoints:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { defineConfig } from "vite";
|
|
32
|
+
import { keelVite } from "@shaferllc/keel/vite";
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
plugins: [
|
|
36
|
+
keelVite({
|
|
37
|
+
entrypoints: ["resources/js/app.ts"],
|
|
38
|
+
// Optional: full-reload the browser when a server view changes.
|
|
39
|
+
reload: ["resources/views/**/*.tsx"],
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Create the entrypoint (and import your CSS from it so Vite bundles it):
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// resources/js/app.ts
|
|
49
|
+
import "../css/app.css";
|
|
50
|
+
console.log("⚓ Keel + Vite");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Bind the `Vite` service in a provider and load the manifest/hot file once at
|
|
54
|
+
boot:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { ServiceProvider, singleton, Vite } from "@shaferllc/keel/core";
|
|
58
|
+
|
|
59
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
60
|
+
register(): void {
|
|
61
|
+
singleton(Vite, () => new Vite({ entrypoints: ["resources/js/app.ts"] }));
|
|
62
|
+
}
|
|
63
|
+
async boot(): Promise<void> {
|
|
64
|
+
await this.app.make(Vite).loadFromDisk();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Add the client scripts and ignore the build artifacts:
|
|
70
|
+
|
|
71
|
+
```jsonc
|
|
72
|
+
// package.json
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev:client": "vite", // dev server with HMR (writes public/hot)
|
|
75
|
+
"build:client": "vite build" // production build → public/assets + manifest
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```gitignore
|
|
80
|
+
public/hot
|
|
81
|
+
public/assets/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Rendering the tags
|
|
85
|
+
|
|
86
|
+
Drop the helpers into your JSX layout's `<head>`. `viteReactRefresh()` comes
|
|
87
|
+
first (it's a no-op unless you use React); `viteTags()` renders the entrypoint:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { viteTags, viteReactRefresh } from "@shaferllc/keel/core";
|
|
91
|
+
|
|
92
|
+
export const Layout = ({ title, children }) => (
|
|
93
|
+
<html>
|
|
94
|
+
<head>
|
|
95
|
+
<title>{title}</title>
|
|
96
|
+
{viteReactRefresh()}
|
|
97
|
+
{viteTags("resources/js/app.ts")}
|
|
98
|
+
</head>
|
|
99
|
+
<body>{children}</body>
|
|
100
|
+
</html>
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Both return raw HTML (a Hono `HtmlEscapedString`), so they render unescaped in
|
|
105
|
+
JSX. For an asset that isn't imported by your JS — an image or font referenced
|
|
106
|
+
straight from a template — use `viteAsset()`, which returns the dev-server URL in
|
|
107
|
+
development and the hashed URL in production:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<img src={viteAsset("resources/images/logo.png")} alt="Logo" />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Dev vs. production
|
|
114
|
+
|
|
115
|
+
Run **two** processes in development — the Vite dev server and the Keel server:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run dev:client # terminal 1 — Vite + HMR, writes public/hot
|
|
119
|
+
npm run dev # terminal 2 — the Keel app
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
With `public/hot` present, `viteTags()` renders the Vite client plus a module
|
|
123
|
+
script pointing at the dev server:
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<script type="module" src="http://localhost:5173/@vite/client"></script>
|
|
127
|
+
<script type="module" src="http://localhost:5173/resources/js/app.ts"></script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
For production, build once — `vite build` writes hashed files and
|
|
131
|
+
`public/assets/.vite/manifest.json` — and the same call renders the manifest's
|
|
132
|
+
output, with the CSS extracted to a `<link>` and imported chunks preloaded:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<link rel="stylesheet" href="/assets/app-ghi789.css">
|
|
136
|
+
<link rel="modulepreload" href="/assets/vendor-def456.js">
|
|
137
|
+
<script type="module" src="/assets/app-abc123.js"></script>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Serve those built files with the [static middleware](./static-files.md) pointed
|
|
141
|
+
at `public/` (Keel's default) — a request for `/assets/app-abc123.js` maps to
|
|
142
|
+
`public/assets/app-abc123.js`:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
this.use(serveStatic({ root: "./public" }));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Multiple entrypoints
|
|
149
|
+
|
|
150
|
+
Each entrypoint produces its own bundle. List them in the config and tag whichever
|
|
151
|
+
a page needs — shared vendor chunks are preloaded once, deduplicated:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
keelVite({ entrypoints: ["resources/js/app.ts", "resources/js/admin.ts"] });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
{viteTags(["resources/js/app.ts", "resources/js/admin.ts"])}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## React (and other frameworks)
|
|
162
|
+
|
|
163
|
+
Add the React plugin to `vite.config.ts` and keep `viteReactRefresh()` before
|
|
164
|
+
`viteTags()` in your layout — it injects the Fast Refresh preamble in
|
|
165
|
+
development and renders nothing in production:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import react from "@vitejs/plugin-react";
|
|
169
|
+
|
|
170
|
+
export default defineConfig({
|
|
171
|
+
plugins: [keelVite({ entrypoints: ["resources/js/app.tsx"] }), react()],
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This is exactly what the [Inertia](./inertia.md) adapter's root view uses to load
|
|
176
|
+
its client bundle.
|
|
177
|
+
|
|
178
|
+
## Serving from a CDN
|
|
179
|
+
|
|
180
|
+
Set `assetsUrl` to your CDN base in **both** the plugin and the service, then
|
|
181
|
+
upload `public/assets` after building:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
keelVite({ entrypoints: ["resources/js/app.ts"], assetsUrl: "https://cdn.example.com" });
|
|
185
|
+
new Vite({ entrypoints: ["resources/js/app.ts"], assetsUrl: "https://cdn.example.com" });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Production tags then point at `https://cdn.example.com/app-abc123.js`.
|
|
189
|
+
|
|
190
|
+
## Custom tag attributes
|
|
191
|
+
|
|
192
|
+
Pass `scriptAttributes` / `styleAttributes` to add attributes to the generated
|
|
193
|
+
tags — as a static object, or a function that decides per asset:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
new Vite({
|
|
197
|
+
entrypoints: ["resources/js/app.ts"],
|
|
198
|
+
scriptAttributes: { defer: true, crossorigin: "anonymous" },
|
|
199
|
+
styleAttributes: ({ src }) =>
|
|
200
|
+
src.includes("admin") ? { "data-turbo-track": "reload" } : undefined,
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
A `true` value renders a bare attribute (`defer`); `false`/`undefined` drops it.
|
|
205
|
+
|
|
206
|
+
## On the edge
|
|
207
|
+
|
|
208
|
+
There's no filesystem on Workers, so skip `loadFromDisk` and hand the bundled
|
|
209
|
+
manifest straight in — bundle `manifest.json` as a JSON import and pass it to
|
|
210
|
+
`useManifest`:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import manifest from "../public/assets/.vite/manifest.json";
|
|
214
|
+
singleton(Vite, () => new Vite({ entrypoints: ["resources/js/app.ts"] }).useManifest(manifest));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Tag generation from there is pure and edge-safe.
|
|
218
|
+
|
|
219
|
+
## Related
|
|
220
|
+
|
|
221
|
+
Vite pairs with [views](./views.md) (the JSX layout that renders the tags),
|
|
222
|
+
[static files](./static-files.md) (serving the build in production), and
|
|
223
|
+
[Inertia](./inertia.md) (whose root view loads the client bundle through it).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## API reference
|
|
228
|
+
|
|
229
|
+
### `keelVite(options)` — `@shaferllc/keel/vite`
|
|
230
|
+
|
|
231
|
+
`keelVite(options: KeelViteOptions): Plugin[]`
|
|
232
|
+
|
|
233
|
+
The build-time plugin for `vite.config.ts`. Configures the manifest, output
|
|
234
|
+
directory, entrypoints, and `base`, and manages the `public/hot` dev marker.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
export default defineConfig({
|
|
238
|
+
plugins: [keelVite({ entrypoints: ["resources/js/app.ts"] })],
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Notes:** returns an array (spread into `plugins`). Sets `build.manifest`,
|
|
243
|
+
`build.outDir` (= `buildDirectory`), a flat `build.assetsDir`, and
|
|
244
|
+
`rollupOptions.input`; `base` is `assetsUrl` for a build and `/` for the dev
|
|
245
|
+
server. It leaves any of these alone if you set them yourself. Throws if
|
|
246
|
+
`entrypoints` is empty.
|
|
247
|
+
|
|
248
|
+
#### `KeelViteOptions`
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
interface KeelViteOptions {
|
|
252
|
+
entrypoints: string | string[]; // required — one bundle per entry
|
|
253
|
+
buildDirectory?: string; // default "public/assets" (match the service)
|
|
254
|
+
hotFile?: string; // default "public/hot"
|
|
255
|
+
assetsUrl?: string; // default "/assets" (or a CDN base)
|
|
256
|
+
reload?: string[]; // globs that trigger a full page reload
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`reload` globs support `*`, `**`, and `?`. A change to a matching file sends a
|
|
261
|
+
`full-reload` to the browser — useful for server-rendered views Vite doesn't
|
|
262
|
+
otherwise watch.
|
|
263
|
+
|
|
264
|
+
### `Vite` — `@shaferllc/keel/core`
|
|
265
|
+
|
|
266
|
+
The server service. Bind it as a singleton, `loadFromDisk()` at boot, and render
|
|
267
|
+
its tags from your views (usually through the `viteTags` helper).
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
const vite = await new Vite({ entrypoints: ["resources/js/app.ts"] }).loadFromDisk();
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### `new Vite(options?)`
|
|
274
|
+
|
|
275
|
+
`new Vite(options?: ViteOptions)`
|
|
276
|
+
|
|
277
|
+
Constructs the service. All options are optional; sensible defaults match the
|
|
278
|
+
plugin.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
new Vite({ entrypoints: ["resources/js/app.ts"], assetsUrl: "/assets" });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `loadFromDisk()`
|
|
285
|
+
|
|
286
|
+
`loadFromDisk(): Promise<this>`
|
|
287
|
+
|
|
288
|
+
Reads the hot file (dev) or the build manifest (prod) from disk. Node only —
|
|
289
|
+
imports `node:fs` dynamically. Call once at boot.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
async boot() { await this.app.make(Vite).loadFromDisk(); }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Notes:** if neither a hot file nor a manifest exists yet, it resolves anyway;
|
|
296
|
+
the clear error is raised later, when tags are actually generated. In dev it
|
|
297
|
+
re-checks the hot file on each render, so starting the dev server after the app
|
|
298
|
+
still works.
|
|
299
|
+
|
|
300
|
+
#### `useManifest(manifest)`
|
|
301
|
+
|
|
302
|
+
`useManifest(manifest: Manifest): this`
|
|
303
|
+
|
|
304
|
+
Injects a manifest directly instead of reading disk — the edge path.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
new Vite({ … }).useManifest(manifest);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `useHotUrl(url)`
|
|
311
|
+
|
|
312
|
+
`useHotUrl(url: string | null): this`
|
|
313
|
+
|
|
314
|
+
Forces the dev-server URL (or `null` for production), bypassing the hot file.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
new Vite({ … }).useHotUrl("http://localhost:5173");
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### `generateEntryPointsTags(entrypoints?)`
|
|
321
|
+
|
|
322
|
+
`generateEntryPointsTags(entrypoints?: string | string[]): HtmlEscapedString`
|
|
323
|
+
|
|
324
|
+
The `<script>`/`<link>` tags for the given entrypoints (or the constructor's).
|
|
325
|
+
Dev-server tags with HMR while developing; hashed, preloaded tags in production.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
vite.generateEntryPointsTags("resources/js/app.ts");
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Notes:** the `viteTags()` helper wraps this. Throws if an entrypoint is missing
|
|
332
|
+
from the manifest, or if neither a hot file nor a manifest is available.
|
|
333
|
+
|
|
334
|
+
#### `assetPath(asset)`
|
|
335
|
+
|
|
336
|
+
`assetPath(asset: string): string`
|
|
337
|
+
|
|
338
|
+
The public URL for one asset — the dev-server URL in development, the hashed
|
|
339
|
+
manifest URL in production.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
vite.assetPath("resources/images/logo.png");
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Notes:** the `viteAsset()` helper wraps this. Throws in production if the asset
|
|
346
|
+
isn't in the manifest.
|
|
347
|
+
|
|
348
|
+
#### `reactHMR()`
|
|
349
|
+
|
|
350
|
+
`reactHMR(): HtmlEscapedString`
|
|
351
|
+
|
|
352
|
+
The React Fast Refresh preamble (dev only; empty in production). Render it before
|
|
353
|
+
your entry script when using `@vitejs/plugin-react`.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
vite.reactHMR();
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Notes:** the `viteReactRefresh()` helper wraps this.
|
|
360
|
+
|
|
361
|
+
#### `hot()`
|
|
362
|
+
|
|
363
|
+
`hot(): string | null`
|
|
364
|
+
|
|
365
|
+
The dev-server URL when Vite is running, else `null`. A loaded manifest always
|
|
366
|
+
means production.
|
|
367
|
+
|
|
368
|
+
#### `manifest()`
|
|
369
|
+
|
|
370
|
+
`manifest(): Manifest`
|
|
371
|
+
|
|
372
|
+
The parsed production manifest. Throws a helpful error if no build has been
|
|
373
|
+
loaded.
|
|
374
|
+
|
|
375
|
+
### Helpers — `@shaferllc/keel/core`
|
|
376
|
+
|
|
377
|
+
Free functions that resolve the app's bound `Vite` instance — use them in views
|
|
378
|
+
without threading the container through.
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
viteTags(entrypoints?: string | string[]): HtmlEscapedString
|
|
382
|
+
viteAsset(asset: string): string
|
|
383
|
+
viteReactRefresh(): HtmlEscapedString
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Each throws a configuration error if `Vite` isn't bound in a provider.
|
|
387
|
+
|
|
388
|
+
### Types
|
|
389
|
+
|
|
390
|
+
#### `ViteOptions`
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
interface ViteOptions {
|
|
394
|
+
entrypoints?: string | string[];
|
|
395
|
+
hotFile?: string; // default "public/hot"
|
|
396
|
+
buildDirectory?: string; // default "public/assets"
|
|
397
|
+
manifestFile?: string; // default "<buildDirectory>/.vite/manifest.json"
|
|
398
|
+
assetsUrl?: string; // default "/assets"
|
|
399
|
+
scriptAttributes?: ViteAttributes;
|
|
400
|
+
styleAttributes?: ViteAttributes;
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### `ViteAttributes`
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
type ViteAttributes =
|
|
408
|
+
| Record<string, string | boolean | null | undefined>
|
|
409
|
+
| ((asset: { src: string; url: string }) => Record<string, AttrValue> | undefined);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Attributes for generated tags. `true` → a bare attribute; `false`/`null`/
|
|
413
|
+
`undefined` → omitted; a string → `key="value"` (HTML-escaped).
|
|
414
|
+
|
|
415
|
+
#### `Manifest` / `ManifestChunk`
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
type Manifest = Record<string, ManifestChunk>;
|
|
419
|
+
|
|
420
|
+
interface ManifestChunk {
|
|
421
|
+
file: string;
|
|
422
|
+
name?: string;
|
|
423
|
+
src?: string;
|
|
424
|
+
isEntry?: boolean;
|
|
425
|
+
isDynamicEntry?: boolean;
|
|
426
|
+
imports?: string[];
|
|
427
|
+
dynamicImports?: string[];
|
|
428
|
+
css?: string[];
|
|
429
|
+
assets?: string[];
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Vite's `manifest.json`, as produced by the build. You rarely touch it directly —
|
|
434
|
+
`generateEntryPointsTags` and `assetPath` read it for you.
|
package/docs/watch.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Watch
|
|
2
|
+
|
|
3
|
+
Keel Watch is a debug dashboard — a Telescope for Keel. It records the requests,
|
|
4
|
+
queries, exceptions, logs, jobs, mail, notifications, cache lookups, events, and
|
|
5
|
+
scheduled tasks flowing through your app, and shows them in a single-page UI at
|
|
6
|
+
`/watch`, with every request linked to the queries and logs it produced.
|
|
7
|
+
|
|
8
|
+
It ships as a Keel [package](./packages.md) and is its reference implementation:
|
|
9
|
+
one `register()` turns it on, and its watchers observe the framework's
|
|
10
|
+
instrumentation event stream — they patch nothing, so installing Watch changes no
|
|
11
|
+
behaviour, only visibility.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// bootstrap/providers.ts
|
|
17
|
+
import { WatchServiceProvider } from "@shaferllc/keel/watch";
|
|
18
|
+
|
|
19
|
+
export const providers = [AppServiceProvider, WatchServiceProvider];
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Publish the config and create the table:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
keel vendor:publish --tag watch-config # writes config/watch.ts
|
|
26
|
+
keel migrate # creates watch_entries
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then open `http://localhost:3000/watch`.
|
|
30
|
+
|
|
31
|
+
> Watch exposes requests, payloads, and stack traces. It is gated shut in
|
|
32
|
+
> production by default (see [Access](#access)) — keep it that way, or lock it
|
|
33
|
+
> down with your own check.
|
|
34
|
+
|
|
35
|
+
## What it records
|
|
36
|
+
|
|
37
|
+
A tab per type, each behind an on/off switch:
|
|
38
|
+
|
|
39
|
+
| Watcher | Records |
|
|
40
|
+
|---------|---------|
|
|
41
|
+
| Requests | method, path, status, duration, headers (auth/cookies redacted) |
|
|
42
|
+
| Queries | SQL, bindings, duration, connection; slow queries are tagged |
|
|
43
|
+
| Exceptions | class, message, stack, the request that threw |
|
|
44
|
+
| Logs | every log line, at any level |
|
|
45
|
+
| Mail | sent messages (subject, recipients, body) |
|
|
46
|
+
| Jobs | queued jobs as they complete or fail |
|
|
47
|
+
| Notifications | deliveries and their channels |
|
|
48
|
+
| Cache | hits and misses *(off by default — noisy)* |
|
|
49
|
+
| Events | app events *(off by default — noisy)* |
|
|
50
|
+
| Schedule | scheduled tasks as they run |
|
|
51
|
+
|
|
52
|
+
Clicking any entry shows its full detail and **everything else in its batch** —
|
|
53
|
+
the request it belonged to and every query, log, and exception that request
|
|
54
|
+
produced.
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
`config/watch.ts` (publish it, then edit):
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
export default {
|
|
62
|
+
enabled: env("WATCH_ENABLED", true),
|
|
63
|
+
path: "watch", // the dashboard mounts at /watch
|
|
64
|
+
storage: "database", // "database" persists; "memory" is a per-process ring
|
|
65
|
+
connection: undefined, // which DB connection (default when omitted)
|
|
66
|
+
table: "watch_entries",
|
|
67
|
+
limit: 100, // API page size; the memory ring is 10× this
|
|
68
|
+
sampling: 1, // record this fraction of entries (0–1)
|
|
69
|
+
slowQueryMs: 100, // queries at/above this are tagged "slow"
|
|
70
|
+
ignorePaths: [], // request paths to skip
|
|
71
|
+
retentionHours: 24, // keel watch:prune deletes entries older than this
|
|
72
|
+
watchers: { cache: false, event: false /* … the rest default on */ },
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Storage
|
|
77
|
+
|
|
78
|
+
- **`database`** (default) — a `watch_entries` table via any registered
|
|
79
|
+
connection. Survives restarts, shared across processes; needs `keel migrate`.
|
|
80
|
+
- **`memory`** — a per-process ring buffer. Zero setup, great for a single dev
|
|
81
|
+
process or the edge; entries vanish on restart and aren't shared.
|
|
82
|
+
|
|
83
|
+
## Access
|
|
84
|
+
|
|
85
|
+
By default the dashboard is open only when `app.debug` is on or the app isn't in
|
|
86
|
+
production. Anywhere else, lock it to your own check:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { Watch } from "@shaferllc/keel/watch";
|
|
90
|
+
import { auth } from "@shaferllc/keel/core";
|
|
91
|
+
|
|
92
|
+
Watch.auth(() => auth().check() && Boolean(auth().user()?.isAdmin));
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The gate guards the JSON API too, so entries can't be read by anyone the
|
|
96
|
+
dashboard wouldn't show them to.
|
|
97
|
+
|
|
98
|
+
## Pruning
|
|
99
|
+
|
|
100
|
+
With database storage, keep the table small:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
keel watch:prune # delete entries older than retentionHours
|
|
104
|
+
keel watch:prune --hours=6
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Schedule it (see [Scheduling](./scheduling.md)) to run it automatically.
|
|
108
|
+
|
|
109
|
+
## How it works
|
|
110
|
+
|
|
111
|
+
Watch never wraps or monkey-patches the framework. The core emits a typed
|
|
112
|
+
[instrumentation event stream](./packages.md#observing-the-framework) —
|
|
113
|
+
`db.query`, `request.handled`, `exception`, `job.*`, and so on — and each watcher
|
|
114
|
+
is just a listener that turns an event into a stored entry, stamped with the
|
|
115
|
+
current request id so related entries group into one batch. Recording is
|
|
116
|
+
fire-and-forget: it never blocks or breaks the request it's watching, and the
|
|
117
|
+
store's own queries are filtered out so it never records itself.
|
|
118
|
+
```
|