@shaferllc/keel 0.66.0 → 0.68.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.
Files changed (132) hide show
  1. package/AGENTS.md +167 -0
  2. package/README.md +30 -1
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/core/application.d.ts +5 -5
  5. package/dist/core/application.js +2 -2
  6. package/dist/core/cache.d.ts +82 -5
  7. package/dist/core/cache.js +181 -23
  8. package/dist/core/cli/stubs.d.ts +12 -0
  9. package/dist/core/cli/stubs.js +120 -0
  10. package/dist/core/events.d.ts +129 -5
  11. package/dist/core/events.js +165 -7
  12. package/dist/core/health.d.ts +141 -0
  13. package/dist/core/health.js +226 -0
  14. package/dist/core/helpers.d.ts +9 -3
  15. package/dist/core/helpers.js +11 -3
  16. package/dist/core/index.d.ts +15 -10
  17. package/dist/core/index.js +7 -4
  18. package/dist/core/lock.d.ts +139 -0
  19. package/dist/core/lock.js +215 -0
  20. package/dist/core/logger.d.ts +82 -4
  21. package/dist/core/logger.js +141 -23
  22. package/dist/core/mail.d.ts +128 -7
  23. package/dist/core/mail.js +264 -16
  24. package/dist/core/queue.d.ts +134 -9
  25. package/dist/core/queue.js +304 -14
  26. package/dist/core/storage.d.ts +159 -6
  27. package/dist/core/storage.js +287 -7
  28. package/dist/mcp/server.d.ts +19 -0
  29. package/dist/mcp/server.js +355 -0
  30. package/docs/ai-manifest.json +2472 -0
  31. package/docs/ai.md +128 -0
  32. package/docs/architecture.md +331 -0
  33. package/docs/authentication.md +453 -0
  34. package/docs/authorization.md +167 -0
  35. package/docs/broadcasting.md +137 -0
  36. package/docs/broker.md +500 -0
  37. package/docs/cache.md +558 -0
  38. package/docs/configuration.md +311 -0
  39. package/docs/console.md +356 -0
  40. package/docs/container.md +467 -0
  41. package/docs/controllers.md +265 -0
  42. package/docs/cors.md +51 -0
  43. package/docs/database.md +530 -0
  44. package/docs/debugging.md +129 -0
  45. package/docs/decorators.md +127 -0
  46. package/docs/errors.md +395 -0
  47. package/docs/events.md +496 -0
  48. package/docs/examples/architecture-app.ts +27 -0
  49. package/docs/examples/authentication.ts +61 -0
  50. package/docs/examples/authorization.ts +79 -0
  51. package/docs/examples/broadcasting.ts +60 -0
  52. package/docs/examples/broker-cache-validate.ts +34 -0
  53. package/docs/examples/broker-fault-tolerance.ts +29 -0
  54. package/docs/examples/broker-middleware.ts +27 -0
  55. package/docs/examples/broker.ts +203 -0
  56. package/docs/examples/cache.ts +222 -0
  57. package/docs/examples/configuration.ts +81 -0
  58. package/docs/examples/container.ts +134 -0
  59. package/docs/examples/controllers.ts +86 -0
  60. package/docs/examples/database.ts +118 -0
  61. package/docs/examples/debugging.ts +41 -0
  62. package/docs/examples/decorators.ts +40 -0
  63. package/docs/examples/errors.ts +121 -0
  64. package/docs/examples/events.ts +204 -0
  65. package/docs/examples/factories.ts +84 -0
  66. package/docs/examples/hashing.ts +71 -0
  67. package/docs/examples/health.ts +94 -0
  68. package/docs/examples/helpers.ts +171 -0
  69. package/docs/examples/hooks.ts +54 -0
  70. package/docs/examples/inertia.ts +81 -0
  71. package/docs/examples/locks.ts +120 -0
  72. package/docs/examples/logger.ts +92 -0
  73. package/docs/examples/mail.ts +160 -0
  74. package/docs/examples/middleware.ts +119 -0
  75. package/docs/examples/migrations.ts +126 -0
  76. package/docs/examples/models.ts +239 -0
  77. package/docs/examples/notification.ts +124 -0
  78. package/docs/examples/providers.ts +123 -0
  79. package/docs/examples/queues.ts +254 -0
  80. package/docs/examples/rate-limiting.ts +42 -0
  81. package/docs/examples/redis.ts +99 -0
  82. package/docs/examples/request-response.ts +197 -0
  83. package/docs/examples/routing.ts +186 -0
  84. package/docs/examples/scheduling.ts +62 -0
  85. package/docs/examples/sessions.ts +102 -0
  86. package/docs/examples/static-files.ts +63 -0
  87. package/docs/examples/storage.ts +132 -0
  88. package/docs/examples/templates.ts +58 -0
  89. package/docs/examples/testing.ts +66 -0
  90. package/docs/examples/transformer.ts +141 -0
  91. package/docs/examples/transformers.ts +49 -0
  92. package/docs/examples/url-builder.ts +86 -0
  93. package/docs/examples/validation.ts +102 -0
  94. package/docs/examples/views.tsx +62 -0
  95. package/docs/examples/vite.ts +106 -0
  96. package/docs/factories.md +166 -0
  97. package/docs/getting-started.md +290 -0
  98. package/docs/hashing.md +259 -0
  99. package/docs/health.md +225 -0
  100. package/docs/helpers.md +347 -0
  101. package/docs/hono.md +186 -0
  102. package/docs/hooks.md +118 -0
  103. package/docs/inertia.md +241 -0
  104. package/docs/locks.md +323 -0
  105. package/docs/logger.md +290 -0
  106. package/docs/mail.md +678 -0
  107. package/docs/middleware.md +425 -0
  108. package/docs/migrations.md +476 -0
  109. package/docs/models.md +810 -0
  110. package/docs/notifications.md +474 -0
  111. package/docs/providers.md +363 -0
  112. package/docs/queues.md +679 -0
  113. package/docs/rate-limiting.md +155 -0
  114. package/docs/redis.md +178 -0
  115. package/docs/request-response.md +953 -0
  116. package/docs/routing.md +804 -0
  117. package/docs/scheduling.md +110 -0
  118. package/docs/security.md +85 -0
  119. package/docs/sessions.md +354 -0
  120. package/docs/social-auth.md +174 -0
  121. package/docs/static-files.md +211 -0
  122. package/docs/storage.md +450 -0
  123. package/docs/templates.md +315 -0
  124. package/docs/testing.md +125 -0
  125. package/docs/transformers.md +381 -0
  126. package/docs/url-builder.md +295 -0
  127. package/docs/validation.md +288 -0
  128. package/docs/views.md +267 -0
  129. package/docs/vite.md +434 -0
  130. package/llms-full.txt +17694 -0
  131. package/llms.txt +116 -0
  132. package/package.json +26 -7
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Health checks — the two endpoints an orchestrator (Kubernetes, Fly, Railway,
3
+ * a load balancer) asks about before it sends you traffic:
4
+ *
5
+ * /health/live is the process up? — answers instantly, checks nothing
6
+ * /health/ready can it serve requests? — runs every registered check
7
+ *
8
+ * A liveness probe that touched the database would restart a healthy app during a
9
+ * database blip, so it deliberately checks nothing. Readiness is where the checks
10
+ * live: it reports 200 while everything it depends on is reachable, and 503 when
11
+ * something isn't — which pulls the instance out of the pool without killing it.
12
+ *
13
+ * health().register([new DatabaseCheck(), new RedisCheck()]);
14
+ * this.use(healthCheck()); // serves both endpoints
15
+ *
16
+ * Keel ships the checks that mean something wherever it runs — a database, Redis,
17
+ * the cache. Deliberately absent are AdonisJS's disk-space and heap/RSS checks:
18
+ * they measure a Node process, and on Workers there isn't one.
19
+ */
20
+ import { connection } from "./database.js";
21
+ import { redis } from "./redis.js";
22
+ import { cache } from "./helpers.js";
23
+ /** The outcome of a single check. Build one with `Result.ok/warning/failed`. */
24
+ export class Result {
25
+ status;
26
+ message;
27
+ error;
28
+ meta;
29
+ constructor(status, message, error, meta) {
30
+ this.status = status;
31
+ this.message = message;
32
+ this.error = error;
33
+ this.meta = meta;
34
+ }
35
+ /** Healthy. */
36
+ static ok(message) {
37
+ return new Result("ok", message);
38
+ }
39
+ /**
40
+ * Working, but degraded — worth paging someone, not worth pulling the instance
41
+ * out of the pool. A warning keeps readiness at 200.
42
+ */
43
+ static warning(message) {
44
+ return new Result("warning", message);
45
+ }
46
+ /** Broken. Any failed check takes readiness to 503. */
47
+ static failed(message, error) {
48
+ return new Result("error", message, error);
49
+ }
50
+ /** Attach arbitrary detail — connection counts, latencies, versions. */
51
+ withMeta(meta) {
52
+ this.meta = { ...this.meta, ...meta };
53
+ return this;
54
+ }
55
+ }
56
+ /* --------------------------------- checks --------------------------------- */
57
+ /** One thing worth knowing about before serving traffic. */
58
+ export class BaseCheck {
59
+ ttl = 0;
60
+ cached;
61
+ /**
62
+ * Reuse this check's last result for `seconds`, so a probe every few seconds
63
+ * doesn't hammer the thing it's checking. The report marks a reused result
64
+ * with `isCached: true`.
65
+ */
66
+ cacheFor(seconds) {
67
+ this.ttl = seconds;
68
+ return this;
69
+ }
70
+ /**
71
+ * Run the check, honouring `cacheFor`, and never throw: a check that blows up
72
+ * is itself a failure, and one broken check must not take down the report.
73
+ *
74
+ * @internal — called by `HealthChecks.run()`.
75
+ */
76
+ async execute() {
77
+ const now = Date.now();
78
+ if (this.cached && this.cached.expiresAt > now) {
79
+ return { result: this.cached.result, isCached: true };
80
+ }
81
+ let result;
82
+ try {
83
+ result = await this.run();
84
+ }
85
+ catch (error) {
86
+ result = Result.failed(error instanceof Error ? error.message : `The "${this.name}" check threw.`, error);
87
+ }
88
+ if (this.ttl > 0) {
89
+ this.cached = { result, expiresAt: now + this.ttl * 1000 };
90
+ }
91
+ return { result, isCached: false };
92
+ }
93
+ }
94
+ /**
95
+ * A check from a plain function — the escape hatch, for anything Keel doesn't
96
+ * ship a check for.
97
+ *
98
+ * health().register([
99
+ * check("stripe", async () => {
100
+ * const res = await fetch("https://api.stripe.com/healthcheck");
101
+ * return res.ok ? Result.ok("Stripe is reachable") : Result.failed("Stripe is down");
102
+ * }),
103
+ * ]);
104
+ */
105
+ export function check(name, run) {
106
+ return new (class extends BaseCheck {
107
+ name = name;
108
+ async run() {
109
+ return run();
110
+ }
111
+ })();
112
+ }
113
+ /** Is the database reachable? Runs `SELECT 1` on a connection. */
114
+ export class DatabaseCheck extends BaseCheck {
115
+ connectionName;
116
+ name;
117
+ constructor(connectionName) {
118
+ super();
119
+ this.connectionName = connectionName;
120
+ this.name = connectionName ? `database (${connectionName})` : "database";
121
+ }
122
+ async run() {
123
+ const started = Date.now();
124
+ await connection(this.connectionName).select("SELECT 1");
125
+ return Result.ok("Database is reachable").withMeta({ durationMs: Date.now() - started });
126
+ }
127
+ }
128
+ /** Is Redis reachable? Reads a key — a failed read means a broken connection. */
129
+ export class RedisCheck extends BaseCheck {
130
+ name = "redis";
131
+ async run() {
132
+ const started = Date.now();
133
+ await redis().get("keel:health");
134
+ return Result.ok("Redis is reachable").withMeta({ durationMs: Date.now() - started });
135
+ }
136
+ }
137
+ /** Does the cache round-trip? Writes a key, reads it back, deletes it. */
138
+ export class CacheCheck extends BaseCheck {
139
+ name = "cache";
140
+ async run() {
141
+ const started = Date.now();
142
+ const key = "keel:health";
143
+ const store = cache();
144
+ await store.put(key, "ok", 10);
145
+ const value = await store.get(key);
146
+ await store.forget(key);
147
+ if (value !== "ok") {
148
+ return Result.failed("The cache did not return the value just written to it.");
149
+ }
150
+ return Result.ok("Cache is reachable").withMeta({ durationMs: Date.now() - started });
151
+ }
152
+ }
153
+ /* ------------------------------- the registry ------------------------------ */
154
+ export class HealthChecks {
155
+ checks = [];
156
+ /** Add checks. Call it once, in a provider's `boot()`. */
157
+ register(checks) {
158
+ this.checks.push(...checks);
159
+ return this;
160
+ }
161
+ /** Every registered check. */
162
+ all() {
163
+ return [...this.checks];
164
+ }
165
+ clear() {
166
+ this.checks = [];
167
+ return this;
168
+ }
169
+ /** Run every check — concurrently, since they're independent I/O. */
170
+ async run() {
171
+ const results = await Promise.all(this.checks.map(async (c) => {
172
+ const { result, isCached } = await c.execute();
173
+ return {
174
+ name: c.name,
175
+ status: result.status,
176
+ message: result.message,
177
+ isCached,
178
+ finishedAt: new Date().toISOString(),
179
+ ...(result.meta ? { meta: result.meta } : {}),
180
+ };
181
+ }));
182
+ const status = results.some((r) => r.status === "error")
183
+ ? "error"
184
+ : results.some((r) => r.status === "warning")
185
+ ? "warning"
186
+ : "ok";
187
+ return {
188
+ isHealthy: status !== "error",
189
+ status,
190
+ finishedAt: new Date().toISOString(),
191
+ checks: results,
192
+ };
193
+ }
194
+ }
195
+ const registry = new HealthChecks();
196
+ /** The application's health-check registry. */
197
+ export function health() {
198
+ return registry;
199
+ }
200
+ /**
201
+ * Serve `/health/live` and `/health/ready`. Anything else falls through to your
202
+ * routes.
203
+ *
204
+ * this.use(healthCheck());
205
+ * this.use(healthCheck({ secret: env.HEALTH_SECRET }));
206
+ */
207
+ export function healthCheck(options = {}) {
208
+ const basePath = (options.basePath ?? "/health").replace(/\/+$/, "");
209
+ return async (c, next) => {
210
+ if (c.req.method !== "GET" && c.req.method !== "HEAD")
211
+ return next();
212
+ const { pathname } = new URL(c.req.url);
213
+ // Liveness: the process answered, which is the whole question. Checking a
214
+ // dependency here would get a healthy app restarted during a database blip.
215
+ if (pathname === `${basePath}/live`) {
216
+ return c.json({ isHealthy: true, status: "ok" });
217
+ }
218
+ if (pathname !== `${basePath}/ready`)
219
+ return next();
220
+ if (options.secret && c.req.header("Authorization") !== `Bearer ${options.secret}`) {
221
+ return c.json({ message: "Unauthorized" }, 401);
222
+ }
223
+ const report = await (options.checks ?? registry).run();
224
+ return c.json(report, report.isHealthy ? 200 : 503);
225
+ };
226
+ }
@@ -10,11 +10,17 @@
10
10
  import type { Application } from "./application.js";
