@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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keel OpenAPI — generate an OpenAPI 3 spec from your routes and serve Swagger UI,
|
|
3
|
+
* shipped as a Keel package. One line turns it on:
|
|
4
|
+
*
|
|
5
|
+
* app.register(OpenApiServiceProvider);
|
|
6
|
+
*
|
|
7
|
+
* The spec is built from Keel's own route table plus whatever each route attaches
|
|
8
|
+
* with `.config(apiDoc(...))`. Nothing is scraped or guessed: paths and methods
|
|
9
|
+
* are always right, and schemas are as rich as the metadata a route provides.
|
|
10
|
+
*/
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { PackageProvider } from "../core/package.js";
|
|
14
|
+
import { resolveConfig, defaultConfig } from "./config.js";
|
|
15
|
+
import { registerOpenApiRoutes } from "./routes.js";
|
|
16
|
+
import { exportCommand } from "./export.js";
|
|
17
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
export class OpenApiServiceProvider extends PackageProvider {
|
|
19
|
+
name = "openapi";
|
|
20
|
+
config;
|
|
21
|
+
base() {
|
|
22
|
+
return "/" + this.config.path.replace(/^\/|\/$/g, "");
|
|
23
|
+
}
|
|
24
|
+
register() {
|
|
25
|
+
this.mergeConfig("openapi", defaultConfig);
|
|
26
|
+
this.config = resolveConfig();
|
|
27
|
+
this.publishes({ [join(here, "openapi.config.stub")]: "config/openapi.ts" }, "openapi-config");
|
|
28
|
+
this.commands([exportCommand(() => this.app.router(), this.config, this.base())]);
|
|
29
|
+
}
|
|
30
|
+
boot() {
|
|
31
|
+
if (!this.config.enabled)
|
|
32
|
+
return;
|
|
33
|
+
this.routes((r) => registerOpenApiRoutes(r, this.config, this.app.router(), this.base()), { prefix: this.config.path, as: "openapi" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The docs HTTP surface: the generated spec at `/openapi.json` and the Swagger UI
|
|
3
|
+
* at the base path. The spec is built once, lazily, from the full route table
|
|
4
|
+
* (which is complete only after the app has loaded its routes) and cached.
|
|
5
|
+
* Both routes are behind the gate.
|
|
6
|
+
*/
|
|
7
|
+
import type { Router } from "../core/http/router.js";
|
|
8
|
+
import type { OpenApiConfig } from "./config.js";
|
|
9
|
+
export declare function registerOpenApiRoutes(r: Router, config: OpenApiConfig, router: Router, base: string): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The docs HTTP surface: the generated spec at `/openapi.json` and the Swagger UI
|
|
3
|
+
* at the base path. The spec is built once, lazily, from the full route table
|
|
4
|
+
* (which is complete only after the app has loaded its routes) and cached.
|
|
5
|
+
* Both routes are behind the gate.
|
|
6
|
+
*/
|
|
7
|
+
import { buildSpec } from "./spec.js";
|
|
8
|
+
import { swaggerHtml } from "./ui.js";
|
|
9
|
+
import { passesGate } from "./gate.js";
|
|
10
|
+
export function registerOpenApiRoutes(r, config, router, base) {
|
|
11
|
+
let cached;
|
|
12
|
+
const spec = () => (cached ??= buildSpec(router.all(), config, base));
|
|
13
|
+
r.get("/openapi.json", async (c) => {
|
|
14
|
+
if (!(await passesGate(c, config)))
|
|
15
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
16
|
+
return c.json(spec());
|
|
17
|
+
}).name("spec");
|
|
18
|
+
r.get("/", async (c) => {
|
|
19
|
+
if (!(await passesGate(c, config)))
|
|
20
|
+
return c.text("Forbidden", 403);
|
|
21
|
+
return c.html(swaggerHtml(`${base}/openapi.json`, config));
|
|
22
|
+
}).name("ui");
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an OpenAPI 3.0 document from Keel's own route table. Every registered
|
|
3
|
+
* route already carries its methods, path, name, param constraints, and any
|
|
4
|
+
* `.config(apiDoc(...))` metadata — that's the whole input. Paths and methods
|
|
5
|
+
* come for free; request/response schemas come from the docs metadata a route
|
|
6
|
+
* chose to attach. A route with no metadata still appears, just bare.
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteDefinition } from "../core/http/router.js";
|
|
9
|
+
import type { OpenApiConfig } from "./config.js";
|
|
10
|
+
export interface OpenApiDocument {
|
|
11
|
+
openapi: string;
|
|
12
|
+
info: {
|
|
13
|
+
title: string;
|
|
14
|
+
version: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
};
|
|
17
|
+
servers?: {
|
|
18
|
+
url: string;
|
|
19
|
+
}[];
|
|
20
|
+
paths: Record<string, Record<string, unknown>>;
|
|
21
|
+
}
|
|
22
|
+
/** Assemble the whole document from the route table. */
|
|
23
|
+
export declare function buildSpec(routes: RouteDefinition[], config: OpenApiConfig, base: string): OpenApiDocument;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an OpenAPI 3.0 document from Keel's own route table. Every registered
|
|
3
|
+
* route already carries its methods, path, name, param constraints, and any
|
|
4
|
+
* `.config(apiDoc(...))` metadata — that's the whole input. Paths and methods
|
|
5
|
+
* come for free; request/response schemas come from the docs metadata a route
|
|
6
|
+
* chose to attach. A route with no metadata still appears, just bare.
|
|
7
|
+
*/
|
|
8
|
+
import { OPENAPI_KEY } from "./doc.js";
|
|
9
|
+
import { toJsonSchema } from "./zod.js";
|
|
10
|
+
/** `/users/:id` (and `:id?`) → `/users/{id}` — OpenAPI's path-template syntax. */
|
|
11
|
+
function toTemplate(path) {
|
|
12
|
+
return path.replace(/:([A-Za-z0-9_]+)\??/g, "{$1}");
|
|
13
|
+
}
|
|
14
|
+
/** The `:param` names in a path, in order. */
|
|
15
|
+
function paramNames(path) {
|
|
16
|
+
return [...path.matchAll(/:([A-Za-z0-9_]+)\??/g)].map((m) => m[1]);
|
|
17
|
+
}
|
|
18
|
+
/** An operationId that's stable and unique: the route name, else method + path. */
|
|
19
|
+
function operationId(route, method) {
|
|
20
|
+
if (route.name)
|
|
21
|
+
return route.name;
|
|
22
|
+
const slug = route.path.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
23
|
+
return `${method.toLowerCase()}_${slug || "root"}`;
|
|
24
|
+
}
|
|
25
|
+
/** Path parameters — from the URL template, enriched by any `params` schema/`where`. */
|
|
26
|
+
function pathParameters(route, doc) {
|
|
27
|
+
const paramsSchema = doc?.request?.params ? toJsonSchema(doc.request.params) : undefined;
|
|
28
|
+
const props = (paramsSchema?.properties ?? {});
|
|
29
|
+
return paramNames(route.path).map((name) => {
|
|
30
|
+
const schema = props[name] ?? { type: "string" };
|
|
31
|
+
// A `.where("id", /\d+/)` constraint becomes a pattern hint.
|
|
32
|
+
if (route.wheres[name] && !("pattern" in schema)) {
|
|
33
|
+
schema.pattern = `^${route.wheres[name]}$`;
|
|
34
|
+
}
|
|
35
|
+
return { name, in: "path", required: true, schema };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Query parameters — expanded from a `query` schema's top-level properties. */
|
|
39
|
+
function queryParameters(doc) {
|
|
40
|
+
const schema = doc?.request?.query ? toJsonSchema(doc.request.query) : undefined;
|
|
41
|
+
if (!schema?.properties)
|
|
42
|
+
return [];
|
|
43
|
+
const required = new Set(schema.required ?? []);
|
|
44
|
+
return Object.entries(schema.properties).map(([name, propSchema]) => ({
|
|
45
|
+
name,
|
|
46
|
+
in: "query",
|
|
47
|
+
required: required.has(name),
|
|
48
|
+
schema: propSchema,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
const HAS_BODY = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
52
|
+
/** Build one operation object for a (route, method) pair. */
|
|
53
|
+
function buildOperation(route, method, doc) {
|
|
54
|
+
const op = {
|
|
55
|
+
operationId: doc?.operationId ?? operationId(route, method),
|
|
56
|
+
tags: doc?.tags ?? [defaultTag(route.path)],
|
|
57
|
+
parameters: [...pathParameters(route, doc), ...queryParameters(doc)],
|
|
58
|
+
};
|
|
59
|
+
if (doc?.summary)
|
|
60
|
+
op.summary = doc.summary;
|
|
61
|
+
if (doc?.description)
|
|
62
|
+
op.description = doc.description;
|
|
63
|
+
if (doc?.deprecated)
|
|
64
|
+
op.deprecated = true;
|
|
65
|
+
const body = doc?.request?.body ? toJsonSchema(doc.request.body) : undefined;
|
|
66
|
+
if (body && HAS_BODY.has(method)) {
|
|
67
|
+
op.requestBody = { required: true, content: { "application/json": { schema: body } } };
|
|
68
|
+
}
|
|
69
|
+
op.responses = buildResponses(doc);
|
|
70
|
+
return op;
|
|
71
|
+
}
|
|
72
|
+
/** Responses from the docs, or a sensible default, plus a 422 when input is validated. */
|
|
73
|
+
function buildResponses(doc) {
|
|
74
|
+
const responses = {};
|
|
75
|
+
if (doc?.responses) {
|
|
76
|
+
for (const [status, res] of Object.entries(doc.responses)) {
|
|
77
|
+
const schema = res.schema ? toJsonSchema(res.schema) : undefined;
|
|
78
|
+
responses[status] = {
|
|
79
|
+
description: res.description ?? "",
|
|
80
|
+
...(schema ? { content: { "application/json": { schema } } } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!Object.keys(responses).length)
|
|
85
|
+
responses["200"] = { description: "OK" };
|
|
86
|
+
// A documented request means validation can reject it.
|
|
87
|
+
if (doc?.request && !responses["422"]) {
|
|
88
|
+
responses["422"] = { description: "Validation failed" };
|
|
89
|
+
}
|
|
90
|
+
return responses;
|
|
91
|
+
}
|
|
92
|
+
/** The tag for an undocumented route: its first path segment. */
|
|
93
|
+
function defaultTag(path) {
|
|
94
|
+
const seg = path.split("/").filter(Boolean)[0] ?? "default";
|
|
95
|
+
return seg.startsWith(":") ? "default" : seg;
|
|
96
|
+
}
|
|
97
|
+
/** Whether a route is left out of the spec. */
|
|
98
|
+
function skip(route, config, base) {
|
|
99
|
+
const doc = route.config?.[OPENAPI_KEY];
|
|
100
|
+
if (doc?.hidden)
|
|
101
|
+
return true;
|
|
102
|
+
if (route.path.includes("*"))
|
|
103
|
+
return true; // wildcard/asset routes aren't API endpoints
|
|
104
|
+
if (route.path === base || route.path.startsWith(`${base}/`))
|
|
105
|
+
return true; // the docs' own routes
|
|
106
|
+
return config.ignorePaths.some((p) => route.path.startsWith(p));
|
|
107
|
+
}
|
|
108
|
+
/** Assemble the whole document from the route table. */
|
|
109
|
+
export function buildSpec(routes, config, base) {
|
|
110
|
+
const paths = {};
|
|
111
|
+
for (const route of routes) {
|
|
112
|
+
if (skip(route, config, base))
|
|
113
|
+
continue;
|
|
114
|
+
const doc = route.config?.[OPENAPI_KEY];
|
|
115
|
+
const template = toTemplate(route.path);
|
|
116
|
+
for (const method of route.methods) {
|
|
117
|
+
if (method === "HEAD" || method === "OPTIONS")
|
|
118
|
+
continue;
|
|
119
|
+
(paths[template] ??= {})[method.toLowerCase()] = buildOperation(route, method, doc);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
openapi: "3.0.3",
|
|
124
|
+
info: {
|
|
125
|
+
title: config.title,
|
|
126
|
+
version: config.version,
|
|
127
|
+
...(config.description ? { description: config.description } : {}),
|
|
128
|
+
},
|
|
129
|
+
...(config.servers.length ? { servers: config.servers.map((url) => ({ url })) } : {}),
|
|
130
|
+
paths,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The docs UI: a Swagger UI shell pointed at the generated spec. Swagger UI's
|
|
3
|
+
* assets load from the configured `cdn` (pin it, or point it at a copy you host)
|
|
4
|
+
* — the one external dependency in this package, kept to the UI. The spec itself
|
|
5
|
+
* (`/openapi.json`) is generated with zero dependencies and works anywhere.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenApiConfig } from "./config.js";
|
|
8
|
+
export declare function swaggerHtml(specUrl: string, cfg: OpenApiConfig): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The docs UI: a Swagger UI shell pointed at the generated spec. Swagger UI's
|
|
3
|
+
* assets load from the configured `cdn` (pin it, or point it at a copy you host)
|
|
4
|
+
* — the one external dependency in this package, kept to the UI. The spec itself
|
|
5
|
+
* (`/openapi.json`) is generated with zero dependencies and works anywhere.
|
|
6
|
+
*/
|
|
7
|
+
import { escapeHtml } from "../core/template.js";
|
|
8
|
+
export function swaggerHtml(specUrl, cfg) {
|
|
9
|
+
const cdn = cfg.cdn.replace(/\/+$/, "");
|
|
10
|
+
return `<!doctype html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8" />
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
15
|
+
<title>${escapeHtml(cfg.title)} — API docs</title>
|
|
16
|
+
<link rel="stylesheet" href="${cdn}/swagger-ui.css" />
|
|
17
|
+
<style>body { margin: 0; }</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="swagger-ui"></div>
|
|
21
|
+
<script src="${cdn}/swagger-ui-bundle.js" crossorigin></script>
|
|
22
|
+
<script>
|
|
23
|
+
window.ui = SwaggerUIBundle({
|
|
24
|
+
url: ${JSON.stringify(specUrl)},
|
|
25
|
+
dom_id: "#swagger-ui",
|
|
26
|
+
deepLinking: true,
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema conversion. A route documents itself with the same Zod schemas it
|
|
3
|
+
* validates with; the spec needs JSON Schema. Zod 4 ships that conversion
|
|
4
|
+
* (`z.toJSONSchema`), so this is a thin, defensive wrapper: Zod schemas are
|
|
5
|
+
* converted (targeting OpenAPI 3.0's dialect), plain JSON Schema objects pass
|
|
6
|
+
* through untouched, and anything unrecognized yields `undefined` rather than
|
|
7
|
+
* throwing — a missing schema shouldn't sink the whole document.
|
|
8
|
+
*/
|
|
9
|
+
type JsonSchema = Record<string, unknown>;
|
|
10
|
+
/** Convert a Zod schema or JSON Schema to an OpenAPI-flavoured JSON Schema. */
|
|
11
|
+
export declare function toJsonSchema(schema: unknown): JsonSchema | undefined;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema conversion. A route documents itself with the same Zod schemas it
|
|
3
|
+
* validates with; the spec needs JSON Schema. Zod 4 ships that conversion
|
|
4
|
+
* (`z.toJSONSchema`), so this is a thin, defensive wrapper: Zod schemas are
|
|
5
|
+
* converted (targeting OpenAPI 3.0's dialect), plain JSON Schema objects pass
|
|
6
|
+
* through untouched, and anything unrecognized yields `undefined` rather than
|
|
7
|
+
* throwing — a missing schema shouldn't sink the whole document.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
/** Duck-type a Zod (or Zod-like) schema. */
|
|
11
|
+
function isZod(value) {
|
|
12
|
+
return (!!value &&
|
|
13
|
+
typeof value.parse === "function" &&
|
|
14
|
+
typeof value.safeParse === "function");
|
|
15
|
+
}
|
|
16
|
+
/** A value that already looks like a JSON Schema (or a `$ref`). */
|
|
17
|
+
function isJsonSchema(value) {
|
|
18
|
+
if (!value || typeof value !== "object" || isZod(value))
|
|
19
|
+
return false;
|
|
20
|
+
const keys = ["type", "$ref", "properties", "items", "oneOf", "anyOf", "allOf", "enum"];
|
|
21
|
+
return keys.some((k) => k in value);
|
|
22
|
+
}
|
|
23
|
+
/** Convert a Zod schema or JSON Schema to an OpenAPI-flavoured JSON Schema. */
|
|
24
|
+
export function toJsonSchema(schema) {
|
|
25
|
+
if (!schema)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (isJsonSchema(schema))
|
|
28
|
+
return schema;
|
|
29
|
+
if (!isZod(schema))
|
|
30
|
+
return undefined;
|
|
31
|
+
const convert = z.toJSONSchema;
|
|
32
|
+
if (typeof convert !== "function")
|
|
33
|
+
return undefined;
|
|
34
|
+
try {
|
|
35
|
+
// OpenAPI 3.0 uses `nullable` and a few other divergences from raw JSON Schema.
|
|
36
|
+
return convert(schema, { target: "openapi-3.0", io: "input" });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
try {
|
|
40
|
+
return convert(schema);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch configuration. Defaults live here and are merged under `config("watch")`
|
|
3
|
+
* by the provider; an app overrides any of them in `config/watch.ts` (publish it
|
|
4
|
+
* with `keel vendor:publish --tag watch-config`).
|
|
5
|
+
*/
|
|
6
|
+
import type { EntryType } from "./entry.js";
|
|
7
|
+
export interface WatchConfig {
|
|
8
|
+
/** Master switch. Off → no watchers install and the dashboard 404s. */
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
/** URL prefix the dashboard and API mount under. Default: "watch". */
|
|
11
|
+
path: string;
|
|
12
|
+
/** Where entries live. "database" persists them; "memory" is a per-process ring. */
|
|
13
|
+
storage: "database" | "memory";
|
|
14
|
+
/** The database connection to use (when storage is "database"). */
|
|
15
|
+
connection?: string;
|
|
16
|
+
/** The table entries are stored in. */
|
|
17
|
+
table: string;
|
|
18
|
+
/** Ring size for the memory store, and the default page size for the API. */
|
|
19
|
+
limit: number;
|
|
20
|
+
/** Record only this fraction of eligible entries (0–1). 1 records everything. */
|
|
21
|
+
sampling: number;
|
|
22
|
+
/** Tag queries slower than this (ms) with `slow`. */
|
|
23
|
+
slowQueryMs: number;
|
|
24
|
+
/** Request path prefixes to never record (the dashboard's own are always skipped). */
|
|
25
|
+
ignorePaths: string[];
|
|
26
|
+
/** Delete entries older than this many hours when `watch:prune` runs. */
|
|
27
|
+
retentionHours: number;
|
|
28
|
+
/** Per-type on/off switches. Noisy watchers (cache, event) default off. */
|
|
29
|
+
watchers: Record<EntryType, boolean>;
|
|
30
|
+
}
|
|
31
|
+
export declare const defaultConfig: WatchConfig;
|
|
32
|
+
/** Read the effective Watch config off the application, filling any gaps. */
|
|
33
|
+
export declare function resolveConfig(): WatchConfig;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch configuration. Defaults live here and are merged under `config("watch")`
|
|
3
|
+
* by the provider; an app overrides any of them in `config/watch.ts` (publish it
|
|
4
|
+
* with `keel vendor:publish --tag watch-config`).
|
|
5
|
+
*/
|
|
6
|
+
import { config } from "../core/helpers.js";
|
|
7
|
+
export const defaultConfig = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
path: "watch",
|
|
10
|
+
storage: "database",
|
|
11
|
+
table: "watch_entries",
|
|
12
|
+
limit: 100,
|
|
13
|
+
sampling: 1,
|
|
14
|
+
slowQueryMs: 100,
|
|
15
|
+
ignorePaths: [],
|
|
16
|
+
retentionHours: 24,
|
|
17
|
+
watchers: {
|
|
18
|
+
request: true,
|
|
19
|
+
query: true,
|
|
20
|
+
exception: true,
|
|
21
|
+
log: true,
|
|
22
|
+
mail: true,
|
|
23
|
+
job: true,
|
|
24
|
+
notification: true,
|
|
25
|
+
cache: false,
|
|
26
|
+
event: false,
|
|
27
|
+
schedule: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/** Read the effective Watch config off the application, filling any gaps. */
|
|
31
|
+
export function resolveConfig() {
|
|
32
|
+
const raw = config("watch", {});
|
|
33
|
+
return {
|
|
34
|
+
...defaultConfig,
|
|
35
|
+
...raw,
|
|
36
|
+
watchers: { ...defaultConfig.watchers, ...(raw.watchers ?? {}) },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A recorded entry — one thing that happened in the app: a request, a query, an
|
|
3
|
+
* exception, a log line. Every watcher produces these; the store persists them;
|
|
4
|
+
* the dashboard reads them back.
|
|
5
|
+
*
|
|
6
|
+
* Two ids give the UI its shape. `batchId` ties an entry to the request (or job,
|
|
7
|
+
* or scheduled run) it happened inside, so the dashboard can show a request and
|
|
8
|
+
* every query, log, and exception it produced. `familyHash` groups entries that
|
|
9
|
+
* are "the same" — the same SQL shape, the same exception — so recurring things
|
|
10
|
+
* can be counted and collapsed.
|
|
11
|
+
*/
|
|
12
|
+
export type EntryType = "request" | "query" | "exception" | "log" | "mail" | "job" | "notification" | "cache" | "event" | "schedule";
|
|
13
|
+
/** Every entry type, in the order the dashboard shows its tabs. */
|
|
14
|
+
export declare const ENTRY_TYPES: EntryType[];
|
|
15
|
+
export interface Entry {
|
|
16
|
+
/** Unique id (32 hex). */
|
|
17
|
+
uuid: string;
|
|
18
|
+
/** The request/job/run this happened inside; groups related entries. */
|
|
19
|
+
batchId: string;
|
|
20
|
+
type: EntryType;
|
|
21
|
+
/** Groups "the same" entry — same SQL shape, same exception class+message. */
|
|
22
|
+
familyHash?: string;
|
|
23
|
+
/** The type-specific detail the dashboard renders. Always JSON-safe. */
|
|
24
|
+
content: Record<string, unknown>;
|
|
25
|
+
/** Free-form labels for filtering: `status:500`, `slow`, `connection:default`. */
|
|
26
|
+
tags: string[];
|
|
27
|
+
/** Epoch milliseconds. */
|
|
28
|
+
createdAt: number;
|
|
29
|
+
}
|
|
30
|
+
/** A filter over stored entries, for the list endpoint. */
|
|
31
|
+
export interface EntryFilter {
|
|
32
|
+
type?: EntryType;
|
|
33
|
+
tag?: string;
|
|
34
|
+
batchId?: string;
|
|
35
|
+
/** Keyset pagination: only entries created strictly before this timestamp. */
|
|
36
|
+
before?: number;
|
|
37
|
+
limit?: number;
|
|
38
|
+
}
|
|
39
|
+
/** A 32-hex id for an entry. */
|
|
40
|
+
export declare function newUuid(): string;
|
|
41
|
+
/** A short, stable hash (8 hex) of a string — for `familyHash`. */
|
|
42
|
+
export declare function familyHash(input: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Make an arbitrary value safe to store as JSON: unwrap Errors, truncate long
|
|
45
|
+
* strings, drop functions, and break cycles. Watchers hand user data (request
|
|
46
|
+
* bodies, job payloads, event payloads) straight through, so this is the barrier
|
|
47
|
+
* that keeps one weird value from poisoning the whole store.
|
|
48
|
+
*/
|
|
49
|
+
export declare function jsonSafe(value: unknown, seen?: WeakSet<object>): unknown;
|
|
50
|
+
/** Redact auth/cookie headers before an entry is stored or shown. */
|
|
51
|
+
export declare function redactHeaders(headers: Record<string, string>): Record<string, string>;
|
|
52
|
+
/** Collapse a SQL statement to its shape, so `id = 1` and `id = 2` group together. */
|
|
53
|
+
export declare function sqlShape(sql: string): string;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A recorded entry — one thing that happened in the app: a request, a query, an
|
|
3
|
+
* exception, a log line. Every watcher produces these; the store persists them;
|
|
4
|
+
* the dashboard reads them back.
|
|
5
|
+
*
|
|
6
|
+
* Two ids give the UI its shape. `batchId` ties an entry to the request (or job,
|
|
7
|
+
* or scheduled run) it happened inside, so the dashboard can show a request and
|
|
8
|
+
* every query, log, and exception it produced. `familyHash` groups entries that
|
|
9
|
+
* are "the same" — the same SQL shape, the same exception — so recurring things
|
|
10
|
+
* can be counted and collapsed.
|
|
11
|
+
*/
|
|
12
|
+
/** Every entry type, in the order the dashboard shows its tabs. */
|
|
13
|
+
export const ENTRY_TYPES = [
|
|
14
|
+
"request",
|
|
15
|
+
"query",
|
|
16
|
+
"exception",
|
|
17
|
+
"log",
|
|
18
|
+
"mail",
|
|
19
|
+
"job",
|
|
20
|
+
"notification",
|
|
21
|
+
"cache",
|
|
22
|
+
"event",
|
|
23
|
+
"schedule",
|
|
24
|
+
];
|
|
25
|
+
/* --------------------------------- ids ------------------------------------ */
|
|
26
|
+
/** A 32-hex id for an entry. */
|
|
27
|
+
export function newUuid() {
|
|
28
|
+
const bytes = new Uint8Array(16);
|
|
29
|
+
crypto.getRandomValues(bytes);
|
|
30
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
31
|
+
}
|
|
32
|
+
/** A short, stable hash (8 hex) of a string — for `familyHash`. */
|
|
33
|
+
export function familyHash(input) {
|
|
34
|
+
// FNV-1a: tiny, dependency-free, good enough to group like with like.
|
|
35
|
+
let h = 0x811c9dc5;
|
|
36
|
+
for (let i = 0; i < input.length; i++) {
|
|
37
|
+
h ^= input.charCodeAt(i);
|
|
38
|
+
h = Math.imul(h, 0x01000193);
|
|
39
|
+
}
|
|
40
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
41
|
+
}
|
|
42
|
+
/* ------------------------------- serializing ------------------------------ */
|
|
43
|
+
const MAX_STRING = 8_000;
|
|
44
|
+
/**
|
|
45
|
+
* Make an arbitrary value safe to store as JSON: unwrap Errors, truncate long
|
|
46
|
+
* strings, drop functions, and break cycles. Watchers hand user data (request
|
|
47
|
+
* bodies, job payloads, event payloads) straight through, so this is the barrier
|
|
48
|
+
* that keeps one weird value from poisoning the whole store.
|
|
49
|
+
*/
|
|
50
|
+
export function jsonSafe(value, seen = new WeakSet()) {
|
|
51
|
+
if (value === null || value === undefined)
|
|
52
|
+
return value ?? null;
|
|
53
|
+
const t = typeof value;
|
|
54
|
+
if (t === "string") {
|
|
55
|
+
const s = value;
|
|
56
|
+
return s.length > MAX_STRING ? `${s.slice(0, MAX_STRING)}… (${s.length} chars)` : s;
|
|
57
|
+
}
|
|
58
|
+
if (t === "number" || t === "boolean")
|
|
59
|
+
return value;
|
|
60
|
+
if (t === "bigint")
|
|
61
|
+
return `${value.toString()}n`;
|
|
62
|
+
if (t === "function" || t === "symbol")
|
|
63
|
+
return `[${t}]`;
|
|
64
|
+
if (value instanceof Error) {
|
|
65
|
+
return {
|
|
66
|
+
name: value.name,
|
|
67
|
+
message: value.message,
|
|
68
|
+
stack: (value.stack ?? "").split("\n").map((l) => l.trim()),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (value instanceof Date)
|
|
72
|
+
return value.toISOString();
|
|
73
|
+
if (typeof value === "object") {
|
|
74
|
+
if (seen.has(value))
|
|
75
|
+
return "[Circular]";
|
|
76
|
+
seen.add(value);
|
|
77
|
+
if (Array.isArray(value))
|
|
78
|
+
return value.slice(0, 200).map((v) => jsonSafe(v, seen));
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [k, v] of Object.entries(value)) {
|
|
81
|
+
out[k] = jsonSafe(v, seen);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
return String(value);
|
|
86
|
+
}
|
|
87
|
+
const REDACTED = "[redacted]";
|
|
88
|
+
const SENSITIVE_HEADERS = new Set(["authorization", "cookie", "set-cookie", "proxy-authorization"]);
|
|
89
|
+
/** Redact auth/cookie headers before an entry is stored or shown. */
|
|
90
|
+
export function redactHeaders(headers) {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
93
|
+
out[k] = SENSITIVE_HEADERS.has(k.toLowerCase()) ? REDACTED : v;
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Collapse a SQL statement to its shape, so `id = 1` and `id = 2` group together. */
|
|
98
|
+
export function sqlShape(sql) {
|
|
99
|
+
return sql
|
|
100
|
+
.replace(/\s+/g, " ")
|
|
101
|
+
.replace(/'[^']*'/g, "?")
|
|
102
|
+
.replace(/\b\d+\b/g, "?")
|
|
103
|
+
.trim()
|
|
104
|
+
.toLowerCase();
|
|
105
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may see the dashboard. It exposes requests, payloads, and stack traces, so
|
|
3
|
+
* it must never be world-readable in production. By default it's open only when
|
|
4
|
+
* `app.debug` is on or the app isn't in production; call `Watch.auth()` to plug
|
|
5
|
+
* in your own check (an admin session, an allow-list) — mirrors Telescope's gate.
|
|
6
|
+
*/
|
|
7
|
+
import type { Ctx } from "../core/http/router.js";
|
|
8
|
+
export type WatchGate = (c: Ctx) => boolean | Promise<boolean>;
|
|
9
|
+
export declare const Watch: {
|
|
10
|
+
/**
|
|
11
|
+
* Restrict dashboard access. Return true to allow the request.
|
|
12
|
+
*
|
|
13
|
+
* Watch.auth((c) => auth().check() && auth().user()?.isAdmin);
|
|
14
|
+
*/
|
|
15
|
+
auth(fn: WatchGate): void;
|
|
16
|
+
/** Remove a custom gate, reverting to the default. */
|
|
17
|
+
clearAuth(): void;
|
|
18
|
+
};
|
|
19
|
+
/** Whether the current request may see the dashboard. */
|
|
20
|
+
export declare function passesGate(c: Ctx): Promise<boolean>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may see the dashboard. It exposes requests, payloads, and stack traces, so
|
|
3
|
+
* it must never be world-readable in production. By default it's open only when
|
|
4
|
+
* `app.debug` is on or the app isn't in production; call `Watch.auth()` to plug
|
|
5
|
+
* in your own check (an admin session, an allow-list) — mirrors Telescope's gate.
|
|
6
|
+
*/
|
|
7
|
+
import { config } from "../core/helpers.js";
|
|
8
|
+
let gate;
|
|
9
|
+
export const Watch = {
|
|
10
|
+
/**
|
|
11
|
+
* Restrict dashboard access. Return true to allow the request.
|
|
12
|
+
*
|
|
13
|
+
* Watch.auth((c) => auth().check() && auth().user()?.isAdmin);
|
|
14
|
+
*/
|
|
15
|
+
auth(fn) {
|
|
16
|
+
gate = fn;
|
|
17
|
+
},
|
|
18
|
+
/** Remove a custom gate, reverting to the default. */
|
|
19
|
+
clearAuth() {
|
|
20
|
+
gate = undefined;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/** Allow when debugging, or anywhere that isn't production. */
|
|
24
|
+
function defaultGate() {
|
|
25
|
+
if (config("app.debug", false))
|
|
26
|
+
return true;
|
|
27
|
+
return config("app.env", "production") !== "production";
|
|
28
|
+
}
|
|
29
|
+
/** Whether the current request may see the dashboard. */
|
|
30
|
+
export async function passesGate(c) {
|
|
31
|
+
return gate ? gate(c) : defaultGate();
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keel Watch — a debug dashboard for Keel apps, imported from
|
|
3
|
+
* `@shaferllc/keel/watch`. Register the provider and open `/watch`:
|
|
4
|
+
*
|
|
5
|
+
* import { WatchServiceProvider } from "@shaferllc/keel/watch";
|
|
6
|
+
* app.register(WatchServiceProvider);
|
|
7
|
+
*
|
|
8
|
+
* Lock down who can see it with `Watch.auth()`.
|
|
9
|
+
*/
|
|
10
|
+
export { WatchServiceProvider } from "./provider.js";
|
|
11
|
+
export { Watch } from "./gate.js";
|
|
12
|
+
export type { WatchGate } from "./gate.js";
|
|
13
|
+
export { resolveConfig, defaultConfig } from "./config.js";
|
|
14
|
+
export type { WatchConfig } from "./config.js";
|
|
15
|
+
export { MemoryStore, DatabaseStore } from "./store.js";
|
|
16
|
+
export type { EntryStore } from "./store.js";
|
|
17
|
+
export { watchMigration } from "./migration.js";
|
|
18
|
+
export { Recorder } from "./recorder.js";
|
|
19
|
+
export { installWatchers } from "./watchers.js";
|
|
20
|
+
export { ENTRY_TYPES, newUuid, familyHash, jsonSafe, redactHeaders, sqlShape, } from "./entry.js";
|
|
21
|
+
export type { Entry, EntryType, EntryFilter } from "./entry.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keel Watch — a debug dashboard for Keel apps, imported from
|
|
3
|
+
* `@shaferllc/keel/watch`. Register the provider and open `/watch`:
|
|
4
|
+
*
|
|
5
|
+
* import { WatchServiceProvider } from "@shaferllc/keel/watch";
|
|
6
|
+
* app.register(WatchServiceProvider);
|
|
7
|
+
*
|
|
8
|
+
* Lock down who can see it with `Watch.auth()`.
|
|
9
|
+
*/
|
|
10
|
+
export { WatchServiceProvider } from "./provider.js";
|
|
11
|
+
export { Watch } from "./gate.js";
|
|
12
|
+
export { resolveConfig, defaultConfig } from "./config.js";
|
|
13
|
+
export { MemoryStore, DatabaseStore } from "./store.js";
|
|
14
|
+
export { watchMigration } from "./migration.js";
|
|
15
|
+
export { Recorder } from "./recorder.js";
|
|
16
|
+
export { installWatchers } from "./watchers.js";
|
|
17
|
+
export { ENTRY_TYPES, newUuid, familyHash, jsonSafe, redactHeaders, sqlShape, } from "./entry.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `watch_entries` table. Contributed by the provider via `this.migrations()`
|
|
3
|
+
* and run by `keel migrate` alongside the app's own — the same path any package
|
|
4
|
+
* ships schema on.
|
|
5
|
+
*/
|
|
6
|
+
import type { Migration } from "../core/migrations.js";
|
|
7
|
+
export declare function watchMigration(table?: string): Migration;
|