easy-ping 0.3.0 → 0.5.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 (127) hide show
  1. package/README.md +174 -82
  2. package/dist/adapter-B7R4rXtJ.d.cts +17 -0
  3. package/dist/adapter-B7R4rXtJ.d.ts +17 -0
  4. package/dist/{adapter-BfLe2J0t.d.cts → adapter-D3lTy-4E.d.cts} +6 -32
  5. package/dist/{adapter-BfLe2J0t.d.ts → adapter-D3lTy-4E.d.ts} +6 -32
  6. package/dist/adapters/drizzle.cjs +4 -4
  7. package/dist/adapters/drizzle.cjs.map +1 -1
  8. package/dist/adapters/drizzle.d.cts +35 -1
  9. package/dist/adapters/drizzle.d.ts +35 -1
  10. package/dist/adapters/drizzle.js +5 -5
  11. package/dist/adapters/drizzle.js.map +1 -1
  12. package/dist/adapters/mongodb.cjs +8 -7
  13. package/dist/adapters/mongodb.cjs.map +1 -1
  14. package/dist/adapters/mongodb.d.cts +4 -17
  15. package/dist/adapters/mongodb.d.ts +4 -17
  16. package/dist/adapters/mongodb.js +7 -7
  17. package/dist/adapters/mongodb.js.map +1 -1
  18. package/dist/adapters/mysql.cjs +605 -0
  19. package/dist/adapters/mysql.cjs.map +1 -0
  20. package/dist/adapters/mysql.d.cts +38 -0
  21. package/dist/adapters/mysql.d.ts +38 -0
  22. package/dist/adapters/mysql.js +51 -0
  23. package/dist/adapters/mysql.js.map +1 -0
  24. package/dist/adapters/postgres.cjs +19 -11
  25. package/dist/adapters/postgres.cjs.map +1 -1
  26. package/dist/adapters/postgres.d.cts +9 -54
  27. package/dist/adapters/postgres.d.ts +9 -54
  28. package/dist/adapters/postgres.js +6 -41
  29. package/dist/adapters/postgres.js.map +1 -1
  30. package/dist/adapters/sqlite.cjs +593 -0
  31. package/dist/adapters/sqlite.cjs.map +1 -0
  32. package/dist/adapters/sqlite.d.cts +37 -0
  33. package/dist/adapters/sqlite.d.ts +37 -0
  34. package/dist/adapters/sqlite.js +53 -0
  35. package/dist/adapters/sqlite.js.map +1 -0
  36. package/dist/browser.cjs.map +1 -1
  37. package/dist/browser.d.cts +2 -14
  38. package/dist/browser.d.ts +2 -14
  39. package/dist/browser.js.map +1 -1
  40. package/dist/chunk-2EYKPF5A.js +393 -0
  41. package/dist/chunk-2EYKPF5A.js.map +1 -0
  42. package/dist/{chunk-OZSOIX23.js → chunk-3VOQOGDR.js} +54 -12
  43. package/dist/chunk-3VOQOGDR.js.map +1 -0
  44. package/dist/{chunk-NYDVNMKG.js → chunk-AEYYTE5I.js} +4 -3
  45. package/dist/chunk-AEYYTE5I.js.map +1 -0
  46. package/dist/chunk-BECMTNK7.js +315 -0
  47. package/dist/chunk-BECMTNK7.js.map +1 -0
  48. package/dist/chunk-CK5UPVYP.js +48 -0
  49. package/dist/chunk-CK5UPVYP.js.map +1 -0
  50. package/dist/{chunk-BV3U542P.js → chunk-E4IZ2CQC.js} +2 -2
  51. package/dist/chunk-E4IZ2CQC.js.map +1 -0
  52. package/dist/chunk-GJNLOQJS.js +226 -0
  53. package/dist/chunk-GJNLOQJS.js.map +1 -0
  54. package/dist/chunk-HY47TQMY.js +52 -0
  55. package/dist/chunk-HY47TQMY.js.map +1 -0
  56. package/dist/chunk-M4R77BJD.js +64 -0
  57. package/dist/chunk-M4R77BJD.js.map +1 -0
  58. package/dist/{chunk-RBDW4S6F.js → chunk-XIDVLYNQ.js} +14 -4
  59. package/dist/chunk-XIDVLYNQ.js.map +1 -0
  60. package/dist/client.cjs.map +1 -1
  61. package/dist/client.js.map +1 -1
  62. package/dist/index.cjs +362 -64
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +56 -37
  65. package/dist/index.d.ts +56 -37
  66. package/dist/index.js +120 -359
  67. package/dist/index.js.map +1 -1
  68. package/dist/node.cjs +27 -6
  69. package/dist/node.cjs.map +1 -1
  70. package/dist/node.d.cts +8 -2
  71. package/dist/node.d.ts +8 -2
  72. package/dist/node.js +26 -6
  73. package/dist/node.js.map +1 -1
  74. package/dist/{plugin-CmPOFVmn.d.ts → plugin-CHd2ZiGC.d.cts} +37 -25
  75. package/dist/{plugin-f5KaFWSN.d.cts → plugin-Cg9usuUi.d.ts} +37 -25
  76. package/dist/plugins/digests.cjs +1 -4
  77. package/dist/plugins/digests.cjs.map +1 -1
  78. package/dist/plugins/digests.d.cts +6 -31
  79. package/dist/plugins/digests.d.ts +6 -31
  80. package/dist/plugins/digests.js +1 -4
  81. package/dist/plugins/digests.js.map +1 -1
  82. package/dist/plugins/preferences-client.cjs.map +1 -1
  83. package/dist/plugins/preferences-client.d.cts +2 -2
  84. package/dist/plugins/preferences-client.d.ts +2 -2
  85. package/dist/plugins/preferences-client.js +1 -1
  86. package/dist/plugins/preferences-react.cjs.map +1 -1
  87. package/dist/plugins/preferences-react.js.map +1 -1
  88. package/dist/plugins/preferences.cjs +101 -17
  89. package/dist/plugins/preferences.cjs.map +1 -1
  90. package/dist/plugins/preferences.d.cts +4 -2
  91. package/dist/plugins/preferences.d.ts +4 -2
  92. package/dist/plugins/preferences.js +36 -9
  93. package/dist/plugins/preferences.js.map +1 -1
  94. package/dist/plugins/push.cjs +198 -40
  95. package/dist/plugins/push.cjs.map +1 -1
  96. package/dist/plugins/push.d.cts +20 -18
  97. package/dist/plugins/push.d.ts +20 -18
  98. package/dist/plugins/push.js +141 -41
  99. package/dist/plugins/push.js.map +1 -1
  100. package/dist/providers/web-push.cjs +37 -11
  101. package/dist/providers/web-push.cjs.map +1 -1
  102. package/dist/providers/web-push.d.cts +10 -11
  103. package/dist/providers/web-push.d.ts +10 -11
  104. package/dist/providers/web-push.js +37 -12
  105. package/dist/providers/web-push.js.map +1 -1
  106. package/dist/react.cjs.map +1 -1
  107. package/dist/react.d.cts +1 -4
  108. package/dist/react.d.ts +1 -4
  109. package/dist/react.js.map +1 -1
  110. package/dist/schema.cjs +121 -2
  111. package/dist/schema.cjs.map +1 -1
  112. package/dist/schema.d.cts +25 -14
  113. package/dist/schema.d.ts +25 -14
  114. package/dist/schema.js +4 -2
  115. package/dist/testing.cjs +3 -19
  116. package/dist/testing.cjs.map +1 -1
  117. package/dist/testing.d.cts +4 -22
  118. package/dist/testing.d.ts +4 -22
  119. package/dist/testing.js +3 -19
  120. package/dist/testing.js.map +1 -1
  121. package/package.json +12 -1
  122. package/dist/chunk-BV3U542P.js.map +0 -1
  123. package/dist/chunk-NYDVNMKG.js.map +0 -1
  124. package/dist/chunk-OZSOIX23.js.map +0 -1
  125. package/dist/chunk-RBDW4S6F.js.map +0 -1
  126. package/dist/chunk-W3BMEPPQ.js +0 -50
  127. package/dist/chunk-W3BMEPPQ.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-BfLe2J0t.cjs';
