@classytic/arc-next 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 +148 -9
- package/dist/api.d.ts +59 -75
- package/dist/api.js +25 -6
- package/dist/cache.d.ts +48 -4
- package/dist/cache.js +132 -26
- package/dist/client.d.ts +340 -72
- package/dist/client.js +500 -91
- package/dist/hooks.d.ts +72 -8
- package/dist/hooks.js +109 -33
- package/dist/prefetch.d.ts +23 -0
- package/dist/prefetch.js +20 -0
- package/dist/presets/bulk.d.ts +5 -4
- package/dist/presets/search.d.ts +5 -4
- package/dist/presets/slug.d.ts +2 -2
- package/dist/presets/soft-delete.d.ts +4 -3
- package/dist/presets/tree.d.ts +4 -3
- package/dist/query.d.ts +87 -32
- package/dist/query.js +72 -46
- package/dist/sse.js +71 -5
- package/dist/upload.js +33 -2
- package/dist/ws.js +42 -5
- package/package.json +9 -2
package/dist/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ErrorDetail } from "@classytic/repo-core/errors";
|
|
2
|
+
|
|
1
3
|
//#region src/client.d.ts
|
|
2
4
|
interface ToastHandler {
|
|
3
5
|
success: (message: string) => void;
|
|
@@ -12,39 +14,33 @@ type UseRouterHook = () => {
|
|
|
12
14
|
}) => void;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Canonical error codes arc and repo-core emit on `json.code`. Single
|
|
18
|
+
* top-level slot — arc 2.13's `createError` lifts business codes from
|
|
19
|
+
* `details` to top-level so `repo-core`'s `toErrorContract` round-trips
|
|
20
|
+
* them on the wire. There is no separate `detailsCode` slot; everything
|
|
21
|
+
* lives at `error.code`.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
* `
|
|
32
|
-
* still satisfy it.
|
|
33
|
-
*/
|
|
34
|
-
type ArcTopLevelErrorCode = (typeof KNOWN_TOP_LEVEL_CODES)[number] | (string & {});
|
|
35
|
-
/**
|
|
36
|
-
* Nested business-logic codes arc's mixins / preset routes / org guards emit
|
|
37
|
-
* on `json.details.code`. These are distinct from the HTTP-status code above —
|
|
38
|
-
* `error.code` (top-level) vs `error.detailsCode` (nested). When a route
|
|
39
|
-
* returns 403 with `details.code: 'ORG_CONTEXT_REQUIRED'`, hosts read
|
|
40
|
-
* `detailsCode` to disambiguate "missing org context" from "permission denied".
|
|
23
|
+
* Three families compose this list:
|
|
24
|
+
* 1. **repo-core canonical** (`validation_error`, `not_found`, ...) — RFC 7807
|
|
25
|
+
* / Stripe-shaped lowercase + snake_case. Cross-package universals.
|
|
26
|
+
* 2. **arc hierarchical** (`arc.forbidden`, `arc.validation_error`,
|
|
27
|
+
* `arc.org.access_denied`, ...) — what arc's `errorHandlerPlugin`
|
|
28
|
+
* emits for HTTP-status throws + arc-classified errors.
|
|
29
|
+
* 3. **arc business** (`ORG_CONTEXT_REQUIRED`, `ALL_FIELDS_STRIPPED`,
|
|
30
|
+
* `OWNERSHIP_DENIED`, ...) — emitted by mixins / org guards via
|
|
31
|
+
* `createError(status, msg, { code })`. The UPPER_SNAKE form is
|
|
32
|
+
* intentional: these are reason codes, not HTTP-status codes.
|
|
33
|
+
*
|
|
34
|
+
* `(string & {})` keeps the type open so domain packages and custom
|
|
35
|
+
* `errorMappers` codes still satisfy it.
|
|
41
36
|
*/
|
|
42
|
-
declare const
|
|
37
|
+
declare const KNOWN_ARC_ERROR_CODES: readonly ["validation_error", "not_found", "conflict", "unauthorized", "forbidden", "rate_limited", "idempotency_conflict", "precondition_failed", "internal_error", "service_unavailable", "timeout", "arc.bad_request", "arc.unauthorized", "arc.forbidden", "arc.not_found", "arc.conflict", "arc.unprocessable_entity", "arc.rate_limited", "arc.internal_error", "arc.bad_gateway", "arc.service_unavailable", "arc.gateway_timeout", "arc.validation_error", "arc.invalid_id", "arc.org.selection_required", "arc.org.access_denied", "ORG_CONTEXT_REQUIRED", "ORG_ROLE_REQUIRED", "OWNERSHIP_DENIED", "MIXED_UPDATE_SHAPE", "ALL_FIELDS_STRIPPED", "BEFORE_RESTORE_HOOK_ERROR", "duplicate_key"];
|
|
43
38
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
39
|
+
* Canonical arc error code union. `(string & {})` keeps the type open so
|
|
40
|
+
* domain packages can extend hierarchically (`'order.cart.locked'`,
|
|
41
|
+
* `'payment.gateway.timeout'`) and still satisfy the type.
|
|
46
42
|
*/
|
|
47
|
-
type
|
|
43
|
+
type ArcErrorCode = (typeof KNOWN_ARC_ERROR_CODES)[number] | (string & {});
|
|
48
44
|
interface ArcApiErrorOptions {
|
|
49
45
|
status: number;
|
|
50
46
|
statusText: string;
|
|
@@ -74,39 +70,36 @@ declare class ArcApiError extends Error {
|
|
|
74
70
|
readonly method: HttpMethod;
|
|
75
71
|
constructor(message: string, options: ArcApiErrorOptions);
|
|
76
72
|
/**
|
|
77
|
-
*
|
|
73
|
+
* Canonical error code from arc's wire envelope (`json.code`).
|
|
78
74
|
*
|
|
79
|
-
*
|
|
80
|
-
* `
|
|
81
|
-
*
|
|
75
|
+
* Arc 2.13 + `repo-core` 0.4 emit one canonical {@link ErrorContract}
|
|
76
|
+
* shape — `{ code, message, status, details? }` — with the business
|
|
77
|
+
* code at top-level. Hosts switch on `error.code` directly:
|
|
82
78
|
*
|
|
83
79
|
* @example
|
|
84
|
-
* if (error.code === '
|
|
80
|
+
* if (error.code === 'ORG_CONTEXT_REQUIRED') promptOrgSelector();
|
|
81
|
+
* if (error.code === 'arc.not_found') router.replace('/404');
|
|
82
|
+
* if (error.code === 'duplicate_key') showRetryAsAdmin();
|
|
85
83
|
*/
|
|
86
|
-
get code():
|
|
84
|
+
get code(): ArcErrorCode | null;
|
|
87
85
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* caller's `request.scope.organizationId` is missing).
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* if (error.detailsCode === 'ORG_CONTEXT_REQUIRED') {
|
|
97
|
-
* alert('Configure auth before bulk operations: configureAuth({ getOrgId })');
|
|
98
|
-
* }
|
|
86
|
+
* Canonical structured details — populated for validation failures
|
|
87
|
+
* (one entry per offending field) and duplicate-key conflicts (one entry
|
|
88
|
+
* per offending field). Shape matches `repo-core`'s {@link ErrorDetail}:
|
|
89
|
+
* `{ path?, code, message, meta? }`. Returns `null` for non-arc backends
|
|
90
|
+
* or responses without details.
|
|
99
91
|
*/
|
|
100
|
-
get
|
|
92
|
+
get details(): readonly ErrorDetail[] | null;
|
|
101
93
|
/**
|
|
102
94
|
* Extract field-level validation errors as `{ field: message }` map.
|
|
103
95
|
*
|
|
104
|
-
* Reads
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
96
|
+
* Reads the canonical `ErrorContract.details: ErrorDetail[]` shape first
|
|
97
|
+
* (what arc 2.13 + repo-core emit), then falls back to legacy shapes for
|
|
98
|
+
* non-arc backends:
|
|
99
|
+
* 1. `details: [{ path, code, message }]` — canonical (arc / repo-core).
|
|
100
|
+
* 2. `errors: { email: 'invalid' }` — record form (legacy app handlers).
|
|
101
|
+
* 3. `details: { errors: [{ field|instancePath, message }] }` — pre-2.13 AJV.
|
|
102
|
+
* 4. `errors: [...]` at the top level — third-party frameworks.
|
|
110
103
|
*/
|
|
111
104
|
get fieldErrors(): Record<string, string> | null;
|
|
112
105
|
}
|
|
@@ -137,23 +130,24 @@ declare function isArcApiError(error: unknown): error is ArcApiError;
|
|
|
137
130
|
*/
|
|
138
131
|
declare function isAbortError(error: unknown): boolean;
|
|
139
132
|
/**
|
|
140
|
-
* Generic check: is this an `ArcApiError` carrying a specific
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* `
|
|
133
|
+
* Generic check: is this an `ArcApiError` carrying a specific `code`?
|
|
134
|
+
* Single-slot — arc 2.13 + repo-core 0.4 emit one canonical `code` at
|
|
135
|
+
* top-level. Pass either the canonical lowercase form (`'arc.not_found'`,
|
|
136
|
+
* `'validation_error'`) or arc's UPPER_SNAKE business form
|
|
137
|
+
* (`'ORG_CONTEXT_REQUIRED'`).
|
|
144
138
|
*
|
|
145
139
|
* @example
|
|
146
|
-
* if (isArcErrorCode(error, '
|
|
140
|
+
* if (isArcErrorCode(error, 'duplicate_key')) showRetryUI();
|
|
147
141
|
* if (isArcErrorCode(error, 'ORG_CONTEXT_REQUIRED')) promptOrgSelector();
|
|
148
142
|
*/
|
|
149
|
-
declare function isArcErrorCode(error: unknown, code:
|
|
143
|
+
declare function isArcErrorCode(error: unknown, code: ArcErrorCode): error is ArcApiError;
|
|
150
144
|
/**
|
|
151
145
|
* Specific predicate for arc's bulk-preset + orgGuard safety code.
|
|
152
146
|
*
|
|
153
147
|
* Arc's bulk endpoints (`POST/PATCH/DELETE /:resource/bulk`) reject any call
|
|
154
148
|
* where `request.scope.organizationId` is missing — the wire signal is
|
|
155
|
-
* `403 {
|
|
156
|
-
* need to call `configureAuth({ getOrgId })` before retrying.
|
|
149
|
+
* `403 { code: 'ORG_CONTEXT_REQUIRED', message, status: 403 }`. Hosts hitting
|
|
150
|
+
* this need to call `configureAuth({ getOrgId })` before retrying.
|
|
157
151
|
*
|
|
158
152
|
* @example
|
|
159
153
|
* try { await api.bulkCreate({ data: [...] }); }
|
|
@@ -165,15 +159,16 @@ declare function isArcErrorCode(error: unknown, code: ArcTopLevelErrorCode | Arc
|
|
|
165
159
|
*/
|
|
166
160
|
declare function isOrgContextRequiredError(error: unknown): error is ArcApiError;
|
|
167
161
|
/**
|
|
168
|
-
* Specific predicate for
|
|
162
|
+
* Specific predicate for validation failures (Fastify AJV + Mongoose
|
|
169
163
|
* ValidationError). When true, `error.fieldErrors` is populated with the
|
|
170
|
-
* `{ field: message }` map.
|
|
164
|
+
* `{ field: message }` map. Matches arc's `arc.validation_error` and the
|
|
165
|
+
* canonical `validation_error` from repo-core.
|
|
171
166
|
*/
|
|
172
167
|
declare function isValidationError(error: unknown): error is ArcApiError;
|
|
173
168
|
/**
|
|
174
|
-
* Specific predicate for
|
|
175
|
-
*
|
|
176
|
-
*
|
|
169
|
+
* Specific predicate for unique-constraint violations. Arc's errorHandler
|
|
170
|
+
* classifies these uniformly across MongoDB E11000, Postgres 23505,
|
|
171
|
+
* Prisma P2002 → `arc.conflict` (with `details[].code === 'duplicate_key'`).
|
|
177
172
|
*/
|
|
178
173
|
declare function isDuplicateKeyError(error: unknown): error is ArcApiError;
|
|
179
174
|
interface ClientConfig {
|
|
@@ -327,6 +322,17 @@ declare function getAuthMode(): 'bearer' | 'cookie' | 'header';
|
|
|
327
322
|
declare function getBaseUrl(): string;
|
|
328
323
|
/** Whether auto-idempotency is enabled on the global client. */
|
|
329
324
|
declare function isAutoIdempotency(): boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Whether the globally-configured client carries enough auth to satisfy a
|
|
327
|
+
* protected endpoint without a per-request token. True when any of
|
|
328
|
+
* `internalApiKey`, `defaultHeaders`, or `authMode: 'cookie'` is configured.
|
|
329
|
+
*
|
|
330
|
+
* Read by `createCrudHooks` to decide whether queries should be enabled when
|
|
331
|
+
* `getToken()` returns null — without this, an app that authenticates via a
|
|
332
|
+
* global `internalApiKey` or static headers would see every query stuck in
|
|
333
|
+
* a permanently-disabled state, looking like a clean empty success.
|
|
334
|
+
*/
|
|
335
|
+
declare function hasGlobalStaticAuth(): boolean;
|
|
330
336
|
interface AuthConfig {
|
|
331
337
|
/**
|
|
332
338
|
* Returns the current bearer/API token, or `null` if not authenticated.
|
|
@@ -334,15 +340,83 @@ interface AuthConfig {
|
|
|
334
340
|
* **MUST resolve synchronously.** The signature is `() => string | null`, never
|
|
335
341
|
* `Promise<string | null>`. If your auth library exposes an async session getter
|
|
336
342
|
* (Better Auth, NextAuth, Clerk, OAuth flows), refresh the token out-of-band
|
|
337
|
-
* (timer, event listener, lazy 401 retry) and have
|
|
338
|
-
* value. Returning a Promise will be detected and
|
|
339
|
-
* underlying token will be silently treated
|
|
343
|
+
* (timer, event listener, lazy 401 retry via {@link onAuthError}) and have
|
|
344
|
+
* `getToken` return the *cached* value. Returning a Promise will be detected and
|
|
345
|
+
* logged as an Error in dev — and the underlying token will be silently treated
|
|
346
|
+
* as `null`, causing 401s.
|
|
340
347
|
*/
|
|
341
348
|
getToken?: () => string | null;
|
|
342
349
|
getOrgId?: () => string | null;
|
|
343
350
|
/** Custom auth header name. Used when authMode is 'header'. Default: 'x-api-key' */
|
|
344
351
|
headerName?: string;
|
|
352
|
+
/**
|
|
353
|
+
* Lazy 401-recovery hook. Invoked once per request when the backend returns
|
|
354
|
+
* 401 (or 403, if {@link retryOn403} is true) AND a handler is configured.
|
|
355
|
+
*
|
|
356
|
+
* - Return `'retry'` to re-issue the request once with a fresh token.
|
|
357
|
+
* - Return `'skip'` to surface the original error to the caller (e.g. after
|
|
358
|
+
* a refresh attempt that found no valid session — let the consumer route
|
|
359
|
+
* to sign-in).
|
|
360
|
+
* - Throw to short-circuit; the thrown error propagates to the caller.
|
|
361
|
+
*
|
|
362
|
+
* **Concurrent dedup.** When N requests hit 401 at the same time, the handler
|
|
363
|
+
* fires ONCE — every concurrent caller awaits the same refresh promise and
|
|
364
|
+
* retries with the token it produced. Avoids stampeding the refresh endpoint.
|
|
365
|
+
*
|
|
366
|
+
* **Token propagation.** Inside the handler, call `ctx.setToken(newToken)` to
|
|
367
|
+
* inject the refreshed token for the retry. If you've already updated your
|
|
368
|
+
* global auth state out-of-band (e.g. updated a signal or the auth library's
|
|
369
|
+
* cache), you can omit `setToken` — arc-next re-reads via `getToken()` on
|
|
370
|
+
* the retry attempt.
|
|
371
|
+
*
|
|
372
|
+
* @see {@link createAuthRefreshHandler} for the canonical wiring with any
|
|
373
|
+
* `refresh()` function (Better Auth, NextAuth, custom OAuth flows).
|
|
374
|
+
*/
|
|
375
|
+
onAuthError?: AuthErrorHandler;
|
|
376
|
+
/**
|
|
377
|
+
* Treat 403 as auth-recoverable too. Default: `false` (only 401 triggers
|
|
378
|
+
* the handler). Enable for backends that emit 403 on expired sessions
|
|
379
|
+
* instead of 401.
|
|
380
|
+
*/
|
|
381
|
+
retryOn403?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Cap on auth-recovery retries per individual request. Default: `1`.
|
|
384
|
+
* Bumping above 1 risks pathological loops if the refresh itself
|
|
385
|
+
* triggers 401s — only do it if you have hard guarantees on `getToken()`.
|
|
386
|
+
*/
|
|
387
|
+
maxAuthRetries?: number;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Context passed to {@link AuthConfig.onAuthError} when a request 401s.
|
|
391
|
+
*
|
|
392
|
+
* Inspect `error` to decide whether to refresh. Call `setToken(t)` to inject
|
|
393
|
+
* the refreshed token for the retry — arc-next prefers this value over
|
|
394
|
+
* re-reading `getToken()`, so it works even when your auth library hasn't
|
|
395
|
+
* yet propagated the new session.
|
|
396
|
+
*/
|
|
397
|
+
interface AuthErrorContext {
|
|
398
|
+
/** The 401/403 thrown by the failing request. Carries `status`, `code`, `json`. */
|
|
399
|
+
error: ArcApiError;
|
|
400
|
+
/** The request that failed — useful for path-based heuristics. */
|
|
401
|
+
request: {
|
|
402
|
+
method: HttpMethod;
|
|
403
|
+
endpoint: string;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* 1-indexed auth-recovery attempt. First 401 in a request lifecycle is 1.
|
|
407
|
+
* Reaches {@link AuthConfig.maxAuthRetries} → handler is NOT called again
|
|
408
|
+
* and the 401 surfaces.
|
|
409
|
+
*/
|
|
410
|
+
attempt: number;
|
|
411
|
+
/**
|
|
412
|
+
* Inject the refreshed token for the retry. Pass `null` to retry
|
|
413
|
+
* unauthenticated. Optional — omit if `getToken()` already returns the
|
|
414
|
+
* fresh value.
|
|
415
|
+
*/
|
|
416
|
+
setToken: (token: string | null) => void;
|
|
345
417
|
}
|
|
418
|
+
/** {@link AuthConfig.onAuthError} signature. */
|
|
419
|
+
type AuthErrorHandler = (ctx: AuthErrorContext) => Promise<'retry' | 'skip'>;
|
|
346
420
|
/**
|
|
347
421
|
* Configure auth context for automatic token/orgId injection.
|
|
348
422
|
* When configured, hooks auto-inject these values so you don't need to pass them manually.
|
|
@@ -373,6 +447,95 @@ declare function getAuthContext(): {
|
|
|
373
447
|
};
|
|
374
448
|
/** @internal — exposed for tests; resets the dev-warn dedup flag. */
|
|
375
449
|
declare function _resetAuthWarnings(): void;
|
|
450
|
+
/**
|
|
451
|
+
* Outcome of a single auth-recovery cycle. `overrideToken` is the value the
|
|
452
|
+
* handler injected via `setToken`; `undefined` means the handler didn't call
|
|
453
|
+
* `setToken` and we should re-read via `getToken()`. `null` is meaningful —
|
|
454
|
+
* "retry without a token" (rare but valid for public endpoints).
|
|
455
|
+
*/
|
|
456
|
+
interface AuthRecoveryResult {
|
|
457
|
+
decision: 'retry' | 'skip';
|
|
458
|
+
overrideToken: string | null | undefined;
|
|
459
|
+
}
|
|
460
|
+
/** @internal — exposed for tests; clears the dedup so they don't bleed. */
|
|
461
|
+
declare function _resetAuthRecovery(): void;
|
|
462
|
+
/**
|
|
463
|
+
* @internal
|
|
464
|
+
* Cross-transport access to the configured auth-recovery handler.
|
|
465
|
+
* Upload (XHR) and WebSocket / SSE plumbing share the same dedup as the
|
|
466
|
+
* fetch path — they read the handler here and call {@link _runAuthRecovery}
|
|
467
|
+
* when they detect a transport-specific auth failure (XHR 401, WS close
|
|
468
|
+
* code 1008/4401, SSE pre-flight probe 401).
|
|
469
|
+
*/
|
|
470
|
+
declare function _getAuthErrorHandler(): {
|
|
471
|
+
handler: AuthErrorHandler | undefined;
|
|
472
|
+
retryOn403: boolean;
|
|
473
|
+
maxAuthRetries: number;
|
|
474
|
+
};
|
|
475
|
+
/**
|
|
476
|
+
* @internal
|
|
477
|
+
* Drive the shared recovery cycle from a non-fetch transport. Same dedup
|
|
478
|
+
* as the fetch path — concurrent callers (XHR upload + WebSocket reconnect
|
|
479
|
+
* + SSE probe firing at once) collapse to one refresh.
|
|
480
|
+
*/
|
|
481
|
+
declare function _runAuthRecovery(handler: AuthErrorHandler, ctx: Omit<AuthErrorContext, 'setToken'>): Promise<AuthRecoveryResult>;
|
|
482
|
+
/**
|
|
483
|
+
* @internal
|
|
484
|
+
* Resolve the next-attempt token. Mirrors the priority in `executeRequest`'s
|
|
485
|
+
* auth loop — `setToken` override beats re-reading `getToken()`. Exported so
|
|
486
|
+
* transports outside the fetch path apply the same precedence.
|
|
487
|
+
*/
|
|
488
|
+
declare function _resolveRefreshedToken(overrideToken: string | null | undefined): string | null;
|
|
489
|
+
/**
|
|
490
|
+
* @internal
|
|
491
|
+
* True for any error a transport should run through `onAuthError`. Matches
|
|
492
|
+
* the fetch path's predicate so XHR / WS / SSE failures classify the same
|
|
493
|
+
* way (401, or 403 when `retryOn403`).
|
|
494
|
+
*/
|
|
495
|
+
declare function _isAuthRecoverable(error: unknown, retryOn403: boolean): boolean;
|
|
496
|
+
/**
|
|
497
|
+
* Build an {@link AuthErrorHandler} from any `refresh()` function that
|
|
498
|
+
* returns the new token (or `null` if the session is truly expired).
|
|
499
|
+
*
|
|
500
|
+
* Catches refresh errors and surfaces them as `'skip'` by default so the
|
|
501
|
+
* original 401 reaches the consumer instead of a misleading "refresh failed"
|
|
502
|
+
* trace — consumers expect to handle "session expired" once, not twice. Pass
|
|
503
|
+
* `onRefreshError: 'throw'` to opt in to propagation.
|
|
504
|
+
*
|
|
505
|
+
* @example Better Auth (or any session-based lib)
|
|
506
|
+
* ```ts
|
|
507
|
+
* import { configureAuth, createAuthRefreshHandler } from '@classytic/arc-next/client';
|
|
508
|
+
* import { authClient } from '@/lib/auth-client';
|
|
509
|
+
*
|
|
510
|
+
* configureAuth({
|
|
511
|
+
* getToken: () => authClient.getSession().data?.session.token ?? null,
|
|
512
|
+
* onAuthError: createAuthRefreshHandler({
|
|
513
|
+
* refresh: async () => {
|
|
514
|
+
* const { data } = await authClient.getSession({ disableCookieCache: true });
|
|
515
|
+
* return data?.session.token ?? null;
|
|
516
|
+
* },
|
|
517
|
+
* }),
|
|
518
|
+
* });
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* @example Custom OAuth refresh
|
|
522
|
+
* ```ts
|
|
523
|
+
* configureAuth({
|
|
524
|
+
* getToken: () => tokenStore.getAccessToken(),
|
|
525
|
+
* onAuthError: createAuthRefreshHandler({
|
|
526
|
+
* refresh: () => oauthClient.refresh(tokenStore.getRefreshToken()),
|
|
527
|
+
* }),
|
|
528
|
+
* });
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
declare function createAuthRefreshHandler(opts: {
|
|
532
|
+
refresh: () => Promise<string | null>;
|
|
533
|
+
/**
|
|
534
|
+
* Behavior when the `refresh()` call itself throws. Default: `'skip'`
|
|
535
|
+
* (the original 401 surfaces; consumers handle "session expired" once).
|
|
536
|
+
*/
|
|
537
|
+
onRefreshError?: 'skip' | 'throw';
|
|
538
|
+
}): AuthErrorHandler;
|
|
376
539
|
/** Protocol family the URL should target. `http` keeps `getBaseUrl()` as-is; `ws` rewrites `http(s)://` → `ws(s)://`. */
|
|
377
540
|
type StreamUrlProtocol = 'http' | 'ws';
|
|
378
541
|
/**
|
|
@@ -505,8 +668,8 @@ declare function getClientAuthContext(client?: ArcClient): {
|
|
|
505
668
|
* Handles JSON, binary (PDF, images), CSV, and text responses.
|
|
506
669
|
*
|
|
507
670
|
* @example
|
|
508
|
-
* const
|
|
509
|
-
* const response = await handleApiRequest<
|
|
671
|
+
* const user = await handleApiRequest<User>('GET', '/users/me');
|
|
672
|
+
* const response = await handleApiRequest<PaginatedResult<Product>>('GET', '/products?page=1');
|
|
510
673
|
*/
|
|
511
674
|
declare function handleApiRequest<T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions): Promise<T>;
|
|
512
675
|
/**
|
|
@@ -526,5 +689,110 @@ declare function handleApiRequest<T = unknown>(method: HttpMethod, endpoint: str
|
|
|
526
689
|
* // => 'populate[employeeId][select]=name,email'
|
|
527
690
|
*/
|
|
528
691
|
declare function createQueryString<T extends Record<string, unknown>>(params?: T): string;
|
|
692
|
+
/**
|
|
693
|
+
* Returns the auth headers arc-next would inject on a fetch right now —
|
|
694
|
+
* `Authorization` (or the configured custom `headerName` for `authMode:
|
|
695
|
+
* 'header'`), `x-organization-id`, and `x-internal-api-key`. Use for the
|
|
696
|
+
* rare case where you need full `Response` control via plain `fetch` but
|
|
697
|
+
* still want arc-next's auth wiring.
|
|
698
|
+
*
|
|
699
|
+
* @example
|
|
700
|
+
* const res = await fetch(url, {
|
|
701
|
+
* headers: { ...arcAuthHeaders(), 'X-Custom': '1' },
|
|
702
|
+
* credentials: getAuthMode() === 'cookie' ? 'include' : 'same-origin',
|
|
703
|
+
* });
|
|
704
|
+
*/
|
|
705
|
+
declare function arcAuthHeaders(): Record<string, string>;
|
|
706
|
+
interface ArcFetchOptions extends Omit<RequestInit, 'body' | 'headers'> {
|
|
707
|
+
/**
|
|
708
|
+
* Request body. Plain objects and arrays are auto-`JSON.stringify`d and
|
|
709
|
+
* sent with `Content-Type: application/json`. Binary bodies (`FormData`,
|
|
710
|
+
* `Blob`, `ArrayBuffer`, `URLSearchParams`, `ReadableStream`) and strings
|
|
711
|
+
* pass through unchanged — the caller owns `Content-Type` for those.
|
|
712
|
+
*/
|
|
713
|
+
body?: Record<string, unknown> | unknown[] | BodyInit | null;
|
|
714
|
+
/**
|
|
715
|
+
* Extra headers merged on top of arc-next's auto-injected ones. Auth-
|
|
716
|
+
* related headers (`Authorization`, `x-organization-id`,
|
|
717
|
+
* `x-internal-api-key`, and the configured custom-auth header name) are
|
|
718
|
+
* **protected** — passing them here is silently dropped, so a caller can't
|
|
719
|
+
* accidentally strip the bearer token by spreading their own header map.
|
|
720
|
+
*/
|
|
721
|
+
headers?: Record<string, string>;
|
|
722
|
+
/** AbortSignal — same semantics as `fetch`. */
|
|
723
|
+
signal?: AbortSignal;
|
|
724
|
+
/** Per-call elevated-scope override (`x-arc-scope: platform`). */
|
|
725
|
+
elevated?: boolean;
|
|
726
|
+
/** Per-call `Idempotency-Key` header. */
|
|
727
|
+
idempotencyKey?: string;
|
|
728
|
+
/** Next.js fetch `revalidate` pass-through. */
|
|
729
|
+
revalidate?: number;
|
|
730
|
+
/** Next.js fetch `tags` pass-through. */
|
|
731
|
+
tags?: string[];
|
|
732
|
+
/** `cache` pass-through (browser fetch + Next.js). */
|
|
733
|
+
cache?: RequestCache;
|
|
734
|
+
/**
|
|
735
|
+
* Per-call client. Defaults to a lazily-created `createAuthAwareClient()`
|
|
736
|
+
* that bridges global `configureClient` + `configureAuth`. Pass a
|
|
737
|
+
* dedicated `createClient(...)` for multi-backend apps.
|
|
738
|
+
*/
|
|
739
|
+
client?: ArcClient;
|
|
740
|
+
}
|
|
741
|
+
/** @internal — tests reset the default client between cases. */
|
|
742
|
+
declare function _resetArcFetchClient(): void;
|
|
743
|
+
/**
|
|
744
|
+
* Authenticated, tenant-scoped fetch to an arc endpoint — one line for the
|
|
745
|
+
* non-hook contexts where `useQuery` / `useMutation` aren't available
|
|
746
|
+
* (event handlers, service workers, server actions, custom MDX submits,
|
|
747
|
+
* background polls).
|
|
748
|
+
*
|
|
749
|
+
* Auto-injects on every call:
|
|
750
|
+
* - `Authorization: Bearer <token>` (from `configureAuth().getToken`), or
|
|
751
|
+
* the custom header for `authMode: 'header'`
|
|
752
|
+
* - `x-organization-id` (from `configureAuth().getOrgId`)
|
|
753
|
+
* - `Content-Type: application/json` (only for plain object/array bodies)
|
|
754
|
+
* - `x-internal-api-key`, `Accept-Version`, `Idempotency-Key`,
|
|
755
|
+
* `x-arc-scope` when configured
|
|
756
|
+
*
|
|
757
|
+
* Composes with everything else `configureClient` + `configureAuth` do:
|
|
758
|
+
* - `retry` (5xx backoff)
|
|
759
|
+
* - `onAuthError` (401 → refresh → retry, with concurrent dedup)
|
|
760
|
+
* - `beforeRequest` / `afterResponse` interceptors
|
|
761
|
+
* - `cookie` / `bearer` / `header` auth modes
|
|
762
|
+
*
|
|
763
|
+
* Response handling:
|
|
764
|
+
* - 2xx → parsed body (JSON for `application/json`, Blob for binary,
|
|
765
|
+
* text for `text/*`).
|
|
766
|
+
* - non-2xx → throws `ArcApiError` with parsed body, status, endpoint,
|
|
767
|
+
* method. Use `isArcApiError(err)` + `err.code` to discriminate.
|
|
768
|
+
*
|
|
769
|
+
* For full `Response` control (rare), use plain `fetch` with
|
|
770
|
+
* {@link arcAuthHeaders} instead.
|
|
771
|
+
*
|
|
772
|
+
* @example
|
|
773
|
+
* import { arc } from '@classytic/arc-next/client';
|
|
774
|
+
*
|
|
775
|
+
* // Before — 15 lines of header dance + error parse + JSON
|
|
776
|
+
* // After:
|
|
777
|
+
* const result = await arc.post<{ ok: true }>('/api/statements', statements);
|
|
778
|
+
*/
|
|
779
|
+
declare function arcFetch<T = unknown>(path: string, options?: ArcFetchOptions): Promise<T>;
|
|
780
|
+
/**
|
|
781
|
+
* Method-specific shorthands for the 90% case. Each mirrors `arcFetch` with
|
|
782
|
+
* the HTTP verb pre-filled; mutating verbs accept `body` as the second arg
|
|
783
|
+
* so the call reads as a sentence:
|
|
784
|
+
*
|
|
785
|
+
* `arc.post('/path', payload)` instead of
|
|
786
|
+
* `arcFetch('/path', { method: 'POST', body: payload })`.
|
|
787
|
+
*
|
|
788
|
+
* Identical composition with `onAuthError`, retry, and interceptors.
|
|
789
|
+
*/
|
|
790
|
+
declare const arc: {
|
|
791
|
+
get: <T = unknown>(path: string, opts?: ArcFetchOptions) => Promise<T>;
|
|
792
|
+
post: <T = unknown>(path: string, body?: ArcFetchOptions["body"], opts?: ArcFetchOptions) => Promise<T>;
|
|
793
|
+
put: <T = unknown>(path: string, body?: ArcFetchOptions["body"], opts?: ArcFetchOptions) => Promise<T>;
|
|
794
|
+
patch: <T = unknown>(path: string, body?: ArcFetchOptions["body"], opts?: ArcFetchOptions) => Promise<T>;
|
|
795
|
+
delete: <T = unknown>(path: string, opts?: ArcFetchOptions) => Promise<T>;
|
|
796
|
+
};
|
|
529
797
|
//#endregion
|
|
530
|
-
export { AfterResponseContext, AfterResponseInterceptor, ApiRequestOptions, ArcApiError, ArcApiErrorOptions, ArcClient, ArcClientConfig,
|
|
798
|
+
export { AfterResponseContext, AfterResponseInterceptor, ApiRequestOptions, ArcApiError, ArcApiErrorOptions, ArcClient, ArcClientConfig, ArcErrorCode, ArcFetchOptions, AuthConfig, AuthErrorContext, AuthErrorHandler, BeforeRequestContext, BeforeRequestInterceptor, BlobResponse, ClientConfig, HttpMethod, KNOWN_ARC_ERROR_CODES, RetryConfig, StreamUrlProtocol, TextResponse, ToastHandler, UseRouterHook, _getAuthErrorHandler, _isAuthRecoverable, _resetArcFetchClient, _resetAuthRecovery, _resetAuthWarnings, _resolveRefreshedToken, _runAuthRecovery, arc, arcAuthHeaders, arcFetch, buildStreamUrl, configureAuth, configureClient, createAuthAwareClient, createAuthRefreshHandler, createClient, createQueryString, getAuthContext, getAuthMode, getBaseUrl, getClientAuthContext, handleApiRequest, hasGlobalStaticAuth, isAbortError, isArcApiError, isArcErrorCode, isAutoIdempotency, isDuplicateKeyError, isOrgContextRequiredError, isValidationError };
|