@fanfare-io/fanfare-sdk-core 0.5.0 → 0.7.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/README.md CHANGED
@@ -31,6 +31,17 @@ journey.view$.listen((view) => {
31
31
 
32
32
  The published artifact is self-contained: installable, runnable, and typecheckable using only public package dependencies.
33
33
 
34
+ ## Error handling (`./errors`)
35
+
36
+ The `./errors` subpath exposes the client-side error policy the UI adapters route on:
37
+
38
+ - `classify(code, status?, action?) → { disposition, autoRetryOnce }` — a pure function mapping an error to one of four dispositions (`INLINE` / `STEP_RESET` / `RESTART` / `PANEL`). It is the single source of truth the React/Solid adapters route on and the native SDKs mirror. Unknown codes default to `PANEL`.
39
+ - `toDisplay(error, t) → DisplayError` — projects a `FanfareError` into a render-safe view model whose copy is always SDK-owned i18n (via an injected translator) and which, by type, can hold no raw server content.
40
+ - `toDebug(error) → DebugErrorInfo` — the structured raw payload (status, ids, server `details`) for telemetry; never for rendering.
41
+ - `ensureFanfareError(error)` — normalizes any thrown value into a `FanfareError`.
42
+
43
+ `classify` takes no React/i18n dependency; the translator is injected as a structural `Translate` type.
44
+
34
45
  ## Documentation
35
46
 
36
47
  - [Core SDK quickstart](https://docs.fanfare.io/sdk/core/quickstart)
@@ -0,0 +1,48 @@
1
+ import { Disposition } from './error-disposition';
2
+ import { ConsumerApiError, FanfareError } from './errors';
3
+ /** Options a bound translator accepts. Structurally matches the i18n `t`. */
4
+ export interface TranslateOptions {
5
+ values?: Record<string, string | number>;
6
+ count?: number;
7
+ defaultValue?: string;
8
+ }
9
+ /** Structural type for an injected, locale-bound translate function. */
10
+ export type Translate = (key: string, options?: TranslateOptions) => string;
11
+ /**
12
+ * Sanitized, render-safe view of an error. Every string is SDK-owned i18n copy.
13
+ * Deliberately carries NO `details`/`status`/`requestId`/`body` — sanitization
14
+ * is a property of this type, not of the projector remembering to omit a field.
15
+ */
16
+ export interface DisplayError {
17
+ readonly code: string;
18
+ /** Headline copy, present for terminal (PANEL) errors. */
19
+ readonly title?: string;
20
+ /** Primary message; always present. */
21
+ readonly description: string;
22
+ readonly disposition: Disposition;
23
+ /** Seconds until retry, for RATE_LIMITED countdowns (already interpolated into copy too). */
24
+ readonly retryAfter?: number;
25
+ /** Opaque support reference/confirmation codes — safe to show. */
26
+ readonly support?: ConsumerApiError["support"];
27
+ }
28
+ /** Structured debug payload for `onError` telemetry. NOT for rendering. */
29
+ export interface DebugErrorInfo {
30
+ readonly code: string;
31
+ readonly status?: number;
32
+ readonly requestId?: string;
33
+ readonly correlationId?: string;
34
+ /** Server `details`, or the raw response body when unenveloped. */
35
+ readonly details?: unknown;
36
+ readonly issues?: readonly unknown[];
37
+ }
38
+ /** Project a `FanfareError` into render-safe, i18n-owned `DisplayError`. Pure. */
39
+ export declare function toDisplay(error: FanfareError, t: Translate): DisplayError;
40
+ /**
41
+ * Normalize any thrown value into a `FanfareError` so the adapter sink can always
42
+ * classify and project it. SDK requests already throw `FanfareError`; this guards
43
+ * the rare plain-`Error`/non-error throw (treated as a terminal INTERNAL_ERROR —
44
+ * its raw message reaches `onError` via the debug channel but never the render seam).
45
+ */
46
+ export declare function ensureFanfareError(error: unknown): FanfareError;
47
+ /** Project a `FanfareError` into the structured debug payload for `onError`. Pure. */
48
+ export declare function toDebug(error: FanfareError): DebugErrorInfo;
@@ -0,0 +1 @@
1
+ import{classify as r}from"./error-disposition.js";import{FanfareError as e,ErrorCodes as t}from"./errors.js";const o={[t.OTP_INVALID]:"auth.invalidCode",[t.OTP_EXPIRED]:"auth.codeExpired",[t.INVALID_SESSION]:"common.sessionExpired",[t.INVALID_REFRESH_TOKEN]:"common.sessionExpired"};function i(r){return"INLINE"===r||"STEP_RESET"===r?"error.inlineGeneric":"error.description"}function s(e,s){const{disposition:n}=r(e.code,e.status,e.action),d="number"==typeof e.retryAfter&&Number.isFinite(e.retryAfter)?e.retryAfter:void 0,c=function(r,e,s){return r===t.RATE_LIMITED?s?"error.rateLimited":"error.rateLimitedGeneric":o[r]??i(e)}(e.code,n,void 0!==d);let u=s(c,void 0!==d?{values:{seconds:d}}:void 0);u.includes("{{")&&(u=s(i(n)));const E="PANEL"===n?s("error.title"):void 0;return{code:e.code,title:E,description:u,disposition:n,retryAfter:e.retryAfter,support:e.support}}function n(r){if(r instanceof e)return r;const o=r instanceof Error?r.message:String(r);return new e(o,t.INTERNAL_ERROR)}function d(r){return{code:r.code,status:r.status,requestId:r.requestId,correlationId:r.correlationId,details:r.details,issues:r.issues}}export{n as ensureFanfareError,d as toDebug,s as toDisplay};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Error disposition policy — the SDK's client-side decision about HOW a failure
3
+ * should be surfaced, independent of any UI framework.
4
+ *
5
+ * This is deliberately separate from the wire contract (the server never sends a
6
+ * disposition) and from `classifyHttpError` (which only decides code +
7
+ * transport-retryability). It is the single source of truth that the React/Solid
8
+ * adapters route on and that the native SDKs mirror by hand.
9
+ */
10
+ /**
11
+ * Where/how a failure is surfaced:
12
+ * - `INLINE` — recoverable in place under the active field; preserve journey state.
13
+ * - `STEP_RESET` — the current step's setup is dead; re-arm it (e.g. resend OTP),
14
+ * discarding the now-invalid input.
15
+ * - `RESTART` — session/admission/capability is dead; reroute to an earlier step
16
+ * (reuses the existing `view.reroute()` / `action: "reroute"` machinery).
17
+ * - `PANEL` — terminal; render the full-screen error panel. The conservative
18
+ * default for anything not explicitly recoverable.
19
+ */
20
+ export type Disposition = "INLINE" | "STEP_RESET" | "RESTART" | "PANEL";
21
+ export interface ClassifyResult {
22
+ readonly disposition: Disposition;
23
+ /**
24
+ * Whether a single idempotent silent retry is permitted. Always `false` today:
25
+ * mutating POSTs carry no idempotency key, so a silent retry could double-submit.
26
+ * The flag is computed (transport-transient + not anti-fraud) so the eligibility
27
+ * rule is encoded now, but it is gated off until idempotency keys exist.
28
+ */
29
+ readonly autoRetryOnce: boolean;
30
+ }
31
+ /**
32
+ * Classify a failure into a UI disposition + auto-retry eligibility. Pure and
33
+ * framework-free. `status` is the HTTP status (used only for auto-retry
34
+ * eligibility — disposition is code-driven); `action` is the server flow hint.
35
+ */
36
+ export declare function classify(code: string, status?: number, action?: string): ClassifyResult;
@@ -0,0 +1 @@
1
+ import{ErrorCodes as E}from"./errors.js";const I={[E.OTP_INVALID]:"INLINE",[E.INVALID_CREDENTIALS]:"INLINE",[E.VALIDATION_ERROR]:"INLINE",[E.RATE_LIMITED]:"INLINE",[E.SLOT_OUT_OF_WINDOW]:"INLINE",[E.OTP_EXPIRED]:"STEP_RESET",[E.BOT_CHALLENGE_INVALID]:"STEP_RESET",[E.BOT_CHALLENGE_EXPIRED]:"STEP_RESET",[E.SLOT_EXPIRED]:"STEP_RESET",[E.SLOT_NOT_AVAILABLE]:"STEP_RESET",[E.INVALID_SESSION]:"RESTART",[E.INVALID_REFRESH_TOKEN]:"RESTART",[E.UNAUTHORIZED]:"RESTART",[E.ENTRY_TOKEN_REQUIRED]:"RESTART",[E.ENTRY_TOKEN_INVALID]:"RESTART",[E.ENTRY_TOKEN_EXPIRED]:"RESTART",[E.ADMISSION_GRANT_INVALID]:"RESTART",[E.ADMISSION_PROOF_REQUIRED]:"RESTART",[E.ADMISSION_KEY_THUMBPRINT_REQUIRED]:"RESTART",[E.ADMISSION_ORIGINAL_ENTRY_REQUIRED]:"RESTART",[E.RATE_LIMIT_UNAVAILABLE]:"PANEL",[E.FINGERPRINT_REQUIRED]:"PANEL",[E.FINGERPRINT_DEVICE_MISMATCH]:"PANEL",[E.FINGERPRINT_INVALID]:"PANEL",[E.BOT_CHALLENGE_SUBJECT_MISMATCH]:"PANEL",[E.ADMISSION_KEY_MISMATCH]:"PANEL",[E.DISTRIBUTION_NOT_OPEN]:"PANEL",[E.DISTRIBUTION_CLOSED]:"PANEL",[E.DISTRIBUTION_FULL]:"PANEL",[E.DISTRIBUTION_ORDER_LIMIT]:"PANEL",[E.PROTOCOL_ERROR]:"PANEL"},T=/* @__PURE__ */new Set([E.ENTRY_TOKEN_MISMATCH]);function R(E,R,N){const _=function(E,R){return"reroute"===R&&T.has(E)?"RESTART":I[E]??"PANEL"}(E,N);return{disposition:_,autoRetryOnce:false}}export{R as classify};
@@ -41,6 +41,7 @@ export declare const ErrorCodes: {
41
41
  readonly DISTRIBUTION_NOT_OPEN: "DISTRIBUTION_NOT_OPEN";
42
42
  readonly DISTRIBUTION_CLOSED: "DISTRIBUTION_CLOSED";
43
43
  readonly DISTRIBUTION_FULL: "DISTRIBUTION_FULL";
44
+ readonly DISTRIBUTION_ORDER_LIMIT: "DISTRIBUTION_ORDER_LIMIT";
44
45
  readonly FINGERPRINT_REQUIRED: "FINGERPRINT_REQUIRED";
45
46
  readonly FINGERPRINT_DEVICE_MISMATCH: "FINGERPRINT_DEVICE_MISMATCH";
46
47
  readonly FINGERPRINT_INVALID: "FINGERPRINT_INVALID";
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  /**
2
2
  * Public error surface. `classifyHttpError` is an internal helper and is not exported here.
3
3
  */
4
+ export { ensureFanfareError, toDebug, toDisplay } from './core/error-display';
5
+ export type { DebugErrorInfo, DisplayError, Translate, TranslateOptions } from './core/error-display';
6
+ export { classify } from './core/error-disposition';
7
+ export type { ClassifyResult, Disposition } from './core/error-disposition';
4
8
  export { ErrorCodes, FanfareError, ProtocolError, createError, isCapabilityGrantError, isCapabilityGrantRerouteError, isCapabilityTokenError, isCapabilityTokenRerouteError, isFanfareError, isProtocolError, } from './core/errors';
5
9
  export type { ErrorCode } from './core/errors';
package/dist/errors.js CHANGED
@@ -1 +1 @@
1
- import{ErrorCodes as r,FanfareError as o,ProtocolError as e,createError as m,isCapabilityGrantError as p,isCapabilityGrantRerouteError as s,isCapabilityTokenError as t,isCapabilityTokenRerouteError as c,isFanfareError as f,isProtocolError as i}from"./core/errors.js";export{r as ErrorCodes,o as FanfareError,e as ProtocolError,m as createError,p as isCapabilityGrantError,s as isCapabilityGrantRerouteError,t as isCapabilityTokenError,c as isCapabilityTokenRerouteError,f as isFanfareError,i as isProtocolError};
1
+ import{ensureFanfareError as r,toDebug as o,toDisplay as e}from"./core/error-display.js";import{classify as i}from"./core/error-disposition.js";import{ErrorCodes as s,FanfareError as m,ProtocolError as p,createError as t,isCapabilityGrantError as c,isCapabilityGrantRerouteError as f,isCapabilityTokenError as j,isCapabilityTokenRerouteError as d,isFanfareError as a,isProtocolError as l}from"./core/errors.js";export{s as ErrorCodes,m as FanfareError,p as ProtocolError,i as classify,t as createError,r as ensureFanfareError,c as isCapabilityGrantError,f as isCapabilityGrantRerouteError,j as isCapabilityTokenError,d as isCapabilityTokenRerouteError,a as isFanfareError,l as isProtocolError,o as toDebug,e as toDisplay};
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * SDK version
3
3
  * This is automatically updated during the build process
4
4
  */
5
- export declare const version = "0.5.0";
5
+ export declare const version = "0.7.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- const o="0.5.0";export{o as version};
1
+ const o="0.7.0";export{o as version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanfare-io/fanfare-sdk-core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Official Fanfare Browser SDK for queue, draw, auction, and appointment experiences",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -122,7 +122,7 @@
122
122
  "uuid": "^14.0.0",
123
123
  "wretch": "^2.11.0",
124
124
  "zustand": "^5.0.6",
125
- "@fanfare-io/fanfare-sdk-contracts": "0.5.0"
125
+ "@fanfare-io/fanfare-sdk-contracts": "0.7.0"
126
126
  },
127
127
  "peerDependencies": {
128
128
  "valibot": "^1.1.0",
@@ -165,7 +165,7 @@
165
165
  "vite-plugin-dts": "^4.5.4",
166
166
  "vite-tsconfig-paths": "^5.1.4",
167
167
  "vitest": "^3.2.4",
168
- "@fanfare-io/shared-models": "0.5.0"
168
+ "@fanfare-io/shared-models": "0.7.0"
169
169
  },
170
170
  "sideEffects": false,
171
171
  "keywords": [