11
11
  import type { Token, Factory } from "./container.js";
12
12
  import { type Renderable } from "./view.js";
13
- import { Events, type Listener } from "./events.js";
13
+ import { Events, type Listener, type EventName, type EmitArgs, type Resolve } from "./events.js";
14
14
  import { Cache } from "./cache.js";
15
15
  import { Logger } from "./logger.js";
16
16
  /** Register the active application. Called by the Application constructor. */
17
17
  export declare function setApplication(app: Application): void;
18
+ /**
19
+ * Whether an application has been bootstrapped. Primitives that must work
20
+ * without one — a queue worker, a mailer in a unit test — check this before
21
+ * reaching for `logger()` or `emit()`, both of which throw when there's no app.
22
+ */
23
+ export declare function hasApplication(): boolean;
18
24
  /** The active application container. Throws if none has been created. */
19
25
  export declare function app(): Application;
20
26
  /** Run a callback once the active application has booted (see `Application.onReady`). */
@@ -47,9 +53,9 @@ export declare function restore(token?: Token): void;
47
53
  /** The application's event emitter. */
48
54
  export declare function events(): Events;
49
55
  /** Emit an event, awaiting every listener. */
50
- export declare function emit<T = unknown>(event: string, payload?: T): Promise<void>;
56
+ export declare function emit<T = unknown, E extends EventName = EventName>(event: E, ...args: EmitArgs<Resolve<T, E>>): Promise<void>;
51
57
  /** Subscribe to an event; returns an unsubscribe function. */
