@luckystack/core 0.10.2 → 0.11.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/AGENTS.md +11 -0
- package/CHANGELOG.md +7 -0
- package/README.md +2 -88
- package/dist/apiInterceptors.d.ts +36 -0
- package/dist/apiMethodMapRegistry.d.ts +5 -0
- package/dist/apiRequest.d.ts +71 -0
- package/dist/apiTypeStubs.d.ts +3 -5
- package/dist/avatarConfig.d.ts +22 -0
- package/dist/bindAddress.d.ts +15 -0
- package/dist/bootUuid.d.ts +24 -0
- package/dist/cancelRegistry.d.ts +7 -0
- package/dist/checkOrigin.d.ts +6 -0
- package/dist/client.d.ts +46 -335
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/clientHookBus.d.ts +62 -0
- package/dist/clients.d.ts +16 -0
- package/dist/config.d.ts +8 -2
- package/dist/configUtils.d.ts +16 -0
- package/dist/consoleLog.d.ts +1 -0
- package/dist/cookies.d.ts +28 -0
- package/dist/createRegistry.d.ts +39 -0
- package/dist/csrf.d.ts +30 -0
- package/dist/csrfConfig.d.ts +32 -0
- package/dist/db.d.ts +12 -0
- package/dist/dbHealthCheck.d.ts +6 -0
- package/dist/{config-CwW3KY9r.d.ts → deployConfigRegistry.d.ts} +8 -43
- package/dist/emailRegistry.d.ts +139 -0
- package/dist/env.d.ts +38 -0
- package/dist/errorFormatterRegistry.d.ts +54 -0
- package/dist/errorTrackerIdentity.d.ts +14 -0
- package/dist/errorTrackerRegistry.d.ts +85 -0
- package/dist/escapeHtml.d.ts +6 -0
- package/dist/eslint/index.d.ts +5 -8
- package/dist/eslint/internal/hasPackage.d.ts +1 -0
- package/dist/eslint/internal/ruleTypes.d.ts +4 -0
- package/dist/eslint/rules/no-arbitrary-tailwind-color.d.ts +3 -0
- package/dist/eslint/rules/no-direct-prisma-import-in-components.d.ts +3 -0
- package/dist/eslint/rules/no-local-socket-enumeration.d.ts +3 -0
- package/dist/eslint/rules/no-raw-fetch-in-src.d.ts +3 -0
- package/dist/eslint/rules/no-raw-try-catch.d.ts +3 -0
- package/dist/eslint/rules/no-unsafe-api-wrappers.d.ts +3 -0
- package/dist/eslint/rules/no-unsafe-sync-wrappers.d.ts +3 -0
- package/dist/eslint/rules/prefer-luckystack-confirm.d.ts +3 -0
- package/dist/eslint/rules/prefer-luckystack-dropdown.d.ts +3 -0
- package/dist/eslint/rules/prefer-luckystack-notify.d.ts +3 -0
- package/dist/extractToken.d.ts +12 -0
- package/dist/extractTokenFromRequest.d.ts +12 -0
- package/dist/getParams.d.ts +9 -0
- package/dist/hooks/registry.d.ts +12 -0
- package/dist/hooks/types.d.ts +446 -0
- package/dist/httpApiUtils.d.ts +54 -0
- package/dist/index.d.ts +110 -1892
- package/dist/index.js.map +1 -1
- package/dist/lease.d.ts +18 -0
- package/dist/localSocketEnumerationGuard.d.ts +22 -0
- package/dist/localeReloader.d.ts +3 -0
- package/dist/localesRegistry.d.ts +8 -0
- package/dist/localizedNormalizer.d.ts +50 -0
- package/dist/loggerRegistry.d.ts +12 -0
- package/dist/middlewareRegistry.d.ts +45 -0
- package/dist/notifier.d.ts +17 -0
- package/dist/offlineQueue.d.ts +48 -0
- package/dist/oneTimeToken.d.ts +43 -0
- package/dist/pageRouteValidation.d.ts +37 -0
- package/dist/paths.d.ts +35 -0
- package/dist/peerDeps.d.ts +23 -0
- package/dist/portOverrideRegistry.d.ts +3 -0
- package/dist/processUpload.d.ts +44 -0
- package/dist/{sessionTypes-DanjuPaE.d.ts → projectConfig.d.ts} +32 -129
- package/dist/rateLimiter.d.ts +90 -0
- package/dist/react/AvatarProvider.d.ts +11 -0
- package/dist/react/Middleware.d.ts +10 -0
- package/dist/react/Router.d.ts +1 -0
- package/dist/react/TranslationProvider.d.ts +22 -0
- package/dist/react/notify.d.ts +12 -0
- package/dist/react/sessionContext.d.ts +31 -0
- package/dist/react/useTheme.d.ts +5 -0
- package/dist/redactedLogKeys.d.ts +22 -0
- package/dist/redis.d.ts +15 -0
- package/dist/redisKeyFormatter.d.ts +7 -0
- package/dist/resolveClientIp.d.ts +51 -0
- package/dist/responseNormalizer.d.ts +33 -0
- package/dist/roomNameFormatterRegistry.d.ts +36 -0
- package/dist/roomSockets.d.ts +23 -0
- package/dist/routedDataQuery.d.ts +7 -0
- package/dist/routedHttpInvocation.d.ts +20 -0
- package/dist/runtimeMapsRegistry.d.ts +18 -0
- package/dist/runtimeTypeValidation.d.ts +13 -0
- package/dist/secretsResolved.d.ts +3 -0
- package/dist/sentrySetup.d.ts +21 -0
- package/dist/serveAvatars.d.ts +5 -0
- package/dist/serviceRoute.d.ts +27 -0
- package/dist/servicesConfigRegistry.d.ts +22 -0
- package/dist/sessionProviderRegistry.d.ts +46 -0
- package/dist/sessionTypes.d.ts +79 -0
- package/dist/sleep.d.ts +1 -0
- package/dist/socketEvents.d.ts +37 -0
- package/dist/socketMiddlewareRegistry.d.ts +20 -0
- package/dist/socketRedisAdapter.d.ts +21 -0
- package/dist/socketState.d.ts +5 -0
- package/dist/socketStatusTypes.d.ts +6 -0
- package/dist/socketTypes.d.ts +34 -0
- package/dist/synchronizedEnvHashes.d.ts +66 -0
- package/dist/testFileConvention.d.ts +11 -0
- package/dist/testResetTargets.d.ts +5 -0
- package/dist/tryCatch.d.ts +1 -0
- package/dist/tryCatchClient.d.ts +1 -0
- package/dist/tryCatchSync.d.ts +1 -0
- package/dist/validateRequest.d.ts +50 -0
- package/docs/app-bootstrap.md +4 -449
- package/docs/config-registry.md +3 -346
- package/docs/csrf-config.md +3 -86
- package/docs/error-tracker-registry.md +3 -192
- package/docs/hooks.md +2 -295
- package/docs/rate-limit-strategy.md +3 -168
- package/docs/redis-adapter.md +5 -110
- package/docs/session-types.md +3 -178
- package/docs/socket-bootstrap.md +5 -385
- package/package.json +2 -2
- package/CLAUDE.md +0 -256
- package/dist/pageRouteValidation-B4Cmtfjv.d.ts +0 -280
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
type DeepPartial
|
|
2
|
-
|
|
3
|
-
};
|
|
4
|
-
/**
|
|
5
|
-
* True when `value` is a plain object literal (prototype is `Object.prototype`
|
|
6
|
-
* or `null`). Arrays, class instances, Maps, Dates, and `null` are rejected so
|
|
7
|
-
* they are treated as opaque replacement values during a merge.
|
|
8
|
-
*/
|
|
9
|
-
declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
|
|
10
|
-
/**
|
|
11
|
-
* Deep-merge a deep-partial `override` over a fully-populated `base`,
|
|
12
|
-
* returning a new object. See the module header for the exact behaviour
|
|
13
|
-
* contract. Arrays and non-plain-object values are replaced wholesale; nested
|
|
14
|
-
* plain objects merge recursively; `undefined` override values are ignored.
|
|
15
|
-
*/
|
|
16
|
-
declare const deepMerge: <T>(base: T, override: DeepPartial<T> | undefined) => T;
|
|
17
|
-
|
|
18
|
-
interface LoggingConfig {
|
|
1
|
+
import { type DeepPartial } from './configUtils.js';
|
|
2
|
+
export interface LoggingConfig {
|
|
19
3
|
devLogs: boolean;
|
|
20
4
|
devNotifications: boolean;
|
|
21
5
|
socketStatus: boolean;
|
|
@@ -32,7 +16,7 @@ interface LoggingConfig {
|
|
|
32
16
|
*/
|
|
33
17
|
timestamps: boolean;
|
|
34
18
|
}
|
|
35
|
-
interface RateLimitingConfig {
|
|
19
|
+
export interface RateLimitingConfig {
|
|
36
20
|
/**
|
|
37
21
|
* Global kill-switch for all rate limiting. When `false`, every
|
|
38
22
|
* `checkRateLimit` call short-circuits to "allowed" and counters are not
|
|
@@ -94,7 +78,7 @@ interface RateLimitingConfig {
|
|
|
94
78
|
auth: AuthRateLimitConfig;
|
|
95
79
|
}
|
|
96
80
|
/** Inputs handed to the {@link RateLimitingConfig.identity} callback. */
|
|
97
|
-
interface RateLimitIdentityParams {
|
|
81
|
+
export interface RateLimitIdentityParams {
|
|
98
82
|
/** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */
|
|
99
83
|
routeName: string;
|
|
100
84
|
/** Session user id when the request is authenticated, else null. */
|
|
@@ -105,13 +89,13 @@ interface RateLimitIdentityParams {
|
|
|
105
89
|
transport: 'socket' | 'http';
|
|
106
90
|
}
|
|
107
91
|
/** Result of a {@link RateLimitingConfig.identity} callback. */
|
|
108
|
-
interface RateLimitIdentity {
|
|
92
|
+
export interface RateLimitIdentity {
|
|
109
93
|
/** Which dimension the key is built from. */
|
|
110
94
|
scope: 'user' | 'ip' | 'custom';
|
|
111
95
|
/** The identity value (user id, ip, or a custom string). Never a raw token. */
|
|
112
96
|
id: string;
|
|
113
97
|
}
|
|
114
|
-
interface AuthRateLimitConfig {
|
|
98
|
+
export interface AuthRateLimitConfig {
|
|
115
99
|
/** When false (default), no per-account lockout is applied. */
|
|
116
100
|
enabled: boolean;
|
|
117
101
|
/** Max failed attempts per account FROM ONE IP within `windowMs` before that
|
|
@@ -129,7 +113,7 @@ interface AuthRateLimitConfig {
|
|
|
129
113
|
/** Rolling window in ms over which both caps are counted. Default 900000 (15 min). */
|
|
130
114
|
windowMs: number;
|
|
131
115
|
}
|
|
132
|
-
interface SessionConfig {
|
|
116
|
+
export interface SessionConfig {
|
|
133
117
|
basedToken: boolean;
|
|
134
118
|
expiryDays: number;
|
|
135
119
|
/**
|
|
@@ -162,7 +146,7 @@ interface SessionConfig {
|
|
|
162
146
|
*/
|
|
163
147
|
projectName: string;
|
|
164
148
|
}
|
|
165
|
-
interface AppConfig {
|
|
149
|
+
export interface AppConfig {
|
|
166
150
|
/**
|
|
167
151
|
* Public URL of the app — used by OAuth callback redirects, transactional
|
|
168
152
|
* email links (when `@luckystack/email` is installed), and any other
|
|
@@ -172,7 +156,7 @@ interface AppConfig {
|
|
|
172
156
|
*/
|
|
173
157
|
publicUrl: string;
|
|
174
158
|
}
|
|
175
|
-
interface HttpStreamConfig {
|
|
159
|
+
export interface HttpStreamConfig {
|
|
176
160
|
/** Query-string flag clients pass to opt into SSE streaming. */
|
|
177
161
|
queryParam: string;
|
|
178
162
|
/** Value the query-string flag must have. */
|
|
@@ -180,13 +164,13 @@ interface HttpStreamConfig {
|
|
|
180
164
|
/** Initial comment line written when an SSE stream opens (keeps proxies happy). */
|
|
181
165
|
connectedComment: string;
|
|
182
166
|
}
|
|
183
|
-
interface SecurityHeadersConfig {
|
|
167
|
+
export interface SecurityHeadersConfig {
|
|
184
168
|
frameOptions: string;
|
|
185
169
|
referrerPolicy: string;
|
|
186
170
|
xssProtection: string;
|
|
187
171
|
contentTypeOptions: string;
|
|
188
172
|
}
|
|
189
|
-
interface CorsConfig {
|
|
173
|
+
export interface CorsConfig {
|
|
190
174
|
allowedMethods: string;
|
|
191
175
|
allowedHeaders: string;
|
|
192
176
|
exposedHeaders: string;
|
|
@@ -224,7 +208,7 @@ interface CorsConfig {
|
|
|
224
208
|
*/
|
|
225
209
|
allowOriginless?: boolean;
|
|
226
210
|
}
|
|
227
|
-
interface HttpConfig {
|
|
211
|
+
export interface HttpConfig {
|
|
228
212
|
sessionCookieName: string;
|
|
229
213
|
sessionCookieSameSite: 'Strict' | 'Lax' | 'None';
|
|
230
214
|
sessionCookiePath: string;
|
|
@@ -331,7 +315,7 @@ interface HttpConfig {
|
|
|
331
315
|
securityHeaders: SecurityHeadersConfig;
|
|
332
316
|
cors: CorsConfig;
|
|
333
317
|
}
|
|
334
|
-
interface HealthHashConfig {
|
|
318
|
+
export interface HealthHashConfig {
|
|
335
319
|
/**
|
|
336
320
|
* - `'plain'` — unsalted `sha256(value)`; the pre-0.2.0 behavior, kept so an
|
|
337
321
|
* existing router boot handshake keeps comparing successfully. Opt in with
|
|
@@ -355,7 +339,7 @@ interface HealthHashConfig {
|
|
|
355
339
|
*/
|
|
356
340
|
salt: string;
|
|
357
341
|
}
|
|
358
|
-
interface PasswordPolicyConfig {
|
|
342
|
+
export interface PasswordPolicyConfig {
|
|
359
343
|
/** Minimum total length. Default 8. */
|
|
360
344
|
minLength: number;
|
|
361
345
|
/** Maximum total length. Default 191 (DB column-friendly). */
|
|
@@ -381,7 +365,7 @@ interface PasswordPolicyConfig {
|
|
|
381
365
|
*/
|
|
382
366
|
customValidator?: (password: string) => string | null;
|
|
383
367
|
}
|
|
384
|
-
interface AuthConfig {
|
|
368
|
+
export interface AuthConfig {
|
|
385
369
|
/**
|
|
386
370
|
* Whether email+password (credentials) auth is enabled. When `true` (default)
|
|
387
371
|
* `@luckystack/login/register` registers the credentials provider, so it shows
|
|
@@ -497,7 +481,7 @@ interface AuthConfig {
|
|
|
497
481
|
/** Wrong-code attempts before a pending 2FA challenge is burned. DEFAULT 5. */
|
|
498
482
|
twoFactorMaxAttempts: number;
|
|
499
483
|
}
|
|
500
|
-
interface OfflineQueueConfig {
|
|
484
|
+
export interface OfflineQueueConfig {
|
|
501
485
|
/** Hard cap on items per queue (api + sync are tracked separately). Default 200. */
|
|
502
486
|
maxSize: number;
|
|
503
487
|
/** Drop items older than this many ms when flushing/enqueuing. Default 1 hour. */
|
|
@@ -510,7 +494,7 @@ interface OfflineQueueConfig {
|
|
|
510
494
|
*/
|
|
511
495
|
dropPolicy: 'drop-oldest' | 'drop-newest' | 'reject';
|
|
512
496
|
}
|
|
513
|
-
interface SyncStreamThrottleConfig {
|
|
497
|
+
export interface SyncStreamThrottleConfig {
|
|
514
498
|
/** Default `flushAtChars` for `createStreamThrottle({...})`. */
|
|
515
499
|
flushAtChars: number;
|
|
516
500
|
/** Default `flushEveryMs` for `createStreamThrottle({...})`. */
|
|
@@ -518,7 +502,7 @@ interface SyncStreamThrottleConfig {
|
|
|
518
502
|
/** Default `field` (payload key) for emitted chunks. */
|
|
519
503
|
field: string;
|
|
520
504
|
}
|
|
521
|
-
interface TransportConfig {
|
|
505
|
+
export interface TransportConfig {
|
|
522
506
|
/**
|
|
523
507
|
* Transport used for typed API/sync invocations. Socket.io remains connected
|
|
524
508
|
* in both modes for rooms, presence, reconnect state and realtime callbacks.
|
|
@@ -528,7 +512,7 @@ interface TransportConfig {
|
|
|
528
512
|
*/
|
|
529
513
|
invocation: 'socket' | 'routed-http';
|
|
530
514
|
}
|
|
531
|
-
interface ApiConfig {
|
|
515
|
+
export interface ApiConfig {
|
|
532
516
|
/**
|
|
533
517
|
* Default response timeout (ms) for `apiRequest`. After this elapses with no
|
|
534
518
|
* response (e.g. server restart/crash between emit and reply) the request
|
|
@@ -538,7 +522,7 @@ interface ApiConfig {
|
|
|
538
522
|
*/
|
|
539
523
|
requestTimeoutMs: number | false;
|
|
540
524
|
}
|
|
541
|
-
interface ValidationConfig {
|
|
525
|
+
export interface ValidationConfig {
|
|
542
526
|
/**
|
|
543
527
|
* Whether runtime input validation runs in PRODUCTION (CORE-01).
|
|
544
528
|
*
|
|
@@ -558,7 +542,7 @@ interface ValidationConfig {
|
|
|
558
542
|
*/
|
|
559
543
|
runtimeMode: 'enforce' | 'off';
|
|
560
544
|
}
|
|
561
|
-
interface SyncConfig {
|
|
545
|
+
export interface SyncConfig {
|
|
562
546
|
streamThrottle: SyncStreamThrottleConfig;
|
|
563
547
|
/**
|
|
564
548
|
* Default response timeout (ms) for `syncRequest`'s acknowledgement. Same
|
|
@@ -605,7 +589,7 @@ interface SyncConfig {
|
|
|
605
589
|
*/
|
|
606
590
|
flushPressure: SyncFlushPressureConfig;
|
|
607
591
|
}
|
|
608
|
-
interface SyncFlushPressureConfig {
|
|
592
|
+
export interface SyncFlushPressureConfig {
|
|
609
593
|
/** Max queued chunks before the stream applies backpressure (pauses). Default 1000. */
|
|
610
594
|
highWaterMarkChunks: number;
|
|
611
595
|
/** Resume emitting once the queue drains below this. Default 250. */
|
|
@@ -613,7 +597,7 @@ interface SyncFlushPressureConfig {
|
|
|
613
597
|
/** Hard cap on bytes buffered for a single stream before dropping/erroring. Default 5_242_880 (5 MiB). */
|
|
614
598
|
maxBufferedBytes: number;
|
|
615
599
|
}
|
|
616
|
-
interface SocketConfig {
|
|
600
|
+
export interface SocketConfig {
|
|
617
601
|
/**
|
|
618
602
|
* Maximum payload size (bytes) for any single Socket.io message (whole-frame
|
|
619
603
|
* buffer). DEFAULT 5 MiB (5 * 1024 * 1024). This is the SOCKET-transport cap; the
|
|
@@ -642,7 +626,7 @@ interface SocketConfig {
|
|
|
642
626
|
*/
|
|
643
627
|
maxRoomsPerSession: number | false;
|
|
644
628
|
}
|
|
645
|
-
interface DevConfig {
|
|
629
|
+
export interface DevConfig {
|
|
646
630
|
/** Debounce window for hot-reload triggers. */
|
|
647
631
|
hotReloadDebounceMs: number;
|
|
648
632
|
/** Chokidar `awaitWriteFinish.stabilityThreshold`. */
|
|
@@ -658,7 +642,7 @@ interface DevConfig {
|
|
|
658
642
|
*/
|
|
659
643
|
warnOnMissingInputType?: boolean;
|
|
660
644
|
}
|
|
661
|
-
interface PathsConfig {
|
|
645
|
+
export interface PathsConfig {
|
|
662
646
|
/** Frontend source root. */
|
|
663
647
|
srcDir: string;
|
|
664
648
|
/** Server-side source root. */
|
|
@@ -698,7 +682,7 @@ interface PathsConfig {
|
|
|
698
682
|
/** Generated API docs JSON output path. */
|
|
699
683
|
generatedApiDocs: string;
|
|
700
684
|
}
|
|
701
|
-
interface ProjectConfig {
|
|
685
|
+
export interface ProjectConfig {
|
|
702
686
|
app: AppConfig;
|
|
703
687
|
logging: LoggingConfig;
|
|
704
688
|
rateLimiting: RateLimitingConfig;
|
|
@@ -734,90 +718,9 @@ interface ProjectConfig {
|
|
|
734
718
|
*/
|
|
735
719
|
oauthCallbackBase?: string;
|
|
736
720
|
}
|
|
737
|
-
type ProjectConfigInput = DeepPartial<ProjectConfig>;
|
|
738
|
-
declare const DEFAULT_PROJECT_CONFIG: ProjectConfig;
|
|
739
|
-
declare const registerProjectConfig: (config: ProjectConfigInput) => void;
|
|
740
|
-
declare const getProjectConfig: () => ProjectConfig;
|
|
741
|
-
declare const getProjectName: () => string;
|
|
742
|
-
declare const isProjectConfigRegistered: () => boolean;
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* What `T` becomes after `JSON.stringify` → `JSON.parse`, at the type level.
|
|
746
|
-
*
|
|
747
|
-
* The mirror of what the codegen does to route OUTPUTS. Use it wherever you
|
|
748
|
-
* hand-write a type for something that CROSSES THE WIRE — the client never
|
|
749
|
-
* receives your server type, it receives JSON, and JSON has no `Date`:
|
|
750
|
-
*
|
|
751
|
-
* type ClientUser = Jsonify<User>; // createdAt: string, not Date
|
|
752
|
-
*
|
|
753
|
-
* Without it a type says `Date`, the client gets `"2026-07-15T15:20:15.553Z"`,
|
|
754
|
-
* and `user.createdAt.getTime()` compiles and then throws at runtime.
|
|
755
|
-
*
|
|
756
|
-
* Deliberately SHALLOW — one level of properties, descending only into arrays.
|
|
757
|
-
* A fully recursive object mapper does not survive contact with Prisma's
|
|
758
|
-
* self-referential `JsonValue`. Already-JSON-stable values are therefore an
|
|
759
|
-
* identity boundary: do not recursively rebuild what JSON already represents.
|
|
760
|
-
* The NON-DISTRIBUTIVE tuple guard is load-bearing — it recognizes the complete
|
|
761
|
-
* `JsonValue` union before the Date branch distributes `Date | null` into
|
|
762
|
-
* `string | null`.
|
|
763
|
-
*/
|
|
764
|
-
type JsonStableValue = string | number | boolean | null | JsonStableValue[] | {
|
|
765
|
-
[key: string]: JsonStableValue | undefined;
|
|
766
|
-
};
|
|
767
|
-
type JsonifyValue<TValue> = [TValue] extends [JsonStableValue] ? TValue : TValue extends Date ? string : TValue extends readonly (infer TElement)[] ? JsonifyValue<TElement>[] : TValue;
|
|
768
|
-
type Jsonify<T> = {
|
|
769
|
-
[TKey in keyof T]: JsonifyValue<T[TKey]>;
|
|
770
|
-
};
|
|
771
|
-
interface SessionLocation {
|
|
772
|
-
pathName: string;
|
|
773
|
-
searchParams?: Record<string, string>;
|
|
774
|
-
}
|
|
775
|
-
interface BaseSessionLayout {
|
|
776
|
-
id: string;
|
|
777
|
-
token?: string;
|
|
778
|
-
email?: string | null;
|
|
779
|
-
name?: string | null;
|
|
780
|
-
avatar?: string | null;
|
|
781
|
-
avatarFallback?: string | null;
|
|
782
|
-
admin?: boolean | null;
|
|
783
|
-
language?: string | null;
|
|
784
|
-
location?: SessionLocation;
|
|
785
|
-
roomCodes?: string[];
|
|
786
|
-
/**
|
|
787
|
-
* CSRF token bound to this session. Generated by `saveSession` on first
|
|
788
|
-
* write. Validated by the HTTP handler on state-changing requests when
|
|
789
|
-
* the session is in cookie mode. Token-mode sessions don't need this
|
|
790
|
-
* (cross-origin requests don't auto-attach a sessionStorage value).
|
|
791
|
-
*/
|
|
792
|
-
csrfToken?: string;
|
|
793
|
-
/**
|
|
794
|
-
* Most recent successful login time (set to "now" on each login). Optional
|
|
795
|
-
* — populated only when the User schema has a `lastLogin` column.
|
|
796
|
-
*/
|
|
797
|
-
lastLogin?: Date | string | null;
|
|
798
|
-
/**
|
|
799
|
-
* The previous successful login time, captured at this login's moment.
|
|
800
|
-
* Useful for "Last sign-in: X ago" UI on the post-login dashboard.
|
|
801
|
-
* Null on first-ever login. Runtime-only — not persisted to the DB.
|
|
802
|
-
*/
|
|
803
|
-
previousLogin?: Date | string | null;
|
|
804
|
-
}
|
|
805
|
-
interface AuthProps {
|
|
806
|
-
/** If true, user must have a valid session with an ID */
|
|
807
|
-
login: boolean;
|
|
808
|
-
/** Additional validation rules for session properties */
|
|
809
|
-
additional?: {
|
|
810
|
-
/** The session property to check (e.g., 'admin', 'email') */
|
|
811
|
-
key: keyof BaseSessionLayout;
|
|
812
|
-
/** Exact value the property must equal (strict comparison) */
|
|
813
|
-
value?: unknown;
|
|
814
|
-
/** Type the property must be */
|
|
815
|
-
type?: 'string' | 'number' | 'boolean';
|
|
816
|
-
/** If true, property must be null/undefined. If false, must NOT be null/undefined */
|
|
817
|
-
nullish?: boolean;
|
|
818
|
-
/** If true, property must be falsy. If false, must be truthy */
|
|
819
|
-
mustBeFalsy?: boolean;
|
|
820
|
-
}[];
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
export { type AppConfig as A, type BaseSessionLayout as B, type CorsConfig as C, type DeepPartial as D, type HealthHashConfig as H, type Jsonify as J, type LoggingConfig as L, type OfflineQueueConfig as O, type ProjectConfig as P, type RateLimitingConfig as R, type SessionConfig as S, type TransportConfig as T, type ValidationConfig as V, type AuthProps as a, type SessionLocation as b, type ApiConfig as c, type AuthConfig as d, type AuthRateLimitConfig as e, DEFAULT_PROJECT_CONFIG as f, getProjectConfig as g, type DevConfig as h, isProjectConfigRegistered as i, type HttpConfig as j, type HttpStreamConfig as k, type PasswordPolicyConfig as l, type PathsConfig as m, type ProjectConfigInput as n, type RateLimitIdentity as o, type RateLimitIdentityParams as p, type SecurityHeadersConfig as q, registerProjectConfig as r, type SocketConfig as s, type SyncConfig as t, type SyncFlushPressureConfig as u, type SyncStreamThrottleConfig as v, deepMerge as w, getProjectName as x, isPlainObject as y };
|
|
721
|
+
export type ProjectConfigInput = DeepPartial<ProjectConfig>;
|
|
722
|
+
export declare const DEFAULT_PROJECT_CONFIG: ProjectConfig;
|
|
723
|
+
export declare const registerProjectConfig: (config: ProjectConfigInput) => void;
|
|
724
|
+
export declare const getProjectConfig: () => ProjectConfig;
|
|
725
|
+
export declare const getProjectName: () => string;
|
|
726
|
+
export declare const isProjectConfigRegistered: () => boolean;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter
|
|
3
|
+
*
|
|
4
|
+
* The framework's rate-limit dispatch is pluggable via
|
|
5
|
+
* `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`
|
|
6
|
+
* implements in-memory counters with optional Redis backing for
|
|
7
|
+
* multi-instance deployments. Consumers can plug in alternative strategies
|
|
8
|
+
* (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by
|
|
9
|
+
* implementing the `RateLimitStrategy` interface.
|
|
10
|
+
*/
|
|
11
|
+
export interface CheckRateLimitParams {
|
|
12
|
+
/** Unique key for rate limiting (e.g., "user:123:api:getData") */
|
|
13
|
+
key: string;
|
|
14
|
+
/** Maximum requests allowed in window */
|
|
15
|
+
limit: number;
|
|
16
|
+
/** Time window in milliseconds (default: 60_000 = 1 minute) */
|
|
17
|
+
windowMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface RateLimitResult {
|
|
20
|
+
/** Whether the request is allowed */
|
|
21
|
+
allowed: boolean;
|
|
22
|
+
/** Remaining requests in current window */
|
|
23
|
+
remaining: number;
|
|
24
|
+
/** Seconds until rate limit resets */
|
|
25
|
+
resetIn: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pluggable rate-limit backend. The framework calls these methods through
|
|
29
|
+
* the active strategy slot. Default implementation is memory + Redis.
|
|
30
|
+
* Replace via `registerRateLimitStrategy(...)` at boot.
|
|
31
|
+
*/
|
|
32
|
+
export interface RateLimitStrategy {
|
|
33
|
+
/** Human-readable identifier (used in logs/diagnostics). */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Increment the counter for `key` and return whether the request is allowed. */
|
|
36
|
+
check(params: CheckRateLimitParams): Promise<RateLimitResult>;
|
|
37
|
+
/** Read current status without incrementing — useful for response headers. */
|
|
38
|
+
getStatus(key: string, limit: number): Promise<RateLimitResult>;
|
|
39
|
+
/** Reset the counter for a single key. */
|
|
40
|
+
clear(key: string): Promise<void>;
|
|
41
|
+
/** Reset all counters under this strategy's namespace. */
|
|
42
|
+
clearAll(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Built-in strategy: in-memory counters with optional Redis backing for
|
|
46
|
+
* multi-instance deployments. Mode is controlled by
|
|
47
|
+
* `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`
|
|
48
|
+
* mode fails (e.g. Redis disconnect), the strategy silently degrades to
|
|
49
|
+
* in-memory and logs a one-shot warning.
|
|
50
|
+
*/
|
|
51
|
+
export declare const defaultRateLimitStrategy: RateLimitStrategy;
|
|
52
|
+
/**
|
|
53
|
+
* Replace the active rate-limit backend. Pass a strategy that implements
|
|
54
|
+
* `RateLimitStrategy`. Common use cases:
|
|
55
|
+
*
|
|
56
|
+
* - Token-bucket / sliding-window algorithms for smoother burst handling.
|
|
57
|
+
* - Per-user tier limits (premium accounts get higher caps).
|
|
58
|
+
* - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.
|
|
59
|
+
* - No-op strategy for test environments.
|
|
60
|
+
*
|
|
61
|
+
* Call this once at boot (typically from `luckystack/server/index.ts`)
|
|
62
|
+
* BEFORE the first request lands. Subsequent calls overwrite the previous
|
|
63
|
+
* registration (last-write-wins).
|
|
64
|
+
*/
|
|
65
|
+
export declare const registerRateLimitStrategy: (strategy: RateLimitStrategy) => void;
|
|
66
|
+
/** Read the currently-active strategy (defaults to the built-in). */
|
|
67
|
+
export declare const getRateLimitStrategy: () => RateLimitStrategy;
|
|
68
|
+
/**
|
|
69
|
+
* Check if a request is allowed under rate limiting rules.
|
|
70
|
+
* Increments the counter for the key if allowed.
|
|
71
|
+
*/
|
|
72
|
+
export declare const checkRateLimit: (params: CheckRateLimitParams) => Promise<RateLimitResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Get current rate limit status without incrementing counter.
|
|
75
|
+
* Useful for rate limit headers in responses.
|
|
76
|
+
*/
|
|
77
|
+
export declare const getRateLimitStatus: (key: string, limit: number) => Promise<RateLimitResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Clear rate limit for a specific key.
|
|
80
|
+
* Useful for admin overrides or testing.
|
|
81
|
+
*/
|
|
82
|
+
export declare const clearRateLimit: (key: string) => Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Clear all rate limits.
|
|
85
|
+
* Intended for testing or dev-mode server reset only.
|
|
86
|
+
* CORE-O8: throws in production to prevent accidental global counter wipe across
|
|
87
|
+
* all tenants. If you need a production-safe reset, pass a scoped key to
|
|
88
|
+
* `clearRateLimit` or implement a tenant-namespaced strategy.
|
|
89
|
+
*/
|
|
90
|
+
export declare const clearAllRateLimits: () => Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export type AvatarStatus = 'avatar' | 'fallback';
|
|
3
|
+
interface AvatarContextType {
|
|
4
|
+
avatarStatuses: Record<string, AvatarStatus>;
|
|
5
|
+
setAvatarStatus: (key: string, status: AvatarStatus) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const AvatarProvider: ({ children }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}) => import("react").JSX.Element;
|
|
10
|
+
export declare function useAvatarContext(): AvatarContextType;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export default function Middleware({ children, denied, }: {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* Rendered instead of the page when middleware denies IN PLACE
|
|
6
|
+
* (`{ success: false, status }`) — the URL is kept. Pass a node, or a
|
|
7
|
+
* function to vary by status. Defaults to a minimal built-in view.
|
|
8
|
+
*/
|
|
9
|
+
denied?: ReactNode | ((status: number) => ReactNode);
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useRouter(): (path: string) => Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ReactNode, Dispatch, SetStateAction } from 'react';
|
|
2
|
+
import { getActiveLanguage, getDefaultLocale, getRegisteredLocales } from '../localesRegistry.js';
|
|
3
|
+
export type TranslationRecord = Record<string, unknown>;
|
|
4
|
+
export declare function TranslationProvider({ children }: {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function useTranslation(): TranslationRecord;
|
|
8
|
+
export declare function useUpdateLanguage(): Dispatch<SetStateAction<string>>;
|
|
9
|
+
export interface TranslateParam {
|
|
10
|
+
key: string;
|
|
11
|
+
value: string | number | boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function translate({ translationList, key, params }: {
|
|
14
|
+
translationList: TranslationRecord;
|
|
15
|
+
key: unknown;
|
|
16
|
+
params?: TranslateParam[];
|
|
17
|
+
}): string;
|
|
18
|
+
export declare const useTranslator: () => ({ key, params }: {
|
|
19
|
+
key: string;
|
|
20
|
+
params?: TranslateParam[];
|
|
21
|
+
}) => string;
|
|
22
|
+
export { getActiveLanguage, getDefaultLocale, getRegisteredLocales };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type TranslateParam } from './TranslationProvider.js';
|
|
2
|
+
interface NotifyInput {
|
|
3
|
+
key: string;
|
|
4
|
+
params?: TranslateParam[];
|
|
5
|
+
}
|
|
6
|
+
declare const notify: {
|
|
7
|
+
success: (input: NotifyInput) => void;
|
|
8
|
+
error: (input: NotifyInput) => void;
|
|
9
|
+
info: (input: NotifyInput) => void;
|
|
10
|
+
warning: (input: NotifyInput) => void;
|
|
11
|
+
};
|
|
12
|
+
export default notify;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BaseSessionLayout } from '../sessionTypes.js';
|
|
2
|
+
import { type ClientHookStopSignal } from '../clientHookBus.js';
|
|
3
|
+
export interface SessionContextValue<TSession extends BaseSessionLayout = BaseSessionLayout> {
|
|
4
|
+
session: TSession | null;
|
|
5
|
+
sessionLoaded: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const SessionContext: import("react").Context<SessionContextValue<BaseSessionLayout>>;
|
|
8
|
+
export declare function useSession<TSession extends BaseSessionLayout = BaseSessionLayout>(): SessionContextValue<TSession>;
|
|
9
|
+
export declare const setLatestSession: (session: BaseSessionLayout | null) => void;
|
|
10
|
+
export declare const getCurrentSession: <TSession extends BaseSessionLayout = BaseSessionLayout>() => TSession | null;
|
|
11
|
+
/**
|
|
12
|
+
* Vetoable entry point for committing a null → session transition (i.e. a
|
|
13
|
+
* fresh login or a page-load session restore). Dispatches the `preLogin`
|
|
14
|
+
* client hook to every registered handler in parallel; if any returns a
|
|
15
|
+
* stop signal, the transition is aborted and `{ committed: false, signal }`
|
|
16
|
+
* is returned. Otherwise, `setLatestSession(session)` runs synchronously
|
|
17
|
+
* (which fires `postLogin`) and `{ committed: true }` comes back.
|
|
18
|
+
*
|
|
19
|
+
* Consumer's `SessionProvider` should `await` this for non-null session
|
|
20
|
+
* commits and roll local React state back to `null` when a veto comes
|
|
21
|
+
* through, so the UI doesn't render a half-logged-in state.
|
|
22
|
+
*/
|
|
23
|
+
export type ProposeLoginResult = {
|
|
24
|
+
stopped: false;
|
|
25
|
+
committed: true;
|
|
26
|
+
} | {
|
|
27
|
+
stopped: true;
|
|
28
|
+
signal: ClientHookStopSignal;
|
|
29
|
+
committed: false;
|
|
30
|
+
};
|
|
31
|
+
export declare const proposeLogin: (session: BaseSessionLayout) => Promise<ProposeLoginResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const DEFAULT_REDACTED_LOG_KEYS: readonly string[];
|
|
2
|
+
export declare const registerRedactedLogKeys: (keys: readonly string[]) => void;
|
|
3
|
+
export declare const getRedactedLogKeys: () => readonly string[];
|
|
4
|
+
export declare const isRedactedLogKey: (key: string) => boolean;
|
|
5
|
+
export declare const resetRedactedLogKeysForTests: () => void;
|
|
6
|
+
/** Placeholder substituted for a redacted value. */
|
|
7
|
+
export declare const REDACTED_PLACEHOLDER = "[redacted]";
|
|
8
|
+
export declare const DEPTH_TRUNCATED_PLACEHOLDER = "[truncated: max depth]";
|
|
9
|
+
/**
|
|
10
|
+
* Recursively deep-copy `value`, replacing any property whose key matches the
|
|
11
|
+
* registered redacted-keys set (case-insensitive) with {@link REDACTED_PLACEHOLDER}
|
|
12
|
+
* (SYNC-17 defense-in-depth). Use it at raw log / `captureException` context
|
|
13
|
+
* sites so a token/password nested anywhere in a payload never lands verbatim in
|
|
14
|
+
* a logger sink or error-tracker breadcrumb.
|
|
15
|
+
*
|
|
16
|
+
* - Primitives pass through unchanged.
|
|
17
|
+
* - Arrays are mapped element-wise (array indices are never "keys", so an
|
|
18
|
+
* element is only redacted by its own nested object keys).
|
|
19
|
+
* - Cycles / over-deep graphs are cut at {@link MAX_SANITIZE_DEPTH} with
|
|
20
|
+
* {@link DEPTH_TRUNCATED_PLACEHOLDER}; the input is never mutated.
|
|
21
|
+
*/
|
|
22
|
+
export declare const sanitizeForLog: (value: unknown, depth?: number, _seen?: WeakSet<object>) => unknown;
|
package/dist/redis.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
import type { Redis as RedisClient } from 'ioredis';
|
|
3
|
+
export declare const resetDefaultRedisClient: () => void;
|
|
4
|
+
export declare const rebuildDefaultRedisClient: () => RedisClient;
|
|
5
|
+
export interface RedisConnectionOptions {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
username?: string;
|
|
9
|
+
password?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const getRedisConnectionOptions: () => RedisConnectionOptions;
|
|
12
|
+
export declare const registerStrayPrefixCommand: (...commands: string[]) => void;
|
|
13
|
+
declare const redisProxy: Redis;
|
|
14
|
+
export declare const redis: Redis;
|
|
15
|
+
export default redisProxy;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type RedisKeyFormatter = (namespace: string, suffix: string) => string;
|
|
2
|
+
export declare const defaultRedisKeyFormatter: RedisKeyFormatter;
|
|
3
|
+
export declare const registerRedisKeyFormatter: (fn: RedisKeyFormatter) => void;
|
|
4
|
+
export declare const getRedisKeyFormatter: () => RedisKeyFormatter;
|
|
5
|
+
export declare const resetRedisKeyFormatterForTests: () => void;
|
|
6
|
+
export declare const formatKey: (namespace: string, suffix?: string) => string;
|
|
7
|
+
export declare const applyStrayKeyPrefix: (key: string) => string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Stable sentinel returned when no usable client address could be resolved.
|
|
4
|
+
* Kept as a single exported constant so every rate-limit key-site funnels
|
|
5
|
+
* missing addresses into one deterministic bucket instead of ad-hoc literals.
|
|
6
|
+
*/
|
|
7
|
+
export declare const UNKNOWN_CLIENT_IP = "unknown";
|
|
8
|
+
export interface ResolveClientIpParams {
|
|
9
|
+
/**
|
|
10
|
+
* The raw transport peer address — `socket.handshake.address` (Socket.io)
|
|
11
|
+
* or `req.socket.remoteAddress` (raw HTTP). May be `undefined`/`null` when
|
|
12
|
+
* the connection has already been torn down.
|
|
13
|
+
*/
|
|
14
|
+
rawAddress: string | null | undefined;
|
|
15
|
+
/** Request / handshake headers. Only consulted when `trustProxy` is true. */
|
|
16
|
+
headers: IncomingHttpHeaders;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a known reverse proxy sits in front of this server. DEFAULT false.
|
|
19
|
+
* When false the raw peer address is returned verbatim (only IPv4-mapped
|
|
20
|
+
* IPv6 canonicalization is applied), preserving historical behaviour. Only
|
|
21
|
+
* enable when a trusted proxy populates `X-Forwarded-For` / `X-Real-IP`,
|
|
22
|
+
* otherwise a client can spoof its own IP via those headers.
|
|
23
|
+
*/
|
|
24
|
+
trustProxy?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Number of proxy hops to skip from the RIGHT of `X-Forwarded-For` when
|
|
27
|
+
* `trustProxy` is on (CORE-O3). The rightmost entries are appended by your own
|
|
28
|
+
* trusted proxies; the resolved client IP is the entry that many hops in from
|
|
29
|
+
* the end. The leftmost hop is client-controlled and is never trusted. DEFAULT
|
|
30
|
+
* 1 (the immediate upstream proxy). Clamped to the list length so an over-large
|
|
31
|
+
* count falls back to the leftmost trusted hop rather than the spoofable entry.
|
|
32
|
+
*/
|
|
33
|
+
trustedProxyHopCount?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether a resolved client IP is a loopback / unresolved address — used by the
|
|
37
|
+
* api/sync transports to honour `rateLimiting.skipLoopbackInDev` (api F5/F11).
|
|
38
|
+
* Matches IPv4 loopback (`127.0.0.0/8`), IPv6 loopback (`::1`), the IPv4-mapped
|
|
39
|
+
* form (`::ffff:127.0.0.1`, already canonicalized away by {@link resolveClientIp}),
|
|
40
|
+
* and the {@link UNKNOWN_CLIENT_IP} sentinel. Pass an already-resolved IP.
|
|
41
|
+
*/
|
|
42
|
+
export declare const isLoopbackIp: (ip: string) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Derive the deterministic, non-reversible bucket component for a session
|
|
45
|
+
* token. The same token always yields the same value (bucket identity is
|
|
46
|
+
* preserved) while the raw token never appears in the returned string.
|
|
47
|
+
* Used by api AND sync rate-limit key builders — lives in core so both
|
|
48
|
+
* packages can share one implementation without a cross-package dependency.
|
|
49
|
+
*/
|
|
50
|
+
export declare const deriveTokenBucketId: (token: string) => string;
|
|
51
|
+
export declare const resolveClientIp: ({ rawAddress, headers, trustProxy, trustedProxyHopCount }: ResolveClientIpParams) => string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ErrorParam {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string | number | boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ErrorResponseInput {
|
|
6
|
+
status?: unknown;
|
|
7
|
+
errorCode?: unknown;
|
|
8
|
+
errorParams?: unknown;
|
|
9
|
+
httpStatus?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface NormalizedErrorResponse {
|
|
12
|
+
status: 'error';
|
|
13
|
+
message: string;
|
|
14
|
+
errorCode: string;
|
|
15
|
+
errorParams?: ErrorParam[];
|
|
16
|
+
httpStatus?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const INVALID_ERROR_RESPONSE_CODE = "error.invalidResponse";
|
|
19
|
+
export declare const isErrorParamArray: (value: unknown) => value is ErrorParam[];
|
|
20
|
+
export declare const normalizeErrorResponseCore: ({ response, fallbackHttpStatus, fallbackErrorCode, resolveMessage, }: {
|
|
21
|
+
response: ErrorResponseInput;
|
|
22
|
+
fallbackHttpStatus?: number;
|
|
23
|
+
fallbackErrorCode?: string;
|
|
24
|
+
resolveMessage?: (args: {
|
|
25
|
+
errorCode: string;
|
|
26
|
+
errorParams?: ErrorParam[];
|
|
27
|
+
}) => string;
|
|
28
|
+
}) => NormalizedErrorResponse;
|
|
29
|
+
export declare const defaultHttpStatusForResponse: ({ status, explicitHttpStatus, fallbackErrorStatus, }: {
|
|
30
|
+
status: 'success' | 'error';
|
|
31
|
+
explicitHttpStatus?: number;
|
|
32
|
+
fallbackErrorStatus?: number;
|
|
33
|
+
}) => number;
|