2
- export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-BfLe2J0t.cjs';
1
+ import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-D3lTy-4E.cjs';
2
+ export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-D3lTy-4E.cjs';
3
3
  import { StandardSchemaV1 } from '@standard-schema/spec';
4
- import { A as AnyPlugin, L as Logger } from './plugin-f5KaFWSN.cjs';
5
- export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, P as PluginHooks, j as PrepareContext, k as Promisable, R as ResolveChannelsContext, l as RouteContext, m as RouteDefinition, n as RouteScope, S as SchemaDeclaration, T as TableDeclaration, U as UnionToIntersection, V as ValidationError, o as consoleLogger, p as defineClientPlugin, q as definePlugin } from './plugin-f5KaFWSN.cjs';
4
+ import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-CHd2ZiGC.cjs';
5
+ export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-CHd2ZiGC.cjs';
6
6
  import { E as EmailProvider } from './provider-CA45WVqt.cjs';
7
7
  export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.cjs';
8
8
 
@@ -23,10 +23,7 @@ type NotificationDefinition<TSchema extends StandardSchemaV1 | undefined = Stand
23
23
  };
24
24
  /** Binds TSchema so email callbacks infer the payload; TS cannot infer from a sibling property. */
25
25
  declare function defineNotification<TSchema extends StandardSchemaV1 | undefined = undefined>(definition: NotificationDefinition<TSchema>): NotificationDefinition<TSchema>;
