@eminuckan/spine 0.2.0 → 0.2.1
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 +2 -2
- package/dist/api-client/api-config.server.d.ts +40 -0
- package/dist/api-client/api-config.server.d.ts.map +1 -0
- package/dist/api-client/error-codes.d.ts +57 -0
- package/dist/api-client/error-codes.d.ts.map +1 -0
- package/dist/api-client/error-handler.d.ts +52 -0
- package/dist/api-client/error-handler.d.ts.map +1 -0
- package/dist/api-client/fetch-client.server.d.ts +29 -0
- package/dist/api-client/fetch-client.server.d.ts.map +1 -0
- package/dist/api-client/index.d.ts +11 -3
- package/dist/api-client/index.d.ts.map +1 -0
- package/dist/api-client/index.js +3 -2
- package/dist/api-client/retry-handler.d.ts +29 -0
- package/dist/api-client/retry-handler.d.ts.map +1 -0
- package/dist/api-client/server.d.ts +9 -4
- package/dist/api-client/server.d.ts.map +1 -0
- package/dist/api-client/server.js +4 -3
- package/dist/api-client/types.d.ts +121 -0
- package/dist/api-client/types.d.ts.map +1 -0
- package/dist/{api-client-Ftxqm3Ep.js → api-client-DsrxK05Z.js} +1 -1
- package/dist/{api-client-Ftxqm3Ep.js.map → api-client-DsrxK05Z.js.map} +1 -1
- package/dist/auth/auth.server.d.ts +124 -0
- package/dist/auth/auth.server.d.ts.map +1 -0
- package/dist/auth/index.d.ts +8 -2
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/redis-session-storage.server.d.ts +21 -0
- package/dist/auth/redis-session-storage.server.d.ts.map +1 -0
- package/dist/auth/route-protection.server.d.ts +107 -0
- package/dist/auth/route-protection.server.d.ts.map +1 -0
- package/dist/auth/server.d.ts +13 -3
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +3 -3
- package/dist/auth/token-refresh.server.d.ts +17 -0
- package/dist/auth/token-refresh.server.d.ts.map +1 -0
- package/dist/auth/types.d.ts +160 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth.server-BPiCtlJE.js +5 -0
- package/dist/{auth.server-C979TR6W.js → auth.server-Cx2h2qu3.js} +3 -3
- package/dist/{auth.server-C979TR6W.js.map → auth.server-Cx2h2qu3.js.map} +1 -1
- package/dist/http/response.d.ts +12 -0
- package/dist/http/response.d.ts.map +1 -0
- package/dist/identity/identity-context-provider.d.ts +19 -0
- package/dist/identity/identity-context-provider.d.ts.map +1 -0
- package/dist/identity/identity-context.server.d.ts +73 -0
- package/dist/identity/identity-context.server.d.ts.map +1 -0
- package/dist/identity/identity-store.d.ts +55 -0
- package/dist/identity/identity-store.d.ts.map +1 -0
- package/dist/identity/index.d.ts +9 -4
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/server.d.ts +6 -4
- package/dist/identity/server.d.ts.map +1 -0
- package/dist/identity/server.js +2 -1
- package/dist/identity/types.d.ts +91 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -5
- package/dist/logging/config.d.ts +7 -0
- package/dist/logging/config.d.ts.map +1 -0
- package/dist/logging/index.d.ts +9 -3
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/logger.d.ts +22 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/types.d.ts +27 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/permissions/components.d.ts +48 -0
- package/dist/permissions/components.d.ts.map +1 -0
- package/dist/permissions/constants.d.ts +9 -0
- package/dist/permissions/constants.d.ts.map +1 -0
- package/dist/permissions/hooks.d.ts +47 -0
- package/dist/permissions/hooks.d.ts.map +1 -0
- package/dist/permissions/index.d.ts +28 -2
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/permission-initializer.d.ts +18 -0
- package/dist/permissions/permission-initializer.d.ts.map +1 -0
- package/dist/permissions/permission-service.d.ts +53 -0
- package/dist/permissions/permission-service.d.ts.map +1 -0
- package/dist/permissions/permission-store.d.ts +42 -0
- package/dist/permissions/permission-store.d.ts.map +1 -0
- package/dist/permissions/route-protection.server.d.ts +33 -0
- package/dist/permissions/route-protection.server.d.ts.map +1 -0
- package/dist/permissions/types.d.ts +44 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/query-client/index.d.ts +8 -2
- package/dist/query-client/index.d.ts.map +1 -0
- package/dist/query-client/index.js +1 -1
- package/dist/query-client/query-config.d.ts +55 -0
- package/dist/query-client/query-config.d.ts.map +1 -0
- package/dist/query-client/types.d.ts +115 -0
- package/dist/query-client/types.d.ts.map +1 -0
- package/dist/{query-client-DRPQIKHX.js → query-client-Bpe08pk9.js} +1 -1
- package/dist/query-client-Bpe08pk9.js.map +1 -0
- package/dist/react-router/index.d.ts +9 -12
- package/dist/react-router/index.d.ts.map +1 -0
- package/dist/react-router/index.js +6 -5
- package/dist/react-router/server.d.ts +9 -18
- package/dist/react-router/server.d.ts.map +1 -0
- package/dist/react-router/server.js +13 -12
- package/dist/{redis-session-storage.server-DhufyPjd.js → redis-session-storage.server-BR_0y-QO.js} +2 -2
- package/dist/{redis-session-storage.server-DhufyPjd.js.map → redis-session-storage.server-BR_0y-QO.js.map} +1 -1
- package/dist/{server-CiSbg-9C.js → server-BHfKHKF_.js} +5 -5
- package/dist/{server-CiSbg-9C.js.map → server-BHfKHKF_.js.map} +1 -1
- package/dist/{server-C-DiVP9e.js → server-BxvRxjCK.js} +2 -2
- package/dist/{server-C-DiVP9e.js.map → server-BxvRxjCK.js.map} +1 -1
- package/dist/{server-iWm3oeCM.js → server-C3L8J7iE.js} +3 -2
- package/dist/server-C3L8J7iE.js.map +1 -0
- package/dist/{server-CNp6gjFE.js → server-CouYoZiw.js} +1 -1
- package/dist/{server-CNp6gjFE.js.map → server-CouYoZiw.js.map} +1 -1
- package/dist/server-DZmcd2Lr.js +422 -0
- package/dist/server-DZmcd2Lr.js.map +1 -0
- package/dist/server.d.ts +14 -18
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +13 -12
- package/dist/signalr/index.d.ts +8 -4
- package/dist/signalr/index.d.ts.map +1 -0
- package/dist/signalr/index.js +1 -1
- package/dist/signalr/signalr-client.d.ts +98 -0
- package/dist/signalr/signalr-client.d.ts.map +1 -0
- package/dist/signalr/types.d.ts +42 -0
- package/dist/signalr/types.d.ts.map +1 -0
- package/dist/{signalr-BJGcXc0m.js → signalr-SJ-1sIxw.js} +1 -1
- package/dist/{signalr-BJGcXc0m.js.map → signalr-SJ-1sIxw.js.map} +1 -1
- package/dist/tenant/index.d.ts +9 -3
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/tenant/server.d.ts +7 -3
- package/dist/tenant/server.d.ts.map +1 -0
- package/dist/tenant/server.js +2 -2
- package/dist/tenant/tenant-context.d.ts +45 -0
- package/dist/tenant/tenant-context.d.ts.map +1 -0
- package/dist/tenant/tenant-cookie.server.d.ts +35 -0
- package/dist/tenant/tenant-cookie.server.d.ts.map +1 -0
- package/dist/tenant/tenant-store.d.ts +58 -0
- package/dist/tenant/tenant-store.d.ts.map +1 -0
- package/dist/tenant/tenant.server.d.ts +49 -0
- package/dist/tenant/tenant.server.d.ts.map +1 -0
- package/dist/tenant/types.d.ts +51 -0
- package/dist/tenant/types.d.ts.map +1 -0
- package/dist/types-BnKaWdfe.js +8 -0
- package/dist/types-BnKaWdfe.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +2 -3
- package/dist/auth.server-DU-x2LPA.js +0 -5
- package/dist/index-BIJOC8Fb.d.ts +0 -272
- package/dist/index-BIJOC8Fb.d.ts.map +0 -1
- package/dist/index-Bt9XE6gj.d.ts +0 -104
- package/dist/index-Bt9XE6gj.d.ts.map +0 -1
- package/dist/index-C2hGpzGr.d.ts +0 -74
- package/dist/index-C2hGpzGr.d.ts.map +0 -1
- package/dist/index-CfQsb3l_.d.ts +0 -170
- package/dist/index-CfQsb3l_.d.ts.map +0 -1
- package/dist/index-D6RK_5vm.d.ts +0 -199
- package/dist/index-D6RK_5vm.d.ts.map +0 -1
- package/dist/index-DrttYPAj.d.ts +0 -30
- package/dist/index-DrttYPAj.d.ts.map +0 -1
- package/dist/index-geY2ynnN.d.ts +0 -141
- package/dist/index-geY2ynnN.d.ts.map +0 -1
- package/dist/index-kr8Wk7v5.d.ts +0 -1
- package/dist/query-client-DRPQIKHX.js.map +0 -1
- package/dist/server-BoeUiXSh.d.ts +0 -40
- package/dist/server-BoeUiXSh.d.ts.map +0 -1
- package/dist/server-CLbksnvg.d.ts +0 -144
- package/dist/server-CLbksnvg.d.ts.map +0 -1
- package/dist/server-CXGaRaiS.d.ts +0 -256
- package/dist/server-CXGaRaiS.d.ts.map +0 -1
- package/dist/server-DLgH3FRR.js +0 -312
- package/dist/server-DLgH3FRR.js.map +0 -1
- package/dist/server-DazqdM63.d.ts +0 -93
- package/dist/server-DazqdM63.d.ts.map +0 -1
- package/dist/server-iWm3oeCM.js.map +0 -1
- package/dist/server-zEKMDpP7.d.ts +0 -80
- package/dist/server-zEKMDpP7.d.ts.map +0 -1
- package/dist/types-BTjFzIhv.d.ts +0 -31
- package/dist/types-BTjFzIhv.d.ts.map +0 -1
- package/dist/types-DoIfDhl6.d.ts +0 -55
- package/dist/types-DoIfDhl6.d.ts.map +0 -1
- package/dist/types-DufLRY7A.d.ts +0 -164
- package/dist/types-DufLRY7A.d.ts.map +0 -1
- package/dist/types-uDrDMkCq.d.ts +0 -93
- package/dist/types-uDrDMkCq.d.ts.map +0 -1
- /package/dist/{src-D7mh9MKA.js → src-CJ2RLi94.js} +0 -0
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Spine aims to be:
|
|
|
46
46
|
| `@eminuckan/spine/identity` | Identity store/provider/types |
|
|
47
47
|
| `@eminuckan/spine/identity/server` | Identity context cache and server orchestration |
|
|
48
48
|
| `@eminuckan/spine/api-client` | Client-side API types and errors |
|
|
49
|
-
| `@eminuckan/spine/api-client/server` | Server-side API config and
|
|
49
|
+
| `@eminuckan/spine/api-client/server` | Server-side API config and fetch helpers |
|
|
50
50
|
| `@eminuckan/spine/permissions` | Client-side permission primitives |
|
|
51
51
|
| `@eminuckan/spine/logging` | Logging primitives |
|
|
52
52
|
| `@eminuckan/spine/query-client` | TanStack Query helpers |
|
|
@@ -60,7 +60,7 @@ Spine aims to be:
|
|
|
60
60
|
- Server-side permission route protection configuration
|
|
61
61
|
- Multi-tenant client state and server helpers
|
|
62
62
|
- Identity context cache, fetch orchestration, and client store/provider
|
|
63
|
-
- API client factory and
|
|
63
|
+
- API client factory and fetch middleware setup
|
|
64
64
|
- TanStack Query client defaults and cache presets
|
|
65
65
|
- SignalR client helpers
|
|
66
66
|
- Logging primitives
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Configuration Server Module
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration for all API clients,
|
|
5
|
+
* automatically handling authentication tokens and tenant context.
|
|
6
|
+
*/
|
|
7
|
+
import type { APIConfig, CreateAPIConfigOptions, GetAccessTokenFn, GetCurrentTenantFn, APILogger } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* API Config Factory Options
|
|
10
|
+
*/
|
|
11
|
+
export interface APIConfigFactoryOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for API requests
|
|
14
|
+
*/
|
|
15
|
+
baseURL?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Default headers applied to all requests
|
|
18
|
+
*/
|
|
19
|
+
defaultHeaders?: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* User agent string
|
|
22
|
+
*/
|
|
23
|
+
userAgent?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Default timeout in milliseconds
|
|
26
|
+
*/
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create API Config Factory
|
|
31
|
+
*
|
|
32
|
+
* Creates a factory function that generates API configurations
|
|
33
|
+
* with the provided auth and tenant resolvers.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createAPIConfigFactory(getAccessToken: GetAccessTokenFn, getCurrentTenant: GetCurrentTenantFn, logger?: APILogger, factoryOptions?: APIConfigFactoryOptions): {
|
|
36
|
+
createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>;
|
|
37
|
+
getAPIBaseURL: () => string;
|
|
38
|
+
};
|
|
39
|
+
export type APIConfigFactory = ReturnType<typeof createAPIConfigFactory>;
|
|
40
|
+
//# sourceMappingURL=api-config.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-config.server.d.ts","sourceRoot":"","sources":["../../src/api-client/api-config.server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAiBlH;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,gBAAgB,EAChC,gBAAgB,EAAE,kBAAkB,EACpC,MAAM,CAAC,EAAE,SAAS,EAClB,cAAc,GAAE,uBAA4B;+BA+DjC,OAAO,YACP,sBAAsB,KAC9B,OAAO,CAAC,SAAS,CAAC;yBAgEK,MAAM;EAQjC;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Codes
|
|
3
|
+
*
|
|
4
|
+
* Standard error codes from backend.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ErrorCodes: {
|
|
7
|
+
readonly Validation: {
|
|
8
|
+
readonly Failed: "ValidationFailed";
|
|
9
|
+
};
|
|
10
|
+
readonly Identity: {
|
|
11
|
+
readonly InvalidCredentials: "InvalidCredentials";
|
|
12
|
+
readonly UserInactive: "UserInactive";
|
|
13
|
+
readonly TenantContextMissing: "TenantContextMissing";
|
|
14
|
+
readonly InvalidCurrentPassword: "InvalidCurrentPassword";
|
|
15
|
+
readonly OwnerCannotDisableMembership: "OwnerCannotDisableMembership";
|
|
16
|
+
readonly CurrentUserMissing: "CurrentUserMissing";
|
|
17
|
+
readonly UserNotFound: "UserNotFound";
|
|
18
|
+
readonly EmailAlreadyExists: "EmailAlreadyExists";
|
|
19
|
+
};
|
|
20
|
+
readonly Accounting: {
|
|
21
|
+
readonly OpeningBalanceLockedReconciliation: "OpeningBalanceLockedReconciliation";
|
|
22
|
+
readonly OpeningBalanceLockedJournal: "OpeningBalanceLockedJournal";
|
|
23
|
+
readonly SystemLocked: "SystemLocked";
|
|
24
|
+
readonly AccountsDuplicate: "AccountsDuplicate";
|
|
25
|
+
readonly AccountNotFound: "AccountNotFound";
|
|
26
|
+
};
|
|
27
|
+
readonly Subscription: {
|
|
28
|
+
readonly SubscriptionRequired: "SubscriptionRequired";
|
|
29
|
+
readonly SubscriptionExpired: "SubscriptionExpired";
|
|
30
|
+
readonly FeatureNotAvailable: "FeatureNotAvailable";
|
|
31
|
+
};
|
|
32
|
+
readonly PropertyManagement: {
|
|
33
|
+
readonly PropertyNotFound: "PropertyNotFound";
|
|
34
|
+
readonly UnitNotFound: "UnitNotFound";
|
|
35
|
+
readonly DuplicateProperty: "DuplicateProperty";
|
|
36
|
+
readonly DuplicateUnit: "DuplicateUnit";
|
|
37
|
+
};
|
|
38
|
+
readonly Leasing: {
|
|
39
|
+
readonly LeaseNotFound: "LeaseNotFound";
|
|
40
|
+
readonly LeaseConflict: "LeaseConflict";
|
|
41
|
+
readonly TenantNotFound: "TenantNotFound";
|
|
42
|
+
readonly LateFeeValidationError: "LateFeeValidationError";
|
|
43
|
+
};
|
|
44
|
+
readonly NotFound: "NotFound";
|
|
45
|
+
readonly Unauthorized: "Unauthorized";
|
|
46
|
+
readonly Forbidden: "Forbidden";
|
|
47
|
+
readonly InternalError: "InternalError";
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Check if error code is from PropertyManagement module
|
|
51
|
+
*/
|
|
52
|
+
export declare function isPropertyManagementError(code: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Check if error code is from Leasing module
|
|
55
|
+
*/
|
|
56
|
+
export declare function isLeasingError(code: string): boolean;
|
|
57
|
+
//# sourceMappingURL=error-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../../src/api-client/error-codes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDb,CAAC;AAEX;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler
|
|
3
|
+
*
|
|
4
|
+
* Centralized error handling with ProblemDetails parsing.
|
|
5
|
+
*/
|
|
6
|
+
import type { ProblemDetails, ValidationError } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* API Error Class
|
|
9
|
+
*/
|
|
10
|
+
export declare class ApiError extends Error {
|
|
11
|
+
problemDetails: ProblemDetails;
|
|
12
|
+
response: Response;
|
|
13
|
+
constructor(problemDetails: ProblemDetails, response: Response);
|
|
14
|
+
get code(): string;
|
|
15
|
+
get status(): number;
|
|
16
|
+
get isValidationError(): boolean;
|
|
17
|
+
get validationErrors(): ValidationError[];
|
|
18
|
+
get traceId(): string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse API error response
|
|
22
|
+
*/
|
|
23
|
+
export declare function handleApiError(response: Response): Promise<never>;
|
|
24
|
+
/**
|
|
25
|
+
* Error Handler Configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface ErrorHandlerConfig {
|
|
28
|
+
handlers?: Partial<Record<string, (error: ApiError) => void>>;
|
|
29
|
+
showToast?: boolean;
|
|
30
|
+
toastMessages?: Partial<Record<string, string>>;
|
|
31
|
+
onError?: (error: ApiError) => void;
|
|
32
|
+
toast?: (message: string, type: 'error' | 'success' | 'info') => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Error Handler Class
|
|
36
|
+
*/
|
|
37
|
+
export declare class ErrorHandler {
|
|
38
|
+
private config;
|
|
39
|
+
constructor(config?: ErrorHandlerConfig);
|
|
40
|
+
handle(error: ApiError): void;
|
|
41
|
+
private showToast;
|
|
42
|
+
private showDefaultToast;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create global error handler
|
|
46
|
+
*/
|
|
47
|
+
export declare function createErrorHandler(config?: ErrorHandlerConfig): ErrorHandler;
|
|
48
|
+
/**
|
|
49
|
+
* Map validation errors to form fields
|
|
50
|
+
*/
|
|
51
|
+
export declare function mapValidationErrors(errors: ValidationError[]): Record<string, string>;
|
|
52
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/api-client/error-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/D;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAExB,cAAc,EAAE,cAAc;IAC9B,QAAQ,EAAE,QAAQ;gBADlB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ;IAM3B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED,IAAI,gBAAgB,IAAI,eAAe,EAAE,CAExC;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAuBvE;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAC;CACvE;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAqB;gBAEvB,MAAM,GAAE,kBAAuB;IAO3C,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAuB7B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,gBAAgB;CAOzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,YAAY,CAE5E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch-native API client setup for generated OpenAPI clients.
|
|
3
|
+
*
|
|
4
|
+
* This module preserves the shared request behaviors we need around fetch:
|
|
5
|
+
* - request ID enrichment
|
|
6
|
+
* - serialized token refresh on 401
|
|
7
|
+
* - retry with exponential backoff
|
|
8
|
+
* - ProblemDetails-aware error shaping
|
|
9
|
+
*
|
|
10
|
+
* It gives downstream apps a single fetch-native entry point for generated clients.
|
|
11
|
+
*/
|
|
12
|
+
import type { APIConfig, APILogger, CreateAPIConfigOptions, EnhancedAPIClient, FetchMiddleware, RetryConfig, TokenRefreshFn } from './types';
|
|
13
|
+
type GeneratedFetchClient = object;
|
|
14
|
+
export interface FetchSetupOptions {
|
|
15
|
+
logger?: APILogger;
|
|
16
|
+
attemptTokenRefresh?: TokenRefreshFn;
|
|
17
|
+
retryConfig?: RetryConfig;
|
|
18
|
+
addRequestId?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function createFetchMiddleware(request: Request, options?: FetchSetupOptions): FetchMiddleware[];
|
|
21
|
+
export interface CreateApiClientOptions extends FetchSetupOptions, CreateAPIConfigOptions {
|
|
22
|
+
credentials?: RequestCredentials;
|
|
23
|
+
}
|
|
24
|
+
export declare function createApiClient<T extends GeneratedFetchClient>(request: Request, ClientClass: new (configuration?: unknown) => T, createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>, ConfigurationClass: new (params?: Record<string, unknown>) => unknown, options?: CreateApiClientOptions): Promise<{
|
|
25
|
+
client: EnhancedAPIClient<T>;
|
|
26
|
+
tenantId: string;
|
|
27
|
+
}>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=fetch-client.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-client.server.d.ts","sourceRoot":"","sources":["../../src/api-client/fetch-client.server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAEV,SAAS,EAET,SAAS,EACT,sBAAsB,EACtB,iBAAiB,EAEjB,eAAe,EAGf,WAAW,EACX,cAAc,EAEf,MAAM,SAAS,CAAC;AASjB,KAAK,oBAAoB,GAAG,MAAM,CAAC;AA4PnC,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mBAAmB,CAAC,EAAE,cAAc,CAAC;IACrC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,iBAAsB,GAC9B,eAAe,EAAE,CAuHnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB,EAAE,sBAAsB;IACvF,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AA8ED,wBAAsB,eAAe,CAAC,CAAC,SAAS,oBAAoB,EAClE,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,aAAa,CAAC,EAAE,OAAO,KAAK,CAAC,EAC/C,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,SAAS,CAAC,EAC3F,kBAAkB,EAAE,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,EACrE,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC7D"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* API Client Module - Client Exports
|
|
3
|
+
*
|
|
4
|
+
* Client-side exports for API client module.
|
|
5
|
+
*/
|
|
6
|
+
export type { APIConfig, APIRequestConfig, APIResponse, APIClientError, CreateAPIConfigOptions, ProblemDetails, ValidationError, RetryConfig, APILogger, GetAccessTokenFn, GetCurrentTenantFn, TokenRefreshResult, TokenRefreshFn, FetchAPI, FetchMiddleware, FetchRequestContext, FetchResponseContext, FetchErrorContext, EnhancedAPIClient, } from './types';
|
|
7
|
+
export { isApiClientError } from './types';
|
|
8
|
+
export { ErrorCodes } from './error-codes';
|
|
9
|
+
export { ApiError, ErrorHandler, handleApiError } from './error-handler';
|
|
10
|
+
export { calculateBackoffDelay, isRetryableError, withRetry, fetchWithTimeout, createRetryFetch } from './retry-handler';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api-client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC"}
|
package/dist/api-client/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../logging-_h2mi3L9.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isApiClientError } from "../types-BnKaWdfe.js";
|
|
3
|
+
import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-DsrxK05Z.js";
|
|
3
4
|
|
|
4
|
-
export { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry };
|
|
5
|
+
export { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isApiClientError, isRetryableError, withRetry };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Handler
|
|
3
|
+
*
|
|
4
|
+
* Retry logic for API requests.
|
|
5
|
+
*/
|
|
6
|
+
import type { RetryConfig } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Calculate backoff delay
|
|
9
|
+
*/
|
|
10
|
+
export declare function calculateBackoffDelay(attempt: number, baseDelay: number, maxDelay: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* Check if error is retryable
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Execute function with retry logic
|
|
17
|
+
*/
|
|
18
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Fetch with timeout
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchWithTimeout(input: RequestInfo | URL, init?: RequestInit & {
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}): Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* Create fetch function with retry
|
|
27
|
+
*/
|
|
28
|
+
export declare function createRetryFetch(config?: RetryConfig): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
29
|
+
//# sourceMappingURL=retry-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-handler.d.ts","sourceRoot":"","sources":["../../src/api-client/retry-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAc3C;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASxD;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,QAAQ,CAAC,CAenB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,IACrC,OAAO,WAAW,GAAG,GAAG,EAAE,OAAO,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAG/E"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* API Client Module - Server Exports
|
|
3
|
+
*
|
|
4
|
+
* Server-side exports for API client module.
|
|
5
|
+
*/
|
|
6
|
+
export * from './index';
|
|
7
|
+
export { createAPIConfigFactory, type APIConfigFactory, type APIConfigFactoryOptions, } from './api-config.server';
|
|
8
|
+
export { createFetchMiddleware, createApiClient, type FetchSetupOptions, type CreateApiClientOptions, } from './fetch-client.server';
|
|
9
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api-client/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../logging-_h2mi3L9.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { isApiClientError } from "../types-BnKaWdfe.js";
|
|
3
|
+
import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-DsrxK05Z.js";
|
|
4
|
+
import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-DZmcd2Lr.js";
|
|
4
5
|
|
|
5
|
-
export { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createAPIConfigFactory,
|
|
6
|
+
export { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createAPIConfigFactory, createApiClient, createFetchMiddleware, createRetryFetch, fetchWithTimeout, handleApiError, isApiClientError, isRetryableError, withRetry };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client Types
|
|
3
|
+
*/
|
|
4
|
+
export type { LoggerConfig as Logger } from '../logging/types';
|
|
5
|
+
export interface APIConfig {
|
|
6
|
+
basePath: string;
|
|
7
|
+
accessToken?: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
tenantId: string;
|
|
10
|
+
credentials?: RequestCredentials;
|
|
11
|
+
baseOptions?: {
|
|
12
|
+
timeout?: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface CreateAPIConfigOptions {
|
|
16
|
+
requireTenant?: boolean;
|
|
17
|
+
includeAuth?: boolean;
|
|
18
|
+
tenantId?: string;
|
|
19
|
+
customHeaders?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface ProblemDetails {
|
|
22
|
+
type: string;
|
|
23
|
+
title: string;
|
|
24
|
+
status: number;
|
|
25
|
+
detail: string;
|
|
26
|
+
traceId: string;
|
|
27
|
+
requestId?: string;
|
|
28
|
+
instance?: string;
|
|
29
|
+
meta?: {
|
|
30
|
+
errors?: ValidationError[];
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface ValidationError {
|
|
35
|
+
field: string;
|
|
36
|
+
message: string;
|
|
37
|
+
code?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface RetryConfig {
|
|
40
|
+
maxRetries?: number;
|
|
41
|
+
baseDelay?: number;
|
|
42
|
+
maxDelay?: number;
|
|
43
|
+
retryCondition?: (error: unknown) => boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface APIRequestConfig {
|
|
46
|
+
url?: string;
|
|
47
|
+
method?: string;
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
export interface APIResponse<T = unknown> {
|
|
51
|
+
data: T;
|
|
52
|
+
status: number;
|
|
53
|
+
statusText: string;
|
|
54
|
+
headers: Record<string, string>;
|
|
55
|
+
raw?: Response;
|
|
56
|
+
}
|
|
57
|
+
export interface APIClientError<T = unknown> extends Error {
|
|
58
|
+
isApiClientError: true;
|
|
59
|
+
response?: APIResponse<T>;
|
|
60
|
+
config?: APIRequestConfig;
|
|
61
|
+
problemDetails?: ProblemDetails;
|
|
62
|
+
shouldLogout?: boolean;
|
|
63
|
+
originalError?: unknown;
|
|
64
|
+
cause?: unknown;
|
|
65
|
+
}
|
|
66
|
+
export declare function isApiClientError(error: unknown): error is APIClientError;
|
|
67
|
+
export type FetchAPI = typeof fetch;
|
|
68
|
+
export interface FetchRequestContext {
|
|
69
|
+
fetch: FetchAPI;
|
|
70
|
+
url: string;
|
|
71
|
+
init: RequestInit;
|
|
72
|
+
}
|
|
73
|
+
export interface FetchResponseContext extends FetchRequestContext {
|
|
74
|
+
response: Response;
|
|
75
|
+
}
|
|
76
|
+
export interface FetchErrorContext extends FetchRequestContext {
|
|
77
|
+
error: unknown;
|
|
78
|
+
response?: Response;
|
|
79
|
+
}
|
|
80
|
+
export interface FetchMiddleware {
|
|
81
|
+
pre?: (context: FetchRequestContext) => Promise<{
|
|
82
|
+
url: string;
|
|
83
|
+
init: RequestInit;
|
|
84
|
+
} | void>;
|
|
85
|
+
post?: (context: FetchResponseContext) => Promise<Response | void>;
|
|
86
|
+
onError?: (context: FetchErrorContext) => Promise<Response | void>;
|
|
87
|
+
}
|
|
88
|
+
export type EnhancedAPIClient<T> = {
|
|
89
|
+
[K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R> ? K extends `${string}Raw` ? T[K] : (...args: A) => Promise<APIResponse<R>> : T[K];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* API Logger interface for dependency injection (simplified)
|
|
93
|
+
*/
|
|
94
|
+
export interface APILogger {
|
|
95
|
+
debug?: (message: string, context?: Record<string, unknown>) => void;
|
|
96
|
+
info?: (message: string, context?: Record<string, unknown>) => void;
|
|
97
|
+
warn?: (message: string, error?: Error, context?: Record<string, unknown>) => void;
|
|
98
|
+
error?: (message: string, error?: Error, context?: Record<string, unknown>) => void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Function type for getting access token from request
|
|
102
|
+
*/
|
|
103
|
+
export type GetAccessTokenFn = (request: Request) => Promise<string | null>;
|
|
104
|
+
/**
|
|
105
|
+
* Function type for getting current tenant from request
|
|
106
|
+
*/
|
|
107
|
+
export type GetCurrentTenantFn = (request: Request) => Promise<string | null>;
|
|
108
|
+
/**
|
|
109
|
+
* Token refresh result
|
|
110
|
+
*/
|
|
111
|
+
export interface TokenRefreshResult {
|
|
112
|
+
success: boolean;
|
|
113
|
+
newAccessToken?: string;
|
|
114
|
+
shouldLogout?: boolean;
|
|
115
|
+
error?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Function type for attempting token refresh
|
|
119
|
+
*/
|
|
120
|
+
export type TokenRefreshFn = (request: Request) => Promise<TokenRefreshResult>;
|
|
121
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api-client/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EAAE,YAAY,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/D,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,WAAW,CAAC,EAAE;QACZ,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;QAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,GAAG,CAAC,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO,CAAE,SAAQ,KAAK;IACxD,gBAAgB,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAOxE;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC;AAEpC,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC3F,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CACpE;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;KAChC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAC/D,CAAC,SAAS,GAAG,MAAM,KAAK,GACtB,CAAC,CAAC,CAAC,CAAC,GACJ,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GACzC,CAAC,CAAC,CAAC,CAAC;CACT,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACrE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACpE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACnF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACrF;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -209,4 +209,4 @@ function createRetryFetch(config) {
|
|
|
209
209
|
|
|
210
210
|
//#endregion
|
|
211
211
|
export { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry };
|
|
212
|
-
//# sourceMappingURL=api-client-
|
|
212
|
+
//# sourceMappingURL=api-client-DsrxK05Z.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client-Ftxqm3Ep.js","names":["problemDetails: ProblemDetails","response: Response","config: ErrorHandlerConfig","error: ApiError","message: string","type: 'error' | 'success' | 'info'","DEFAULT_RETRY_CONFIG: Required<RetryConfig>","error: unknown","attempt: number","baseDelay: number","maxDelay: number","fn: () => Promise<T>","config?: RetryConfig","lastError: unknown","input: RequestInfo | URL","init?: RequestInit & { timeout?: number }","init?: RequestInit"],"sources":["../src/api-client/error-codes.ts","../src/api-client/error-handler.ts","../src/api-client/retry-handler.ts"],"sourcesContent":["/**\n * Error Codes\n * \n * Standard error codes from backend.\n */\n\nexport const ErrorCodes = {\n // Validation\n Validation: {\n Failed: 'ValidationFailed',\n },\n\n // Identity\n Identity: {\n InvalidCredentials: 'InvalidCredentials',\n UserInactive: 'UserInactive',\n TenantContextMissing: 'TenantContextMissing',\n InvalidCurrentPassword: 'InvalidCurrentPassword',\n OwnerCannotDisableMembership: 'OwnerCannotDisableMembership',\n CurrentUserMissing: 'CurrentUserMissing',\n UserNotFound: 'UserNotFound',\n EmailAlreadyExists: 'EmailAlreadyExists',\n },\n\n // Accounting\n Accounting: {\n OpeningBalanceLockedReconciliation: 'OpeningBalanceLockedReconciliation',\n OpeningBalanceLockedJournal: 'OpeningBalanceLockedJournal',\n SystemLocked: 'SystemLocked',\n AccountsDuplicate: 'AccountsDuplicate',\n AccountNotFound: 'AccountNotFound',\n },\n\n // Subscription\n Subscription: {\n SubscriptionRequired: 'SubscriptionRequired',\n SubscriptionExpired: 'SubscriptionExpired',\n FeatureNotAvailable: 'FeatureNotAvailable',\n },\n\n // Property Management\n PropertyManagement: {\n PropertyNotFound: 'PropertyNotFound',\n UnitNotFound: 'UnitNotFound',\n DuplicateProperty: 'DuplicateProperty',\n DuplicateUnit: 'DuplicateUnit',\n },\n\n // Leasing\n Leasing: {\n LeaseNotFound: 'LeaseNotFound',\n LeaseConflict: 'LeaseConflict',\n TenantNotFound: 'TenantNotFound',\n LateFeeValidationError: 'LateFeeValidationError',\n },\n\n // General\n NotFound: 'NotFound',\n Unauthorized: 'Unauthorized',\n Forbidden: 'Forbidden',\n InternalError: 'InternalError',\n} as const;\n\n/**\n * Check if error code is from PropertyManagement module\n */\nexport function isPropertyManagementError(code: string): boolean {\n return Object.values(ErrorCodes.PropertyManagement).includes(code as any);\n}\n\n/**\n * Check if error code is from Leasing module\n */\nexport function isLeasingError(code: string): boolean {\n return Object.values(ErrorCodes.Leasing).includes(code as any);\n}\n","/**\n * Error Handler\n * \n * Centralized error handling with ProblemDetails parsing.\n */\n\nimport type { ProblemDetails, ValidationError } from './types';\nimport { ErrorCodes } from './error-codes';\nimport { logger } from '../logging';\n\n/**\n * API Error Class\n */\nexport class ApiError extends Error {\n constructor(\n public problemDetails: ProblemDetails,\n public response: Response\n ) {\n super(problemDetails.detail);\n this.name = 'ApiError';\n }\n\n get code(): string {\n return this.problemDetails.title;\n }\n\n get status(): number {\n return this.problemDetails.status;\n }\n\n get isValidationError(): boolean {\n return this.code === ErrorCodes.Validation.Failed;\n }\n\n get validationErrors(): ValidationError[] {\n return this.problemDetails.meta?.errors ?? [];\n }\n\n get traceId(): string {\n return this.problemDetails.traceId;\n }\n}\n\n/**\n * Parse API error response\n */\nexport async function handleApiError(response: Response): Promise<never> {\n let problemDetails: ProblemDetails;\n\n try {\n problemDetails = await response.json();\n } catch {\n problemDetails = {\n type: `https://httpstatuses.com/${response.status}`,\n title: 'UnknownError',\n status: response.status,\n detail: response.statusText || 'An unknown error occurred',\n traceId: response.headers.get('x-trace-id') ?? 'unknown',\n };\n }\n\n logger.error('API request failed', undefined, {\n code: problemDetails.title,\n status: problemDetails.status,\n detail: problemDetails.detail,\n traceId: problemDetails.traceId,\n });\n\n throw new ApiError(problemDetails, response);\n}\n\n/**\n * Error Handler Configuration\n */\nexport interface ErrorHandlerConfig {\n handlers?: Partial<Record<string, (error: ApiError) => void>>;\n showToast?: boolean;\n toastMessages?: Partial<Record<string, string>>;\n onError?: (error: ApiError) => void;\n toast?: (message: string, type: 'error' | 'success' | 'info') => void;\n}\n\n/**\n * Error Handler Class\n */\nexport class ErrorHandler {\n private config: ErrorHandlerConfig;\n\n constructor(config: ErrorHandlerConfig = {}) {\n this.config = {\n showToast: true,\n ...config,\n };\n }\n\n handle(error: ApiError): void {\n const code = error.code;\n\n logger.error('Handling API Error', undefined, {\n code,\n status: error.status,\n traceId: error.traceId,\n });\n\n // Try custom handler first\n if (this.config.handlers?.[code]) {\n this.config.handlers[code]!(error);\n return;\n }\n\n // Fallback\n if (this.config.onError) {\n this.config.onError(error);\n } else {\n this.showDefaultToast(error);\n }\n }\n\n private showToast(message: string, type: 'error' | 'success' | 'info'): void {\n if (this.config.showToast && this.config.toast) {\n this.config.toast(message, type);\n }\n }\n\n private showDefaultToast(error: ApiError): void {\n const message =\n this.config.toastMessages?.[error.code] ||\n error.problemDetails.detail ||\n 'An error occurred';\n this.showToast(message, 'error');\n }\n}\n\n/**\n * Create global error handler\n */\nexport function createErrorHandler(config?: ErrorHandlerConfig): ErrorHandler {\n return new ErrorHandler(config);\n}\n\n/**\n * Map validation errors to form fields\n */\nexport function mapValidationErrors(errors: ValidationError[]): Record<string, string> {\n const mapped: Record<string, string> = {};\n for (const error of errors) {\n const fieldName = error.field.charAt(0).toLowerCase() + error.field.slice(1);\n mapped[fieldName] = error.message;\n }\n return mapped;\n}\n","/**\n * Retry Handler\n * \n * Retry logic for API requests.\n */\n\nimport type { RetryConfig } from './types';\n\nconst DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 10000,\n retryCondition: (error: unknown) => {\n if (error instanceof Response) {\n return error.status >= 500 || error.status === 429;\n }\n return false;\n },\n};\n\n/**\n * Calculate backoff delay\n */\nexport function calculateBackoffDelay(\n attempt: number,\n baseDelay: number,\n maxDelay: number\n): number {\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * delay;\n return Math.min(delay + jitter, maxDelay);\n}\n\n/**\n * Check if error is retryable\n */\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status >= 500 || error.status === 429;\n }\n if (error instanceof TypeError) {\n // Network errors\n return true;\n }\n return false;\n}\n\n/**\n * Execute function with retry logic\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config?: RetryConfig\n): Promise<T> {\n const { maxRetries, baseDelay, maxDelay, retryCondition } = {\n ...DEFAULT_RETRY_CONFIG,\n ...config,\n };\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt === maxRetries || !retryCondition(error)) {\n throw error;\n }\n\n const delay = calculateBackoffDelay(attempt, baseDelay, maxDelay);\n console.log(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError;\n}\n\n/**\n * Fetch with timeout\n */\nexport async function fetchWithTimeout(\n input: RequestInfo | URL,\n init?: RequestInit & { timeout?: number }\n): Promise<Response> {\n const { timeout = 30000, ...fetchInit } = init || {};\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(input, {\n ...fetchInit,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Create fetch function with retry\n */\nexport function createRetryFetch(config?: RetryConfig) {\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n return withRetry(() => fetch(input, init), config);\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-client-DsrxK05Z.js","names":["problemDetails: ProblemDetails","response: Response","config: ErrorHandlerConfig","error: ApiError","message: string","type: 'error' | 'success' | 'info'","DEFAULT_RETRY_CONFIG: Required<RetryConfig>","error: unknown","attempt: number","baseDelay: number","maxDelay: number","fn: () => Promise<T>","config?: RetryConfig","lastError: unknown","input: RequestInfo | URL","init?: RequestInit & { timeout?: number }","init?: RequestInit"],"sources":["../src/api-client/error-codes.ts","../src/api-client/error-handler.ts","../src/api-client/retry-handler.ts"],"sourcesContent":["/**\n * Error Codes\n * \n * Standard error codes from backend.\n */\n\nexport const ErrorCodes = {\n // Validation\n Validation: {\n Failed: 'ValidationFailed',\n },\n\n // Identity\n Identity: {\n InvalidCredentials: 'InvalidCredentials',\n UserInactive: 'UserInactive',\n TenantContextMissing: 'TenantContextMissing',\n InvalidCurrentPassword: 'InvalidCurrentPassword',\n OwnerCannotDisableMembership: 'OwnerCannotDisableMembership',\n CurrentUserMissing: 'CurrentUserMissing',\n UserNotFound: 'UserNotFound',\n EmailAlreadyExists: 'EmailAlreadyExists',\n },\n\n // Accounting\n Accounting: {\n OpeningBalanceLockedReconciliation: 'OpeningBalanceLockedReconciliation',\n OpeningBalanceLockedJournal: 'OpeningBalanceLockedJournal',\n SystemLocked: 'SystemLocked',\n AccountsDuplicate: 'AccountsDuplicate',\n AccountNotFound: 'AccountNotFound',\n },\n\n // Subscription\n Subscription: {\n SubscriptionRequired: 'SubscriptionRequired',\n SubscriptionExpired: 'SubscriptionExpired',\n FeatureNotAvailable: 'FeatureNotAvailable',\n },\n\n // Property Management\n PropertyManagement: {\n PropertyNotFound: 'PropertyNotFound',\n UnitNotFound: 'UnitNotFound',\n DuplicateProperty: 'DuplicateProperty',\n DuplicateUnit: 'DuplicateUnit',\n },\n\n // Leasing\n Leasing: {\n LeaseNotFound: 'LeaseNotFound',\n LeaseConflict: 'LeaseConflict',\n TenantNotFound: 'TenantNotFound',\n LateFeeValidationError: 'LateFeeValidationError',\n },\n\n // General\n NotFound: 'NotFound',\n Unauthorized: 'Unauthorized',\n Forbidden: 'Forbidden',\n InternalError: 'InternalError',\n} as const;\n\n/**\n * Check if error code is from PropertyManagement module\n */\nexport function isPropertyManagementError(code: string): boolean {\n return Object.values(ErrorCodes.PropertyManagement).includes(code as any);\n}\n\n/**\n * Check if error code is from Leasing module\n */\nexport function isLeasingError(code: string): boolean {\n return Object.values(ErrorCodes.Leasing).includes(code as any);\n}\n","/**\n * Error Handler\n * \n * Centralized error handling with ProblemDetails parsing.\n */\n\nimport type { ProblemDetails, ValidationError } from './types';\nimport { ErrorCodes } from './error-codes';\nimport { logger } from '../logging';\n\n/**\n * API Error Class\n */\nexport class ApiError extends Error {\n constructor(\n public problemDetails: ProblemDetails,\n public response: Response\n ) {\n super(problemDetails.detail);\n this.name = 'ApiError';\n }\n\n get code(): string {\n return this.problemDetails.title;\n }\n\n get status(): number {\n return this.problemDetails.status;\n }\n\n get isValidationError(): boolean {\n return this.code === ErrorCodes.Validation.Failed;\n }\n\n get validationErrors(): ValidationError[] {\n return this.problemDetails.meta?.errors ?? [];\n }\n\n get traceId(): string {\n return this.problemDetails.traceId;\n }\n}\n\n/**\n * Parse API error response\n */\nexport async function handleApiError(response: Response): Promise<never> {\n let problemDetails: ProblemDetails;\n\n try {\n problemDetails = await response.json();\n } catch {\n problemDetails = {\n type: `https://httpstatuses.com/${response.status}`,\n title: 'UnknownError',\n status: response.status,\n detail: response.statusText || 'An unknown error occurred',\n traceId: response.headers.get('x-trace-id') ?? 'unknown',\n };\n }\n\n logger.error('API request failed', undefined, {\n code: problemDetails.title,\n status: problemDetails.status,\n detail: problemDetails.detail,\n traceId: problemDetails.traceId,\n });\n\n throw new ApiError(problemDetails, response);\n}\n\n/**\n * Error Handler Configuration\n */\nexport interface ErrorHandlerConfig {\n handlers?: Partial<Record<string, (error: ApiError) => void>>;\n showToast?: boolean;\n toastMessages?: Partial<Record<string, string>>;\n onError?: (error: ApiError) => void;\n toast?: (message: string, type: 'error' | 'success' | 'info') => void;\n}\n\n/**\n * Error Handler Class\n */\nexport class ErrorHandler {\n private config: ErrorHandlerConfig;\n\n constructor(config: ErrorHandlerConfig = {}) {\n this.config = {\n showToast: true,\n ...config,\n };\n }\n\n handle(error: ApiError): void {\n const code = error.code;\n\n logger.error('Handling API Error', undefined, {\n code,\n status: error.status,\n traceId: error.traceId,\n });\n\n // Try custom handler first\n if (this.config.handlers?.[code]) {\n this.config.handlers[code]!(error);\n return;\n }\n\n // Fallback\n if (this.config.onError) {\n this.config.onError(error);\n } else {\n this.showDefaultToast(error);\n }\n }\n\n private showToast(message: string, type: 'error' | 'success' | 'info'): void {\n if (this.config.showToast && this.config.toast) {\n this.config.toast(message, type);\n }\n }\n\n private showDefaultToast(error: ApiError): void {\n const message =\n this.config.toastMessages?.[error.code] ||\n error.problemDetails.detail ||\n 'An error occurred';\n this.showToast(message, 'error');\n }\n}\n\n/**\n * Create global error handler\n */\nexport function createErrorHandler(config?: ErrorHandlerConfig): ErrorHandler {\n return new ErrorHandler(config);\n}\n\n/**\n * Map validation errors to form fields\n */\nexport function mapValidationErrors(errors: ValidationError[]): Record<string, string> {\n const mapped: Record<string, string> = {};\n for (const error of errors) {\n const fieldName = error.field.charAt(0).toLowerCase() + error.field.slice(1);\n mapped[fieldName] = error.message;\n }\n return mapped;\n}\n","/**\n * Retry Handler\n * \n * Retry logic for API requests.\n */\n\nimport type { RetryConfig } from './types';\n\nconst DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 10000,\n retryCondition: (error: unknown) => {\n if (error instanceof Response) {\n return error.status >= 500 || error.status === 429;\n }\n return false;\n },\n};\n\n/**\n * Calculate backoff delay\n */\nexport function calculateBackoffDelay(\n attempt: number,\n baseDelay: number,\n maxDelay: number\n): number {\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * delay;\n return Math.min(delay + jitter, maxDelay);\n}\n\n/**\n * Check if error is retryable\n */\nexport function isRetryableError(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status >= 500 || error.status === 429;\n }\n if (error instanceof TypeError) {\n // Network errors\n return true;\n }\n return false;\n}\n\n/**\n * Execute function with retry logic\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config?: RetryConfig\n): Promise<T> {\n const { maxRetries, baseDelay, maxDelay, retryCondition } = {\n ...DEFAULT_RETRY_CONFIG,\n ...config,\n };\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (attempt === maxRetries || !retryCondition(error)) {\n throw error;\n }\n\n const delay = calculateBackoffDelay(attempt, baseDelay, maxDelay);\n console.log(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError;\n}\n\n/**\n * Fetch with timeout\n */\nexport async function fetchWithTimeout(\n input: RequestInfo | URL,\n init?: RequestInit & { timeout?: number }\n): Promise<Response> {\n const { timeout = 30000, ...fetchInit } = init || {};\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(input, {\n ...fetchInit,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Create fetch function with retry\n */\nexport function createRetryFetch(config?: RetryConfig) {\n return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n return withRetry(() => fetch(input, init), config);\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2/OIDC Authentication Server Module
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Login flow (PKCE)
|
|
6
|
+
* - Token exchange
|
|
7
|
+
* - Token refresh
|
|
8
|
+
* - Logout
|
|
9
|
+
* - Session management
|
|
10
|
+
*/
|
|
11
|
+
import { isSessionValid } from './redis-session-storage.server';
|
|
12
|
+
import type { AuthClaimMapping, UserInfo, TokenRefreshResult, LoginOptions, AuthError } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Configure claim mapping so different providers/backends can project their
|
|
15
|
+
* own claim names onto the shared auth/session primitives.
|
|
16
|
+
*/
|
|
17
|
+
export declare function configureAuthClaimMapping(mapping: AuthClaimMapping): void;
|
|
18
|
+
/**
|
|
19
|
+
* Reset claim mapping to the built-in defaults.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resetAuthClaimMapping(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Extract user info from ID token claims
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractUserInfo(claims: Record<string, unknown>): {
|
|
26
|
+
tenants: string[];
|
|
27
|
+
currentTenant?: string;
|
|
28
|
+
isOnboarded: boolean;
|
|
29
|
+
tenantRoles: Record<string, string[]>;
|
|
30
|
+
permissions: string[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Check if there's an auth error in cookies
|
|
34
|
+
*
|
|
35
|
+
* This should be called by apps (especially those without landing pages like Dashboard)
|
|
36
|
+
* BEFORE starting OAuth flow to prevent redirect loops.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // In dashboard's index route loader
|
|
41
|
+
* const authError = getAuthError(request);
|
|
42
|
+
* if (authError) {
|
|
43
|
+
* return { authError }; // Show error to user
|
|
44
|
+
* }
|
|
45
|
+
* // No error, proceed with session check
|
|
46
|
+
* const user = await getUser(request);
|
|
47
|
+
* if (!user) {
|
|
48
|
+
* return login(request);
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function getAuthError(request: Request): AuthError | null;
|
|
53
|
+
/**
|
|
54
|
+
* Clear auth error cookies
|
|
55
|
+
*
|
|
56
|
+
* Call this after displaying the error to the user
|
|
57
|
+
*/
|
|
58
|
+
export declare function clearAuthErrorHeaders(): Headers;
|
|
59
|
+
/**
|
|
60
|
+
* Check if request has auth error (quick check without parsing)
|
|
61
|
+
*/
|
|
62
|
+
export declare function hasAuthError(request: Request): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Initiate OAuth login flow
|
|
65
|
+
*
|
|
66
|
+
* @param request - The incoming request
|
|
67
|
+
* @param returnUrlOrOptions - Either a return URL string or LoginOptions object
|
|
68
|
+
*/
|
|
69
|
+
export declare function login(request: Request, returnUrlOrOptions?: string | LoginOptions): Promise<Response>;
|
|
70
|
+
/**
|
|
71
|
+
* Handle OAuth callback
|
|
72
|
+
*
|
|
73
|
+
* Handles OAuth authorization response including success and error cases.
|
|
74
|
+
*
|
|
75
|
+
* Error Handling:
|
|
76
|
+
* - `access_denied`: User doesn't have access to this application
|
|
77
|
+
* - For apps with landing page: Redirect home with error cookie
|
|
78
|
+
* - For apps without landing page: Redirect home with error cookie (must handle specially)
|
|
79
|
+
* - `login_required`: No active identity session
|
|
80
|
+
* - Redirect to home to start fresh login
|
|
81
|
+
*/
|
|
82
|
+
export declare function handleCallback(request: Request): Promise<Response>;
|
|
83
|
+
/**
|
|
84
|
+
* Logout user
|
|
85
|
+
*
|
|
86
|
+
* Backend uses id_token_hint to identify which tokens to revoke.
|
|
87
|
+
*
|
|
88
|
+
* **Scenario 1: Full SSO Logout (Manual Sign Out)**
|
|
89
|
+
* - Query param: sso_logout=true
|
|
90
|
+
* - Result: Full logout + identity cookie cleared
|
|
91
|
+
* - User must re-authenticate everywhere
|
|
92
|
+
*
|
|
93
|
+
* **Scenario 2: Standard Logout (Automatic/Token Expired)**
|
|
94
|
+
* - No sso_logout param
|
|
95
|
+
* - Result: Tokens revoked based on id_token_hint
|
|
96
|
+
* - Backend determines which tokens to revoke
|
|
97
|
+
*
|
|
98
|
+
* Configuration:
|
|
99
|
+
* - `OIDC_SSO_LOGOUT` env var ('true' | 'false') - default behavior
|
|
100
|
+
* - Query param `sso_logout=true` - override for full logout
|
|
101
|
+
*/
|
|
102
|
+
export declare function logout(request: Request): Promise<Response>;
|
|
103
|
+
/**
|
|
104
|
+
* Get current user from session
|
|
105
|
+
*/
|
|
106
|
+
export declare function getUser(request: Request): Promise<UserInfo | null>;
|
|
107
|
+
/**
|
|
108
|
+
* Require authenticated user (throws redirect to login if not authenticated)
|
|
109
|
+
*/
|
|
110
|
+
export declare function requireAuth(request: Request): Promise<UserInfo>;
|
|
111
|
+
/**
|
|
112
|
+
* Get access token from session
|
|
113
|
+
*/
|
|
114
|
+
export declare function getAccessToken(request: Request): Promise<string | null>;
|
|
115
|
+
/**
|
|
116
|
+
* Refresh tokens
|
|
117
|
+
*/
|
|
118
|
+
export declare function refreshTokens(request: Request, refreshToken?: string): Promise<TokenRefreshResult>;
|
|
119
|
+
export { isSessionValid };
|
|
120
|
+
/**
|
|
121
|
+
* Clear auth server cache (useful for testing)
|
|
122
|
+
*/
|
|
123
|
+
export declare function clearAuthServerCache(): void;
|
|
124
|
+
//# sourceMappingURL=auth.server.d.ts.map
|