@zerotal/core 1.0.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/CHANGELOG.md +79 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/package.json +72 -0
- package/src/application/Application.ts +1671 -0
- package/src/application/BootDoctor.ts +108 -0
- package/src/application/DevErrorPage.ts +567 -0
- package/src/application/ExceptionHandler.ts +183 -0
- package/src/application/currentApp.ts +73 -0
- package/src/assets/assets.ts +79 -0
- package/src/assets/index.ts +16 -0
- package/src/auth/AuthenticatedUser.ts +18 -0
- package/src/build/PackageLinter.ts +146 -0
- package/src/build/PackageScaffold.ts +127 -0
- package/src/build/codemod.ts +64 -0
- package/src/build/index.ts +12 -0
- package/src/command/Command.ts +254 -0
- package/src/command/CommandRunner.ts +593 -0
- package/src/command/OutputWriter.ts +61 -0
- package/src/command/builtin/CompileCommand.ts +46 -0
- package/src/command/builtin/CssBuildCommand.ts +71 -0
- package/src/command/builtin/KeyGenerateCommand.ts +58 -0
- package/src/command/builtin/LintPackagesCommand.ts +72 -0
- package/src/command/builtin/MakeCommandCommand.ts +85 -0
- package/src/command/builtin/MakeControllerCommand.ts +95 -0
- package/src/command/builtin/MakeEventCommand.ts +85 -0
- package/src/command/builtin/MakeJobCommand.ts +53 -0
- package/src/command/builtin/MakeListenerCommand.ts +35 -0
- package/src/command/builtin/MakeMiddlewareCommand.ts +63 -0
- package/src/command/builtin/MakeNotificationCommand.ts +48 -0
- package/src/command/builtin/MakeObserverCommand.ts +78 -0
- package/src/command/builtin/MakePackageCommand.ts +45 -0
- package/src/command/builtin/MakePolicyCommand.ts +66 -0
- package/src/command/builtin/MakeProviderCommand.ts +75 -0
- package/src/command/builtin/MakeRequestCommand.ts +47 -0
- package/src/command/builtin/MakeResourceCommand.ts +61 -0
- package/src/command/builtin/MakeTestCommand.ts +120 -0
- package/src/command/builtin/ReloadCommand.ts +52 -0
- package/src/command/builtin/ReplCommand.ts +174 -0
- package/src/command/builtin/RouteListCommand.ts +188 -0
- package/src/command/builtin/ServeCommand.ts +321 -0
- package/src/command/builtin/StartCommand.ts +3 -0
- package/src/command/builtin/StatusCommand.ts +71 -0
- package/src/command/builtin/TestCommand.ts +172 -0
- package/src/command/builtin/WorkerCommand.ts +27 -0
- package/src/command/builtin/index.ts +53 -0
- package/src/command/scaffold/worker.ts.txt +12 -0
- package/src/command/scaffold/zerotal.ts.txt +26 -0
- package/src/command/startZerotal.ts +55 -0
- package/src/config/AppConfig.ts +253 -0
- package/src/config/ConfigLoader.ts +117 -0
- package/src/config/ConfigManager.ts +169 -0
- package/src/config/index.ts +46 -0
- package/src/config/registry.ts +59 -0
- package/src/config/validation.ts +117 -0
- package/src/container/Container.ts +606 -0
- package/src/container/ContextualBindingBuilder.ts +57 -0
- package/src/container/ScopedResolver.ts +117 -0
- package/src/container/index.ts +32 -0
- package/src/container/inject.ts +55 -0
- package/src/container/types.ts +71 -0
- package/src/context/RequestContext.ts +91 -0
- package/src/contracts/auth.ts +24 -0
- package/src/contracts/index.ts +23 -0
- package/src/contracts/session.ts +70 -0
- package/src/contracts/transaction.ts +26 -0
- package/src/conventions/ConventionLoader.ts +128 -0
- package/src/conventions/builtinConcerns.ts +131 -0
- package/src/crypt/Crypt.ts +141 -0
- package/src/crypt/URLSigner.ts +96 -0
- package/src/datetime/Carbon.ts +1396 -0
- package/src/datetime/CarbonInterval.ts +421 -0
- package/src/datetime/clock.ts +28 -0
- package/src/datetime/index.ts +23 -0
- package/src/datetime/temporal-shim.ts +1 -0
- package/src/dev/BuildOutput.ts +131 -0
- package/src/dev/CssPlugins.ts +184 -0
- package/src/dev/DevBuildHook.ts +74 -0
- package/src/dev/DevOrchestrator.ts +213 -0
- package/src/dev/DevReloadMiddleware.ts +101 -0
- package/src/dev/DevReloadServer.ts +85 -0
- package/src/dev/DevWsServer.ts +45 -0
- package/src/dev/index.ts +19 -0
- package/src/dev/reloadClient.ts +39 -0
- package/src/env/Def.ts +232 -0
- package/src/env/EnvSchema.ts +105 -0
- package/src/env/index.ts +34 -0
- package/src/env/t.ts +128 -0
- package/src/errors/ConfigError.ts +12 -0
- package/src/errors/ContainerErrors.ts +143 -0
- package/src/errors/HttpError.ts +127 -0
- package/src/errors/ValidationError.ts +19 -0
- package/src/errors/ZerotalError.ts +25 -0
- package/src/errors/index.ts +46 -0
- package/src/events/CallQueuedListener.ts +66 -0
- package/src/events/Emitter.ts +280 -0
- package/src/events/EventFake.ts +160 -0
- package/src/events/FrameworkEvents.ts +252 -0
- package/src/facade/Facade.ts +101 -0
- package/src/facade/facades/App.ts +155 -0
- package/src/facade/facades/Artisan.ts +63 -0
- package/src/facade/facades/Config.ts +21 -0
- package/src/facade/facades/Events.ts +19 -0
- package/src/facade/facades/index.ts +28 -0
- package/src/global.d.ts +9 -0
- package/src/hash/Hash.ts +60 -0
- package/src/health/Health.ts +221 -0
- package/src/health/index.ts +27 -0
- package/src/helpers/Collection.ts +435 -0
- package/src/helpers/config.ts +59 -0
- package/src/helpers/fluent.ts +52 -0
- package/src/helpers/html.ts +11 -0
- package/src/helpers/index.ts +266 -0
- package/src/helpers/make.ts +35 -0
- package/src/helpers/markdown.ts +73 -0
- package/src/helpers/pageElements.ts +27 -0
- package/src/helpers/request.ts +62 -0
- package/src/helpers/response.ts +411 -0
- package/src/helpers/str.ts +208 -0
- package/src/http/Http.ts +298 -0
- package/src/http/HttpClient.ts +289 -0
- package/src/http/Resource.ts +171 -0
- package/src/http/UploadedFile.ts +204 -0
- package/src/http/Uri.ts +490 -0
- package/src/http/index.ts +46 -0
- package/src/http/negotiate.ts +213 -0
- package/src/http/originGuard.ts +76 -0
- package/src/http/sniffContentType.ts +105 -0
- package/src/http/url.ts +204 -0
- package/src/http/withHeaders.ts +24 -0
- package/src/index.ts +250 -0
- package/src/lock/LockManager.ts +228 -0
- package/src/lock/config.ts +49 -0
- package/src/lock/drivers/LockDriver.ts +32 -0
- package/src/lock/drivers/MemoryLockDriver.ts +52 -0
- package/src/lock/drivers/RedisLockDriver.ts +58 -0
- package/src/lock/drivers/SqliteLockDriver.ts +85 -0
- package/src/lock/errors.ts +20 -0
- package/src/lock/facades/Lock.ts +114 -0
- package/src/lock/index.ts +53 -0
- package/src/logger/Log.ts +35 -0
- package/src/logger/LogManager.ts +430 -0
- package/src/logger/LoggerMiddleware.ts +125 -0
- package/src/logger/channels/ConsoleChannel.ts +139 -0
- package/src/logger/channels/DailyChannel.ts +74 -0
- package/src/logger/channels/NullChannel.ts +17 -0
- package/src/logger/channels/SingleChannel.ts +34 -0
- package/src/logger/channels/StackChannel.ts +29 -0
- package/src/logger/config.ts +90 -0
- package/src/logger/format.ts +96 -0
- package/src/logger/frameworkLog.ts +93 -0
- package/src/logger/index.ts +68 -0
- package/src/logger/renderTable.ts +111 -0
- package/src/logger/types.ts +212 -0
- package/src/macros/config.macro.ts +50 -0
- package/src/metrics/HttpMetrics.ts +114 -0
- package/src/metrics/index.ts +18 -0
- package/src/middleware/BaseMiddleware.ts +72 -0
- package/src/middleware/CorsMiddleware.ts +152 -0
- package/src/middleware/RateLimiter.ts +255 -0
- package/src/middleware/SecureHeadersMiddleware.ts +127 -0
- package/src/middleware/ThrottleMiddleware.ts +252 -0
- package/src/middleware/WebhookMiddleware.ts +204 -0
- package/src/pipeline/ContextRegistry.ts +42 -0
- package/src/pipeline/HttpContext.ts +865 -0
- package/src/pipeline/Pipeline.ts +150 -0
- package/src/pipeline/currentPage.ts +46 -0
- package/src/pipeline/types.ts +80 -0
- package/src/provider/LockProvider.ts +64 -0
- package/src/provider/LogProvider.ts +137 -0
- package/src/provider/ServiceProvider.ts +84 -0
- package/src/provider/StorageProvider.ts +45 -0
- package/src/router/FileRouter.ts +526 -0
- package/src/router/Route.ts +76 -0
- package/src/router/RouteHandler.ts +335 -0
- package/src/router/Router.ts +1247 -0
- package/src/router/domain.ts +65 -0
- package/src/security/index.ts +22 -0
- package/src/storage/FakeDisk.ts +233 -0
- package/src/storage/StorageFilesMiddleware.ts +150 -0
- package/src/storage/StorageManager.ts +173 -0
- package/src/storage/config.ts +47 -0
- package/src/storage/drivers/LocalDriver.ts +138 -0
- package/src/storage/drivers/S3Driver.ts +169 -0
- package/src/storage/errors.ts +135 -0
- package/src/storage/facades/Storage.ts +3 -0
- package/src/storage/global.d.ts +7 -0
- package/src/storage/index.ts +22 -0
- package/src/storage/root.ts +59 -0
- package/src/storage/types.ts +104 -0
- package/src/support/appKey.ts +38 -0
- package/src/support/cookie.ts +72 -0
- package/src/support/crypto.ts +52 -0
- package/src/support/deepMerge.ts +117 -0
- package/src/support/env.ts +71 -0
- package/src/support/network.ts +79 -0
- package/src/support/port.ts +197 -0
- package/src/support/str.ts +122 -0
- package/src/view/FileRouteResolver.ts +59 -0
- package/src/view/index.ts +144 -0
- package/src/view/jsx-runtime.ts +233 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook signature-verification middleware: rejects requests whose HMAC
|
|
3
|
+
* signature is missing, expired, or invalid before they reach a controller.
|
|
4
|
+
* Ships GitHub and Stripe presets and seeds the parsed body for downstream use.
|
|
5
|
+
*/
|
|
6
|
+
import type { NextFn } from "../pipeline/types.ts";
|
|
7
|
+
import type { HttpContext } from "../pipeline/HttpContext.ts";
|
|
8
|
+
import { BaseMiddleware, deepMerge } from "./BaseMiddleware.ts";
|
|
9
|
+
import { rescueSync } from "../helpers/index.ts";
|
|
10
|
+
import { safeEqual } from "../support/crypto.ts";
|
|
11
|
+
|
|
12
|
+
export interface WebhookOptions {
|
|
13
|
+
/** HMAC secret used to verify the signature. */
|
|
14
|
+
secret: string;
|
|
15
|
+
/** Request header that carries the signature. Default: 'x-webhook-signature'. */
|
|
16
|
+
header?: string;
|
|
17
|
+
/** HMAC algorithm. Default: 'sha256'. */
|
|
18
|
+
algorithm?: "sha256" | "sha1" | "sha512";
|
|
19
|
+
/**
|
|
20
|
+
* Expected signature prefix that will be stripped before comparison
|
|
21
|
+
* (e.g. `'sha256='` for GitHub). Default: `''`.
|
|
22
|
+
*/
|
|
23
|
+
prefix?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Max age in seconds for replay-attack protection.
|
|
26
|
+
* Requires `timestampHeader` (or `format: 'stripe'`). Disabled when `undefined`.
|
|
27
|
+
*/
|
|
28
|
+
tolerance?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Header that carries the Unix timestamp of the request.
|
|
31
|
+
* When present the signed payload becomes `{timestamp}{timestampSeparator}{body}`.
|
|
32
|
+
*/
|
|
33
|
+
timestampHeader?: string;
|
|
34
|
+
/** Separator between timestamp and body in the signed payload. Default: `'.'`. */
|
|
35
|
+
timestampSeparator?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Header format.
|
|
38
|
+
* - `'raw'` (default) — signature is the full (or prefix-stripped) header value.
|
|
39
|
+
* - `'stripe'` — header is parsed as `t=<ts>,v1=<sig>` (Stripe-Signature style).
|
|
40
|
+
*/
|
|
41
|
+
format?: "raw" | "stripe";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Verifies HMAC webhook signatures before allowing the request to proceed.
|
|
46
|
+
*
|
|
47
|
+
* Responds with `401 Unauthorized` when the signature is missing, expired, or invalid.
|
|
48
|
+
* The raw body is read once and the parsed JSON is seeded into the body cache so
|
|
49
|
+
* downstream controllers can still call `await ctx.body()` normally.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Generic
|
|
53
|
+
* app.use([WebhookMiddleware.with({ secret: env('WEBHOOK_SECRET') })]);
|
|
54
|
+
*
|
|
55
|
+
* // GitHub
|
|
56
|
+
* Router.post('/webhooks/github', GithubController, 'handle', [
|
|
57
|
+
* WebhookMiddleware.github(env('GITHUB_WEBHOOK_SECRET')),
|
|
58
|
+
* ]);
|
|
59
|
+
*
|
|
60
|
+
* // Stripe
|
|
61
|
+
* Router.post('/webhooks/stripe', StripeController, 'handle', [
|
|
62
|
+
* WebhookMiddleware.stripe(env('STRIPE_WEBHOOK_SECRET')),
|
|
63
|
+
* ]);
|
|
64
|
+
*/
|
|
65
|
+
export class WebhookMiddleware extends BaseMiddleware<WebhookOptions> {
|
|
66
|
+
protected options: WebhookOptions = {
|
|
67
|
+
secret: "",
|
|
68
|
+
header: "x-webhook-signature",
|
|
69
|
+
algorithm: "sha256",
|
|
70
|
+
prefix: "",
|
|
71
|
+
timestampSeparator: ".",
|
|
72
|
+
format: "raw",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
constructor(options: Partial<WebhookOptions> = {}) {
|
|
76
|
+
super();
|
|
77
|
+
this.options = deepMerge(this.options, options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async handle(http: HttpContext, next: NextFn): Promise<Response | void> {
|
|
81
|
+
const options = this.options;
|
|
82
|
+
|
|
83
|
+
// Read the raw body exactly once; the stream is consumed after this.
|
|
84
|
+
const rawBody = await http.request.text();
|
|
85
|
+
|
|
86
|
+
// Seed the body cache so http.body() / http.input() work downstream.
|
|
87
|
+
http._primeBody(_tryParseJson(rawBody));
|
|
88
|
+
|
|
89
|
+
let signature: string;
|
|
90
|
+
let payload: string;
|
|
91
|
+
|
|
92
|
+
if (options.format === "stripe") {
|
|
93
|
+
const parsed = _parseStripeHeader(http.header(options.header ?? "stripe-signature") ?? "");
|
|
94
|
+
if (!parsed) {
|
|
95
|
+
return new Response("Missing or malformed webhook signature", { status: 401 });
|
|
96
|
+
}
|
|
97
|
+
if (options.tolerance !== undefined) {
|
|
98
|
+
const age = Math.abs(Date.now() / 1000 - parsed.timestamp);
|
|
99
|
+
if (age > options.tolerance) {
|
|
100
|
+
return new Response("Webhook timestamp expired", { status: 401 });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
signature = parsed.signature;
|
|
104
|
+
payload = `${parsed.timestamp}.${rawBody}`;
|
|
105
|
+
} else {
|
|
106
|
+
const signatureHeader = http.header(options.header!);
|
|
107
|
+
if (!signatureHeader) {
|
|
108
|
+
return new Response("Missing webhook signature", { status: 401 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const prefix = options.prefix ?? "";
|
|
112
|
+
signature = signatureHeader.startsWith(prefix)
|
|
113
|
+
? signatureHeader.slice(prefix.length)
|
|
114
|
+
: signatureHeader;
|
|
115
|
+
|
|
116
|
+
if (options.timestampHeader) {
|
|
117
|
+
const timestamp = http.header(options.timestampHeader);
|
|
118
|
+
if (!timestamp) {
|
|
119
|
+
return new Response("Missing webhook timestamp", { status: 401 });
|
|
120
|
+
}
|
|
121
|
+
if (options.tolerance !== undefined) {
|
|
122
|
+
const timestampSeconds = parseInt(timestamp, 10);
|
|
123
|
+
if (
|
|
124
|
+
isNaN(timestampSeconds) ||
|
|
125
|
+
Math.abs(Date.now() / 1000 - timestampSeconds) > options.tolerance
|
|
126
|
+
) {
|
|
127
|
+
return new Response("Webhook timestamp expired", { status: 401 });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
payload = `${timestamp}${options.timestampSeparator ?? "."}${rawBody}`;
|
|
131
|
+
} else {
|
|
132
|
+
payload = rawBody;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Bun.CryptoHasher computes an HMAC when constructed with a key.
|
|
137
|
+
const expected = new Bun.CryptoHasher(options.algorithm ?? "sha256", options.secret)
|
|
138
|
+
.update(payload)
|
|
139
|
+
.digest("hex");
|
|
140
|
+
|
|
141
|
+
if (!_timingSafeHexCompare(signature, expected)) {
|
|
142
|
+
return new Response("Invalid webhook signature", { status: 401 });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return next();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** GitHub webhook preset: `X-Hub-Signature-256` header, `sha256=` prefix. */
|
|
149
|
+
static github(secret: string): WebhookMiddleware {
|
|
150
|
+
return new WebhookMiddleware({
|
|
151
|
+
secret,
|
|
152
|
+
header: "x-hub-signature-256",
|
|
153
|
+
algorithm: "sha256",
|
|
154
|
+
prefix: "sha256=",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Stripe webhook preset: `Stripe-Signature` header with `t=<ts>,v1=<sig>` format,
|
|
160
|
+
* 5-minute replay-attack tolerance.
|
|
161
|
+
*/
|
|
162
|
+
static stripe(secret: string, toleranceSeconds = 300): WebhookMiddleware {
|
|
163
|
+
return new WebhookMiddleware({
|
|
164
|
+
secret,
|
|
165
|
+
header: "stripe-signature",
|
|
166
|
+
algorithm: "sha256",
|
|
167
|
+
format: "stripe",
|
|
168
|
+
tolerance: toleranceSeconds,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
function _parseStripeHeader(header: string): { timestamp: number; signature: string } | null {
|
|
176
|
+
let timestamp: number | undefined;
|
|
177
|
+
let signature: string | undefined;
|
|
178
|
+
|
|
179
|
+
for (const part of header.split(",")) {
|
|
180
|
+
const equalsIndex = part.indexOf("=");
|
|
181
|
+
if (equalsIndex === -1) continue;
|
|
182
|
+
const key = part.slice(0, equalsIndex).trim();
|
|
183
|
+
const value = part.slice(equalsIndex + 1).trim();
|
|
184
|
+
if (key === "t") timestamp = parseInt(value, 10);
|
|
185
|
+
if (key === "v1") signature = value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (timestamp === undefined || isNaN(timestamp) || !signature) return null;
|
|
189
|
+
return { timestamp, signature };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Constant-time comparison of two hex digests via the shared {@link safeEqual}.
|
|
194
|
+
* Case-insensitive (like the byte-decoding compare it replaced) — `expected`
|
|
195
|
+
* is always the lowercase output of `.digest("hex")`.
|
|
196
|
+
*/
|
|
197
|
+
function _timingSafeHexCompare(actual: string, expected: string): boolean {
|
|
198
|
+
if (actual.length !== expected.length) return false;
|
|
199
|
+
return safeEqual(actual.toLowerCase(), expected.toLowerCase());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _tryParseJson(raw: string): Record<string, unknown> {
|
|
203
|
+
return rescueSync(() => JSON.parse(raw) as Record<string, unknown>, {});
|
|
204
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed registry of well-known per-request context keys — the context
|
|
3
|
+
* analogue of `ContainerBindings` (services) and `ConfigRegistry` (config).
|
|
4
|
+
*
|
|
5
|
+
* `HttpContext` carries a small per-request key/value store for framework
|
|
6
|
+
* packages to hang request-scoped state on (a rendered island, a resolved
|
|
7
|
+
* tenant, a devtools marker). Packages merge their keys here so the store is
|
|
8
|
+
* typed at every call site: `ctx.getInternal("flow.island")` is inferred, a
|
|
9
|
+
* key collision between two packages is a compile error, and a grep for a key
|
|
10
|
+
* string finds its owner.
|
|
11
|
+
*
|
|
12
|
+
* Keys are flat, namespaced strings (`"<package>.<name>"`) — like
|
|
13
|
+
* `ContainerBindings`, not the dot-path expansion of `ConfigRegistry`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // In @zerotal/flow
|
|
18
|
+
* declare module "@zerotal/core" {
|
|
19
|
+
* interface ContextRegistry {
|
|
20
|
+
* "flow.island": IslandState;
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // Anywhere with an HttpContext — typed, no cast:
|
|
25
|
+
* ctx.setInternal("flow.island", state);
|
|
26
|
+
* const island = ctx.getInternal("flow.island"); // IslandState | undefined
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* An empty (unaugmented) registry leaves every key on the graceful `string`
|
|
31
|
+
* fallback overload, so dynamic access such as `ctx.getInternal("trace-id")`
|
|
32
|
+
* still compiles and returns `unknown`.
|
|
33
|
+
*
|
|
34
|
+
* @category Extension registries
|
|
35
|
+
*/
|
|
36
|
+
export interface ContextRegistry {}
|
|
37
|
+
|
|
38
|
+
/** Union of every registered context key. `never` until a package augments {@link ContextRegistry}. */
|
|
39
|
+
export type ContextKey = keyof ContextRegistry;
|
|
40
|
+
|
|
41
|
+
/** The value type stored under a registered context key. */
|
|
42
|
+
export type ContextValue<K extends ContextKey> = ContextRegistry[K];
|