@shaferllc/keel 0.68.0 → 0.77.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 +2 -0
- package/README.md +15 -5
- 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 +93 -0
- package/dist/api/resource.js +185 -0
- package/dist/core/application.js +6 -0
- package/dist/core/cache.d.ts +1 -2
- package/dist/core/cache.js +9 -2
- package/dist/core/cli/index.d.ts +28 -0
- package/dist/core/cli/index.js +427 -0
- package/dist/core/cli/stubs.d.ts +14 -0
- package/dist/core/cli/stubs.js +105 -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/health.d.ts +2 -2
- package/dist/core/health.js +2 -2
- 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 +25 -4
- package/dist/core/index.js +13 -3
- package/dist/core/instrumentation.d.ts +113 -0
- package/dist/core/instrumentation.js +52 -0
- package/dist/core/logger.d.ts +7 -0
- package/dist/core/logger.js +28 -1
- package/dist/core/notification.js +10 -1
- package/dist/core/package.d.ts +117 -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.js +26 -5
- 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.js +15 -3
- 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 +171 -1
- package/dist/core/testing.js +505 -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/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 +22 -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 +737 -3
- package/docs/api-resources.md +119 -0
- package/docs/configuration.md +74 -0
- package/docs/console.md +217 -13
- package/docs/database.md +101 -0
- package/docs/examples/api-resources.ts +86 -0
- package/docs/examples/configuration.ts +40 -0
- package/docs/examples/console.ts +134 -0
- package/docs/examples/database.ts +90 -0
- package/docs/examples/i18n.ts +117 -0
- package/docs/examples/logger.ts +74 -0
- package/docs/examples/mail.ts +103 -0
- package/docs/examples/pages.ts +82 -0
- package/docs/examples/telemetry.ts +127 -0
- package/docs/examples/testing.ts +150 -0
- package/docs/health.md +4 -4
- package/docs/i18n.md +302 -0
- package/docs/logger.md +156 -10
- package/docs/mail.md +73 -0
- package/docs/openapi.md +111 -0
- package/docs/packages.md +118 -0
- package/docs/pages.md +217 -0
- package/docs/storage.md +5 -3
- package/docs/telemetry.md +263 -0
- package/docs/templates.md +3 -4
- package/docs/testing.md +252 -0
- package/docs/watch.md +118 -0
- package/llms-full.txt +2389 -241
- package/llms.txt +13 -1
- package/package.json +32 -6
package/dist/core/index.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ export type { Token, Constructor, Factory } from "./container.js";
|
|
|
4
4
|
export { Application } from "./application.js";
|
|
5
5
|
export type { BootOptions, LifecycleHook, Configurator } from "./application.js";
|
|
6
6
|
export { Config, env } from "./config.js";
|
|
7
|
+
export { defineEnv, envVar, EnvValidationError } from "./env.js";
|
|
8
|
+
export type { EnvRule, EnvSchema, EnvValues, EnvType, DefineEnvOptions } from "./env.js";
|
|
7
9
|
export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
8
10
|
export { Logger } from "./logger.js";
|
|
9
|
-
export { consoleSink, MemorySink, setLogger, namedLogger } from "./logger.js";
|
|
11
|
+
export { consoleSink, MemorySink, setLogger, namedLogger, tapLogs } from "./logger.js";
|
|
10
12
|
export type { LogLevel, LoggerOptions, LogRecord, Sink, RedactOptions } from "./logger.js";
|
|
11
13
|
export { requestLogger, requestLog } from "./request-logger.js";
|
|
12
14
|
export type { RequestLoggerOptions } from "./request-logger.js";
|
|
@@ -16,6 +18,8 @@ export { Cache, MemoryStore } from "./cache.js";
|
|
|
16
18
|
export type { CacheStore, RememberOptions, PutOptions } from "./cache.js";
|
|
17
19
|
export { serveStatic } from "./static.js";
|
|
18
20
|
export type { StaticOptions } from "./static.js";
|
|
21
|
+
export { i18n, t, getI18n, setI18n, setTranslations, objectLoader, detectLocale, negotiateLocale, formatMessage, I18n, I18nManager, } from "./i18n.js";
|
|
22
|
+
export type { Translations, TranslationsByLocale, TranslationLoader, I18nOptions, DetectLocaleOptions, } from "./i18n.js";
|
|
19
23
|
export { lock, restoreLock, setLockStore, getLockStore, Lock, MemoryLockStore, LockNotHeldError, } from "./lock.js";
|
|
20
24
|
export type { LockStore, AcquireOptions } from "./lock.js";
|
|
21
25
|
export { health, healthCheck, check, Result, BaseCheck, HealthChecks, DatabaseCheck, RedisCheck, CacheCheck, } from "./health.js";
|
|
@@ -33,8 +37,8 @@ export { securityHeaders } from "./shield.js";
|
|
|
33
37
|
export type { SecurityHeadersOptions, HstsOptions } from "./shield.js";
|
|
34
38
|
export { csrf, csrfToken, csrfField } from "./csrf.js";
|
|
35
39
|
export type { CsrfOptions } from "./csrf.js";
|
|
36
|
-
export { db, connection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, QueryBuilder, } from "./database.js";
|
|
37
|
-
export type { Connection, ConnectionHandle, WriteResult, Row, Dialect, Operator, Paginated } from "./database.js";
|
|
40
|
+
export { db, connection, getConnection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, transaction, inTransaction, QueryBuilder, } from "./database.js";
|
|
41
|
+
export type { Connection, TransactionConnection, TransactionHandle, ConnectionHandle, WriteResult, Row, Dialect, Operator, Paginated, } from "./database.js";
|
|
38
42
|
export { Model } from "./model.js";
|
|
39
43
|
export type { CastType, Casts } from "./casts.js";
|
|
40
44
|
export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
|
|
@@ -63,12 +67,29 @@ export { TemplateEngine, escapeHtml, templates, setTemplateEngine, render, } fro
|
|
|
63
67
|
export type { Filter, RenderContext } from "./template.js";
|
|
64
68
|
export { ServiceProvider } from "./provider.js";
|
|
65
69
|
export type { ProviderClass } from "./provider.js";
|
|
70
|
+
export { PackageProvider, MigrationRegistry, CommandRegistry, PublishRegistry, } from "./package.js";
|
|
71
|
+
export type { PackageCommand, PublishEntry, PackageRouteOptions, PackageAssetOptions, PackageMiddleware, } from "./package.js";
|
|
72
|
+
export { instrument, runRequest, currentRequestId, newRequestId, } from "./instrumentation.js";
|
|
73
|
+
export type { QueryEvent, RequestEvent, ExceptionEvent, JobEvent, CacheEvent, NotificationEvent, ScheduleEvent, InstrumentEvent, } from "./instrumentation.js";
|
|
66
74
|
export { Router, Route, RouteGroup, RouteResource, matchers } from "./http/router.js";
|
|
67
75
|
export type { Ctx, RouteHandler, RouteDefinition, Method, Matcher, MiddlewareRef, UrlOptions, SignedUrlOptions, } from "./http/router.js";
|
|
68
76
|
export { Inertia, inertia, inertiaPageAttr } from "./inertia.js";
|
|
69
77
|
export type { InertiaPage, InertiaOptions } from "./inertia.js";
|
|
70
78
|
export { HttpKernel } from "./http/kernel.js";
|
|
71
|
-
export {
|
|
79
|
+
export { defineCommand, arg, flag, ConsoleKernel, ConsoleError, parseArgv } from "./console.js";
|
|
80
|
+
export type { CommandDefinition, CommandContext, AnyCommand, ArgSpec, FlagSpec, ArgsSpec, FlagsSpec, ConsoleKernelOptions, } from "./console.js";
|
|
81
|
+
export { createUi, stripAnsi } from "./console-ui.js";
|
|
82
|
+
export type { Ui, UiOptions, Colors, ColorName, Table, Tasks, TaskHandle } from "./console-ui.js";
|
|
83
|
+
export { createPrompt } from "./console-prompt.js";
|
|
84
|
+
export type { Prompt, PromptOptions, ChoiceOptions, Choice, Trap } from "./console-prompt.js";
|
|
85
|
+
export { startRepl } from "./repl.js";
|
|
86
|
+
export type { ReplHelper, ReplOptions } from "./repl.js";
|
|
87
|
+
export { pages, definePages, routePattern, routeName } from "./pages.js";
|
|
88
|
+
export type { PageProps, PageModule, PagesOptions, RegisteredPage, PageMiddleware } from "./pages.js";
|
|
89
|
+
export { TestClient, TestResponse, CommandResult, testClient, runCommand, resetState, freezeTime, timeTravel, restoreTime, timeIsFrozen, spy, spyOn, restoreSpies, truncate, assertDatabaseHas, assertDatabaseMissing, assertDatabaseCount, assertDatabaseEmpty, } from "./testing.js";
|
|
90
|
+
export type { Spy } from "./testing.js";
|
|
91
|
+
export { Tracer, Span, MemoryExporter, otlpExporter, consoleExporter, telemetry, setTelemetry, trace, currentSpan, setAttributes, addEvent, traceIds, flushTelemetry, tracing, parseTraceparent, traceparent, injectTraceContext, } from "./telemetry.js";
|
|
92
|
+
export type { SpanContext, SpanData, SpanEvent, SpanKind, SpanStatus, SpanOptions, SpanExporter, SpanAttributes, SpanAttributeValue, TracerOptions, TracingOptions, OtlpOptions, } from "./telemetry.js";
|
|
72
93
|
export { HttpException, BadRequestException, UnauthorizedException, PaymentRequiredException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, LengthRequiredException, ValidationException, TooManyRequestsException, ServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, createError, STATUS_TEXT, } from "./exceptions.js";
|
|
73
94
|
export { validate, validateRequest, validated } from "./validation.js";
|
|
74
95
|
export type { Schema, RequestSchemas } from "./validation.js";
|
package/dist/core/index.js
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
export { Container } from "./container.js";
|
|
3
3
|
export { Application } from "./application.js";
|
|
4
4
|
export { Config, env } from "./config.js";
|
|
5
|
+
export { defineEnv, envVar, EnvValidationError } from "./env.js";
|
|
5
6
|
export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
|
|
6
7
|
export { Logger } from "./logger.js";
|
|
7
|
-
export { consoleSink, MemorySink, setLogger, namedLogger } from "./logger.js";
|
|
8
|
+
export { consoleSink, MemorySink, setLogger, namedLogger, tapLogs } from "./logger.js";
|
|
8
9
|
export { requestLogger, requestLog } from "./request-logger.js";
|
|
9
10
|
export { Events, EventBuffer } from "./events.js";
|
|
10
11
|
export { Cache, MemoryStore } from "./cache.js";
|
|
11
12
|
export { serveStatic } from "./static.js";
|
|
13
|
+
export { i18n, t, getI18n, setI18n, setTranslations, objectLoader, detectLocale, negotiateLocale, formatMessage, I18n, I18nManager, } from "./i18n.js";
|
|
12
14
|
export { lock, restoreLock, setLockStore, getLockStore, Lock, MemoryLockStore, LockNotHeldError, } from "./lock.js";
|
|
13
15
|
export { health, healthCheck, check, Result, BaseCheck, HealthChecks, DatabaseCheck, RedisCheck, CacheCheck, } from "./health.js";
|
|
14
16
|
export { Storage, FakeStorage, MemoryDisk, storage, setDisk, fakeDisk, restoreDisk, serveStorage, signStorageUrl, verifyStorageUrl, contentTypeFor, } from "./storage.js";
|
|
@@ -18,7 +20,7 @@ export { rateLimiter } from "./rate-limit.js";
|
|
|
18
20
|
export { cors } from "./cors.js";
|
|
19
21
|
export { securityHeaders } from "./shield.js";
|
|
20
22
|
export { csrf, csrfToken, csrfField } from "./csrf.js";
|
|
21
|
-
export { db, connection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, QueryBuilder, } from "./database.js";
|
|
23
|
+
export { db, connection, getConnection, setConnection, addConnection, setDefaultConnection, connectionNames, clearConnections, transaction, inTransaction, QueryBuilder, } from "./database.js";
|
|
22
24
|
export { Model } from "./model.js";
|
|
23
25
|
export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
|
|
24
26
|
export { Faker, Factory as ModelFactory, factory, Seeder, seed } from "./factory.js";
|
|
@@ -34,10 +36,18 @@ export { decorateRequest, hasRequestDecorator, decorated, setRequestValue, clear
|
|
|
34
36
|
export { View } from "./view.js";
|
|
35
37
|
export { TemplateEngine, escapeHtml, templates, setTemplateEngine, render, } from "./template.js";
|
|
36
38
|
export { ServiceProvider } from "./provider.js";
|
|
39
|
+
export { PackageProvider, MigrationRegistry, CommandRegistry, PublishRegistry, } from "./package.js";
|
|
40
|
+
export { instrument, runRequest, currentRequestId, newRequestId, } from "./instrumentation.js";
|
|
37
41
|
export { Router, Route, RouteGroup, RouteResource, matchers } from "./http/router.js";
|
|
38
42
|
export { Inertia, inertia, inertiaPageAttr } from "./inertia.js";
|
|
39
43
|
export { HttpKernel } from "./http/kernel.js";
|
|
40
|
-
export {
|
|
44
|
+
export { defineCommand, arg, flag, ConsoleKernel, ConsoleError, parseArgv } from "./console.js";
|
|
45
|
+
export { createUi, stripAnsi } from "./console-ui.js";
|
|
46
|
+
export { createPrompt } from "./console-prompt.js";
|
|
47
|
+
export { startRepl } from "./repl.js";
|
|
48
|
+
export { pages, definePages, routePattern, routeName } from "./pages.js";
|
|
49
|
+
export { TestClient, TestResponse, CommandResult, testClient, runCommand, resetState, freezeTime, timeTravel, restoreTime, timeIsFrozen, spy, spyOn, restoreSpies, truncate, assertDatabaseHas, assertDatabaseMissing, assertDatabaseCount, assertDatabaseEmpty, } from "./testing.js";
|
|
50
|
+
export { Tracer, Span, MemoryExporter, otlpExporter, consoleExporter, telemetry, setTelemetry, trace, currentSpan, setAttributes, addEvent, traceIds, flushTelemetry, tracing, parseTraceparent, traceparent, injectTraceContext, } from "./telemetry.js";
|
|
41
51
|
export { HttpException, BadRequestException, UnauthorizedException, PaymentRequiredException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, LengthRequiredException, ValidationException, TooManyRequestsException, ServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, createError, STATUS_TEXT, } from "./exceptions.js";
|
|
42
52
|
export { validate, validateRequest, validated } from "./validation.js";
|
|
43
53
|
export { Session, session, sessionMiddleware } from "./session.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework instrumentation — the typed event stream that lets a package observe
|
|
3
|
+
* the framework without patching it. Core seams (the query builder, the HTTP
|
|
4
|
+
* kernel, the queue, the cache, …) call `instrument(event, payload)`; anything
|
|
5
|
+
* that wants to watch subscribes with `listen()`. The mail layer already works
|
|
6
|
+
* this way (`mail.sent`); this generalises it.
|
|
7
|
+
*
|
|
8
|
+
* These events are deliberately fire-and-forget: `instrument()` never blocks the
|
|
9
|
+
* work it describes, and a listener that throws can't break a query or a
|
|
10
|
+
* request. That's the difference between instrumentation and business logic.
|
|
11
|
+
*
|
|
12
|
+
* A **request id** ties a request to everything that happened inside it — the
|
|
13
|
+
* queries it ran, the logs it wrote, the jobs it dispatched. The kernel opens a
|
|
14
|
+
* request scope with `runRequest(id, …)`; anything emitted inside it can read
|
|
15
|
+
* `currentRequestId()` to attribute itself. It flows through `await` chains via
|
|
16
|
+
* `AsyncLocalStorage`, so nothing has to be threaded by hand.
|
|
17
|
+
*/
|
|
18
|
+
/** A SQL statement that ran on a connection. */
|
|
19
|
+
export interface QueryEvent {
|
|
20
|
+
sql: string;
|
|
21
|
+
bindings: unknown[];
|
|
22
|
+
/** Wall-clock time the statement took, in milliseconds. */
|
|
23
|
+
durationMs: number;
|
|
24
|
+
/** The registered connection name it ran on. */
|
|
25
|
+
connection: string;
|
|
26
|
+
/** "select" (row-returning) or "write" (insert/update/delete/DDL). */
|
|
27
|
+
kind: "select" | "write";
|
|
28
|
+
/** The request that ran it, if any. */
|
|
29
|
+
requestId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A request that finished (or threw). */
|
|
32
|
+
export interface RequestEvent {
|
|
33
|
+
id: string;
|
|
34
|
+
method: string;
|
|
35
|
+
path: string;
|
|
36
|
+
status: number;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
/** Request headers, lower-cased (auth/cookies are redacted by watchers). */
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
ip?: string;
|
|
41
|
+
}
|
|
42
|
+
/** An error that reached the HTTP kernel. */
|
|
43
|
+
export interface ExceptionEvent {
|
|
44
|
+
error: unknown;
|
|
45
|
+
method?: string;
|
|
46
|
+
path?: string;
|
|
47
|
+
/** The status it rendered as. */
|
|
48
|
+
status: number;
|
|
49
|
+
requestId?: string;
|
|
50
|
+
}
|
|
51
|
+
/** A queued job at one point in its lifecycle. */
|
|
52
|
+
export interface JobEvent {
|
|
53
|
+
job: string;
|
|
54
|
+
payload?: unknown;
|
|
55
|
+
/** Set on `job.processed` / `job.failed`. */
|
|
56
|
+
durationMs?: number;
|
|
57
|
+
/** Set on `job.failed`. */
|
|
58
|
+
error?: unknown;
|
|
59
|
+
requestId?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A cache lookup. */
|
|
62
|
+
export interface CacheEvent {
|
|
63
|
+
key: string;
|
|
64
|
+
store: string;
|
|
65
|
+
requestId?: string;
|
|
66
|
+
}
|
|
67
|
+
/** A notification that was sent. */
|
|
68
|
+
export interface NotificationEvent {
|
|
69
|
+
notification: string;
|
|
70
|
+
channels: string[];
|
|
71
|
+
notifiable?: unknown;
|
|
72
|
+
requestId?: string;
|
|
73
|
+
}
|
|
74
|
+
/** A scheduled task that ran. */
|
|
75
|
+
export interface ScheduleEvent {
|
|
76
|
+
task: string;
|
|
77
|
+
durationMs: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register the instrumentation events on the framework's typed event registry,
|
|
81
|
+
* so `listen("db.query", (e) => …)` gets a fully-typed `e`. Any code that
|
|
82
|
+
* augments `EventsList` merges with this — declaring your own events elsewhere
|
|
83
|
+
* still works.
|
|
84
|
+
*/
|
|
85
|
+
declare module "./events.js" {
|
|
86
|
+
interface EventsList {
|
|
87
|
+
"db.query": QueryEvent;
|
|
88
|
+
"request.handled": RequestEvent;
|
|
89
|
+
"exception": ExceptionEvent;
|
|
90
|
+
"job.processing": JobEvent;
|
|
91
|
+
"job.processed": JobEvent;
|
|
92
|
+
"job.failed": JobEvent;
|
|
93
|
+
"cache.hit": CacheEvent;
|
|
94
|
+
"cache.miss": CacheEvent;
|
|
95
|
+
"notification.sent": NotificationEvent;
|
|
96
|
+
"schedule.task.run": ScheduleEvent;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** The instrumentation event names, for iteration and toggling. */
|
|
100
|
+
export type InstrumentEvent = "db.query" | "request.handled" | "exception" | "job.processing" | "job.processed" | "job.failed" | "cache.hit" | "cache.miss" | "notification.sent" | "schedule.task.run";
|
|
101
|
+
/** A random id for a request/batch. 16 hex chars is plenty to be unique. */
|
|
102
|
+
export declare function newRequestId(): string;
|
|
103
|
+
/** Run `fn` inside a request scope, so everything it does can be attributed. */
|
|
104
|
+
export declare function runRequest<T>(id: string, fn: () => T): T;
|
|
105
|
+
/** The id of the request currently in scope, if any. */
|
|
106
|
+
export declare function currentRequestId(): string | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Emit an instrumentation event — fire-and-forget. Never throws, never blocks:
|
|
109
|
+
* a broken watcher can't take down the request it was watching, and the work
|
|
110
|
+
* being measured doesn't wait on the measurement. A no-op when there's no live
|
|
111
|
+
* application (e.g. a unit test that never booted one).
|
|
112
|
+
*/
|
|
113
|
+
export declare function instrument<E extends InstrumentEvent>(event: E, payload: import("./events.js").EventsList[E]): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework instrumentation — the typed event stream that lets a package observe
|
|
3
|
+
* the framework without patching it. Core seams (the query builder, the HTTP
|
|
4
|
+
* kernel, the queue, the cache, …) call `instrument(event, payload)`; anything
|
|
5
|
+
* that wants to watch subscribes with `listen()`. The mail layer already works
|
|
6
|
+
* this way (`mail.sent`); this generalises it.
|
|
7
|
+
*
|
|
8
|
+
* These events are deliberately fire-and-forget: `instrument()` never blocks the
|
|
9
|
+
* work it describes, and a listener that throws can't break a query or a
|
|
10
|
+
* request. That's the difference between instrumentation and business logic.
|
|
11
|
+
*
|
|
12
|
+
* A **request id** ties a request to everything that happened inside it — the
|
|
13
|
+
* queries it ran, the logs it wrote, the jobs it dispatched. The kernel opens a
|
|
14
|
+
* request scope with `runRequest(id, …)`; anything emitted inside it can read
|
|
15
|
+
* `currentRequestId()` to attribute itself. It flows through `await` chains via
|
|
16
|
+
* `AsyncLocalStorage`, so nothing has to be threaded by hand.
|
|
17
|
+
*/
|
|
18
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
|
+
import { emit, hasApplication } from "./helpers.js";
|
|
20
|
+
/* ------------------------------ request scope ----------------------------- */
|
|
21
|
+
const store = new AsyncLocalStorage();
|
|
22
|
+
/** A random id for a request/batch. 16 hex chars is plenty to be unique. */
|
|
23
|
+
export function newRequestId() {
|
|
24
|
+
const bytes = new Uint8Array(8);
|
|
25
|
+
crypto.getRandomValues(bytes);
|
|
26
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
27
|
+
}
|
|
28
|
+
/** Run `fn` inside a request scope, so everything it does can be attributed. */
|
|
29
|
+
export function runRequest(id, fn) {
|
|
30
|
+
return store.run({ id }, fn);
|
|
31
|
+
}
|
|
32
|
+
/** The id of the request currently in scope, if any. */
|
|
33
|
+
export function currentRequestId() {
|
|
34
|
+
return store.getStore()?.id;
|
|
35
|
+
}
|
|
36
|
+
/* -------------------------------- emitting -------------------------------- */
|
|
37
|
+
/**
|
|
38
|
+
* Emit an instrumentation event — fire-and-forget. Never throws, never blocks:
|
|
39
|
+
* a broken watcher can't take down the request it was watching, and the work
|
|
40
|
+
* being measured doesn't wait on the measurement. A no-op when there's no live
|
|
41
|
+
* application (e.g. a unit test that never booted one).
|
|
42
|
+
*/
|
|
43
|
+
export function instrument(event, payload) {
|
|
44
|
+
if (!hasApplication())
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
void Promise.resolve(emit(event, payload)).catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// hasApplication() raced with teardown — instrumentation stays silent.
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/core/logger.d.ts
CHANGED
|
@@ -66,6 +66,13 @@ export declare class MemorySink {
|
|
|
66
66
|
messages(): string[];
|
|
67
67
|
clear(): void;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Observe every log record emitted anywhere in the app. Returns an unsubscribe.
|
|
71
|
+
* A tap that throws is ignored — watching logs must never break logging.
|
|
72
|
+
*
|
|
73
|
+
* const off = tapLogs((record) => store.record(record));
|
|
74
|
+
*/
|
|
75
|
+
export declare function tapLogs(sink: Sink): () => void;
|
|
69
76
|
export declare class Logger {
|
|
70
77
|
private options;
|
|
71
78
|
private threshold;
|
package/dist/core/logger.js
CHANGED
|
@@ -93,6 +93,22 @@ export class MemorySink {
|
|
|
93
93
|
this.records.length = 0;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Sinks that observe *every* log record from *every* logger — an audit trail, a
|
|
98
|
+
* debug dashboard. Kept separate from a logger's own `sink` so tapping is
|
|
99
|
+
* non-invasive: it never changes where a log normally goes.
|
|
100
|
+
*/
|
|
101
|
+
const globalTaps = new Set();
|
|
102
|
+
/**
|
|
103
|
+
* Observe every log record emitted anywhere in the app. Returns an unsubscribe.
|
|
104
|
+
* A tap that throws is ignored — watching logs must never break logging.
|
|
105
|
+
*
|
|
106
|
+
* const off = tapLogs((record) => store.record(record));
|
|
107
|
+
*/
|
|
108
|
+
export function tapLogs(sink) {
|
|
109
|
+
globalTaps.add(sink);
|
|
110
|
+
return () => globalTaps.delete(sink);
|
|
111
|
+
}
|
|
96
112
|
export class Logger {
|
|
97
113
|
options;
|
|
98
114
|
threshold;
|
|
@@ -130,7 +146,18 @@ export class Logger {
|
|
|
130
146
|
fields = redactPath(fields, path.split("."), this.redact);
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
|
-
|
|
149
|
+
const record = { level, time: new Date().toISOString(), msg: message, fields };
|
|
150
|
+
this.sink(record);
|
|
151
|
+
if (globalTaps.size) {
|
|
152
|
+
for (const tap of globalTaps) {
|
|
153
|
+
try {
|
|
154
|
+
tap(record);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// a tap must never break logging
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
134
161
|
}
|
|
135
162
|
trace(message, context) {
|
|
136
163
|
this.write("trace", message, context);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { db } from "./database.js";
|
|
21
21
|
import { getMailer } from "./mail.js";
|
|
22
22
|
import { dispatch } from "./queue.js";
|
|
23
|
+
import { instrument, currentRequestId } from "./instrumentation.js";
|
|
23
24
|
export class Notification {
|
|
24
25
|
/** Deliver from a queued job instead of inline. */
|
|
25
26
|
shouldQueue = false;
|
|
@@ -103,8 +104,16 @@ export class Notifier {
|
|
|
103
104
|
async send(notifiables, notification) {
|
|
104
105
|
const recipients = Array.isArray(notifiables) ? notifiables : [notifiables];
|
|
105
106
|
const run = async () => {
|
|
106
|
-
for (const recipient of recipients)
|
|
107
|
+
for (const recipient of recipients) {
|
|
107
108
|
await this.deliver(recipient, notification);
|
|
109
|
+
const requestId = currentRequestId();
|
|
110
|
+
instrument("notification.sent", {
|
|
111
|
+
notification: notification.constructor.name,
|
|
112
|
+
channels: notification.via(recipient),
|
|
113
|
+
notifiable: recipient,
|
|
114
|
+
...(requestId ? { requestId } : {}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
108
117
|
};
|
|
109
118
|
if (notification.shouldQueue)
|
|
110
119
|
await dispatch(run);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package system. Keel's `ServiceProvider` is already its plugin unit —
|
|
3
|
+
* `PackageProvider` is the same thing with batteries for the jobs a *shippable*
|
|
4
|
+
* package does over and over: merge its config defaults, register its routes and
|
|
5
|
+
* bundled UI assets, contribute migrations and console commands, and declare
|
|
6
|
+
* files a consuming app can publish. Each helper is a thin wrapper over an
|
|
7
|
+
* existing Keel primitive; the value is the convention, not new machinery.
|
|
8
|
+
*
|
|
9
|
+
* export class WatchServiceProvider extends PackageProvider {
|
|
10
|
+
* readonly name = "watch";
|
|
11
|
+
* register() {
|
|
12
|
+
* this.mergeConfig("watch", defaultConfig);
|
|
13
|
+
* this.migrations([watchMigration]);
|
|
14
|
+
* this.publishes({ [stub]: "config/watch.ts" }, "watch-config");
|
|
15
|
+
* }
|
|
16
|
+
* boot() {
|
|
17
|
+
* this.assets("watch/assets", uiDir, { immutable: true });
|
|
18
|
+
* this.routes((r) => registerWatchRoutes(r), { prefix: "watch", as: "watch" });
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* A note on lifecycle: register/boot run *before* the app's HTTP kernel is bound
|
|
23
|
+
* (see `bootstrap/app.ts`), so a package must not reach for the kernel. That's
|
|
24
|
+
* why `routes()` and `assets()` go through the `Router` (bound in the
|
|
25
|
+
* Application constructor) — routes are compiled onto the kernel later, at build.
|
|
26
|
+
*/
|
|
27
|
+
import type { MiddlewareHandler } from "hono";
|
|
28
|
+
import type { AnyCommand } from "./console.js";
|
|
29
|
+
import { ServiceProvider } from "./provider.js";
|
|
30
|
+
import type { Application } from "./application.js";
|
|
31
|
+
import type { Router, RouteGroup, MiddlewareRef } from "./http/router.js";
|
|
32
|
+
import type { Migration } from "./migrations.js";
|
|
33
|
+
/**
|
|
34
|
+
* Every migration a package contributes, gathered in one place so `keel migrate`
|
|
35
|
+
* can run them alongside the app's own. Bound as a singleton on the Application.
|
|
36
|
+
*/
|
|
37
|
+
export declare class MigrationRegistry {
|
|
38
|
+
private list;
|
|
39
|
+
add(migrations: Migration[]): void;
|
|
40
|
+
all(): Migration[];
|
|
41
|
+
}
|
|
42
|
+
/** A console command a package adds to `keel`. */
|
|
43
|
+
/**
|
|
44
|
+
* A command a package contributes to the console — the same `defineCommand()`
|
|
45
|
+
* shape an app uses, so a package's command gets typed args and flags, generated
|
|
46
|
+
* help, the terminal UI, and the prompt-trapping test story for free.
|
|
47
|
+
*/
|
|
48
|
+
export type PackageCommand = AnyCommand;
|
|
49
|
+
/** Package-contributed console commands, mounted by the CLI after boot. */
|
|
50
|
+
export declare class CommandRegistry {
|
|
51
|
+
private list;
|
|
52
|
+
add(commands: PackageCommand[]): void;
|
|
53
|
+
all(): PackageCommand[];
|
|
54
|
+
}
|
|
55
|
+
/** One `publishes()` declaration: source→destination files, optionally tagged. */
|
|
56
|
+
export interface PublishEntry {
|
|
57
|
+
package: string;
|
|
58
|
+
tag?: string;
|
|
59
|
+
/** Absolute-or-package source path → app-relative destination path. */
|
|
60
|
+
files: Record<string, string>;
|
|
61
|
+
}
|
|
62
|
+
/** What `keel vendor:publish` copies into a consuming app. */
|
|
63
|
+
export declare class PublishRegistry {
|
|
64
|
+
private entries;
|
|
65
|
+
add(entry: PublishEntry): void;
|
|
66
|
+
all(tag?: string): PublishEntry[];
|
|
67
|
+
tags(): string[];
|
|
68
|
+
}
|
|
69
|
+
export interface PackageRouteOptions {
|
|
70
|
+
/** Mount every route under this path prefix. */
|
|
71
|
+
prefix?: string;
|
|
72
|
+
/** Middleware to run before every route in the group. */
|
|
73
|
+
middleware?: MiddlewareRef | MiddlewareRef[];
|
|
74
|
+
/** Prefix every named route with `<as>.`. */
|
|
75
|
+
as?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface PackageAssetOptions {
|
|
78
|
+
/** Cache-Control max-age (seconds). */
|
|
79
|
+
maxAge?: number;
|
|
80
|
+
/** Add `immutable` — for content-hashed filenames. */
|
|
81
|
+
immutable?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare abstract class PackageProvider<O = Record<string, unknown>> extends ServiceProvider<O> {
|
|
84
|
+
/** The package's short name — used for publish grouping and diagnostics. */
|
|
85
|
+
abstract readonly name: string;
|
|
86
|
+
constructor(app: Application, options?: O);
|
|
87
|
+
/**
|
|
88
|
+
* Set config defaults under `key` without clobbering what the app already
|
|
89
|
+
* configured. Config files load before providers, so the app's `config/<key>.ts`
|
|
90
|
+
* (if any) is deep-merged *over* these defaults — the app always wins.
|
|
91
|
+
*/
|
|
92
|
+
protected mergeConfig(key: string, defaults: Record<string, unknown>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Register a group of routes. The callback receives the `Router`; the returned
|
|
95
|
+
* `RouteGroup` is already prefixed/guarded/named per `options`, and chainable
|
|
96
|
+
* for anything more.
|
|
97
|
+
*/
|
|
98
|
+
protected routes(register: (router: Router) => void, options?: PackageRouteOptions): RouteGroup;
|
|
99
|
+
/**
|
|
100
|
+
* Serve a directory of static files (a bundled UI, images, …) under a URL
|
|
101
|
+
* prefix. Node-only. Mounts one wildcard route on the `Router`.
|
|
102
|
+
*/
|
|
103
|
+
protected assets(urlPrefix: string, dir: string, options?: PackageAssetOptions): void;
|
|
104
|
+
/** Contribute migrations, run by `keel migrate` with the app's own. */
|
|
105
|
+
protected migrations(list: Migration[]): void;
|
|
106
|
+
/** Contribute console commands, mounted on `keel` after the app boots. */
|
|
107
|
+
protected commands(list: PackageCommand[]): void;
|
|
108
|
+
/**
|
|
109
|
+
* Declare files a consuming app can copy into itself with
|
|
110
|
+
* `keel vendor:publish` (optionally `--tag <tag>`): config stubs, views, etc.
|
|
111
|
+
* Keys are source paths (usually inside the package), values app-relative
|
|
112
|
+
* destinations.
|
|
113
|
+
*/
|
|
114
|
+
protected publishes(files: Record<string, string>, tag?: string): void;
|
|
115
|
+
}
|
|
116
|
+
/** A middleware alias, re-exported so packages needn't reach into `hono`. */
|
|
117
|
+
export type PackageMiddleware = MiddlewareHandler;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package system. Keel's `ServiceProvider` is already its plugin unit —
|
|
3
|
+
* `PackageProvider` is the same thing with batteries for the jobs a *shippable*
|
|
4
|
+
* package does over and over: merge its config defaults, register its routes and
|
|
5
|
+
* bundled UI assets, contribute migrations and console commands, and declare
|
|
6
|
+
* files a consuming app can publish. Each helper is a thin wrapper over an
|
|
7
|
+
* existing Keel primitive; the value is the convention, not new machinery.
|
|
8
|
+
*
|
|
9
|
+
* export class WatchServiceProvider extends PackageProvider {
|
|
10
|
+
* readonly name = "watch";
|
|
11
|
+
* register() {
|
|
12
|
+
* this.mergeConfig("watch", defaultConfig);
|
|
13
|
+
* this.migrations([watchMigration]);
|
|
14
|
+
* this.publishes({ [stub]: "config/watch.ts" }, "watch-config");
|
|
15
|
+
* }
|
|
16
|
+
* boot() {
|
|
17
|
+
* this.assets("watch/assets", uiDir, { immutable: true });
|
|
18
|
+
* this.routes((r) => registerWatchRoutes(r), { prefix: "watch", as: "watch" });
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* A note on lifecycle: register/boot run *before* the app's HTTP kernel is bound
|
|
23
|
+
* (see `bootstrap/app.ts`), so a package must not reach for the kernel. That's
|
|
24
|
+
* why `routes()` and `assets()` go through the `Router` (bound in the
|
|
25
|
+
* Application constructor) — routes are compiled onto the kernel later, at build.
|
|
26
|
+
*/
|
|
27
|
+
import { getMimeType } from "hono/utils/mime";
|
|
28
|
+
import { ServiceProvider } from "./provider.js";
|
|
29
|
+
import { NotFoundException } from "./exceptions.js";
|
|
30
|
+
/* ------------------------------- registries ------------------------------- */
|
|
31
|
+
/**
|
|
32
|
+
* Every migration a package contributes, gathered in one place so `keel migrate`
|
|
33
|
+
* can run them alongside the app's own. Bound as a singleton on the Application.
|
|
34
|
+
*/
|
|
35
|
+
export class MigrationRegistry {
|
|
36
|
+
list = [];
|
|
37
|
+
add(migrations) {
|
|
38
|
+
this.list.push(...migrations);
|
|
39
|
+
}
|
|
40
|
+
all() {
|
|
41
|
+
return [...this.list];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Package-contributed console commands, mounted by the CLI after boot. */
|
|
45
|
+
export class CommandRegistry {
|
|
46
|
+
list = [];
|
|
47
|
+
add(commands) {
|
|
48
|
+
this.list.push(...commands);
|
|
49
|
+
}
|
|
50
|
+
all() {
|
|
51
|
+
return [...this.list];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** What `keel vendor:publish` copies into a consuming app. */
|
|
55
|
+
export class PublishRegistry {
|
|
56
|
+
entries = [];
|
|
57
|
+
add(entry) {
|
|
58
|
+
this.entries.push(entry);
|
|
59
|
+
}
|
|
60
|
+
all(tag) {
|
|
61
|
+
return tag ? this.entries.filter((e) => e.tag === tag) : [...this.entries];
|
|
62
|
+
}
|
|
63
|
+
tags() {
|
|
64
|
+
return [...new Set(this.entries.map((e) => e.tag).filter((t) => !!t))];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/* --------------------------------- helpers -------------------------------- */
|
|
68
|
+
/** Deep-merge two plain objects; `override` wins, arrays are replaced wholesale. */
|
|
69
|
+
function deepMerge(base, override) {
|
|
70
|
+
const out = { ...base };
|
|
71
|
+
for (const [key, value] of Object.entries(override)) {
|
|
72
|
+
const existing = out[key];
|
|
73
|
+
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
74
|
+
out[key] = deepMerge(existing, value);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
out[key] = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function isPlainObject(value) {
|
|
83
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A route handler that serves files from `dir` for requests under `prefix`. Used
|
|
87
|
+
* by `PackageProvider.assets()` so a package can ship a built UI without the
|
|
88
|
+
* consuming app copying anything into `public/`. Node-only (uses `node:fs`);
|
|
89
|
+
* a miss becomes a 404 so it doesn't shadow real routes.
|
|
90
|
+
*/
|
|
91
|
+
function packageAssetHandler(prefix, dir, options) {
|
|
92
|
+
const root = dir.replace(/\/+$/, "");
|
|
93
|
+
return async (c) => {
|
|
94
|
+
const pathname = decodeURIComponent(new URL(c.req.url).pathname);
|
|
95
|
+
const rel = pathname.slice(prefix.length).replace(/^\/+/, "");
|
|
96
|
+
if (rel.includes(".."))
|
|
97
|
+
throw new NotFoundException();
|
|
98
|
+
const { stat, readFile } = await import("node:fs/promises");
|
|
99
|
+
const filePath = `${root}/${rel}`;
|
|
100
|
+
const stats = await stat(filePath).catch(() => null);
|
|
101
|
+
if (!stats?.isFile())
|
|
102
|
+
throw new NotFoundException();
|
|
103
|
+
const headers = {
|
|
104
|
+
"Content-Type": getMimeType(filePath) ?? "application/octet-stream",
|
|
105
|
+
};
|
|
106
|
+
if (options.maxAge != null) {
|
|
107
|
+
headers["Cache-Control"] =
|
|
108
|
+
`public, max-age=${options.maxAge}${options.immutable ? ", immutable" : ""}`;
|
|
109
|
+
}
|
|
110
|
+
return new Response(await readFile(filePath), { headers });
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/* ----------------------------- PackageProvider ---------------------------- */
|
|
114
|
+
export class PackageProvider extends ServiceProvider {
|
|
115
|
+
constructor(app, options) {
|
|
116
|
+
super(app, options);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Set config defaults under `key` without clobbering what the app already
|
|
120
|
+
* configured. Config files load before providers, so the app's `config/<key>.ts`
|
|
121
|
+
* (if any) is deep-merged *over* these defaults — the app always wins.
|
|
122
|
+
*/
|
|
123
|
+
mergeConfig(key, defaults) {
|
|
124
|
+
const cfg = this.app.config();
|
|
125
|
+
const existing = cfg.get(key);
|
|
126
|
+
cfg.set(key, isPlainObject(existing) ? deepMerge(defaults, existing) : defaults);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Register a group of routes. The callback receives the `Router`; the returned
|
|
130
|
+
* `RouteGroup` is already prefixed/guarded/named per `options`, and chainable
|
|
131
|
+
* for anything more.
|
|
132
|
+
*/
|
|
133
|
+
routes(register, options = {}) {
|
|
134
|
+
const router = this.app.router();
|
|
135
|
+
const group = router.group(() => register(router));
|
|
136
|
+
if (options.prefix)
|
|
137
|
+
group.prefix(options.prefix);
|
|
138
|
+
if (options.middleware)
|
|
139
|
+
group.middleware(options.middleware);
|
|
140
|
+
if (options.as)
|
|
141
|
+
group.as(options.as);
|
|
142
|
+
return group;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Serve a directory of static files (a bundled UI, images, …) under a URL
|
|
146
|
+
* prefix. Node-only. Mounts one wildcard route on the `Router`.
|
|
147
|
+
*/
|
|
148
|
+
assets(urlPrefix, dir, options = {}) {
|
|
149
|
+
const prefix = "/" + urlPrefix.replace(/^\/|\/$/g, "");
|
|
150
|
+
this.app.router().get(`${prefix}/*`, packageAssetHandler(prefix, dir, options));
|
|
151
|
+
}
|
|
152
|
+
/** Contribute migrations, run by `keel migrate` with the app's own. */
|
|
153
|
+
migrations(list) {
|
|
154
|
+
this.app.make(MigrationRegistry).add(list);
|
|
155
|
+
}
|
|
156
|
+
/** Contribute console commands, mounted on `keel` after the app boots. */
|
|
157
|
+
commands(list) {
|
|
158
|
+
this.app.make(CommandRegistry).add(list);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Declare files a consuming app can copy into itself with
|
|
162
|
+
* `keel vendor:publish` (optionally `--tag <tag>`): config stubs, views, etc.
|
|
163
|
+
* Keys are source paths (usually inside the package), values app-relative
|
|
164
|
+
* destinations.
|
|
165
|
+
*/
|
|
166
|
+
publishes(files, tag) {
|
|
167
|
+
this.app.make(PublishRegistry).add({ package: this.name, ...(tag ? { tag } : {}), files });
|
|
168
|
+
}
|
|
169
|
+
}
|