52
- export declare function listen<T = unknown>(event: string, listener: Listener<T>): () => void;
58
+ export declare function listen<T = unknown, E extends EventName = EventName>(event: E, listener: Listener<Resolve<T, E>>): () => void;
53
59
  /** The application's cache. */
54
60
  export declare function cache(): Cache;
55
61
  /** The application's logger. */
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { Config } from "./config.js";
11
11
  import { View } from "./view.js";
12
- import { Events } from "./events.js";
12
+ import { Events, } from "./events.js";
13
13
  import { Cache } from "./cache.js";
14
14
  import { Logger } from "./logger.js";
15
15
  let current;
@@ -17,6 +17,14 @@ let current;
17
17
  export function setApplication(app) {
18
18
  current = app;
19
19
  }
20
+ /**
21
+ * Whether an application has been bootstrapped. Primitives that must work
22
+ * without one — a queue worker, a mailer in a unit test — check this before
23
+ * reaching for `logger()` or `emit()`, both of which throw when there's no app.
24
+ */
25
+ export function hasApplication() {
26
+ return current !== undefined;
27
+ }
20
28
  /** The active application container. Throws if none has been created. */
21
29
  export function app() {
22
30
  if (!current) {
@@ -83,8 +91,8 @@ export function events() {
83
91
  return app().make(Events);
84
92
  }
85
93
  /** Emit an event, awaiting every listener. */
86
- export function emit(event, payload) {
87
- return events().emit(event, payload);
94
+ export function emit(event, ...args) {
95
+ return events().emit(event, ...args);
88
96
  }
89
97
  /** Subscribe to an event; returns an unsubscribe function. */
90
98
  export function listen(event, listener) {
@@ -6,17 +6,22 @@ export type { BootOptions, LifecycleHook, Configurator } from "./application.js"
6
6
  export { Config, env } from "./config.js";
7
7
  export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
8
8
  export { Logger } from "./logger.js";
9
- export type { LogLevel, LoggerOptions } from "./logger.js";
9
+ export { consoleSink, MemorySink, setLogger, namedLogger } from "./logger.js";
10
+ export type { LogLevel, LoggerOptions, LogRecord, Sink, RedactOptions } from "./logger.js";
10
11
  export { requestLogger, requestLog } from "./request-logger.js";
11
12
  export type { RequestLoggerOptions } from "./request-logger.js";
12
- export { Events } from "./events.js";
13
- export type { Listener } from "./events.js";
13
+ export { Events, EventBuffer } from "./events.js";
14
+ export type { Listener, AnyListener, ErrorHandler, EventsList, EventName, PayloadOf, RecordedEvent, } from "./events.js";
14
15
  export { Cache, MemoryStore } from "./cache.js";
15
- export type { CacheStore } from "./cache.js";
16
+ export type { CacheStore, RememberOptions, PutOptions } from "./cache.js";
16
17
  export { serveStatic } from "./static.js";
17
18
  export type { StaticOptions } from "./static.js";
18
- export { Storage, MemoryDisk, storage, setDisk } from "./storage.js";
19
- export type { Disk, Contents } from "./storage.js";
19
+ export { lock, restoreLock, setLockStore, getLockStore, Lock, MemoryLockStore, LockNotHeldError, } from "./lock.js";
20
+ export type { LockStore, AcquireOptions } from "./lock.js";
21
+ export { health, healthCheck, check, Result, BaseCheck, HealthChecks, DatabaseCheck, RedisCheck, CacheCheck, } from "./health.js";
22
+ export type { HealthStatus, HealthReport, CheckReport, HealthCheckOptions, } from "./health.js";
23
+ export { Storage, FakeStorage, MemoryDisk, storage, setDisk, fakeDisk, restoreDisk, serveStorage, signStorageUrl, verifyStorageUrl, contentTypeFor, } from "./storage.js";
24
+ export type { Disk, Contents, FileVisibility, WriteOptions, FileMetadata, SignedFileOptions, SignedUploadOptions, ServeStorageOptions, } from "./storage.js";
20
25
  export { dump, dd } from "./debug.js";
21
26
  export { hash, encryption, jwt } from "./crypto.js";
22
27
  export type { JwtPayload, JwtSignOptions, JwtVerifyOptions, EncryptOptions } from "./crypto.js";
@@ -35,10 +40,10 @@ export type { CastType, Casts } from "./casts.js";
35
40
  export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
36
41
  export { Faker, Factory as ModelFactory, factory, Seeder, seed } from "./factory.js";
37
42
  export type { Definition } from "./factory.js";
38
- export { Mailer, PendingMail, ArrayTransport, LogTransport, fetchTransport, mail, setMailer, getMailer, } from "./mail.js";
39
- export type { Message, Transport, MailerOptions, FetchTransportOptions } from "./mail.js";
40
- export { Job, Queue, SyncDriver, MemoryDriver, dispatch, work, setQueue, getQueue, } from "./queue.js";
41
- export type { Dispatchable, JobOptions, QueueDriver, Drainable, QueuedJob } from "./queue.js";
43
+ export { Mailer, FakeMailer, PendingMail, BaseMail, SendMailJob, ArrayTransport, LogTransport, fetchTransport, mail, mailer, send, sendLater, setMailer, getMailer, fakeMail, restoreMail, } from "./mail.js";
44
+ export type { Message, Transport, MailerOptions, FetchTransportOptions, Attachment, RecordedMail, } from "./mail.js";
45
+ export { Job, Queue, FakeQueue, SyncDriver, MemoryDriver, dispatch, work, setQueue, getQueue, fakeQueue, restoreQueue, exponentialBackoff, linearBackoff, fixedBackoff, noBackoff, } from "./queue.js";
46
+ export type { Dispatchable, JobOptions, JobContext, JobClass, QueueDriver, Drainable, QueuedJob, FailedJob, Backoff, } from "./queue.js";
42
47
  export { Scheduler, ScheduledTask, scheduler, setScheduler, schedule, cronMatches } from "./scheduler.js";
43
48
  export { MemoryBroadcaster, broadcast, setBroadcaster, getBroadcaster, channelAuth, authorizeChannel, clearChannels, } from "./broadcasting.js";
44
49
  export type { Broadcaster, Subscriber, ChannelAuthorizer } from "./broadcasting.js";
@@ -4,11 +4,14 @@ export { Application } from "./application.js";
4
4
  export { Config, env } from "./config.js";
5
5
  export { app, config, view, bind, singleton, instance, make, bound, alias, swap, restore, events, emit, listen, cache, logger, onReady, onShutdown, terminate, } from "./helpers.js";
6
6
  export { Logger } from "./logger.js";
7
+ export { consoleSink, MemorySink, setLogger, namedLogger } from "./logger.js";
7
8
  export { requestLogger, requestLog } from "./request-logger.js";
8
- export { Events } from "./events.js";
9
+ export { Events, EventBuffer } from "./events.js";
9
10
  export { Cache, MemoryStore } from "./cache.js";
10
11
  export { serveStatic } from "./static.js";
11
- export { Storage, MemoryDisk, storage, setDisk } from "./storage.js";
12
+ export { lock, restoreLock, setLockStore, getLockStore, Lock, MemoryLockStore, LockNotHeldError, } from "./lock.js";
13
+ export { health, healthCheck, check, Result, BaseCheck, HealthChecks, DatabaseCheck, RedisCheck, CacheCheck, } from "./health.js";
14
+ export { Storage, FakeStorage, MemoryDisk, storage, setDisk, fakeDisk, restoreDisk, serveStorage, signStorageUrl, verifyStorageUrl, contentTypeFor, } from "./storage.js";
12
15
  export { dump, dd } from "./debug.js";
13
16
  export { hash, encryption, jwt } from "./crypto.js";
14
17
  export { rateLimiter } from "./rate-limit.js";
@@ -19,8 +22,8 @@ export { db, connection, setConnection, addConnection, setDefaultConnection, con
19
22
  export { Model } from "./model.js";
20
23
  export { Relation, HasOne, HasMany, BelongsTo, BelongsToMany } from "./relations.js";
21
24
  export { Faker, Factory as ModelFactory, factory, Seeder, seed } from "./factory.js";
22
- export { Mailer, PendingMail, ArrayTransport, LogTransport, fetchTransport, mail, setMailer, getMailer, } from "./mail.js";
23
- export { Job, Queue, SyncDriver, MemoryDriver, dispatch, work, setQueue, getQueue, } from "./queue.js";
25
+ export { Mailer, FakeMailer, PendingMail, BaseMail, SendMailJob, ArrayTransport, LogTransport, fetchTransport, mail, mailer, send, sendLater, setMailer, getMailer, fakeMail, restoreMail, } from "./mail.js";
26
+ export { Job, Queue, FakeQueue, SyncDriver, MemoryDriver, dispatch, work, setQueue, getQueue, fakeQueue, restoreQueue, exponentialBackoff, linearBackoff, fixedBackoff, noBackoff, } from "./queue.js";
24
27
  export { Scheduler, ScheduledTask, scheduler, setScheduler, schedule, cronMatches } from "./scheduler.js";
25
28
  export { MemoryBroadcaster, broadcast, setBroadcaster, getBroadcaster, channelAuth, authorizeChannel, clearChannels, } from "./broadcasting.js";
26
29
  export { Redis, MemoryRedis, redis, setRedis, redisStore } from "./redis.js";
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Distributed locks — "only one of you may do this at a time", across processes
3
+ * and across nodes. The counterpart to the cache's stampede protection: that
4
+ * collapses concurrent work *inside one isolate*, this one coordinates work
5
+ * *between* them.
6
+ *
7
+ * const [ran] = await lock("invoice:42").run(async () => charge(invoice));
8
+ * if (!ran) return; // someone else is already charging it
9
+ *
10
+ * Like every other backend in keel, the store is a small pluggable seam and the
11
+ * core imports no driver — `MemoryLockStore` is the built-in default (per
12
+ * isolate, so it's for tests and single-process apps); the locks guide has Redis
13
+ * and database recipes for the real thing.
14
+ *
15
+ * ## Two safety properties, both easy to get wrong
16
+ *
17
+ * **Ownership.** Every acquisition mints a random owner token, and release and
18
+ * extend only succeed for the owner. Without that, a lock whose TTL expired
19
+ * mid-work gets picked up by process B, and process A's `release()` — arriving
20
+ * late — would delete *B's* lock, letting a third process in. A store must
21
+ * compare-and-delete, not just delete.
22
+ *
23
+ * **TTL.** A lock always expires. If a holder crashes, the lock must not be held
24
+ * forever, so there's no "hold until released" mode. Pick a TTL longer than the
25
+ * work; if the work might outrun it, `extend()` from inside.
26
+ */
27
+ /**
28
+ * The bridge to a lock backend.
29
+ *
30
+ * Every method is keyed by `owner` for the reason above: implementations MUST
31
+ * make `acquire` atomic (set-if-absent) and `release`/`extend` conditional on
32
+ * the owner matching. A store that can't do that isn't a lock.
33
+ */
34
+ export interface LockStore {
35
+ /** Set the key if absent. Returns whether this owner got it. Must be atomic. */
36
+ acquire(key: string, owner: string, ttlMs: number): Promise<boolean>;
37
+ /** Delete the key only if `owner` still holds it. Returns whether it did. */
38
+ release(key: string, owner: string): Promise<boolean>;
39
+ /** Push the expiry out, only if `owner` still holds it. */
40
+ extend(key: string, owner: string, ttlMs: number): Promise<boolean>;
41
+ /** Whether anyone currently holds the key. */
42
+ isLocked(key: string): Promise<boolean>;
43
+ /** Milliseconds until the key expires, or null if nobody holds it. */
44
+ remainingTime(key: string): Promise<number | null>;
45
+ }
46
+ /**
47
+ * An in-memory `LockStore` — the default. Per-isolate, so it coordinates
48
+ * *within* one process and nothing more: perfect for tests and single-process
49
+ * apps, useless across a cluster. Point `setLockStore()` at Redis for that.
50
+ */
51
+ export declare class MemoryLockStore implements LockStore {
52
+ private entries;
53
+ /** The live entry for a key, dropping it if it has expired. */
54
+ private live;
55
+ acquire(key: string, owner: string, ttlMs: number): Promise<boolean>;
56
+ release(key: string, owner: string): Promise<boolean>;
57
+ extend(key: string, owner: string, ttlMs: number): Promise<boolean>;
58
+ isLocked(key: string): Promise<boolean>;
59
+ remainingTime(key: string): Promise<number | null>;
60
+ }
61
+ export interface AcquireOptions {
62
+ /**
63
+ * Give up after this many milliseconds of waiting. Default: 0 — don't wait at
64
+ * all, fail immediately if the lock is held.
65
+ */
66
+ timeout?: number;
67
+ /** Milliseconds between attempts while waiting. Default: 50. */
68
+ retryDelay?: number;
69
+ }
70
+ /** A lock that has expired or was never held. */
71
+ export declare class LockNotHeldError extends Error {
72
+ constructor(key: string);
73
+ }
74
+ export declare class Lock {
75
+ readonly key: string;
76
+ /** How long the lock is held before it expires on its own. Milliseconds. */
77
+ readonly ttlMs: number;
78
+ private store;
79
+ /** Non-null only while this instance holds the lock. */
80
+ private owner?;
81
+ constructor(key: string,
82
+ /** How long the lock is held before it expires on its own. Milliseconds. */
83
+ ttlMs: number, store: LockStore, owner?: string);
84
+ /**
85
+ * Take the lock, run `fn`, and always give it back — the form you want almost
86
+ * every time, because the `finally` is what stops a throwing callback from
87
+ * leaving the lock held until its TTL runs out.
88
+ *
89
+ * Returns `[ran, result]`: `ran` is false if someone else holds it, in which
90
+ * case `fn` never ran.
91
+ *
92
+ * const [ran, invoice] = await lock("invoice:42").run(() => charge(id));
93
+ * if (!ran) return; // another worker is on it
94
+ */
95
+ run<T>(fn: () => Promise<T> | T, options?: AcquireOptions): Promise<[boolean, T | undefined]>;
96
+ /** `run()`, but never waits: if the lock is held, give up at once. */
97
+ runImmediately<T>(fn: () => Promise<T> | T): Promise<[boolean, T | undefined]>;
98
+ /**
99
+ * Take the lock, waiting up to `timeout` for it. Returns whether we got it.
100
+ *
101
+ * Prefer `run()` — with a bare `acquire()` you own the `try/finally`, and a
102
+ * throw between here and `release()` leaks the lock for the rest of its TTL.
103
+ */
104
+ acquire(options?: AcquireOptions): Promise<boolean>;
105
+ /** Take the lock only if it's free right now. */
106
+ acquireImmediately(): Promise<boolean>;
107
+ /** Give the lock back. A no-op if we no longer hold it (it may have expired). */
108
+ release(): Promise<boolean>;
109
+ /**
110
+ * Push the expiry out — for work that might outrun the TTL. Throws
111
+ * `LockNotHeldError` if we've already lost it, because the alternative (a
112
+ * silent no-op) would let you carry on believing you hold a lock you don't.
113
+ */
114
+ extend(ttlMs?: number): Promise<void>;
115
+ /** Whether *anyone* holds this key — not necessarily us. */
116
+ isLocked(): Promise<boolean>;
117
+ /** Whether we held this lock but no longer do. */
118
+ isExpired(): Promise<boolean>;
119
+ /** Milliseconds until the lock expires, or null if nobody holds it. */
120
+ getRemainingTime(): Promise<number | null>;
121
+ /**
122
+ * Freeze the lock (key, TTL, owner token) to a string, so another process can
123
+ * `restoreLock()` it and release or extend the *same* lock — the handoff you
124
+ * need when one process takes the lock and another finishes the work.
125
+ */
126
+ serialize(): string;
127
+ }
128
+ /** Register the lock store used by `lock()`. */
129
+ export declare function setLockStore(next: LockStore): LockStore;
130
+ /** The active lock store. */
131
+ export declare function getLockStore(): LockStore;
132
+ /**
133
+ * A lock on `key`, held for `ttlMs` once acquired (default 30s).
134
+ *
135
+ * const [ran] = await lock("invoice:42").run(() => charge(invoice));
136
+ */
137
+ export declare function lock(key: string, ttlMs?: number): Lock;
138
+ /** Rebuild a lock from `serialize()` — same key, same TTL, same owner token. */
139
+ export declare function restoreLock(serialized: string): Lock;