26
- /**
27
- * Erased form. `EmailTemplate<never>` because callbacks are contravariant —
28
- * anything else makes every schema-typed definition fail to assign.
29
- */
26
+ /** Erased form. `EmailTemplate<never>` because callbacks are contravariant. */
30
27
  type AnyNotificationDefinition = {
31
28
  schema?: StandardSchemaV1 | undefined;
32
29
  channels: readonly Channel[];
@@ -38,6 +35,16 @@ type PayloadOf<TDef> = TDef extends {
38
35
  schema: infer TSchema extends StandardSchemaV1;
39
36
  } ? StandardSchemaV1.InferOutput<TSchema> : Record<string, unknown>;
40
37
 
38
+ type RateLimitConfig = {
39
+ /** Requests allowed per key per window. */
40
+ max: number;
41
+ windowMs: number;
42
+ /** Bucket key. Defaults to the client address from proxy headers; null exempts the request. */
43
+ key?: (request: Request) => string | null;
44
+ };
45
+ /** Fixed window in process memory: per isolate or replica, so a bound rather than a quota. */
46
+ declare function createRateLimiter(config: RateLimitConfig): (request: Request) => Response | null;
47
+
41
48
  type SessionConfig = {
42
49
  /** null yields 401; throwing yields 500. Deliberately distinct. */
43
50
  getUserId: (request: Request) => string | null | Promise<string | null>;
@@ -51,10 +58,7 @@ type DeliveryConfig = {
51
58
  /** inline mode only. */
52
59
  throwOnError?: boolean;
53
60
  backoff?: "exponential" | ((attempt: number) => number);
54
- /**
55
- * Next.js `after`, Cloudflare `ctx.waitUntil`, or @vercel/functions.
56
- * Injected, not detected: importing a maybe-absent package breaks bundlers.
57
- */
61
+ /** Next.js `after` or Cloudflare `ctx.waitUntil`. Injected, not detected. */
58
62
  waitUntil?: (promise: Promise<unknown>) => void;
59
63
  };
60
64
  type ChannelsConfig = {
@@ -77,10 +81,22 @@ type EasyPingConfig<TDefs extends NotificationDefinitions> = {
77
81
  /** Required by `cron` and `deferred` modes. See RFC 0002 §4. */
78
82
  cron?: {
79
83
  secret: string;
84
+ /** Sweeps one POST /cron may run before returning. Defaults to 50. */
85
+ maxSweeps?: number;
80
86
  };
87
+ /** Bearer secret for plugin machine routes. Falls back to `cron.secret`. */
88
+ machineSecret?: string;
89
+ /** In-process fixed-window limiter, applied before routing. */
90
+ rateLimit?: RateLimitConfig;
81
91
  plugins?: readonly AnyPlugin[];
82
92
  /** Where the handler is mounted, used to strip the prefix off incoming URLs. */
83
93
  basePath?: string;
94
+ /** Origins other than the request host that may POST. `"*.example.com"` matches subdomains. */
95
+ trustedOrigins?: readonly string[];
96
+ /** Runs before routing. Return a Response to short-circuit — a rate limiter's 429, say. */
97
+ onRequest?: (request: Request) => Promisable<Response | undefined | void>;
98
+ /** Defaults to 64 KiB. */
99
+ maxBodyBytes?: number;
84
100
  /** Table-name prefix; must match the one given to the adapter. */
85
101
  tablePrefix?: string;
86
102
  logger?: Logger;
@@ -111,6 +127,13 @@ type SendResult = {
111
127
  type Worker = {
112
128
  stop: () => Promise<void>;
113
129
  };
130
+ type MountedRoute = {
131
+ method: "GET" | "POST";
132
+ path: string;
133
+ scope: RouteScope;
134
+ /** "core" for the built-in routes, otherwise the plugin id. */
135
+ owner: string;
136
+ };
114
137
  type HealthReport = {
115
138
  mode: DeliveryMode;
116
139
  cronMounted: boolean;
@@ -131,13 +154,9 @@ type EasyPing<TDefs extends NotificationDefinitions> = {
131
154
  batchSize?: number;
132
155
  }): Worker;
133
156
  healthCheck(): Promise<HealthReport>;
134
- /**
135
- * Deliveries that exhausted their attempts, newest first.
136
- *
137
- * The retry machinery is otherwise a black box: rows go quiet and the only
138
- * way to ask what happened is SQL against tables the library owns. Wire this
139
- * to an admin page or an alert.
140
- */
157
+ /** Every mounted route with its auth scope, so `custom`-scoped ones stay visible. RFC 0002 §3. */
158
+ listRoutes(): readonly MountedRoute[];
159
+ /** Deliveries that exhausted their attempts, newest first. Wire it to an admin page or an alert. */
141
160
  getFailedDeliveries(options?: {
142
161
  /** Defaults to the last 24 hours. */
143
162
  since?: Date;
@@ -146,8 +165,6 @@ type EasyPing<TDefs extends NotificationDefinitions> = {
146
165
  }): Promise<readonly DeliveryRecord[]>;
147
166
  };
148
167
 
149
- declare function easyPing<TDefs extends NotificationDefinitions>(config: EasyPingConfig<TDefs>): EasyPing<TDefs>;
150
-
151
168
  type Backoff = "exponential" | ((attempt: number) => number);
152
169
 
153
170
  type RunnerDeps = {
@@ -168,6 +185,8 @@ type SweepResult = {
168
185
  /** Deliveries that had nothing to do. Not errors; see DeliveryOutcome. */
169
186
  skipped: number;
170
187
  };
188
+ /** Persisted to `last_error`, which admin tooling reads: addresses do not go in. */
189
+ declare const redactErrorMessage: (text: string) => string;
171
190
  declare function createRunner(deps: RunnerDeps): {
172
191
  runOnce: (options?: {
173
192
  limit?: number;
@@ -186,20 +205,20 @@ declare function createRunner(deps: RunnerDeps): {
186
205
  };
187
206
  type Runner = ReturnType<typeof createRunner>;
188
207
 
189
- type TokenClaims = {
190
- /** Subject — the user the token acts for. */
191
- uid: string;
192
- /** What the token is allowed to do. Verification requires an exact match. */
193
- purpose: string;
194
- /** Expiry, epoch seconds. */
195
- exp: number;
196
- /** Free-form, purpose-specific. */
197
- data?: Record<string, string>;
198
- };
199
- declare function signToken(secret: string, claims: TokenClaims): Promise<string>;
200
- /** null for every failure mode. Distinguishing them tells an attacker which part to fix. */
201
- declare function verifyToken(secret: string, token: string, purpose: string, now?: Date): Promise<TokenClaims | null>;
202
- declare const DEFAULT_TOKEN_TTL_SECONDS: number;
203
- declare const expiresIn: (seconds: number, now?: Date) => number;
208
+ /** Every JSON body is capped here. Nothing legitimate the routes accept is bigger. */
209
+ declare const MAX_BODY_BYTES: number;
210
+ type JsonBody = {
211
+ body: Record<string, unknown>;
212
+ } | {
213
+ error: Response;
214
+ };
215
+ /** Reads a JSON object body with a streaming byte cap. Malformed JSON is 400; an empty body is `{}`. */
216
+ declare function readJsonBody(request: Request, maxBytes?: number): Promise<JsonBody>;
217
+
218
+ /** For template literals: anything a user typed goes through this, or their HTML ships from your domain. */
219
+ declare const escapeHtml: (value: unknown) => string;
220
+
221
+ declare const MIN_SECRET_LENGTH = 16;
222
+ declare function easyPing<TDefs extends NotificationDefinitions>(config: EasyPingConfig<TDefs>): EasyPing<TDefs>;
204
223
 
205
- export { type AnyNotificationDefinition, AnyPlugin, Channel, type ChannelsConfig, DEFAULT_TOKEN_TTL_SECONDS, DatabaseAdapter, type DeliveryConfig, DeliveryMode, DeliveryRecord, type EasyPing, type EasyPingConfig, EmailProvider, type EmailTemplate, type HealthReport, Logger, type NotificationDefinition, type NotificationDefinitions, type PayloadFromSchema, type PayloadOf, Recipient, type Runner, type RunnerDeps, type SendArgs, type SendResult, type SessionConfig, SkipReason, type SweepResult, type TokenClaims, type Worker, createRunner, decodeBase64Url, defineNotification, easyPing, encodeBase64Url, expiresIn, signToken, verifyToken };
224
+ export { type AnyNotificationDefinition, AnyPlugin, Channel, type ChannelsConfig, DatabaseAdapter, type DeliveryConfig, DeliveryMode, DeliveryRecord, type EasyPing, type EasyPingConfig, EmailProvider, type EmailTemplate, type HealthReport, type JsonBody, Logger, MAX_BODY_BYTES, MIN_SECRET_LENGTH, type MountedRoute, type NotificationDefinition, type NotificationDefinitions, type PayloadFromSchema, type PayloadOf, Promisable, type RateLimitConfig, Recipient, RouteScope, type Runner, type RunnerDeps, type SendArgs, type SendResult, type SessionConfig, SkipReason, type SweepResult, type Worker, createRateLimiter, createRunner, decodeBase64Url, defineNotification, easyPing, encodeBase64Url, escapeHtml, readJsonBody, redactErrorMessage };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-BfLe2J0t.js';
2
- export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-BfLe2J0t.js';
1
+ import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-D3lTy-4E.js';
2
+ export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-D3lTy-4E.js';
3
3
  import { StandardSchemaV1 } from '@standard-schema/spec';
4
- import { A as AnyPlugin, L as Logger } from './plugin-CmPOFVmn.js';
5
- export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, P as PluginHooks, j as PrepareContext, k as Promisable, R as ResolveChannelsContext, l as RouteContext, m as RouteDefinition, n as RouteScope, S as SchemaDeclaration, T as TableDeclaration, U as UnionToIntersection, V as ValidationError, o as consoleLogger, p as defineClientPlugin, q as definePlugin } from './plugin-CmPOFVmn.js';
4
+ import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-Cg9usuUi.js';
5
+ export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-Cg9usuUi.js';
6
6
  import { E as EmailProvider } from './provider-CA45WVqt.js';
7
7
  export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.js';
8
8
 
@@ -23,10 +23,7 @@ type NotificationDefinition<TSchema extends StandardSchemaV1 | undefined = Stand
23
23
  };
24
24
  /** Binds TSchema so email callbacks infer the payload; TS cannot infer from a sibling property. */
25
25
  declare function defineNotification<TSchema extends StandardSchemaV1 | undefined = undefined>(definition: NotificationDefinition<TSchema>): NotificationDefinition<TSchema>;
26
- /**
27
- * Erased form. `EmailTemplate<never>` because callbacks are contravariant —
28
- * anything else makes every schema-typed definition fail to assign.
29
- */
26
+ /** Erased form. `EmailTemplate<never>` because callbacks are contravariant. */
30
27
  type AnyNotificationDefinition = {
31
28
  schema?: StandardSchemaV1 | undefined;
32
29
  channels: readonly Channel[];
@@ -38,6 +35,16 @@ type PayloadOf<TDef> = TDef extends {
38
35
  schema: infer TSchema extends StandardSchemaV1;
39
36
  } ? StandardSchemaV1.InferOutput<TSchema> : Record<string, unknown>;
40
37
 
38
+ type RateLimitConfig = {
39
+ /** Requests allowed per key per window. */
40
+ max: number;
41
+ windowMs: number;
42
+ /** Bucket key. Defaults to the client address from proxy headers; null exempts the request. */
43
+ key?: (request: Request) => string | null;
44
+ };
45
+ /** Fixed window in process memory: per isolate or replica, so a bound rather than a quota. */
46
+ declare function createRateLimiter(config: RateLimitConfig): (request: Request) => Response | null;
47
+
41
48
  type SessionConfig = {
42
49
  /** null yields 401; throwing yields 500. Deliberately distinct. */
43
50
  getUserId: (request: Request) => string | null | Promise<string | null>;
@@ -51,10 +58,7 @@ type DeliveryConfig = {
51
58
  /** inline mode only. */
52
59
  throwOnError?: boolean;
53
60
  backoff?: "exponential" | ((attempt: number) => number);
54
- /**
55
- * Next.js `after`, Cloudflare `ctx.waitUntil`, or @vercel/functions.
56
- * Injected, not detected: importing a maybe-absent package breaks bundlers.
57
- */
61
+ /** Next.js `after` or Cloudflare `ctx.waitUntil`. Injected, not detected. */
58
62
  waitUntil?: (promise: Promise<unknown>) => void;
59
63
  };
60
64
  type ChannelsConfig = {
@@ -77,10 +81,22 @@ type EasyPingConfig<TDefs extends NotificationDefinitions> = {
77
81
  /** Required by `cron` and `deferred` modes. See RFC 0002 §4. */
78
82
  cron?: {
79
83
  secret: string;
84
+ /** Sweeps one POST /cron may run before returning. Defaults to 50. */
85
+ maxSweeps?: number;
80
86
  };
87
+ /** Bearer secret for plugin machine routes. Falls back to `cron.secret`. */
88
+ machineSecret?: string;
89
+ /** In-process fixed-window limiter, applied before routing. */
90
+ rateLimit?: RateLimitConfig;
81
91
  plugins?: readonly AnyPlugin[];
82
92
  /** Where the handler is mounted, used to strip the prefix off incoming URLs. */
83
93
  basePath?: string;
94
+ /** Origins other than the request host that may POST. `"*.example.com"` matches subdomains. */
95
+ trustedOrigins?: readonly string[];
96
+ /** Runs before routing. Return a Response to short-circuit — a rate limiter's 429, say. */
97
+ onRequest?: (request: Request) => Promisable<Response | undefined | void>;
98
+ /** Defaults to 64 KiB. */
99
+ maxBodyBytes?: number;
84
100
  /** Table-name prefix; must match the one given to the adapter. */
85
101
  tablePrefix?: string;
86
102
  logger?: Logger;
@@ -111,6 +127,13 @@ type SendResult = {
111
127
  type Worker = {
112
128
  stop: () => Promise<void>;
113
129
  };
130
+ type MountedRoute = {
131
+ method: "GET" | "POST";
132
+ path: string;
133
+ scope: RouteScope;
134
+ /** "core" for the built-in routes, otherwise the plugin id. */
135
+ owner: string;
136
+ };
114
137
  type HealthReport = {
115
138
  mode: DeliveryMode;
116
139
  cronMounted: boolean;
@@ -131,13 +154,9 @@ type EasyPing<TDefs extends NotificationDefinitions> = {
131
154
  batchSize?: number;
132
155
  }): Worker;
133
156
  healthCheck(): Promise<HealthReport>;
134
- /**
135
- * Deliveries that exhausted their attempts, newest first.
136
- *
137
- * The retry machinery is otherwise a black box: rows go quiet and the only
138
- * way to ask what happened is SQL against tables the library owns. Wire this
139
- * to an admin page or an alert.
140
- */
157
+ /** Every mounted route with its auth scope, so `custom`-scoped ones stay visible. RFC 0002 §3. */
158
+ listRoutes(): readonly MountedRoute[];
159
+ /** Deliveries that exhausted their attempts, newest first. Wire it to an admin page or an alert. */
141
160
  getFailedDeliveries(options?: {
142
161
  /** Defaults to the last 24 hours. */
143
162
  since?: Date;
@@ -146,8 +165,6 @@ type EasyPing<TDefs extends NotificationDefinitions> = {
146
165
  }): Promise<readonly DeliveryRecord[]>;
147
166
  };
148
167
 
149
- declare function easyPing<TDefs extends NotificationDefinitions>(config: EasyPingConfig<TDefs>): EasyPing<TDefs>;
150
-
151
168
  type Backoff = "exponential" | ((attempt: number) => number);
152
169
 
153
170
  type RunnerDeps = {
@@ -168,6 +185,8 @@ type SweepResult = {
168
185
  /** Deliveries that had nothing to do. Not errors; see DeliveryOutcome. */
169
186
  skipped: number;
170
187
  };
188
+ /** Persisted to `last_error`, which admin tooling reads: addresses do not go in. */
189
+ declare const redactErrorMessage: (text: string) => string;
171
190
  declare function createRunner(deps: RunnerDeps): {
172
191
  runOnce: (options?: {
173
192
  limit?: number;
@@ -186,20 +205,20 @@ declare function createRunner(deps: RunnerDeps): {
186
205
  };
187
206
  type Runner = ReturnType<typeof createRunner>;
188
207
 
189
- type TokenClaims = {
190
- /** Subject — the user the token acts for. */
191
- uid: string;
192
- /** What the token is allowed to do. Verification requires an exact match. */
193
- purpose: string;
194
- /** Expiry, epoch seconds. */
195
- exp: number;
196
- /** Free-form, purpose-specific. */
197
- data?: Record<string, string>;
198
- };
199
- declare function signToken(secret: string, claims: TokenClaims): Promise<string>;
200
- /** null for every failure mode. Distinguishing them tells an attacker which part to fix. */
201
- declare function verifyToken(secret: string, token: string, purpose: string, now?: Date): Promise<TokenClaims | null>;
202
- declare const DEFAULT_TOKEN_TTL_SECONDS: number;
203
- declare const expiresIn: (seconds: number, now?: Date) => number;
208
+ /** Every JSON body is capped here. Nothing legitimate the routes accept is bigger. */
209
+ declare const MAX_BODY_BYTES: number;
210
+ type JsonBody = {
211
+ body: Record<string, unknown>;
212
+ } | {
213
+ error: Response;
214
+ };
215
+ /** Reads a JSON object body with a streaming byte cap. Malformed JSON is 400; an empty body is `{}`. */
216
+ declare function readJsonBody(request: Request, maxBytes?: number): Promise<JsonBody>;
217
+
218
+ /** For template literals: anything a user typed goes through this, or their HTML ships from your domain. */
219
+ declare const escapeHtml: (value: unknown) => string;
220
+
221
+ declare const MIN_SECRET_LENGTH = 16;
222
+ declare function easyPing<TDefs extends NotificationDefinitions>(config: EasyPingConfig<TDefs>): EasyPing<TDefs>;
204
223
 
205
- export { type AnyNotificationDefinition, AnyPlugin, Channel, type ChannelsConfig, DEFAULT_TOKEN_TTL_SECONDS, DatabaseAdapter, type DeliveryConfig, DeliveryMode, DeliveryRecord, type EasyPing, type EasyPingConfig, EmailProvider, type EmailTemplate, type HealthReport, Logger, type NotificationDefinition, type NotificationDefinitions, type PayloadFromSchema, type PayloadOf, Recipient, type Runner, type RunnerDeps, type SendArgs, type SendResult, type SessionConfig, SkipReason, type SweepResult, type TokenClaims, type Worker, createRunner, decodeBase64Url, defineNotification, easyPing, encodeBase64Url, expiresIn, signToken, verifyToken };
224
+ export { type AnyNotificationDefinition, AnyPlugin, Channel, type ChannelsConfig, DatabaseAdapter, type DeliveryConfig, DeliveryMode, DeliveryRecord, type EasyPing, type EasyPingConfig, EmailProvider, type EmailTemplate, type HealthReport, type JsonBody, Logger, MAX_BODY_BYTES, MIN_SECRET_LENGTH, type MountedRoute, type NotificationDefinition, type NotificationDefinitions, type PayloadFromSchema, type PayloadOf, Promisable, type RateLimitConfig, Recipient, RouteScope, type Runner, type RunnerDeps, type SendArgs, type SendResult, type SessionConfig, SkipReason, type SweepResult, type Worker, createRateLimiter, createRunner, decodeBase64Url, defineNotification, easyPing, encodeBase64Url, escapeHtml, readJsonBody, redactErrorMessage };