@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/dist/server-DLgH3FRR.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { calculateBackoffDelay, isRetryableError } from "./api-client-Ftxqm3Ep.js";
|
|
2
|
-
|
|
3
|
-
//#region src/api-client/api-config.server.ts
|
|
4
|
-
/**
|
|
5
|
-
* Base URL Configuration
|
|
6
|
-
* Single unified base URL for all API operations
|
|
7
|
-
*/
|
|
8
|
-
const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || "https://localhost:5001";
|
|
9
|
-
/**
|
|
10
|
-
* Default Headers
|
|
11
|
-
* Applied to all API requests
|
|
12
|
-
*/
|
|
13
|
-
const DEFAULT_HEADERS = {
|
|
14
|
-
Accept: "application/json, application/problem+json",
|
|
15
|
-
"Content-Type": "application/json"
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Create API Config Factory
|
|
19
|
-
*
|
|
20
|
-
* Creates a factory function that generates API configurations
|
|
21
|
-
* with the provided auth and tenant resolvers.
|
|
22
|
-
*/
|
|
23
|
-
function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factoryOptions = {}) {
|
|
24
|
-
const { baseURL = DEFAULT_API_BASE_URL, defaultHeaders = DEFAULT_HEADERS, userAgent = "MimirCore/1.0", timeout = 3e4 } = factoryOptions;
|
|
25
|
-
/**
|
|
26
|
-
* Resolve tenant ID from request or options
|
|
27
|
-
*/
|
|
28
|
-
async function resolveTenantId(request, options) {
|
|
29
|
-
if (options.tenantId) {
|
|
30
|
-
logger?.debug?.("Using explicit tenant ID", { tenantId: options.tenantId });
|
|
31
|
-
return options.tenantId;
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const tenantId = await getCurrentTenant(request);
|
|
35
|
-
if (tenantId) logger?.debug?.("Resolved tenant from context", { tenantId });
|
|
36
|
-
return tenantId;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
logger?.warn?.("Failed to resolve current tenant", error instanceof Error ? error : void 0);
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Creates unified API configuration for all services.
|
|
44
|
-
*
|
|
45
|
-
* This function automatically:
|
|
46
|
-
* - Adds authentication token to Authorization header
|
|
47
|
-
* - Injects tenant context as X-Tenant-Id header
|
|
48
|
-
* - Configures base URL from environment
|
|
49
|
-
* - Applies default headers
|
|
50
|
-
*
|
|
51
|
-
* @param request - The incoming request object
|
|
52
|
-
* @param options - Configuration options
|
|
53
|
-
* @returns API configuration object
|
|
54
|
-
* @throws Error if required authentication or tenant context is missing
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ` ypescript
|
|
58
|
-
* // Standard usage with auth and tenant
|
|
59
|
-
* const config = await createAPIConfig(request);
|
|
60
|
-
* const api = new IdentityApi(config);
|
|
61
|
-
*
|
|
62
|
-
* // Without tenant requirement
|
|
63
|
-
* const config = await createAPIConfig(request, { requireTenant: false });
|
|
64
|
-
*
|
|
65
|
-
* // With explicit tenant override
|
|
66
|
-
* const config = await createAPIConfig(request, { tenantId: 'specific-tenant-id' });
|
|
67
|
-
* `
|
|
68
|
-
*/
|
|
69
|
-
async function createAPIConfig(request, options = {}) {
|
|
70
|
-
const { requireTenant = true, includeAuth = true, customHeaders = {} } = options;
|
|
71
|
-
const headers = {
|
|
72
|
-
...defaultHeaders,
|
|
73
|
-
"User-Agent": userAgent,
|
|
74
|
-
...customHeaders
|
|
75
|
-
};
|
|
76
|
-
let accessToken;
|
|
77
|
-
if (includeAuth) {
|
|
78
|
-
const token = await getAccessToken(request);
|
|
79
|
-
if (!token) {
|
|
80
|
-
logger?.error?.("No access token available for API request");
|
|
81
|
-
throw new Error("No access token available");
|
|
82
|
-
}
|
|
83
|
-
accessToken = token;
|
|
84
|
-
headers.Authorization = `Bearer ${token}`;
|
|
85
|
-
logger?.debug?.("Added authorization header to API config");
|
|
86
|
-
}
|
|
87
|
-
const tenantId = await resolveTenantId(request, options);
|
|
88
|
-
if (requireTenant) {
|
|
89
|
-
if (!tenantId) {
|
|
90
|
-
logger?.error?.("Tenant context required but not available");
|
|
91
|
-
throw new Error("Tenant context is required for this operation");
|
|
92
|
-
}
|
|
93
|
-
headers["X-Tenant-Id"] = tenantId;
|
|
94
|
-
logger?.debug?.("Added tenant context header", { tenantId });
|
|
95
|
-
}
|
|
96
|
-
logger?.debug?.("API config created", {
|
|
97
|
-
basePath: baseURL,
|
|
98
|
-
hasAuth: includeAuth,
|
|
99
|
-
hasTenant: !!tenantId,
|
|
100
|
-
requireTenant
|
|
101
|
-
});
|
|
102
|
-
return {
|
|
103
|
-
basePath: baseURL,
|
|
104
|
-
accessToken,
|
|
105
|
-
headers,
|
|
106
|
-
tenantId: tenantId || "",
|
|
107
|
-
baseOptions: { timeout }
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Get the configured API base URL
|
|
112
|
-
*/
|
|
113
|
-
function getAPIBaseURL() {
|
|
114
|
-
return baseURL;
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
createAPIConfig,
|
|
118
|
-
getAPIBaseURL
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/api-client/axios-setup.server.ts
|
|
124
|
-
/**
|
|
125
|
-
* Global token refresh queue - prevents concurrent refresh attempts
|
|
126
|
-
*
|
|
127
|
-
* When multiple requests get 401 simultaneously:
|
|
128
|
-
* 1. First request starts the refresh, creates a promise
|
|
129
|
-
* 2. Other requests wait for the same promise
|
|
130
|
-
* 3. When refresh completes, all waiting requests get the new token
|
|
131
|
-
* 4. This prevents refresh token rotation race conditions
|
|
132
|
-
*/
|
|
133
|
-
const refreshPromiseMap = new Map();
|
|
134
|
-
/**
|
|
135
|
-
* Get or create a token refresh promise for a session
|
|
136
|
-
* Ensures only one refresh happens at a time per session
|
|
137
|
-
*/
|
|
138
|
-
function getOrCreateRefreshPromise(sessionKey, refreshFn, logger) {
|
|
139
|
-
const existingPromise = refreshPromiseMap.get(sessionKey);
|
|
140
|
-
if (existingPromise) {
|
|
141
|
-
logger?.info?.("Token refresh already in progress, waiting for existing refresh");
|
|
142
|
-
return existingPromise;
|
|
143
|
-
}
|
|
144
|
-
logger?.info?.("Starting new token refresh");
|
|
145
|
-
const refreshPromise = refreshFn().finally(() => {
|
|
146
|
-
refreshPromiseMap.delete(sessionKey);
|
|
147
|
-
});
|
|
148
|
-
refreshPromiseMap.set(sessionKey, refreshPromise);
|
|
149
|
-
return refreshPromise;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Extract session key from request for refresh deduplication
|
|
153
|
-
*/
|
|
154
|
-
function getSessionKeyFromRequest(request) {
|
|
155
|
-
const cookie = request.headers.get("cookie") || "";
|
|
156
|
-
return `session:${cookie.slice(0, 50)}`;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Setup Axios interceptors for auto-generated clients
|
|
160
|
-
*
|
|
161
|
-
* Features:
|
|
162
|
-
* - Automatic token refresh on 401
|
|
163
|
-
* - Retry logic with exponential backoff
|
|
164
|
-
* - ProblemDetails error parsing
|
|
165
|
-
* - Timeout management
|
|
166
|
-
* - Comprehensive logging
|
|
167
|
-
*
|
|
168
|
-
* @param axiosInstance - The Axios instance from auto-generated client
|
|
169
|
-
* @param request - The incoming request object (for token refresh)
|
|
170
|
-
* @param options - Configuration options
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* ` ypescript
|
|
174
|
-
* const config = await createAPIConfig(request);
|
|
175
|
-
* const api = new IdentityApi(config);
|
|
176
|
-
* setupAxiosInterceptors(api.axios, request, { logger, attemptTokenRefresh });
|
|
177
|
-
*
|
|
178
|
-
* // Now all API calls have automatic retry, token refresh, error handling
|
|
179
|
-
* const users = await api.getUsers(tenantId);
|
|
180
|
-
* `
|
|
181
|
-
*/
|
|
182
|
-
function setupAxiosInterceptors(axiosInstance, request, options = {}) {
|
|
183
|
-
const { logger, attemptTokenRefresh, retryConfig = {}, addRequestId = true } = options;
|
|
184
|
-
const { maxRetries = 3, baseDelay = 1e3, maxDelay = 1e4 } = retryConfig;
|
|
185
|
-
if (axiosInstance._interceptorsSetup) {
|
|
186
|
-
logger?.debug?.("Interceptors already setup, skipping");
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
axiosInstance._interceptorsSetup = true;
|
|
190
|
-
if (addRequestId) axiosInstance.interceptors.request.use((config) => {
|
|
191
|
-
config.headers["X-Request-Id"] = crypto.randomUUID();
|
|
192
|
-
logger?.debug?.("Axios request initiated", {
|
|
193
|
-
method: config.method,
|
|
194
|
-
url: config.url,
|
|
195
|
-
requestId: config.headers["X-Request-Id"]
|
|
196
|
-
});
|
|
197
|
-
return config;
|
|
198
|
-
}, (error) => {
|
|
199
|
-
logger?.error?.("Axios request setup failed", error);
|
|
200
|
-
return Promise.reject(error);
|
|
201
|
-
});
|
|
202
|
-
if (attemptTokenRefresh) axiosInstance.interceptors.response.use((response) => response, async (error) => {
|
|
203
|
-
const originalRequest = error.config;
|
|
204
|
-
if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
|
|
205
|
-
originalRequest._retry = true;
|
|
206
|
-
logger?.info?.("Received 401 from backend, attempting token refresh");
|
|
207
|
-
const sessionKey = getSessionKeyFromRequest(request);
|
|
208
|
-
const refreshResult = await getOrCreateRefreshPromise(sessionKey, () => attemptTokenRefresh(request), logger);
|
|
209
|
-
if (refreshResult.success && refreshResult.newAccessToken) {
|
|
210
|
-
logger?.info?.("Token refresh successful, retrying original request");
|
|
211
|
-
originalRequest.headers.Authorization = `Bearer ${refreshResult.newAccessToken}`;
|
|
212
|
-
return axiosInstance(originalRequest);
|
|
213
|
-
} else if (refreshResult.shouldLogout) {
|
|
214
|
-
logger?.error?.("Token refresh failed - refresh token expired or invalid");
|
|
215
|
-
const logoutError = new Error("REFRESH_TOKEN_EXPIRED");
|
|
216
|
-
logoutError.shouldLogout = true;
|
|
217
|
-
logoutError.originalError = error;
|
|
218
|
-
return Promise.reject(logoutError);
|
|
219
|
-
} else logger?.error?.("Token refresh failed but not requiring logout", void 0, { error: refreshResult.error });
|
|
220
|
-
}
|
|
221
|
-
return Promise.reject(error);
|
|
222
|
-
});
|
|
223
|
-
axiosInstance.interceptors.response.use((response) => response, async (error) => {
|
|
224
|
-
const config = error.config;
|
|
225
|
-
if (!config) return Promise.reject(error);
|
|
226
|
-
if (error.response?.status === 401) {
|
|
227
|
-
logger?.debug?.("401 detected, not retrying (handled by token refresh interceptor)");
|
|
228
|
-
return Promise.reject(error);
|
|
229
|
-
}
|
|
230
|
-
if (error.response?.status === 409) {
|
|
231
|
-
logger?.debug?.("409 Conflict detected, will not retry");
|
|
232
|
-
return Promise.reject(error);
|
|
233
|
-
}
|
|
234
|
-
if (config._retryCount === void 0) config._retryCount = 0;
|
|
235
|
-
const shouldRetry = isRetryableError(error) && config._retryCount < maxRetries;
|
|
236
|
-
if (shouldRetry) {
|
|
237
|
-
config._retryCount++;
|
|
238
|
-
const delay = calculateBackoffDelay(config._retryCount, baseDelay, maxDelay);
|
|
239
|
-
logger?.info?.("Retrying request", {
|
|
240
|
-
attempt: config._retryCount,
|
|
241
|
-
maxRetries,
|
|
242
|
-
delay,
|
|
243
|
-
error: error.message
|
|
244
|
-
});
|
|
245
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
246
|
-
return axiosInstance(config);
|
|
247
|
-
}
|
|
248
|
-
return Promise.reject(error);
|
|
249
|
-
});
|
|
250
|
-
axiosInstance.interceptors.response.use((response) => response, (error) => {
|
|
251
|
-
if (error.response?.data) {
|
|
252
|
-
const problemDetails = error.response.data;
|
|
253
|
-
logger?.error?.("API error (ProblemDetails)", void 0, {
|
|
254
|
-
code: problemDetails.title,
|
|
255
|
-
status: problemDetails.status,
|
|
256
|
-
detail: problemDetails.detail,
|
|
257
|
-
traceId: problemDetails.traceId
|
|
258
|
-
});
|
|
259
|
-
error.problemDetails = problemDetails;
|
|
260
|
-
}
|
|
261
|
-
return Promise.reject(error);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Helper to create and setup an API client in one call
|
|
266
|
-
*
|
|
267
|
-
* @param request - The incoming request object
|
|
268
|
-
* @param ClientClass - The auto-generated API client class
|
|
269
|
-
* @param createAPIConfig - Function to create API configuration
|
|
270
|
-
* @param ConfigurationClass - The Configuration class from generated API
|
|
271
|
-
* @param options - Configuration options
|
|
272
|
-
* @returns Configured API client instance with interceptors
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* ` ypescript
|
|
276
|
-
* import { IdentityApi, Configuration } from './generated-api';
|
|
277
|
-
*
|
|
278
|
-
* const { client: api, tenantId } = await createEnhancedClient(
|
|
279
|
-
* request,
|
|
280
|
-
* IdentityApi,
|
|
281
|
-
* createAPIConfig,
|
|
282
|
-
* Configuration,
|
|
283
|
-
* { logger, attemptTokenRefresh }
|
|
284
|
-
* );
|
|
285
|
-
* const users = await api.getUsers(tenantId);
|
|
286
|
-
* `
|
|
287
|
-
*/
|
|
288
|
-
async function createEnhancedClient(request, ClientClass, createAPIConfig, ConfigurationClass, options = {}) {
|
|
289
|
-
const { requireTenant, includeAuth, tenantId, customHeaders,...axiosOptions } = options;
|
|
290
|
-
const config = await createAPIConfig(request, {
|
|
291
|
-
requireTenant,
|
|
292
|
-
includeAuth,
|
|
293
|
-
tenantId,
|
|
294
|
-
customHeaders
|
|
295
|
-
});
|
|
296
|
-
const configuration = new ConfigurationClass({
|
|
297
|
-
basePath: config.basePath,
|
|
298
|
-
accessToken: config.accessToken,
|
|
299
|
-
baseOptions: { headers: config.headers }
|
|
300
|
-
});
|
|
301
|
-
const client = new ClientClass(configuration);
|
|
302
|
-
const axiosInstance = client.axios;
|
|
303
|
-
if (axiosInstance) setupAxiosInterceptors(axiosInstance, request, axiosOptions);
|
|
304
|
-
return {
|
|
305
|
-
client,
|
|
306
|
-
tenantId: config.tenantId
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
//#endregion
|
|
311
|
-
export { createAPIConfigFactory, createEnhancedClient, setupAxiosInterceptors };
|
|
312
|
-
//# sourceMappingURL=server-DLgH3FRR.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-DLgH3FRR.js","names":["DEFAULT_HEADERS: Record<string, string>","getAccessToken: GetAccessTokenFn","getCurrentTenant: GetCurrentTenantFn","logger?: APILogger","factoryOptions: APIConfigFactoryOptions","request: Request","options: CreateAPIConfigOptions","headers: Record<string, string>","accessToken: string | undefined","sessionKey: string","refreshFn: () => Promise<TokenRefreshResult>","logger?: APILogger","request: Request","axiosInstance: AxiosInstance","options: AxiosSetupOptions","config: InternalAxiosRequestConfig","error: AxiosError","error: AxiosError<ProblemDetails>","ClientClass: new (configuration?: any, basePath?: string, axios?: AxiosInstance) => T","createAPIConfig: (request: Request, options?: any) => Promise<any>","ConfigurationClass: new (params?: any) => any","options: CreateEnhancedClientOptions"],"sources":["../src/api-client/api-config.server.ts","../src/api-client/axios-setup.server.ts"],"sourcesContent":["/**\n * API Configuration Server Module\n *\n * Centralized configuration for all API clients,\n * automatically handling authentication tokens and tenant context.\n */\n\nimport type { APIConfig, CreateAPIConfigOptions, GetAccessTokenFn, GetCurrentTenantFn, APILogger } from './types';\n\n/**\n * Base URL Configuration\n * Single unified base URL for all API operations\n */\nconst DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://localhost:5001';\n\n/**\n * Default Headers\n * Applied to all API requests\n */\nconst DEFAULT_HEADERS: Record<string, string> = {\n Accept: 'application/json, application/problem+json',\n 'Content-Type': 'application/json',\n};\n\n/**\n * API Config Factory Options\n */\nexport interface APIConfigFactoryOptions {\n /**\n * Base URL for API requests\n */\n baseURL?: string;\n /**\n * Default headers applied to all requests\n */\n defaultHeaders?: Record<string, string>;\n /**\n * User agent string\n */\n userAgent?: string;\n /**\n * Default timeout in milliseconds\n */\n timeout?: number;\n}\n\n/**\n * Create API Config Factory\n *\n * Creates a factory function that generates API configurations\n * with the provided auth and tenant resolvers.\n */\nexport function createAPIConfigFactory(\n getAccessToken: GetAccessTokenFn,\n getCurrentTenant: GetCurrentTenantFn,\n logger?: APILogger,\n factoryOptions: APIConfigFactoryOptions = {}\n) {\n const {\n baseURL = DEFAULT_API_BASE_URL,\n defaultHeaders = DEFAULT_HEADERS,\n userAgent = 'MimirCore/1.0',\n timeout = 30000,\n } = factoryOptions;\n\n /**\n * Resolve tenant ID from request or options\n */\n async function resolveTenantId(\n request: Request,\n options: CreateAPIConfigOptions\n ): Promise<string | null> {\n // Use explicit tenant ID if provided\n if (options.tenantId) {\n logger?.debug?.('Using explicit tenant ID', { tenantId: options.tenantId });\n return options.tenantId;\n }\n\n // Resolve from current tenant context\n try {\n const tenantId = await getCurrentTenant(request);\n if (tenantId) {\n logger?.debug?.('Resolved tenant from context', { tenantId });\n }\n return tenantId;\n } catch (error) {\n logger?.warn?.('Failed to resolve current tenant', error instanceof Error ? error : undefined);\n return null;\n }\n }\n\n /**\n * Creates unified API configuration for all services.\n *\n * This function automatically:\n * - Adds authentication token to Authorization header\n * - Injects tenant context as X-Tenant-Id header\n * - Configures base URL from environment\n * - Applies default headers\n *\n * @param request - The incoming request object\n * @param options - Configuration options\n * @returns API configuration object\n * @throws Error if required authentication or tenant context is missing\n *\n * @example\n * `\typescript\n * // Standard usage with auth and tenant\n * const config = await createAPIConfig(request);\n * const api = new IdentityApi(config);\n *\n * // Without tenant requirement\n * const config = await createAPIConfig(request, { requireTenant: false });\n *\n * // With explicit tenant override\n * const config = await createAPIConfig(request, { tenantId: 'specific-tenant-id' });\n * `\n */\n async function createAPIConfig(\n request: Request,\n options: CreateAPIConfigOptions = {}\n ): Promise<APIConfig> {\n const {\n requireTenant = true,\n includeAuth = true,\n customHeaders = {},\n } = options;\n\n // Start with default headers\n const headers: Record<string, string> = {\n ...defaultHeaders,\n 'User-Agent': userAgent,\n ...customHeaders,\n };\n\n let accessToken: string | undefined;\n\n // Add authentication if required\n if (includeAuth) {\n const token = await getAccessToken(request);\n if (!token) {\n logger?.error?.('No access token available for API request');\n throw new Error('No access token available');\n }\n\n accessToken = token;\n headers.Authorization = `Bearer ${token}`;\n logger?.debug?.('Added authorization header to API config');\n }\n\n // Resolve tenant context\n const tenantId = await resolveTenantId(request, options);\n\n // Add tenant header if required\n if (requireTenant) {\n if (!tenantId) {\n logger?.error?.('Tenant context required but not available');\n throw new Error('Tenant context is required for this operation');\n }\n\n headers['X-Tenant-Id'] = tenantId;\n logger?.debug?.('Added tenant context header', { tenantId });\n }\n\n logger?.debug?.('API config created', {\n basePath: baseURL,\n hasAuth: includeAuth,\n hasTenant: !!tenantId,\n requireTenant,\n });\n\n return {\n basePath: baseURL,\n accessToken,\n headers,\n tenantId: tenantId || '',\n baseOptions: {\n timeout,\n },\n };\n }\n\n /**\n * Get the configured API base URL\n */\n function getAPIBaseURL(): string {\n return baseURL;\n }\n\n return {\n createAPIConfig,\n getAPIBaseURL,\n };\n}\n\nexport type APIConfigFactory = ReturnType<typeof createAPIConfigFactory>;\n","/**\n * Axios Interceptor Setup for Auto-Generated API Clients\n *\n * This module enhances auto-generated Axios-based API clients with:\n * - Automatic token refresh on 401 (serialized to prevent race conditions)\n * - Retry logic with exponential backoff\n * - ProblemDetails error parsing\n * - Timeout management\n * - Comprehensive logging\n *\n * CRITICAL: Token refresh is serialized using a global promise queue.\n * This prevents race conditions when multiple concurrent requests get 401\n * and all try to refresh the token simultaneously (which would fail due\n * to refresh token rotation - the second refresh would use an invalid token).\n */\n\nimport type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';\nimport type { ProblemDetails, RetryConfig, APILogger, TokenRefreshFn, TokenRefreshResult } from './types';\nimport { calculateBackoffDelay, isRetryableError } from './retry-handler';\n\n/**\n * Global token refresh queue - prevents concurrent refresh attempts\n * \n * When multiple requests get 401 simultaneously:\n * 1. First request starts the refresh, creates a promise\n * 2. Other requests wait for the same promise\n * 3. When refresh completes, all waiting requests get the new token\n * 4. This prevents refresh token rotation race conditions\n */\nconst refreshPromiseMap = new Map<string, Promise<TokenRefreshResult>>();\n\n/**\n * Get or create a token refresh promise for a session\n * Ensures only one refresh happens at a time per session\n */\nfunction getOrCreateRefreshPromise(\n sessionKey: string,\n refreshFn: () => Promise<TokenRefreshResult>,\n logger?: APILogger\n): Promise<TokenRefreshResult> {\n // Check if there's already a refresh in progress for this session\n const existingPromise = refreshPromiseMap.get(sessionKey);\n if (existingPromise) {\n logger?.info?.('Token refresh already in progress, waiting for existing refresh');\n return existingPromise;\n }\n\n // Create new refresh promise\n logger?.info?.('Starting new token refresh');\n const refreshPromise = refreshFn().finally(() => {\n // Clean up after refresh completes (success or failure)\n refreshPromiseMap.delete(sessionKey);\n });\n\n refreshPromiseMap.set(sessionKey, refreshPromise);\n return refreshPromise;\n}\n\n/**\n * Extract session key from request for refresh deduplication\n */\nfunction getSessionKeyFromRequest(request: Request): string {\n // Use cookie header as session identifier (contains session ID)\n const cookie = request.headers.get('cookie') || '';\n // Use first 50 chars of cookie as key\n return `session:${cookie.slice(0, 50)}`;\n}\n\n/**\n * Axios Setup Options\n */\nexport interface AxiosSetupOptions {\n /**\n * Logger instance for logging\n */\n logger?: APILogger;\n /**\n * Token refresh function\n */\n attemptTokenRefresh?: TokenRefreshFn;\n /**\n * Retry configuration\n */\n retryConfig?: RetryConfig;\n /**\n * Whether to add request ID header\n */\n addRequestId?: boolean;\n}\n\n/**\n * Setup Axios interceptors for auto-generated clients\n *\n * Features:\n * - Automatic token refresh on 401\n * - Retry logic with exponential backoff\n * - ProblemDetails error parsing\n * - Timeout management\n * - Comprehensive logging\n *\n * @param axiosInstance - The Axios instance from auto-generated client\n * @param request - The incoming request object (for token refresh)\n * @param options - Configuration options\n *\n * @example\n * `\typescript\n * const config = await createAPIConfig(request);\n * const api = new IdentityApi(config);\n * setupAxiosInterceptors(api.axios, request, { logger, attemptTokenRefresh });\n *\n * // Now all API calls have automatic retry, token refresh, error handling\n * const users = await api.getUsers(tenantId);\n * `\n */\nexport function setupAxiosInterceptors(\n axiosInstance: AxiosInstance,\n request: Request,\n options: AxiosSetupOptions = {}\n): void {\n const {\n logger,\n attemptTokenRefresh,\n retryConfig = {},\n addRequestId = true,\n } = options;\n\n const {\n maxRetries = 3,\n baseDelay = 1000,\n maxDelay = 10000,\n } = retryConfig;\n\n // CRITICAL: Check if interceptors are already setup to prevent duplicate interceptors\n // Multiple interceptors cause exponential request multiplication\n if ((axiosInstance as any)._interceptorsSetup) {\n logger?.debug?.('Interceptors already setup, skipping');\n return;\n }\n (axiosInstance as any)._interceptorsSetup = true;\n\n // Request interceptor: Add request ID for tracing\n if (addRequestId) {\n axiosInstance.interceptors.request.use(\n (config: InternalAxiosRequestConfig) => {\n config.headers['X-Request-Id'] = crypto.randomUUID();\n\n logger?.debug?.('Axios request initiated', {\n method: config.method,\n url: config.url,\n requestId: config.headers['X-Request-Id'] as string,\n });\n\n return config;\n },\n (error) => {\n logger?.error?.('Axios request setup failed', error);\n return Promise.reject(error);\n }\n );\n }\n\n // Response interceptor: Token refresh on 401\n if (attemptTokenRefresh) {\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n // Check if 401 and should attempt token refresh\n if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {\n originalRequest._retry = true;\n\n logger?.info?.('Received 401 from backend, attempting token refresh');\n\n // CRITICAL: Use serialized token refresh to prevent race conditions\n // Multiple concurrent 401s will share the same refresh promise\n const sessionKey = getSessionKeyFromRequest(request);\n const refreshResult = await getOrCreateRefreshPromise(\n sessionKey,\n () => attemptTokenRefresh(request),\n logger\n );\n\n if (refreshResult.success && refreshResult.newAccessToken) {\n logger?.info?.('Token refresh successful, retrying original request');\n\n // Update authorization header with new token\n originalRequest.headers.Authorization = `Bearer ${refreshResult.newAccessToken}`;\n\n // Retry the original request with new token\n return axiosInstance(originalRequest);\n } else if (refreshResult.shouldLogout) {\n logger?.error?.('Token refresh failed - refresh token expired or invalid');\n\n // Enhance error with logout flag for API route to handle\n const logoutError = new Error('REFRESH_TOKEN_EXPIRED') as any;\n logoutError.shouldLogout = true;\n logoutError.originalError = error;\n\n return Promise.reject(logoutError);\n } else {\n logger?.error?.('Token refresh failed but not requiring logout', undefined, {\n error: refreshResult.error,\n });\n }\n }\n\n return Promise.reject(error);\n }\n );\n }\n\n // Response interceptor: Retry logic with exponential backoff\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error: AxiosError) => {\n const config = error.config as InternalAxiosRequestConfig & { _retryCount?: number };\n\n if (!config) {\n return Promise.reject(error);\n }\n\n // CRITICAL: Never retry 401 errors - they're handled by token refresh interceptor\n if (error.response?.status === 401) {\n logger?.debug?.('401 detected, not retrying (handled by token refresh interceptor)');\n return Promise.reject(error);\n }\n\n // CRITICAL: Never retry 409 Conflict errors (concurrent modification)\n // These indicate a business logic conflict that won't be resolved by retrying\n if (error.response?.status === 409) {\n logger?.debug?.('409 Conflict detected, will not retry');\n return Promise.reject(error);\n }\n\n if (config._retryCount === undefined) {\n config._retryCount = 0;\n }\n\n const shouldRetry = isRetryableError(error) && config._retryCount < maxRetries;\n\n if (shouldRetry) {\n config._retryCount++;\n\n const delay = calculateBackoffDelay(config._retryCount, baseDelay, maxDelay);\n\n logger?.info?.('Retrying request', {\n attempt: config._retryCount,\n maxRetries,\n delay,\n error: error.message,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return axiosInstance(config);\n }\n\n return Promise.reject(error);\n }\n );\n\n // Response interceptor: Error handling with ProblemDetails parsing\n axiosInstance.interceptors.response.use(\n (response) => response,\n (error: AxiosError<ProblemDetails>) => {\n if (error.response?.data) {\n const problemDetails = error.response.data;\n\n logger?.error?.('API error (ProblemDetails)', undefined, {\n code: problemDetails.title,\n status: problemDetails.status,\n detail: problemDetails.detail,\n traceId: problemDetails.traceId,\n });\n\n // Enhance error with parsed ProblemDetails\n (error as any).problemDetails = problemDetails;\n }\n\n return Promise.reject(error);\n }\n );\n}\n\n/**\n * Create Enhanced Client Options\n */\nexport interface CreateEnhancedClientOptions extends AxiosSetupOptions {\n /**\n * Whether tenant is required\n */\n requireTenant?: boolean;\n /**\n * Whether to include auth\n */\n includeAuth?: boolean;\n /**\n * Explicit tenant ID override\n */\n tenantId?: string;\n /**\n * Custom headers\n */\n customHeaders?: Record<string, string>;\n}\n\n/**\n * Helper to create and setup an API client in one call\n *\n * @param request - The incoming request object\n * @param ClientClass - The auto-generated API client class\n * @param createAPIConfig - Function to create API configuration\n * @param ConfigurationClass - The Configuration class from generated API\n * @param options - Configuration options\n * @returns Configured API client instance with interceptors\n *\n * @example\n * `\typescript\n * import { IdentityApi, Configuration } from './generated-api';\n *\n * const { client: api, tenantId } = await createEnhancedClient(\n * request,\n * IdentityApi,\n * createAPIConfig,\n * Configuration,\n * { logger, attemptTokenRefresh }\n * );\n * const users = await api.getUsers(tenantId);\n * `\n */\nexport async function createEnhancedClient<T>(\n request: Request,\n ClientClass: new (configuration?: any, basePath?: string, axios?: AxiosInstance) => T,\n createAPIConfig: (request: Request, options?: any) => Promise<any>,\n ConfigurationClass: new (params?: any) => any,\n options: CreateEnhancedClientOptions = {}\n): Promise<{ client: T; tenantId: string }> {\n const { requireTenant, includeAuth, tenantId, customHeaders, ...axiosOptions } = options;\n\n const config = await createAPIConfig(request, {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n });\n\n // Create Configuration object for auto-generated clients\n const configuration = new ConfigurationClass({\n basePath: config.basePath,\n accessToken: config.accessToken,\n baseOptions: {\n headers: config.headers,\n },\n });\n\n const client = new ClientClass(configuration);\n\n // Access protected axios instance via type assertion\n const axiosInstance = (client as any).axios as AxiosInstance | undefined;\n\n // Setup interceptors for token refresh and retry logic\n // IMPORTANT: Only setup once per instance to prevent duplication\n if (axiosInstance) {\n setupAxiosInterceptors(axiosInstance, request, axiosOptions);\n }\n\n return { client, tenantId: config.tenantId };\n}\n"],"mappings":"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"}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
//#region src/tenant/tenant.server.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Tenant Server Module
|
|
4
|
-
*
|
|
5
|
-
* Server-side tenant management.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Identity context fetcher type (to avoid circular dependency)
|
|
9
|
-
*/
|
|
10
|
-
type IdentityContextFetcher = (request: Request) => Promise<{
|
|
11
|
-
memberships?: Array<{
|
|
12
|
-
tenantId: string;
|
|
13
|
-
}>;
|
|
14
|
-
} | null>;
|
|
15
|
-
/**
|
|
16
|
-
* Configure identity context fetcher
|
|
17
|
-
*/
|
|
18
|
-
declare function configureIdentityContextFetcher(fetcher: IdentityContextFetcher): void;
|
|
19
|
-
/**
|
|
20
|
-
* Get current active tenant from cookie
|
|
21
|
-
*/
|
|
22
|
-
declare function getCurrentTenant(request: Request): Promise<string | null>;
|
|
23
|
-
/**
|
|
24
|
-
* Get tenant from identity context
|
|
25
|
-
*/
|
|
26
|
-
declare function getTenantFromIdentityContext(request: Request): Promise<string | null>;
|
|
27
|
-
/**
|
|
28
|
-
* Get available tenants from identity context
|
|
29
|
-
*/
|
|
30
|
-
declare function getAvailableTenants(request: Request): Promise<string[]>;
|
|
31
|
-
/**
|
|
32
|
-
* Set current active tenant
|
|
33
|
-
*/
|
|
34
|
-
declare function setCurrentTenant(request: Request, tenantId: string): Promise<{
|
|
35
|
-
headers: Headers;
|
|
36
|
-
success: boolean;
|
|
37
|
-
error?: string;
|
|
38
|
-
}>;
|
|
39
|
-
/**
|
|
40
|
-
* Initialize tenant on first login
|
|
41
|
-
*/
|
|
42
|
-
declare function initializeTenant(request: Request): Promise<{
|
|
43
|
-
headers: Headers;
|
|
44
|
-
tenantId: string;
|
|
45
|
-
} | null>;
|
|
46
|
-
/**
|
|
47
|
-
* Clear current tenant
|
|
48
|
-
*/
|
|
49
|
-
declare function clearCurrentTenant(): Promise<Headers>;
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/tenant/tenant-cookie.server.d.ts
|
|
53
|
-
//# sourceMappingURL=tenant.server.d.ts.map
|
|
54
|
-
/**
|
|
55
|
-
* Tenant Cookie Server Module
|
|
56
|
-
*
|
|
57
|
-
* Cookie-based tenant storage.
|
|
58
|
-
*/
|
|
59
|
-
type TenantCookieSameSite = 'Strict' | 'Lax' | 'None';
|
|
60
|
-
interface TenantCookieConfig {
|
|
61
|
-
name?: string;
|
|
62
|
-
maxAge?: number;
|
|
63
|
-
path?: string;
|
|
64
|
-
secure?: boolean;
|
|
65
|
-
httpOnly?: boolean;
|
|
66
|
-
sameSite?: TenantCookieSameSite;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Get active tenant from cookie
|
|
70
|
-
*/
|
|
71
|
-
declare function getActiveTenant(request: Request): Promise<string | null>;
|
|
72
|
-
/**
|
|
73
|
-
* Set active tenant cookie value
|
|
74
|
-
*/
|
|
75
|
-
declare function setActiveTenant(tenantId: string): Promise<string>;
|
|
76
|
-
/**
|
|
77
|
-
* Clear active tenant cookie value
|
|
78
|
-
*/
|
|
79
|
-
declare function clearActiveTenant(): Promise<string>;
|
|
80
|
-
/**
|
|
81
|
-
* Configure tenant cookie name
|
|
82
|
-
*/
|
|
83
|
-
declare function configureTenantCookie(config: TenantCookieConfig): void;
|
|
84
|
-
/**
|
|
85
|
-
* Reset tenant cookie config back to defaults.
|
|
86
|
-
*/
|
|
87
|
-
declare function resetTenantCookieConfig(): void;
|
|
88
|
-
|
|
89
|
-
//#endregion
|
|
90
|
-
//# sourceMappingURL=tenant-cookie.server.d.ts.map
|
|
91
|
-
|
|
92
|
-
export { IdentityContextFetcher, TenantCookieConfig, TenantCookieSameSite, clearActiveTenant as clearActiveTenant$1, clearCurrentTenant as clearCurrentTenant$1, configureIdentityContextFetcher as configureIdentityContextFetcher$1, configureTenantCookie as configureTenantCookie$1, getActiveTenant as getActiveTenant$1, getAvailableTenants as getAvailableTenants$1, getCurrentTenant as getCurrentTenant$1, getTenantFromIdentityContext as getTenantFromIdentityContext$1, initializeTenant as initializeTenant$1, resetTenantCookieConfig as resetTenantCookieConfig$1, setActiveTenant as setActiveTenant$1, setCurrentTenant as setCurrentTenant$1 };
|
|
93
|
-
//# sourceMappingURL=server-DazqdM63.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-DazqdM63.d.ts","names":[],"sources":["../src/tenant/tenant.server.ts","../src/tenant/tenant-cookie.server.ts"],"sourcesContent":null,"mappings":";;;;;;;;;KAYY,sBAAA,aAAmC,YAAY;EAA/C,WAAA,CAAA,EACI,KADJ,CAAA;IAAsB,QAAA,EAAA,MAAA;EAAA,CAAA,CAAA;CAAoB,GAAA,IACtC,CAAA;;AADkD;;iBASlD,+BAAA,UAAyC;;AAAzD;;iBAOsB,gBAAA,UAA0B,UAAU;;AAA1D;;AAAgD,iBAgB1B,4BAAA,CAhB0B,OAAA,EAgBY,OAhBZ,CAAA,EAgBsB,OAhBtB,CAAA,MAAA,GAAA,IAAA,CAAA;;AAAiB;;iBA4C3C,mBAAA,UAA6B,UAAU;;AA5B7D;;AAA4D,iBAwDtC,gBAAA,CAxDsC,OAAA,EAyDjD,OAzDiD,EAAA,QAAA,EAAA,MAAA,CAAA,EA2DzD,OA3DyD,CAAA;EAAO,OAAG,EA2DhD,OA3DgD;EAAO,OAAA,EAAA,OAAA;;;;AA4B7E;;AAAmD,iBAyD7B,gBAAA,CAzD6B,OAAA,EA0DxC,OA1DwC,CAAA,EA2DhD,OA3DgD,CAAA;EAAO,OAAG,EA2DvC,OA3DuC;EAAO,QAAA,EAAA,MAAA;;;;AA4BpE;AAAsC,iBAgEhB,kBAAA,CAAA,CAhEgB,EAgEM,OAhEN,CAgEc,OAhEd,CAAA;;;;;;;;;;KC9F1B,oBAAA;UAEK,kBAAA;;;EDIL,IAAA,CAAA,EAAA,MAAA;EAAsB,MAAA,CAAA,EAAA,OAAA;EAAA,QAAa,CAAA,EAAA,OAAA;EAAO,QACtC,CAAA,ECCH,oBDDG;;AADkD;;;iBCyB5C,eAAA,UAAyB,UAAU;ADhBzD;;;iBC8BsB,eAAA,oBAAmC;ADvBzD;;;AAA0D,iBCiCpC,iBAAA,CAAA,CDjCoC,ECiCf,ODjCe,CAAA,MAAA,CAAA;AAAO;;;iBC2CjD,qBAAA,SAA8B;AD3B9C;;;AAAsE,iBCqCtD,uBAAA,CAAA,CDrCsD,EAAA,IAAA;;;AAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-iWm3oeCM.js","names":["identityAPIFetcher: IdentityAPIFetcher | null","fetcher: IdentityAPIFetcher","request: Request","error: unknown","userId: string","userId?: string","permissionFetcher: PermissionFetcher | null","fetcher: PermissionFetcher","tenantId: string","context: IdentityContextData","options: ContextToUserInfoOptions","permissions: string[]","newVersion: number"],"sources":["../src/identity/identity-context.server.ts"],"sourcesContent":["/**\n * Identity Context Server Module\n * \n * Server-side identity context management.\n */\n\nimport type { AddressData, IdentityContextData, TenantMembership } from './types';\nimport { logger } from '../logging';\n\n/**\n * Identity API fetcher type (to avoid coupling with specific API client)\n */\nexport type IdentityAPIFetcher = (request: Request) => Promise<IdentityContextData>;\n\nlet identityAPIFetcher: IdentityAPIFetcher | null = null;\n\n/**\n * Configure identity API fetcher\n */\nexport function configureIdentityAPIFetcher(fetcher: IdentityAPIFetcher): void {\n identityAPIFetcher = fetcher;\n}\n\n// In-memory cache for identity context\nconst contextCache = new Map<\n string,\n {\n context: IdentityContextData;\n timestamp: number;\n version: number;\n }\n>();\n\nconst CONTEXT_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Fetch identity context from backend\n */\nexport async function fetchIdentityContext(request: Request): Promise<IdentityContextData> {\n if (!identityAPIFetcher) {\n throw new Error('Identity API fetcher not configured. Call configureIdentityAPIFetcher first.');\n }\n\n try {\n logger.info('Fetching identity context');\n const context = await identityAPIFetcher(request);\n \n logger.debug('Identity context fetched', {\n hasAnyMembership: context.hasAnyMembership,\n hasSubscription: context.hasSubscription,\n isOnboarded: context.isOnboarded,\n membershipCount: context.memberships?.length || 0,\n });\n\n return context;\n } catch (error: unknown) {\n logger.error('Failed to fetch identity context', error instanceof Error ? error : undefined);\n\n // Check if this is a CurrentUserMissing error\n if (\n error &&\n typeof error === 'object' &&\n 'response' in error &&\n (error as { response?: { status?: number; data?: { title?: string } } }).response?.status === 404 &&\n (error as { response?: { data?: { title?: string } } }).response?.data?.title === 'CurrentUserMissing'\n ) {\n logger.error('CurrentUserMissing error detected - session is invalid');\n const invalidSessionError = new Error('INVALID_SESSION');\n (invalidSessionError as Error & { isInvalidSession: boolean }).isInvalidSession = true;\n throw invalidSessionError;\n }\n\n throw error;\n }\n}\n\n/**\n * Get cached identity context or fetch fresh if needed\n */\nexport async function getIdentityContext(\n request: Request,\n userId: string,\n forceRefresh = false\n): Promise<IdentityContextData> {\n const cacheKey = userId;\n const cached = contextCache.get(cacheKey);\n const now = Date.now();\n\n // Return cached if valid and not forcing refresh\n if (!forceRefresh && cached && now - cached.timestamp < CONTEXT_CACHE_TTL) {\n logger.debug('Using cached identity context for user', { userId });\n return cached.context;\n }\n\n // Fetch fresh context\n const context = await fetchIdentityContext(request);\n\n // Cache the result\n contextCache.set(cacheKey, {\n context,\n timestamp: now,\n version: context.contextVersion ?? 0,\n });\n\n logger.debug('Cached fresh identity context for user', { userId });\n return context;\n}\n\n/**\n * Clear identity context cache for user\n */\nexport function clearIdentityContextCache(userId?: string): void {\n if (userId) {\n contextCache.delete(userId);\n logger.debug('Cleared identity context cache for user', { userId });\n } else {\n contextCache.clear();\n logger.debug('Cleared all identity context cache');\n }\n}\n\n/**\n * Permission fetcher type\n */\nexport type PermissionFetcher = (request: Request, tenantId: string) => Promise<string[]>;\n\nlet permissionFetcher: PermissionFetcher | null = null;\n\n/**\n * Configure permission fetcher\n */\nexport function configurePermissionFetcher(fetcher: PermissionFetcher): void {\n permissionFetcher = fetcher;\n}\n\n/**\n * Fetch user permissions for a specific tenant\n */\nexport async function fetchUserPermissions(request: Request, tenantId: string): Promise<string[]> {\n if (!tenantId) {\n logger.warn('No tenant ID provided, skipping permission fetch');\n return [];\n }\n\n if (!permissionFetcher) {\n throw new Error('Permission fetcher not configured. Call configurePermissionFetcher first.');\n }\n\n try {\n return await permissionFetcher(request, tenantId);\n } catch (error) {\n logger.error('Failed to fetch user permissions', error instanceof Error ? error : undefined);\n return [];\n }\n}\n\nexport interface UserContextInfo {\n tenants: string[];\n memberships: TenantMembership[];\n currentTenant?: string;\n isOnboarded: boolean;\n permissions: string[];\n hasSubscription: boolean;\n userId?: string;\n email?: string;\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n profileImageUrl?: string | null;\n phoneNumber?: string | null;\n timeZone?: string;\n addresses?: AddressData[];\n hasOwnerMembership: boolean;\n}\n\nexport interface ContextToUserInfoOptions {\n currentTenant?: string | null;\n request?: Request;\n}\n\n/**\n * Convert raw identity context into a tenant-aware user info shape.\n */\nexport async function contextToUserInfo(\n context: IdentityContextData,\n options: ContextToUserInfoOptions = {}\n): Promise<UserContextInfo> {\n const memberships = context.memberships || [];\n const tenants = memberships.map((membership) => membership.tenantId);\n const resolvedCurrentTenant =\n options.currentTenant && tenants.includes(options.currentTenant)\n ? options.currentTenant\n : tenants[0] || undefined;\n\n let permissions: string[] = [];\n if (resolvedCurrentTenant && options.request) {\n permissions = await fetchUserPermissions(options.request, resolvedCurrentTenant);\n }\n\n return {\n tenants,\n memberships,\n currentTenant: resolvedCurrentTenant,\n isOnboarded: context.isOnboarded ?? false,\n permissions,\n hasSubscription: context.hasSubscription ?? false,\n userId: context.userId,\n email: context.email,\n firstName: context.firstName,\n lastName: context.lastName,\n displayName: context.displayName,\n profileImageUrl: context.profileImageUrl,\n phoneNumber: context.phoneNumber,\n timeZone: context.timeZone,\n addresses: context.addresses,\n hasOwnerMembership: context.hasOwnerMembership ?? false,\n };\n}\n\n/**\n * Check if a cached context version differs from the latest version.\n */\nexport function hasContextVersionChanged(userId: string, newVersion: number): boolean {\n const cached = contextCache.get(userId);\n return !cached || cached.version !== newVersion;\n}\n\n/**\n * Mark a cached identity context as stale after a version change.\n */\nexport function updateContextVersion(userId: string, newVersion: number): void {\n const cached = contextCache.get(userId);\n if (!cached) return;\n\n cached.version = newVersion;\n cached.timestamp = 0;\n logger.debug('Marked identity context as stale for user', {\n userId,\n version: newVersion,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { TenantMembership } from "./types-DoIfDhl6.js";
|
|
2
|
-
import { AddressData, IdentityContextData } from "./types-uDrDMkCq.js";
|
|
3
|
-
|
|
4
|
-
//#region src/identity/identity-context.server.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Identity API fetcher type (to avoid coupling with specific API client)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Identity API fetcher type (to avoid coupling with specific API client)
|
|
11
|
-
*/
|
|
12
|
-
type IdentityAPIFetcher = (request: Request) => Promise<IdentityContextData>;
|
|
13
|
-
/**
|
|
14
|
-
* Configure identity API fetcher
|
|
15
|
-
*/
|
|
16
|
-
declare function configureIdentityAPIFetcher(fetcher: IdentityAPIFetcher): void;
|
|
17
|
-
/**
|
|
18
|
-
* Fetch identity context from backend
|
|
19
|
-
*/
|
|
20
|
-
declare function fetchIdentityContext(request: Request): Promise<IdentityContextData>;
|
|
21
|
-
/**
|
|
22
|
-
* Get cached identity context or fetch fresh if needed
|
|
23
|
-
*/
|
|
24
|
-
declare function getIdentityContext(request: Request, userId: string, forceRefresh?: boolean): Promise<IdentityContextData>;
|
|
25
|
-
/**
|
|
26
|
-
* Clear identity context cache for user
|
|
27
|
-
*/
|
|
28
|
-
declare function clearIdentityContextCache(userId?: string): void;
|
|
29
|
-
/**
|
|
30
|
-
* Permission fetcher type
|
|
31
|
-
*/
|
|
32
|
-
type PermissionFetcher = (request: Request, tenantId: string) => Promise<string[]>;
|
|
33
|
-
/**
|
|
34
|
-
* Configure permission fetcher
|
|
35
|
-
*/
|
|
36
|
-
declare function configurePermissionFetcher(fetcher: PermissionFetcher): void;
|
|
37
|
-
/**
|
|
38
|
-
* Fetch user permissions for a specific tenant
|
|
39
|
-
*/
|
|
40
|
-
declare function fetchUserPermissions(request: Request, tenantId: string): Promise<string[]>;
|
|
41
|
-
interface UserContextInfo {
|
|
42
|
-
tenants: string[];
|
|
43
|
-
memberships: TenantMembership[];
|
|
44
|
-
currentTenant?: string;
|
|
45
|
-
isOnboarded: boolean;
|
|
46
|
-
permissions: string[];
|
|
47
|
-
hasSubscription: boolean;
|
|
48
|
-
userId?: string;
|
|
49
|
-
email?: string;
|
|
50
|
-
firstName?: string | null;
|
|
51
|
-
lastName?: string | null;
|
|
52
|
-
displayName?: string | null;
|
|
53
|
-
profileImageUrl?: string | null;
|
|
54
|
-
phoneNumber?: string | null;
|
|
55
|
-
timeZone?: string;
|
|
56
|
-
addresses?: AddressData[];
|
|
57
|
-
hasOwnerMembership: boolean;
|
|
58
|
-
}
|
|
59
|
-
interface ContextToUserInfoOptions {
|
|
60
|
-
currentTenant?: string | null;
|
|
61
|
-
request?: Request;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Convert raw identity context into a tenant-aware user info shape.
|
|
65
|
-
*/
|
|
66
|
-
declare function contextToUserInfo(context: IdentityContextData, options?: ContextToUserInfoOptions): Promise<UserContextInfo>;
|
|
67
|
-
/**
|
|
68
|
-
* Check if a cached context version differs from the latest version.
|
|
69
|
-
*/
|
|
70
|
-
declare function hasContextVersionChanged(userId: string, newVersion: number): boolean;
|
|
71
|
-
/**
|
|
72
|
-
* Mark a cached identity context as stale after a version change.
|
|
73
|
-
*/
|
|
74
|
-
declare function updateContextVersion(userId: string, newVersion: number): void;
|
|
75
|
-
|
|
76
|
-
//#endregion
|
|
77
|
-
//# sourceMappingURL=identity-context.server.d.ts.map
|
|
78
|
-
|
|
79
|
-
export { ContextToUserInfoOptions, IdentityAPIFetcher, PermissionFetcher, UserContextInfo, clearIdentityContextCache as clearIdentityContextCache$1, configureIdentityAPIFetcher as configureIdentityAPIFetcher$1, configurePermissionFetcher as configurePermissionFetcher$1, contextToUserInfo as contextToUserInfo$1, fetchIdentityContext as fetchIdentityContext$1, fetchUserPermissions as fetchUserPermissions$1, getIdentityContext as getIdentityContext$1, hasContextVersionChanged as hasContextVersionChanged$1, updateContextVersion as updateContextVersion$1 };
|
|
80
|
-
//# sourceMappingURL=server-zEKMDpP7.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-zEKMDpP7.d.ts","names":[],"sources":["../src/identity/identity-context.server.ts"],"sourcesContent":null,"mappings":";;;;;;;;;AAYA;;AAA2C,KAA/B,kBAAA,GAA+B,CAAA,OAAA,EAAA,OAAA,EAAA,GAAY,OAAZ,CAAoB,mBAApB,CAAA;;;AAAmB;iBAO9C,2BAAA,UAAqC;;;AAArD;iBAmBsB,oBAAA,UAA8B,UAAU,QAAQ;;;AAAtE;AAA0C,iBAyCpB,kBAAA,CAzCoB,OAAA,EA0C/B,OA1C+B,EAAA,MAAA,EAAA,MAAA,EAAA,YAAA,CAAA,EAAA,OAAA,CAAA,EA6CvC,OA7CuC,CA6C/B,mBA7C+B,CAAA;;;;AAA2B,iBAyErD,yBAAA,CAzEqD,MAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;;;;AAyC/C,KA6CV,iBAAA,GA7C4B,CAAA,OAAA,EA6CE,OA7CF,EAAA,QAAA,EAAA,MAAA,EAAA,GA6CgC,OA7ChC,CAAA,MAAA,EAAA,CAAA;;;;AAIrC,iBAgDa,0BAAA,CAhDb,OAAA,EAgDiD,iBAhDjD,CAAA,EAAA,IAAA;AAAO;;;iBAuDY,oBAAA,UAA8B,4BAA4B;AA3BhE,UA6CC,eAAA,CA7CwB;;eA+C1B;;EAlCH,WAAA,EAAA,OAAiB;EAAA,WAAA,EAAA,MAAA,EAAA;EAAA,eAAa,EAAA,OAAA;EAAO,MAAuB,CAAA,EAAA,MAAA;EAAO,KAAA,CAAA,EAAA,MAAA;;;;EAO/D,eAAA,CAAA,EAAA,MAAA,GAAA,IAA0B;;;cAwC5B;EAjCQ,kBAAA,EAAA,OAAoB;;AAAU,UAqCnC,wBAAA,CArCmC;EAAO,aAAqB,CAAA,EAAA,MAAA,GAAA,IAAA;EAAO,OAAA,CAAA,EAuC3E,OAvC2E;AAkBvF;;;;AAeyB,iBAYH,iBAAA,CAZG,OAAA,EAad,mBAbc,EAAA,OAAA,CAAA,EAcd,wBAdc,CAAA,EAetB,OAfsB,CAed,eAfc,CAAA;AAIzB;;;iBA+CgB,wBAAA;AAvChB;;;AAEW,iBA6CK,oBAAA,CA7CL,MAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,CAAA,EAAA,IAAA"}
|
package/dist/types-BTjFzIhv.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
//#region src/logging/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Logging Types
|
|
4
|
-
*/
|
|
5
|
-
declare enum LogLevel {
|
|
6
|
-
DEBUG = "debug",
|
|
7
|
-
INFO = "info",
|
|
8
|
-
WARN = "warn",
|
|
9
|
-
ERROR = "error",
|
|
10
|
-
}
|
|
11
|
-
interface LogContext {
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
interface LogEntry {
|
|
15
|
-
timestamp: string;
|
|
16
|
-
level: LogLevel;
|
|
17
|
-
message: string;
|
|
18
|
-
context?: LogContext;
|
|
19
|
-
error?: Error;
|
|
20
|
-
}
|
|
21
|
-
interface LoggerConfig {
|
|
22
|
-
minLevel: LogLevel;
|
|
23
|
-
enableConsole: boolean;
|
|
24
|
-
enableRemote: boolean;
|
|
25
|
-
remoteEndpoint?: string;
|
|
26
|
-
serviceName?: string;
|
|
27
|
-
} //#endregion
|
|
28
|
-
|
|
29
|
-
//# sourceMappingURL=types.d.ts.map
|
|
30
|
-
export { LogContext, LogEntry, LogLevel as LogLevel$1, LoggerConfig };
|
|
31
|
-
//# sourceMappingURL=types-BTjFzIhv.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-BTjFzIhv.d.ts","names":[],"sources":["../src/logging/types.ts"],"sourcesContent":null,"mappings":";;;;aAIY,QAAA;EAAA,KAAA,GAAA,OAAQ;EAOH,IAAA,GAAA,MAAU;EAIV,IAAA,GAAA,MAAQ;EAAA,KAAA,GAAA,OAAA;;AAIb,UARK,UAAA,CAQL;EAAU,CAAA,GACZ,EAAA,MAAA,CAAA,EAAA,OAAA;AAAK;AAGE,UARA,QAAA,CAQY;;SANpB;;YAEG;UACF;;UAGO,YAAA;YACL"}
|
package/dist/types-DoIfDhl6.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
//#region src/tenant/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Tenant Types
|
|
4
|
-
*/
|
|
5
|
-
interface TenantInfo {
|
|
6
|
-
id: string;
|
|
7
|
-
name?: string;
|
|
8
|
-
role?: string;
|
|
9
|
-
}
|
|
10
|
-
interface TenantMembership {
|
|
11
|
-
tenantId: string;
|
|
12
|
-
tenantName?: string;
|
|
13
|
-
tenantSlug?: string | null;
|
|
14
|
-
organizationName?: string;
|
|
15
|
-
role?: string;
|
|
16
|
-
roles?: string[];
|
|
17
|
-
ownerUserId?: string | null;
|
|
18
|
-
isOwner?: boolean;
|
|
19
|
-
}
|
|
20
|
-
interface OrganizationBranding {
|
|
21
|
-
logoUrl?: string | null;
|
|
22
|
-
themePrimary?: string | null;
|
|
23
|
-
themeMode?: string | null;
|
|
24
|
-
emailBranding?: string | null;
|
|
25
|
-
}
|
|
26
|
-
interface OrganizationData {
|
|
27
|
-
organizationId: string;
|
|
28
|
-
name: string;
|
|
29
|
-
slug?: string | null;
|
|
30
|
-
timeZone?: string | null;
|
|
31
|
-
currency?: string | null;
|
|
32
|
-
branding?: OrganizationBranding | null;
|
|
33
|
-
ownerUserId?: string | null;
|
|
34
|
-
ownerAssignedAtUtc?: string | null;
|
|
35
|
-
defaultOperatingAccountId?: string | null;
|
|
36
|
-
}
|
|
37
|
-
interface TenantContextType {
|
|
38
|
-
currentTenant: string | null;
|
|
39
|
-
availableTenants: string[];
|
|
40
|
-
memberships: Map<string, TenantMembership>;
|
|
41
|
-
currentOrganization: OrganizationData | null;
|
|
42
|
-
currentMembership: TenantMembership | null;
|
|
43
|
-
organizations: Map<string, OrganizationData>;
|
|
44
|
-
isLoading: boolean;
|
|
45
|
-
switchTenant: (tenantId: string) => void;
|
|
46
|
-
refreshTenants: () => void;
|
|
47
|
-
refreshOrganization: () => void;
|
|
48
|
-
getOrganization: (tenantId: string) => OrganizationData | null;
|
|
49
|
-
getMembership: (tenantId: string) => TenantMembership | null;
|
|
50
|
-
getTenantName: (tenantId: string) => string;
|
|
51
|
-
} //#endregion
|
|
52
|
-
|
|
53
|
-
//# sourceMappingURL=types.d.ts.map
|
|
54
|
-
export { OrganizationBranding, OrganizationData, TenantContextType, TenantInfo, TenantMembership };
|
|
55
|
-
//# sourceMappingURL=types-DoIfDhl6.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-DoIfDhl6.d.ts","names":[],"sources":["../src/tenant/types.ts"],"sourcesContent":null,"mappings":";;;;UAIiB,UAAA;EAAA,EAAA,EAAA,MAAA;EAMA,IAAA,CAAA,EAAA,MAAA;EAWA,IAAA,CAAA,EAAA,MAAA;AAOjB;AAYiB,UA9BA,gBAAA,CA8BiB;EAAA,QAAA,EAAA,MAAA;EAAA,UAGP,CAAA,EAAA,MAAA;EAAgB,UAA5B,CAAA,EAAA,MAAA,GAAA,IAAA;EAAG,gBACK,CAAA,EAAA,MAAA;EAAgB,IAClB,CAAA,EAAA,MAAA;EAAgB,KACR,CAAA,EAAA,MAAA,EAAA;EAAgB,WAA5B,CAAA,EAAA,MAAA,GAAA,IAAA;EAAG,OAKqB,CAAA,EAAA,OAAA;;AACc,UA/BtC,oBAAA,CA+BsC;;;;;;UAxBtC,gBAAA;;;;;;aAMJ;;;;;UAMI,iBAAA;;;eAGF,YAAY;uBACJ;qBACF;iBACJ,YAAY;;;;;yCAKY;uCACF"}
|