@luckystack/core 0.13.0 → 0.14.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 CHANGED
@@ -5,6 +5,13 @@ All notable changes to `@luckystack/core` are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.14.0] - 2026-10-16
9
+
10
+ ### Added
11
+
12
+ - **`EmailResult` failures can carry `code`, `status` and `blocksSender`.** `code` is the provider's own error name (Resend `daily_quota_exceeded`, nodemailer `EAUTH`), `status` the HTTP status or SMTP reply code, and `blocksSender: true` says the sender refuses every message until an operator acts. All three are optional; a custom adapter that sets none behaves as before.
13
+ - **`dispatchHook` takes an optional `onError`** (third argument, type `DispatchHookOptions`). A throwing handler's error then goes to it instead of the default log line and tracker capture, so a caller can redact it or report many dispatches as one event; `@luckystack/email` uses it to keep addresses out of hook-error reports. Without it nothing changes; an `onError` that throws is ignored, like a failing reporter.
14
+
8
15
  ## [0.12.1] - 2026-10-15
9
16
 
10
17
  ### Fixed
@@ -79,6 +79,16 @@ export type EmailResult = {
79
79
  * whose delivery semantics are adapter-specific.
80
80
  */
81
81
  deliveryOutcome?: EmailDeliveryOutcome;
82
+ /** The provider's own error code, e.g. Resend `daily_quota_exceeded` or nodemailer `EAUTH`. */
83
+ code?: string;
84
+ /** HTTP status for an API provider, SMTP reply code for SMTP. */
85
+ status?: number;
86
+ /**
87
+ * The sender refuses every message until an operator acts (quota used up,
88
+ * credentials rejected); retrying the next message is pointless.
89
+ * `sendEmailBatch` stops at the first such failure.
90
+ */
91
+ blocksSender?: boolean;
82
92
  };
83
93
  export interface EmailSendContext {
84
94
  /** Cooperative cancellation. Custom adapters should abort provider I/O when supported. */
@@ -6,7 +6,14 @@ export type DispatchResult = {
6
6
  signal: HookStopSignal;
7
7
  };
8
8
  export declare const registerHook: <TName extends HookName>(name: TName, handler: HookHandler<HookPayloads[TName]>) => (() => void);
9
- export declare const dispatchHook: <TName extends HookName>(name: TName, payload: HookPayloads[TName]) => Promise<DispatchResult>;
9
+ export interface DispatchHookOptions {
10
+ /**
11
+ * Receives a throwing handler's error instead of the default log line and
12
+ * tracker capture, e.g. to redact it or to report many dispatches at once.
13
+ */
14
+ onError?: (error: unknown) => void;
15
+ }
16
+ export declare const dispatchHook: <TName extends HookName>(name: TName, payload: HookPayloads[TName], options?: DispatchHookOptions) => Promise<DispatchResult>;
10
17
  export declare const clearAllHooks: () => void;
11
18
  export declare const registerSyncHook: <TName extends SyncHookName>(name: TName, handler: SyncHookHandler<SyncHookPayloads[TName]>) => (() => void);
12
19
  export declare const dispatchSyncHook: <TName extends SyncHookName>(name: TName, payload: SyncHookPayloads[TName]) => void;
package/dist/index.d.ts CHANGED
@@ -106,5 +106,5 @@ export type { TestResetTarget } from './testResetTargets.js';
106
106
  export { issueOneTimeToken, consumeOneTimeToken, consumeOneTimeTokenJson, oneTimeTokenKey, } from './oneTimeToken.js';
107
107
  export type { OneTimeTokenHandle } from './oneTimeToken.js';
108
108
  export { registerHook, dispatchHook, clearAllHooks, registerSyncHook, dispatchSyncHook } from './hooks/registry.js';
109
- export type { DispatchResult } from './hooks/registry.js';
109
+ export type { DispatchHookOptions, DispatchResult } from './hooks/registry.js';
110
110
  export type { HookSessionShape, HookStopSignal, HookResult, HookHandler, HookName, HookPayloads, PreApiValidatePayload, PostApiValidatePayload, PreApiExecutePayload, PostApiExecutePayload, PreApiRespondPayload, PostApiRespondPayload, ApiResponseEnvelope, PreHttpRequestPayload, PostHttpRequestPayload, PreSocketMessagePayload, PreSyncAuthorizePayload, PostSyncAuthorizePayload, PreSyncValidatePayload, PostSyncValidatePayload, PreSyncExecutePayload, PostSyncExecutePayload, PreSyncFanoutPayload, PostSyncFanoutPayload, PreSyncRecipientPayload, ApiErrorPayload, SyncErrorPayload, ApiAuthRejectedPayload, RateLimitExceededPayload, CorsRejectedPayload, CsrfMismatchPayload, PreSessionRefreshPayload, PostSessionRefreshPayload, SessionCreatedPayload, SessionRevokedPayload, OnUploadStartPayload, OnUploadCompletePayload, PreAvatarServePayload, PostAvatarServePayload, PreServerStopPayload, SyncHookName, SyncHookHandler, SyncHookPayloads, PreErrorNormalizePayload, PostErrorNormalizePayload, } from './hooks/types.js';
package/dist/index.js CHANGED
@@ -170,13 +170,20 @@ var registerHook = (name, handler) => {
170
170
  if (index !== -1) handlers.splice(index, 1);
171
171
  };
172
172
  };
173
- var dispatchHook = async (name, payload) => {
173
+ var dispatchHook = async (name, payload, options = {}) => {
174
174
  const handlers = hookHandlers.get(name) ?? [];
175
175
  for (const handler of handlers) {
176
176
  let result;
177
177
  try {
178
178
  result = await handler(payload);
179
179
  } catch (error) {
180
+ if (options.onError) {
181
+ try {
182
+ options.onError(error);
183
+ } catch {
184
+ }
185
+ continue;
186
+ }
180
187
  try {
181
188
  getLogger().error(`hook: handler for "${name}" threw`, error, { hook: name });
182
189
  } catch {