@eminuckan/spine 0.2.4 → 0.3.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/CHANGELOG.md +19 -0
- package/CODE_OF_CONDUCT.md +39 -0
- package/CONTRIBUTING.md +78 -0
- package/MAINTAINERS.md +43 -0
- package/README.md +170 -135
- package/ROADMAP.md +58 -0
- package/SECURITY.md +37 -0
- package/dist/access/evaluator.d.ts.map +1 -1
- package/dist/access/index.js +1 -1
- package/dist/access/types.d.ts +10 -2
- package/dist/access/types.d.ts.map +1 -1
- package/dist/{access-DlAqsVGQ.js → access-CGrsDMwu.js} +9 -7
- package/dist/access-CGrsDMwu.js.map +1 -0
- package/dist/api-client/api-config.server.d.ts +27 -0
- package/dist/api-client/api-config.server.d.ts.map +1 -1
- package/dist/api-client/error-codes.d.ts +6 -31
- package/dist/api-client/error-codes.d.ts.map +1 -1
- package/dist/api-client/index.js +2 -2
- package/dist/api-client/server.d.ts +1 -1
- package/dist/api-client/server.d.ts.map +1 -1
- package/dist/api-client/server.js +3 -3
- package/dist/{api-client-CZapXIF8.js → api-client-CgZUMukO.js} +9 -26
- package/dist/api-client-CgZUMukO.js.map +1 -0
- package/dist/auth/auth.server.d.ts.map +1 -1
- package/dist/auth/authorization-parameters.d.ts +5 -0
- package/dist/auth/authorization-parameters.d.ts.map +1 -0
- package/dist/auth/route-protection.server.d.ts +20 -4
- package/dist/auth/route-protection.server.d.ts.map +1 -1
- package/dist/auth/server.d.ts +1 -1
- package/dist/auth/server.d.ts.map +1 -1
- package/dist/auth/server.js +5 -5
- package/dist/auth/types.d.ts +24 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/{auth.server-B3ODGIX4.js → auth.server-D5oiz9aF.js} +62 -53
- package/dist/auth.server-D5oiz9aF.js.map +1 -0
- package/dist/{auth.server-wdKsjQsn.js → auth.server-lC94lmlx.js} +3 -3
- package/dist/identity/identity-context.server.d.ts +2 -0
- package/dist/identity/identity-context.server.d.ts.map +1 -1
- package/dist/identity/identity-store.d.ts +19 -2
- package/dist/identity/identity-store.d.ts.map +1 -1
- package/dist/identity/index.js +2 -2
- package/dist/identity/server.js +2 -2
- package/dist/identity/types.d.ts +3 -1
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/{identity-yT2ijZF0.js → identity-C5un-xAs.js} +104 -60
- package/dist/identity-C5un-xAs.js.map +1 -0
- package/dist/index.js +8 -8
- package/dist/logging/index.js +1 -1
- package/dist/{logging-_h2mi3L9.js → logging-DviiAkLx.js} +2 -2
- package/dist/{logging-_h2mi3L9.js.map → logging-DviiAkLx.js.map} +1 -1
- package/dist/permissions/constants.d.ts +3 -0
- package/dist/permissions/constants.d.ts.map +1 -1
- package/dist/permissions/index.js +1 -1
- package/dist/permissions/permission-store.d.ts +1 -1
- package/dist/permissions/types.d.ts +1 -1
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/{permissions-OUK8Eql2.js → permissions-_n1hIG7Q.js} +6 -12
- package/dist/permissions-_n1hIG7Q.js.map +1 -0
- package/dist/query-client/types.d.ts +9 -9
- package/dist/query-client/types.d.ts.map +1 -1
- package/dist/query-client-Bh3TyB8W.js.map +1 -1
- package/dist/react-router/index.js +8 -8
- package/dist/react-router/server.js +14 -14
- package/dist/{redis-session-storage.server-D0-72FV-.js → redis-session-storage.server-v2VcpFsQ.js} +2 -2
- package/dist/{redis-session-storage.server-D0-72FV-.js.map → redis-session-storage.server-v2VcpFsQ.js.map} +1 -1
- package/dist/{server-B7JN86_0.js → server-B6PCxc-T.js} +31 -83
- package/dist/server-B6PCxc-T.js.map +1 -0
- package/dist/{server-DeBlxoaW.js → server-C96NMFBy.js} +17 -9
- package/dist/server-C96NMFBy.js.map +1 -0
- package/dist/{server-Ce9jQRdL.js → server-Ca-3DJf2.js} +44 -11
- package/dist/server-Ca-3DJf2.js.map +1 -0
- package/dist/{server-BmiuV28c.js → server-Dxsww9v9.js} +5 -4
- package/dist/server-Dxsww9v9.js.map +1 -0
- package/dist/server.js +14 -14
- package/dist/signalr/index.js +1 -1
- package/dist/signalr/types.d.ts +1 -1
- package/dist/{signalr-CSfCz1Ik.js → signalr-k-70VHKo.js} +3 -2
- package/dist/signalr-k-70VHKo.js.map +1 -0
- package/dist/tenant/index.js +2 -2
- package/dist/tenant/server.js +4 -4
- package/dist/tenant/tenant-context.d.ts +17 -1
- package/dist/tenant/tenant-context.d.ts.map +1 -1
- package/dist/tenant/tenant-cookie.server.d.ts.map +1 -1
- package/dist/tenant/tenant-store.d.ts +54 -9
- package/dist/tenant/tenant-store.d.ts.map +1 -1
- package/dist/tenant/tenant.server.d.ts +20 -1
- package/dist/tenant/tenant.server.d.ts.map +1 -1
- package/dist/tenant/types.d.ts +29 -9
- package/dist/tenant/types.d.ts.map +1 -1
- package/dist/tenant-DfLemb6n.js +384 -0
- package/dist/tenant-DfLemb6n.js.map +1 -0
- package/docs/adapters.md +87 -0
- package/docs/architecture.md +193 -0
- package/docs/backend-adaptation.md +249 -0
- package/docs/codex-for-oss-application.md +39 -0
- package/docs/codex-maintenance.md +47 -0
- package/docs/installation.md +213 -0
- package/docs/module-reference.md +207 -0
- package/docs/releasing.md +92 -0
- package/package.json +21 -3
- package/dist/access-DlAqsVGQ.js.map +0 -1
- package/dist/api-client-CZapXIF8.js.map +0 -1
- package/dist/auth.server-B3ODGIX4.js.map +0 -1
- package/dist/identity-yT2ijZF0.js.map +0 -1
- package/dist/permissions-OUK8Eql2.js.map +0 -1
- package/dist/server-B7JN86_0.js.map +0 -1
- package/dist/server-BmiuV28c.js.map +0 -1
- package/dist/server-Ce9jQRdL.js.map +0 -1
- package/dist/server-DeBlxoaW.js.map +0 -1
- package/dist/signalr-CSfCz1Ik.js.map +0 -1
- package/dist/tenant-BErqEENp.js +0 -256
- package/dist/tenant-BErqEENp.js.map +0 -1
|
@@ -3,8 +3,9 @@ import { useEffect } from "react";
|
|
|
3
3
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/identity/identity-store.ts
|
|
6
|
-
const
|
|
6
|
+
const initialIdentityContext = {
|
|
7
7
|
hasAnyMembership: false,
|
|
8
|
+
hasEntitlement: false,
|
|
8
9
|
hasSubscription: false,
|
|
9
10
|
isOnboarded: false,
|
|
10
11
|
tenants: [],
|
|
@@ -31,12 +32,69 @@ const DEFAULT_IDENTITY_STORE_CONFIG = {
|
|
|
31
32
|
fetcher: (input, init) => fetch(input, init)
|
|
32
33
|
};
|
|
33
34
|
let identityStoreConfig = { ...DEFAULT_IDENTITY_STORE_CONFIG };
|
|
35
|
+
var IdentityUnauthorizedError = class extends Error {
|
|
36
|
+
constructor(reason, response) {
|
|
37
|
+
super(reason);
|
|
38
|
+
this.reason = reason;
|
|
39
|
+
this.response = response;
|
|
40
|
+
this.name = "IdentityUnauthorizedError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
34
43
|
function getIdentityStoreConfig() {
|
|
35
44
|
return {
|
|
36
45
|
...DEFAULT_IDENTITY_STORE_CONFIG,
|
|
37
46
|
...identityStoreConfig
|
|
38
47
|
};
|
|
39
48
|
}
|
|
49
|
+
function isRecord(value) {
|
|
50
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
function appendRefreshParam(endpoint, forceRefresh) {
|
|
53
|
+
if (!forceRefresh) return endpoint;
|
|
54
|
+
const separator = endpoint.includes("?") ? "&" : "?";
|
|
55
|
+
return `${endpoint}${separator}refresh=true`;
|
|
56
|
+
}
|
|
57
|
+
async function readJsonResponse(response) {
|
|
58
|
+
const text = await response.text();
|
|
59
|
+
return text.trim().length > 0 ? JSON.parse(text) : null;
|
|
60
|
+
}
|
|
61
|
+
function normalizeIdentityContextPayload(payload) {
|
|
62
|
+
if (!isRecord(payload)) return null;
|
|
63
|
+
if (payload.success === false) return null;
|
|
64
|
+
const nestedContext = payload.context ?? payload.data ?? payload.identity ?? payload.user;
|
|
65
|
+
if (isRecord(nestedContext)) return nestedContext;
|
|
66
|
+
return payload;
|
|
67
|
+
}
|
|
68
|
+
function normalizeIdentityPermissionsPayload(payload) {
|
|
69
|
+
if (Array.isArray(payload)) return payload.filter((permission) => typeof permission === "string");
|
|
70
|
+
if (!isRecord(payload)) return [];
|
|
71
|
+
const nestedPermissions = payload.permissions ?? (isRecord(payload.data) ? payload.data.permissions : payload.data) ?? (isRecord(payload.context) ? payload.context.permissions : void 0);
|
|
72
|
+
return Array.isArray(nestedPermissions) ? nestedPermissions.filter((permission) => typeof permission === "string") : [];
|
|
73
|
+
}
|
|
74
|
+
async function defaultFetchIdentityContext({ endpoint, fetch: fetchFn, forceRefresh }) {
|
|
75
|
+
const response = await fetchFn(appendRefreshParam(endpoint, forceRefresh), {
|
|
76
|
+
method: "GET",
|
|
77
|
+
headers: { "Content-Type": "application/json" }
|
|
78
|
+
});
|
|
79
|
+
if (response.ok) return normalizeIdentityContextPayload(await readJsonResponse(response));
|
|
80
|
+
if (response.status === 404) {
|
|
81
|
+
const errorData = await readJsonResponse(response).catch(() => ({}));
|
|
82
|
+
if (isRecord(errorData) && (errorData.code === "CurrentUserMissing" || errorData.title === "CurrentUserMissing")) throw new IdentityUnauthorizedError("current-user-missing", response);
|
|
83
|
+
}
|
|
84
|
+
if (response.status === 401) throw new IdentityUnauthorizedError("unauthorized", response);
|
|
85
|
+
console.error("Failed to refresh identity context:", response.status);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
async function defaultFetchIdentityPermissions({ endpoint, fetch: fetchFn }) {
|
|
89
|
+
const response = await fetchFn(endpoint, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
headers: { "Content-Type": "application/json" }
|
|
92
|
+
});
|
|
93
|
+
if (response.ok) return normalizeIdentityPermissionsPayload(await readJsonResponse(response));
|
|
94
|
+
if (response.status === 401) throw new IdentityUnauthorizedError("unauthorized", response);
|
|
95
|
+
console.error("Failed to refresh permissions:", response.status);
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
40
98
|
function handleUnauthorized(details) {
|
|
41
99
|
const config = getIdentityStoreConfig();
|
|
42
100
|
const unauthorizedDetails = {
|
|
@@ -70,8 +128,8 @@ function resetIdentityStoreConfig() {
|
|
|
70
128
|
function configureIdentityEndpoints(config) {
|
|
71
129
|
configureIdentityStore(config);
|
|
72
130
|
}
|
|
73
|
-
const useIdentityStore = create((set) => ({
|
|
74
|
-
context:
|
|
131
|
+
const useIdentityStore = create((set, get) => ({
|
|
132
|
+
context: initialIdentityContext,
|
|
75
133
|
setContext: (newContext) => {
|
|
76
134
|
set((state) => ({ context: {
|
|
77
135
|
...state.context,
|
|
@@ -87,48 +145,33 @@ const useIdentityStore = create((set) => ({
|
|
|
87
145
|
...state.context,
|
|
88
146
|
isLoading: true
|
|
89
147
|
} }));
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
148
|
+
const fetchContext = config.fetchContext ?? defaultFetchIdentityContext;
|
|
149
|
+
const newContext = await fetchContext({
|
|
150
|
+
endpoint: config.contextEndpoint,
|
|
151
|
+
fetch: config.fetcher,
|
|
152
|
+
forceRefresh: true,
|
|
153
|
+
currentContext: get().context
|
|
93
154
|
});
|
|
94
|
-
if (
|
|
95
|
-
const newContext = await response.json();
|
|
155
|
+
if (newContext) {
|
|
96
156
|
set({ context: {
|
|
157
|
+
...get().context,
|
|
97
158
|
...newContext,
|
|
98
159
|
isLoading: false,
|
|
99
160
|
lastUpdated: new Date()
|
|
100
161
|
} });
|
|
101
162
|
console.log("Identity context refreshed:", newContext);
|
|
102
|
-
} else
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
response
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
console.error("Failed to refresh identity context:", response.status);
|
|
113
|
-
set((state) => ({ context: {
|
|
114
|
-
...state.context,
|
|
115
|
-
isLoading: false
|
|
116
|
-
} }));
|
|
117
|
-
} else if (response.status === 401) {
|
|
118
|
-
console.error("Identity context refresh returned 401, redirecting to logout");
|
|
163
|
+
} else set((state) => ({ context: {
|
|
164
|
+
...state.context,
|
|
165
|
+
isLoading: false
|
|
166
|
+
} }));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error instanceof IdentityUnauthorizedError) {
|
|
119
169
|
handleUnauthorized({
|
|
120
|
-
reason:
|
|
121
|
-
response
|
|
170
|
+
reason: error.reason,
|
|
171
|
+
response: error.response
|
|
122
172
|
});
|
|
123
173
|
return;
|
|
124
|
-
} else {
|
|
125
|
-
console.error("Failed to refresh identity context:", response.status);
|
|
126
|
-
set((state) => ({ context: {
|
|
127
|
-
...state.context,
|
|
128
|
-
isLoading: false
|
|
129
|
-
} }));
|
|
130
174
|
}
|
|
131
|
-
} catch (error) {
|
|
132
175
|
console.error("Error refreshing identity context:", error);
|
|
133
176
|
set((state) => ({ context: {
|
|
134
177
|
...state.context,
|
|
@@ -140,35 +183,36 @@ const useIdentityStore = create((set) => ({
|
|
|
140
183
|
try {
|
|
141
184
|
const config = getIdentityStoreConfig();
|
|
142
185
|
console.log("Refreshing permissions...");
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
186
|
+
const fetchPermissions = config.fetchPermissions ?? defaultFetchIdentityPermissions;
|
|
187
|
+
const permissions = await fetchPermissions({
|
|
188
|
+
endpoint: config.permissionsEndpoint,
|
|
189
|
+
fetch: config.fetcher,
|
|
190
|
+
currentContext: get().context
|
|
146
191
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log("Permissions refreshed:", { count: permissions.length });
|
|
156
|
-
} else if (response.status === 401) {
|
|
157
|
-
console.error("Permissions refresh returned 401, redirecting to logout");
|
|
192
|
+
set((state) => ({ context: {
|
|
193
|
+
...state.context,
|
|
194
|
+
permissions,
|
|
195
|
+
lastUpdated: new Date()
|
|
196
|
+
} }));
|
|
197
|
+
console.log("Permissions refreshed:", { count: permissions.length });
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof IdentityUnauthorizedError) {
|
|
158
200
|
handleUnauthorized({
|
|
159
|
-
reason:
|
|
160
|
-
response
|
|
201
|
+
reason: error.reason,
|
|
202
|
+
response: error.response
|
|
161
203
|
});
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
164
206
|
console.error("Error refreshing permissions:", error);
|
|
165
207
|
}
|
|
166
208
|
},
|
|
167
209
|
reset: () => {
|
|
168
|
-
set({ context:
|
|
210
|
+
set({ context: initialIdentityContext });
|
|
169
211
|
}
|
|
170
212
|
}));
|
|
171
213
|
const useIsOnboarded = () => useIdentityStore((state) => state.context.isOnboarded);
|
|
214
|
+
const useHasEntitlement = () => useIdentityStore((state) => state.context.hasEntitlement ?? state.context.hasSubscription);
|
|
215
|
+
/** @deprecated Use useHasEntitlement or an app-specific selector instead. */
|
|
172
216
|
const useHasSubscription = () => useIdentityStore((state) => state.context.hasSubscription);
|
|
173
217
|
const useUserTenants = () => useIdentityStore((state) => ({
|
|
174
218
|
tenants: state.context.tenants,
|
|
@@ -188,14 +232,14 @@ const useIdentityContext = () => {
|
|
|
188
232
|
|
|
189
233
|
//#endregion
|
|
190
234
|
//#region src/identity/identity-context-provider.tsx
|
|
191
|
-
function IdentityContextProvider({ children, initialContext
|
|
235
|
+
function IdentityContextProvider({ children, initialContext, accessToken, signalRClient }) {
|
|
192
236
|
const setContext = useIdentityStore((state) => state.setContext);
|
|
193
237
|
const refreshContext = useIdentityStore((state) => state.refreshContext);
|
|
194
238
|
const refreshPermissions = useIdentityStore((state) => state.refreshPermissions);
|
|
195
239
|
const contextVersion = useIdentityStore((state) => state.context.contextVersion);
|
|
196
240
|
useEffect(() => {
|
|
197
|
-
if (initialContext
|
|
198
|
-
}, [initialContext
|
|
241
|
+
if (initialContext) setContext(initialContext);
|
|
242
|
+
}, [initialContext, setContext]);
|
|
199
243
|
useEffect(() => {
|
|
200
244
|
if (!signalRClient) return;
|
|
201
245
|
const handleContextChange = (event) => {
|
|
@@ -218,8 +262,8 @@ function IdentityContextProvider({ children, initialContext: initialContext$1, a
|
|
|
218
262
|
signalRClient
|
|
219
263
|
]);
|
|
220
264
|
useEffect(() => {
|
|
221
|
-
if (!initialContext
|
|
222
|
-
}, [initialContext
|
|
265
|
+
if (!initialContext) refreshContext();
|
|
266
|
+
}, [initialContext, refreshContext]);
|
|
223
267
|
useEffect(() => {
|
|
224
268
|
return () => {
|
|
225
269
|
if (signalRClient) signalRClient.disconnect().catch((error) => {
|
|
@@ -231,5 +275,5 @@ function IdentityContextProvider({ children, initialContext: initialContext$1, a
|
|
|
231
275
|
}
|
|
232
276
|
|
|
233
277
|
//#endregion
|
|
234
|
-
export { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, resetIdentityStoreConfig, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants };
|
|
235
|
-
//# sourceMappingURL=identity-
|
|
278
|
+
export { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants };
|
|
279
|
+
//# sourceMappingURL=identity-C5un-xAs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-C5un-xAs.js","names":["initialIdentityContext: IdentityContextData","DEFAULT_IDENTITY_STORE_CONFIG: Required<\n Pick<IdentityStoreConfig, 'contextEndpoint' | 'permissionsEndpoint' | 'logoutPath' | 'fetcher'>\n>","identityStoreConfig: IdentityStoreConfig","reason: IdentityUnauthorizedDetails['reason']","response?: Response","value: unknown","endpoint: string","forceRefresh: boolean","response: Response","payload: unknown","details: Omit<IdentityUnauthorizedDetails, 'logoutPath'>","unauthorizedDetails: IdentityUnauthorizedDetails","config: IdentityStoreConfig","config: {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n}","event: IdentityContextChangedEvent"],"sources":["../src/identity/identity-store.ts","../src/identity/identity-context-provider.tsx"],"sourcesContent":["/**\n * Identity Store (Zustand)\n * \n * Centralized state management for identity context.\n */\n\nimport { create } from 'zustand';\nimport type { IdentityContextData } from './types';\n\nexport const initialIdentityContext: IdentityContextData = {\n hasAnyMembership: false,\n hasEntitlement: false,\n hasSubscription: false,\n isOnboarded: false,\n tenants: [],\n currentTenant: undefined,\n permissions: [],\n contextVersion: 0,\n isLoading: true,\n userId: undefined,\n email: undefined,\n firstName: undefined,\n lastName: undefined,\n displayName: undefined,\n profileImageUrl: undefined,\n phoneNumber: undefined,\n timeZone: undefined,\n addresses: undefined,\n hasOwnerMembership: false,\n memberships: undefined,\n};\n\nexport interface IdentityContextFetchContext {\n endpoint: string;\n fetch: typeof fetch;\n forceRefresh: boolean;\n currentContext: IdentityContextData;\n}\n\nexport interface IdentityPermissionsFetchContext {\n endpoint: string;\n fetch: typeof fetch;\n currentContext: IdentityContextData;\n}\n\nexport interface IdentityState {\n context: IdentityContextData;\n\n // Actions\n setContext: (context: Partial<IdentityContextData>) => void;\n refreshContext: () => Promise<void>;\n refreshPermissions: () => Promise<void>;\n reset: () => void;\n}\n\nexport interface IdentityUnauthorizedDetails {\n reason: 'current-user-missing' | 'unauthorized';\n logoutPath: string;\n response?: Response;\n}\n\nexport interface IdentityStoreConfig {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n logoutPath?: string;\n fetcher?: typeof fetch;\n fetchContext?: (context: IdentityContextFetchContext) => Promise<Partial<IdentityContextData> | null>;\n fetchPermissions?: (context: IdentityPermissionsFetchContext) => Promise<string[]>;\n onUnauthorized?: (details: IdentityUnauthorizedDetails) => void;\n}\n\nconst DEFAULT_IDENTITY_STORE_CONFIG: Required<\n Pick<IdentityStoreConfig, 'contextEndpoint' | 'permissionsEndpoint' | 'logoutPath' | 'fetcher'>\n> = {\n contextEndpoint: '/api/identity/context',\n permissionsEndpoint: '/api/identity/permissions',\n logoutPath: '/auth/logout',\n fetcher: (input, init) => fetch(input, init),\n};\n\nlet identityStoreConfig: IdentityStoreConfig = {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n};\n\nclass IdentityUnauthorizedError extends Error {\n constructor(\n readonly reason: IdentityUnauthorizedDetails['reason'],\n readonly response?: Response\n ) {\n super(reason);\n this.name = 'IdentityUnauthorizedError';\n }\n}\n\nfunction getIdentityStoreConfig(): IdentityStoreConfig & typeof DEFAULT_IDENTITY_STORE_CONFIG {\n return {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n ...identityStoreConfig,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction appendRefreshParam(endpoint: string, forceRefresh: boolean): string {\n if (!forceRefresh) {\n return endpoint;\n }\n\n const separator = endpoint.includes('?') ? '&' : '?';\n return `${endpoint}${separator}refresh=true`;\n}\n\nasync function readJsonResponse(response: Response): Promise<unknown> {\n const text = await response.text();\n return text.trim().length > 0 ? JSON.parse(text) : null;\n}\n\nexport function normalizeIdentityContextPayload(payload: unknown): Partial<IdentityContextData> | null {\n if (!isRecord(payload)) {\n return null;\n }\n\n if (payload.success === false) {\n return null;\n }\n\n const nestedContext = payload.context ?? payload.data ?? payload.identity ?? payload.user;\n if (isRecord(nestedContext)) {\n return nestedContext as Partial<IdentityContextData>;\n }\n\n return payload as Partial<IdentityContextData>;\n}\n\nexport function normalizeIdentityPermissionsPayload(payload: unknown): string[] {\n if (Array.isArray(payload)) {\n return payload.filter((permission): permission is string => typeof permission === 'string');\n }\n\n if (!isRecord(payload)) {\n return [];\n }\n\n const nestedPermissions =\n payload.permissions ??\n (isRecord(payload.data) ? payload.data.permissions : payload.data) ??\n (isRecord(payload.context) ? payload.context.permissions : undefined);\n\n return Array.isArray(nestedPermissions)\n ? nestedPermissions.filter((permission): permission is string => typeof permission === 'string')\n : [];\n}\n\nasync function defaultFetchIdentityContext({\n endpoint,\n fetch: fetchFn,\n forceRefresh,\n}: IdentityContextFetchContext): Promise<Partial<IdentityContextData> | null> {\n const response = await fetchFn(appendRefreshParam(endpoint, forceRefresh), {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n return normalizeIdentityContextPayload(await readJsonResponse(response));\n }\n\n if (response.status === 404) {\n const errorData = await readJsonResponse(response).catch(() => ({}));\n if (\n isRecord(errorData) &&\n (errorData.code === 'CurrentUserMissing' || errorData.title === 'CurrentUserMissing')\n ) {\n throw new IdentityUnauthorizedError('current-user-missing', response);\n }\n }\n\n if (response.status === 401) {\n throw new IdentityUnauthorizedError('unauthorized', response);\n }\n\n console.error('Failed to refresh identity context:', response.status);\n return null;\n}\n\nasync function defaultFetchIdentityPermissions({\n endpoint,\n fetch: fetchFn,\n}: IdentityPermissionsFetchContext): Promise<string[]> {\n const response = await fetchFn(endpoint, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n return normalizeIdentityPermissionsPayload(await readJsonResponse(response));\n }\n\n if (response.status === 401) {\n throw new IdentityUnauthorizedError('unauthorized', response);\n }\n\n console.error('Failed to refresh permissions:', response.status);\n return [];\n}\n\nfunction handleUnauthorized(details: Omit<IdentityUnauthorizedDetails, 'logoutPath'>): void {\n const config = getIdentityStoreConfig();\n const unauthorizedDetails: IdentityUnauthorizedDetails = {\n ...details,\n logoutPath: config.logoutPath,\n };\n\n if (config.onUnauthorized) {\n config.onUnauthorized(unauthorizedDetails);\n return;\n }\n\n if (typeof window !== 'undefined') {\n window.location.href = config.logoutPath;\n }\n}\n\n/**\n * Configure the identity store for different app/back-end conventions.\n */\nexport function configureIdentityStore(config: IdentityStoreConfig): void {\n identityStoreConfig = {\n ...identityStoreConfig,\n ...config,\n };\n}\n\n/**\n * Reset identity store configuration to the built-in defaults.\n */\nexport function resetIdentityStoreConfig(): void {\n identityStoreConfig = {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n };\n}\n\n/**\n * Backward-compatible endpoint-only configuration helper.\n */\nexport function configureIdentityEndpoints(config: {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n}): void {\n configureIdentityStore(config);\n}\n\nexport const useIdentityStore = create<IdentityState>((set, get) => ({\n context: initialIdentityContext,\n\n setContext: (newContext) => {\n set((state) => ({\n context: {\n ...state.context,\n ...newContext,\n isLoading: false,\n lastUpdated: new Date(),\n },\n }));\n },\n\n refreshContext: async () => {\n try {\n const config = getIdentityStoreConfig();\n\n set((state) => ({\n context: { ...state.context, isLoading: true },\n }));\n\n const fetchContext = config.fetchContext ?? defaultFetchIdentityContext;\n const newContext = await fetchContext({\n endpoint: config.contextEndpoint,\n fetch: config.fetcher,\n forceRefresh: true,\n currentContext: get().context,\n });\n\n if (newContext) {\n set({\n context: {\n ...get().context,\n ...newContext,\n isLoading: false,\n lastUpdated: new Date(),\n },\n });\n console.log('Identity context refreshed:', newContext);\n } else {\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n }\n } catch (error) {\n if (error instanceof IdentityUnauthorizedError) {\n handleUnauthorized({\n reason: error.reason,\n response: error.response,\n });\n return;\n }\n\n console.error('Error refreshing identity context:', error);\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n }\n },\n\n refreshPermissions: async () => {\n try {\n const config = getIdentityStoreConfig();\n\n console.log('Refreshing permissions...');\n\n const fetchPermissions = config.fetchPermissions ?? defaultFetchIdentityPermissions;\n const permissions = await fetchPermissions({\n endpoint: config.permissionsEndpoint,\n fetch: config.fetcher,\n currentContext: get().context,\n });\n\n set((state) => ({\n context: {\n ...state.context,\n permissions,\n lastUpdated: new Date(),\n },\n }));\n\n console.log('Permissions refreshed:', {\n count: permissions.length,\n });\n } catch (error) {\n if (error instanceof IdentityUnauthorizedError) {\n handleUnauthorized({\n reason: error.reason,\n response: error.response,\n });\n return;\n }\n\n console.error('Error refreshing permissions:', error);\n }\n },\n\n reset: () => {\n set({ context: initialIdentityContext });\n },\n}));\n\n// Convenience selectors\nexport const useIsOnboarded = () => useIdentityStore((state) => state.context.isOnboarded);\nexport const useHasEntitlement = () => useIdentityStore((state) => state.context.hasEntitlement ?? state.context.hasSubscription);\n/** @deprecated Use useHasEntitlement or an app-specific selector instead. */\nexport const useHasSubscription = () => useIdentityStore((state) => state.context.hasSubscription);\nexport const useUserTenants = () =>\n useIdentityStore((state) => ({\n tenants: state.context.tenants,\n currentTenant: state.context.currentTenant,\n }));\nexport const useUserPermissions = () => useIdentityStore((state) => state.context.permissions);\nexport const useIdentityContext = () => {\n const context = useIdentityStore((state) => state.context);\n const refreshContext = useIdentityStore((state) => state.refreshContext);\n const refreshPermissions = useIdentityStore((state) => state.refreshPermissions);\n\n return { context, refreshContext, refreshPermissions };\n};\n","/**\n * Identity Context Provider\n * \n * Initializes the identity store and listens to SignalR events.\n */\n\nimport { useEffect, type ReactNode } from 'react';\nimport { useIdentityStore } from './identity-store';\nimport type { IdentityContextData, IdentityContextChangedEvent } from './types';\n\ninterface IdentityContextProviderProps {\n children: ReactNode;\n initialContext?: Partial<IdentityContextData>;\n accessToken?: string | null;\n signalRClient?: {\n onIdentityContextChanged: (handler: (event: IdentityContextChangedEvent) => void) => () => void;\n disconnect: () => Promise<void>;\n };\n}\n\nexport function IdentityContextProvider({\n children,\n initialContext,\n accessToken,\n signalRClient,\n}: IdentityContextProviderProps) {\n const setContext = useIdentityStore((state) => state.setContext);\n const refreshContext = useIdentityStore((state) => state.refreshContext);\n const refreshPermissions = useIdentityStore((state) => state.refreshPermissions);\n const contextVersion = useIdentityStore((state) => state.context.contextVersion);\n\n // Initialize store with initial context from server\n useEffect(() => {\n if (initialContext) {\n setContext(initialContext);\n }\n }, [initialContext, setContext]);\n\n // Handle SignalR identity context changes\n useEffect(() => {\n if (!signalRClient) return;\n\n const handleContextChange = (event: IdentityContextChangedEvent) => {\n console.log('Identity context changed via SignalR:', event);\n\n if (event.contextVersion > (contextVersion ?? 0)) {\n console.log('Refreshing context due to SignalR event...');\n\n if (event.reason === 'PermissionsChanged' || event.reason === 'RoleAssignmentChanged') {\n console.log('Permission-specific change detected, refreshing permissions only...');\n refreshPermissions();\n } else {\n refreshContext();\n }\n }\n };\n\n const unsubscribe = signalRClient.onIdentityContextChanged(handleContextChange);\n\n return unsubscribe;\n }, [accessToken, contextVersion, refreshContext, refreshPermissions, signalRClient]);\n\n // Initial context load if not provided\n useEffect(() => {\n if (!initialContext) {\n refreshContext();\n }\n }, [initialContext, refreshContext]);\n\n // Cleanup SignalR on unmount\n useEffect(() => {\n return () => {\n if (signalRClient) {\n signalRClient.disconnect().catch((error) => {\n console.error('Error disconnecting SignalR:', error);\n });\n }\n };\n }, [signalRClient]);\n\n return <>{children}</>;\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "./logging-
|
|
1
|
+
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "./logging-DviiAkLx.js";
|
|
2
2
|
import { PermissionChecker } from "./permission-service-CvGpwI0d.js";
|
|
3
|
-
import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "./permissions-
|
|
4
|
-
import { evaluateAccessRequirement } from "./access-
|
|
5
|
-
import { TenantProvider, configureTenantEndpoints, initializeTenantStore, useCurrentOrganization, useCurrentTenant, useOrganization, useTenant, useTenantStore, useTenantSwitcher } from "./tenant-
|
|
6
|
-
import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, resetIdentityStoreConfig, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "./identity-
|
|
3
|
+
import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "./permissions-_n1hIG7Q.js";
|
|
4
|
+
import { evaluateAccessRequirement } from "./access-CGrsDMwu.js";
|
|
5
|
+
import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "./tenant-DfLemb6n.js";
|
|
6
|
+
import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "./identity-C5un-xAs.js";
|
|
7
7
|
import { isApiClientError } from "./types-BaaE6wjY.js";
|
|
8
|
-
import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "./api-client-
|
|
9
|
-
import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "./signalr-
|
|
8
|
+
import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "./api-client-CgZUMukO.js";
|
|
9
|
+
import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "./signalr-k-70VHKo.js";
|
|
10
10
|
import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "./query-client-Bh3TyB8W.js";
|
|
11
11
|
import "./src-B9rDUgSb.js";
|
|
12
12
|
|
|
13
|
-
export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, handleApiError, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, resetIdentityStoreConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
|
|
13
|
+
export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantClient, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, getTenantCookieName, handleApiError, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetIdentityStoreConfig, resetTenantClientConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
|
package/dist/logging/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-
|
|
1
|
+
import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
|
|
2
2
|
|
|
3
3
|
export { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger };
|
|
@@ -22,7 +22,7 @@ const defaultLoggerConfig = {
|
|
|
22
22
|
minLevel: process.env.NODE_ENV === "production" ? LogLevel.INFO : LogLevel.DEBUG,
|
|
23
23
|
enableConsole: true,
|
|
24
24
|
enableRemote: false,
|
|
25
|
-
serviceName: "
|
|
25
|
+
serviceName: "spine"
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
@@ -115,4 +115,4 @@ function createLogger(config) {
|
|
|
115
115
|
|
|
116
116
|
//#endregion
|
|
117
117
|
export { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger };
|
|
118
|
-
//# sourceMappingURL=logging-
|
|
118
|
+
//# sourceMappingURL=logging-DviiAkLx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logging-
|
|
1
|
+
{"version":3,"file":"logging-DviiAkLx.js","names":["LOG_LEVEL_PRIORITY: Record<LogLevel, number>","defaultLoggerConfig: LoggerConfig","config?: Partial<LoggerConfig>","message: string","context?: LogContext","error?: Error","level: LogLevel","entry: LogEntry"],"sources":["../src/logging/types.ts","../src/logging/config.ts","../src/logging/logger.ts"],"sourcesContent":["/**\n * Logging Types\n */\n\nexport enum LogLevel {\n DEBUG = 'debug',\n INFO = 'info',\n WARN = 'warn',\n ERROR = 'error',\n}\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: LogContext;\n error?: Error;\n}\n\nexport interface LoggerConfig {\n minLevel: LogLevel;\n enableConsole: boolean;\n enableRemote: boolean;\n remoteEndpoint?: string;\n serviceName?: string;\n}\n","/**\n * Logger Configuration\n */\n\nimport { LogLevel, type LoggerConfig } from './types';\n\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n [LogLevel.DEBUG]: 0,\n [LogLevel.INFO]: 1,\n [LogLevel.WARN]: 2,\n [LogLevel.ERROR]: 3,\n};\n\nexport const defaultLoggerConfig: LoggerConfig = {\n minLevel: process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG,\n enableConsole: true,\n enableRemote: false,\n serviceName: 'spine',\n};\n","/**\n * Logger Service\n * \n * Centralized logging implementation with structured logging format.\n */\n\nimport { LogLevel, type LogContext, type LogEntry, type LoggerConfig } from './types';\nimport { defaultLoggerConfig, LOG_LEVEL_PRIORITY } from './config';\n\nexport class Logger {\n private config: LoggerConfig;\n\n constructor(config?: Partial<LoggerConfig>) {\n this.config = {\n ...defaultLoggerConfig,\n ...config,\n };\n }\n\n debug(message: string, context?: LogContext): void {\n this.log(LogLevel.DEBUG, message, context);\n }\n\n info(message: string, context?: LogContext): void {\n this.log(LogLevel.INFO, message, context);\n }\n\n warn(message: string, context?: LogContext): void {\n this.log(LogLevel.WARN, message, context);\n }\n\n error(message: string, error?: Error, context?: LogContext): void {\n this.log(LogLevel.ERROR, message, context, error);\n }\n\n private log(\n level: LogLevel,\n message: string,\n context?: LogContext,\n error?: Error\n ): void {\n if (!this.shouldLog(level)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n context,\n error,\n };\n\n if (this.config.enableConsole) {\n this.logToConsole(entry);\n }\n\n if (this.config.enableRemote && this.config.remoteEndpoint) {\n this.sendToRemote(entry).catch((err) => {\n console.error('Failed to send log to remote endpoint:', err);\n });\n }\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.minLevel];\n }\n\n private logToConsole(entry: LogEntry): void {\n const formatted = this.formatEntry(entry);\n\n switch (entry.level) {\n case LogLevel.DEBUG:\n console.debug(formatted);\n break;\n case LogLevel.INFO:\n console.info(formatted);\n break;\n case LogLevel.WARN:\n console.warn(formatted);\n break;\n case LogLevel.ERROR:\n console.error(formatted);\n if (entry.error) {\n console.error(entry.error);\n }\n break;\n }\n }\n\n private formatEntry(entry: LogEntry): string {\n const parts = [\n `[${entry.timestamp}]`,\n `[${entry.level.toUpperCase()}]`,\n entry.message,\n ];\n\n if (entry.context && Object.keys(entry.context).length > 0) {\n parts.push(JSON.stringify(entry.context));\n }\n\n return parts.join(' ');\n }\n\n private async sendToRemote(entry: LogEntry): Promise<void> {\n if (!this.config.remoteEndpoint) return;\n\n await fetch(this.config.remoteEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...entry,\n serviceName: this.config.serviceName,\n error: entry.error\n ? {\n name: entry.error.name,\n message: entry.error.message,\n stack: entry.error.stack,\n }\n : undefined,\n }),\n });\n }\n}\n\n// Default logger instance\nexport const logger = new Logger();\n\n// Create logger with custom config\nexport function createLogger(config?: Partial<LoggerConfig>): Logger {\n return new Logger(config);\n}\n"],"mappings":";;;;AAIA,IAAY,gDAAL;AACL;AACA;AACA;AACA;;AACD;;;;ACHD,MAAaA,qBAA+C;EACzD,SAAS,QAAQ;EACjB,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,QAAQ;AACnB;AAED,MAAaC,sBAAoC;CAC/C,UAAU,QAAQ,IAAI,aAAa,eAAe,SAAS,OAAO,SAAS;CAC3E,eAAe;CACf,cAAc;CACd,aAAa;AACd;;;;ACTD,IAAa,SAAb,MAAoB;CAClB,AAAQ;CAER,YAAYC,QAAgC;AAC1C,OAAK,SAAS;GACZ,GAAG;GACH,GAAG;EACJ;CACF;CAED,MAAMC,SAAiBC,SAA4B;AACjD,OAAK,IAAI,SAAS,OAAO,SAAS,QAAQ;CAC3C;CAED,KAAKD,SAAiBC,SAA4B;AAChD,OAAK,IAAI,SAAS,MAAM,SAAS,QAAQ;CAC1C;CAED,KAAKD,SAAiBC,SAA4B;AAChD,OAAK,IAAI,SAAS,MAAM,SAAS,QAAQ;CAC1C;CAED,MAAMD,SAAiBE,OAAeD,SAA4B;AAChE,OAAK,IAAI,SAAS,OAAO,SAAS,SAAS,MAAM;CAClD;CAED,AAAQ,IACNE,OACAH,SACAC,SACAC,OACM;AACN,OAAK,KAAK,UAAU,MAAM,CACxB;EAGF,MAAME,QAAkB;GACtB,WAAW,IAAI,OAAO,aAAa;GACnC;GACA;GACA;GACA;EACD;AAED,MAAI,KAAK,OAAO,cACd,MAAK,aAAa,MAAM;AAG1B,MAAI,KAAK,OAAO,gBAAgB,KAAK,OAAO,eAC1C,MAAK,aAAa,MAAM,CAAC,MAAM,CAAC,QAAQ;AACtC,WAAQ,MAAM,0CAA0C,IAAI;EAC7D,EAAC;CAEL;CAED,AAAQ,UAAUD,OAA0B;AAC1C,SAAO,mBAAmB,UAAU,mBAAmB,KAAK,OAAO;CACpE;CAED,AAAQ,aAAaC,OAAuB;EAC1C,MAAM,YAAY,KAAK,YAAY,MAAM;AAEzC,UAAQ,MAAM,OAAd;GACE,KAAK,SAAS;AACZ,YAAQ,MAAM,UAAU;AACxB;GACF,KAAK,SAAS;AACZ,YAAQ,KAAK,UAAU;AACvB;GACF,KAAK,SAAS;AACZ,YAAQ,KAAK,UAAU;AACvB;GACF,KAAK,SAAS;AACZ,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,MACR,SAAQ,MAAM,MAAM,MAAM;AAE5B;EACH;CACF;CAED,AAAQ,YAAYA,OAAyB;EAC3C,MAAM,QAAQ;IACX,GAAG,MAAM,UAAU;IACnB,GAAG,MAAM,MAAM,aAAa,CAAC;GAC9B,MAAM;EACP;AAED,MAAI,MAAM,WAAW,OAAO,KAAK,MAAM,QAAQ,CAAC,SAAS,EACvD,OAAM,KAAK,KAAK,UAAU,MAAM,QAAQ,CAAC;AAG3C,SAAO,MAAM,KAAK,IAAI;CACvB;CAED,MAAc,aAAaA,OAAgC;AACzD,OAAK,KAAK,OAAO,eAAgB;AAEjC,QAAM,MAAM,KAAK,OAAO,gBAAgB;GACtC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB,GAAG;IACH,aAAa,KAAK,OAAO;IACzB,OAAO,MAAM,QACT;KACE,MAAM,MAAM,MAAM;KAClB,SAAS,MAAM,MAAM;KACrB,OAAO,MAAM,MAAM;IACpB;GAEN,EAAC;EACH,EAAC;CACH;AACF;AAGD,MAAa,SAAS,IAAI;AAG1B,SAAgB,aAAaL,QAAwC;AACnE,QAAO,IAAI,OAAO;AACnB"}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import type { PermissionModule } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* Available permission modules
|
|
7
|
+
*
|
|
8
|
+
* @deprecated Spine does not own an application's permission taxonomy. Define
|
|
9
|
+
* module constants in the consuming app instead.
|
|
7
10
|
*/
|
|
8
11
|
export declare const PERMISSION_MODULES: PermissionModule[];
|
|
9
12
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/permissions/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/permissions/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EAAO,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { PermissionChecker } from "../permission-service-CvGpwI0d.js";
|
|
2
|
-
import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-
|
|
2
|
+
import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-_n1hIG7Q.js";
|
|
3
3
|
|
|
4
4
|
export { PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions };
|
|
@@ -17,7 +17,7 @@ interface PermissionState {
|
|
|
17
17
|
reset: () => void;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Selectors for
|
|
20
|
+
* Selectors for focused store reads
|
|
21
21
|
*/
|
|
22
22
|
export declare const selectChecker: (state: PermissionState) => PermissionChecker;
|
|
23
23
|
export declare const selectPermissions: (state: PermissionState) => string[];
|
|
@@ -8,7 +8,7 @@ export type PermissionCode = string;
|
|
|
8
8
|
/**
|
|
9
9
|
* Permission module types
|
|
10
10
|
*/
|
|
11
|
-
export type PermissionModule =
|
|
11
|
+
export type PermissionModule = string;
|
|
12
12
|
/**
|
|
13
13
|
* Permission check options
|
|
14
14
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/permissions/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/permissions/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,cAAc,EAAE,CAAC;IACrC,kBAAkB,EAAE,cAAc,EAAE,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB"}
|
|
@@ -5,7 +5,7 @@ import { Fragment, jsx } from "react/jsx-runtime";
|
|
|
5
5
|
|
|
6
6
|
//#region src/permissions/permission-store.ts
|
|
7
7
|
/**
|
|
8
|
-
* Selectors for
|
|
8
|
+
* Selectors for focused store reads
|
|
9
9
|
*/
|
|
10
10
|
const selectChecker = (state) => state.checker;
|
|
11
11
|
/**
|
|
@@ -172,18 +172,12 @@ function ProtectedAction({ permission, onAction, children }) {
|
|
|
172
172
|
//#region src/permissions/constants.ts
|
|
173
173
|
/**
|
|
174
174
|
* Available permission modules
|
|
175
|
+
*
|
|
176
|
+
* @deprecated Spine does not own an application's permission taxonomy. Define
|
|
177
|
+
* module constants in the consuming app instead.
|
|
175
178
|
*/
|
|
176
|
-
const PERMISSION_MODULES = [
|
|
177
|
-
"Identity",
|
|
178
|
-
"PropertyManagement",
|
|
179
|
-
"Leasing",
|
|
180
|
-
"Accounting",
|
|
181
|
-
"Tasks",
|
|
182
|
-
"People",
|
|
183
|
-
"Organization",
|
|
184
|
-
"Subscription"
|
|
185
|
-
];
|
|
179
|
+
const PERMISSION_MODULES = [];
|
|
186
180
|
|
|
187
181
|
//#endregion
|
|
188
182
|
export { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions };
|
|
189
|
-
//# sourceMappingURL=permissions-
|
|
183
|
+
//# sourceMappingURL=permissions-_n1hIG7Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions-_n1hIG7Q.js","names":["state: PermissionState","permissions: PermissionCode[]","isLoading: boolean","permission: PermissionCode","permissions: PermissionCode[]","options?: PermissionCheckOptions","module: PermissionModule","PERMISSION_MODULES: PermissionModule[]"],"sources":["../src/permissions/permission-store.ts","../src/permissions/permission-initializer.tsx","../src/permissions/hooks.ts","../src/permissions/components.tsx","../src/permissions/constants.ts"],"sourcesContent":["/**\n * Permission Store (Zustand)\n * \n * State management for permissions.\n */\n\nimport { create } from 'zustand';\nimport { PermissionChecker } from './permission-service';\nimport type { PermissionCode } from './types';\n\n/**\n * Permission store state interface\n */\ninterface PermissionState {\n checker: PermissionChecker;\n permissions: PermissionCode[];\n isLoading: boolean;\n setPermissions: (permissions: PermissionCode[]) => void;\n setLoading: (isLoading: boolean) => void;\n reset: () => void;\n}\n\n/**\n * Selectors for focused store reads\n */\nexport const selectChecker = (state: PermissionState) => state.checker;\nexport const selectPermissions = (state: PermissionState) => state.permissions;\nexport const selectIsLoading = (state: PermissionState) => state.isLoading;\n\n/**\n * Initial state\n */\nconst initialState = {\n checker: new PermissionChecker([]),\n permissions: [] as PermissionCode[],\n isLoading: false,\n};\n\n/**\n * Permission store\n */\nexport const usePermissionStore = create<PermissionState>((set) => ({\n ...initialState,\n\n setPermissions: (permissions: PermissionCode[]) => {\n set({\n permissions,\n checker: new PermissionChecker(permissions),\n });\n },\n\n setLoading: (isLoading: boolean) => {\n set({ isLoading });\n },\n\n reset: () => {\n set(initialState);\n },\n}));\n\n/**\n * Initialize permission store\n */\nexport function initializePermissions(\n permissions: PermissionCode[],\n isLoading: boolean = false\n): void {\n usePermissionStore.getState().setPermissions(permissions);\n usePermissionStore.getState().setLoading(isLoading);\n}\n\n/**\n * Get permission checker outside React components\n */\nexport function getPermissionChecker(): PermissionChecker {\n return usePermissionStore.getState().checker;\n}\n\n/**\n * Get all permissions outside React components\n */\nexport function getPermissions(): PermissionCode[] {\n return usePermissionStore.getState().permissions;\n}\n","/**\n * Permission Initializer Component\n * \n * Initializes permission store from identity context.\n */\n\nimport { useEffect, type ReactNode } from 'react';\nimport { initializePermissions } from './permission-store';\nimport type { PermissionCode } from './types';\n\ninterface PermissionInitializerProps {\n permissions: PermissionCode[];\n isLoading?: boolean;\n children: ReactNode;\n}\n\n/**\n * Component that initializes permissions from props\n */\nexport function PermissionInitializer({\n permissions,\n isLoading = false,\n children,\n}: PermissionInitializerProps) {\n useEffect(() => {\n initializePermissions(permissions, isLoading);\n }, [permissions, isLoading]);\n\n return <>{children}</>;\n}\n","/**\n * Permission Hooks\n * \n * React hooks for permission checks.\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { usePermissionStore, selectChecker } from './permission-store';\nimport type { PermissionCode, PermissionCheckOptions, PermissionModule } from './types';\n\n/**\n * Hook for checking a single permission\n */\nexport function usePermission(permission: PermissionCode): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasPermission(permission), [checker, permission]);\n}\n\n/**\n * Alias for usePermission\n */\nexport const useHasPermission = usePermission;\n\n/**\n * Hook for checking multiple permissions\n */\nexport function usePermissions(\n permissions: PermissionCode[],\n options?: PermissionCheckOptions\n) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.hasPermissions(permissions, options),\n [checker, permissions, options]\n );\n}\n\n/**\n * Alias for usePermissions\n */\nexport const useHasPermissions = usePermissions;\n\n/**\n * Hook for checking module-level permissions\n */\nexport function useModulePermission(module: PermissionModule): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasModulePermission(module), [checker, module]);\n}\n\n/**\n * Alias for useModulePermission\n */\nexport const useHasModulePermission = useModulePermission;\n\n/**\n * Hook for getting a memoized permission checker function\n */\nexport function usePermissionChecker() {\n const checker = usePermissionStore(selectChecker);\n return useCallback(\n (permission: PermissionCode) => checker.hasPermission(permission),\n [checker]\n );\n}\n\n/**\n * Hook for getting all user permissions\n */\nexport function useAllPermissions(): readonly PermissionCode[] {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.getAllPermissions(), [checker]);\n}\n\n/**\n * Hook for field-level permissions\n */\nexport function useFieldPermissions(module: PermissionModule, entity: string) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.getFieldPermissions(module, entity),\n [checker, module, entity]\n );\n}\n\n/**\n * Hook for permission loading state\n */\nexport function usePermissionLoading(): boolean {\n return usePermissionStore((state) => state.isLoading);\n}\n","/**\n * Permission Components\n * \n * Protected UI components that conditionally render based on permissions.\n */\n\nimport React, { type ReactNode, type ComponentProps } from 'react';\nimport { usePermission, usePermissions } from './hooks';\nimport type { PermissionCode, PermissionCheckOptions } from './types';\n\n// ============================================================================\n// ProtectedButton\n// ============================================================================\n\ninterface ProtectedButtonProps extends ComponentProps<'button'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Button that only renders if user has the required permission\n */\nexport function ProtectedButton({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedButtonProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <button {...props}>{children}</button>;\n}\n\n// ============================================================================\n// ProtectedLink\n// ============================================================================\n\ninterface ProtectedLinkProps extends ComponentProps<'a'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Link that only renders if user has the required permission\n */\nexport function ProtectedLink({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedLinkProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <a {...props}>{children}</a>;\n}\n\n// ============================================================================\n// ProtectedSection\n// ============================================================================\n\ninterface ProtectedSectionProps {\n permission?: PermissionCode;\n permissions?: PermissionCode[];\n options?: PermissionCheckOptions;\n fallback?: ReactNode;\n children: ReactNode;\n}\n\n/**\n * Section that only renders if user has the required permission(s)\n */\nexport function ProtectedSection({\n permission,\n permissions,\n options,\n fallback = null,\n children,\n}: ProtectedSectionProps) {\n // Single permission check\n const singlePermissionResult = usePermission(permission || '');\n \n // Multiple permissions check\n const multiplePermissionsResult = usePermissions(permissions || [], options);\n\n // Determine if we should render\n let hasPermission = false;\n \n if (permission) {\n hasPermission = singlePermissionResult;\n } else if (permissions && permissions.length > 0) {\n hasPermission = multiplePermissionsResult.hasPermission;\n } else {\n // No permission specified, render by default\n hasPermission = true;\n }\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n\n// ============================================================================\n// ProtectedAction\n// ============================================================================\n\ninterface ProtectedActionProps {\n permission: PermissionCode;\n onAction: () => void;\n children: (props: { onClick: () => void; disabled: boolean }) => ReactNode;\n}\n\n/**\n * Render prop component for protected actions\n */\nexport function ProtectedAction({\n permission,\n onAction,\n children,\n}: ProtectedActionProps) {\n const hasPermission = usePermission(permission);\n\n return (\n <>\n {children({\n onClick: hasPermission ? onAction : () => {},\n disabled: !hasPermission,\n })}\n </>\n );\n}\n","/**\n * Permission Constants\n */\n\nimport type { PermissionModule } from './types';\n\n/**\n * Available permission modules\n *\n * @deprecated Spine does not own an application's permission taxonomy. Define\n * module constants in the consuming app instead.\n */\nexport const PERMISSION_MODULES: PermissionModule[] = [];\n"],"mappings":"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"}
|
|
@@ -79,7 +79,7 @@ export interface CachePresets {
|
|
|
79
79
|
*/
|
|
80
80
|
static: CachePreset;
|
|
81
81
|
/**
|
|
82
|
-
* For data that changes occasionally (
|
|
82
|
+
* For data that changes occasionally (resources, users, etc.)
|
|
83
83
|
*/
|
|
84
84
|
normal: CachePreset;
|
|
85
85
|
/**
|
|
@@ -96,20 +96,20 @@ export interface CachePresets {
|
|
|
96
96
|
*/
|
|
97
97
|
export interface InvalidationHelpers {
|
|
98
98
|
/**
|
|
99
|
-
* Invalidate all
|
|
99
|
+
* Invalidate all queries for a module
|
|
100
100
|
*/
|
|
101
|
-
|
|
101
|
+
invalidateModule: (queryClient: QueryClient, module: string) => Promise<void>;
|
|
102
102
|
/**
|
|
103
|
-
* Invalidate all
|
|
103
|
+
* Invalidate all list queries for a module
|
|
104
104
|
*/
|
|
105
|
-
|
|
105
|
+
invalidateLists: (queryClient: QueryClient, module: string) => Promise<void>;
|
|
106
106
|
/**
|
|
107
|
-
* Invalidate
|
|
107
|
+
* Invalidate a specific detail query
|
|
108
108
|
*/
|
|
109
|
-
|
|
109
|
+
invalidateDetail: (queryClient: QueryClient, module: string, id: string) => Promise<void>;
|
|
110
110
|
/**
|
|
111
|
-
* Invalidate specific query
|
|
111
|
+
* Invalidate and refetch a specific query key
|
|
112
112
|
*/
|
|
113
|
-
|
|
113
|
+
invalidateAndRefetch: (queryClient: QueryClient, queryKey: QueryKey) => Promise<void>;
|
|
114
114
|
}
|
|
115
115
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/query-client/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,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,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,aAAa,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/query-client/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,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,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,aAAa,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;IACrB;;OAEG;IACH,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,gBAAgB,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;OAEG;IACH,eAAe,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E;;OAEG;IACH,gBAAgB,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F;;OAEG;IACH,oBAAoB,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-client-Bh3TyB8W.js","names":["DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>>","error: unknown","returnUrl?: string","config: QueryClientConfig","cachePresets: CachePresets","module: T","filters?: Record<string, unknown>","id: string","queryClient: QueryClient","module: string","queryKey: readonly unknown[]"],"sources":["../src/query-client/query-config.ts"],"sourcesContent":["/**\n * Query Client Configuration\n * \n * Optimized caching strategy for different types of data:\n * - Static data (rarely changes): Long staleTime and gcTime\n * - Dynamic data (changes frequently): Short staleTime\n * - Critical data (needs to be fresh): Very short staleTime with prefetching\n */\n\nimport { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';\nimport type { QueryClientConfig, CachePresets } from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>> = {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n maxRetries: 3,\n maxMutationRetries: 1,\n refetchOnWindowFocus: true,\n refetchOnReconnect: false,\n refetchOnMount: false,\n};\n\n/**\n * Check if error is a 401 Unauthorized error\n */\nfunction is401Error(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status === 401;\n }\n\n if (error && typeof error === 'object' && 'status' in error) {\n return (error as any).status === 401;\n }\n\n return false;\n}\n\n/**\n * Check if error requires logout (refresh token expired)\n */\nfunction shouldLogout(error: unknown): boolean {\n if (error && typeof error === 'object') {\n // Check for shouldLogout flag from server\n if ('shouldLogout' in error && (error as any).shouldLogout === true) {\n return true;\n }\n\n // Check for SESSION_EXPIRED code\n if ('code' in error && (error as any).code === 'SESSION_EXPIRED') {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default logout handler\n */\nfunction createDefaultLogoutHandler(): (returnUrl?: string) => void {\n return (returnUrl?: string) => {\n if (typeof window !== 'undefined') {\n const currentUrl = returnUrl || window.location.pathname + window.location.search;\n window.location.href = `/auth/logout?logout=local&returnUrl=${encodeURIComponent(currentUrl)}`;\n }\n };\n}\n\n/**\n * Create optimized QueryClient instance\n * \n * @param config - Configuration options\n * @returns Configured QueryClient with optimized defaults\n */\nexport function createQueryClient(config: QueryClientConfig = {}): QueryClient {\n const {\n staleTime,\n gcTime,\n maxRetries,\n maxMutationRetries,\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n logger,\n onLogout,\n } = { ...DEFAULT_CONFIG, ...config };\n\n const handleLogout = onLogout || createDefaultLogoutHandler();\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime,\n gcTime,\n retry: (failureCount, error) => {\n // Don't retry 401 errors - the fetch client handles refresh flows separately\n if (is401Error(error)) {\n logger?.debug?.('Query failed with 401, not retrying');\n return false;\n }\n return failureCount < maxRetries;\n },\n retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n },\n mutations: {\n retry: (failureCount, error) => {\n if (is401Error(error)) {\n logger?.debug?.('Mutation failed with 401, not retrying');\n return false;\n }\n return failureCount < maxMutationRetries;\n },\n retryDelay: 1000,\n },\n },\n queryCache: new QueryCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Query error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n mutationCache: new MutationCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Mutation error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n });\n\n return queryClient;\n}\n\n/**\n * Cache Presets for different data types\n * \n * Use these presets for consistent caching across the application.\n */\nexport const cachePresets: CachePresets = {\n /**\n * Static data - rarely changes\n * Good for: permissions, roles, system settings\n */\n static: {\n staleTime: 30 * 60 * 1000, // 30 minutes\n gcTime: 60 * 60 * 1000, // 1 hour\n refetchOnWindowFocus: false,\n },\n\n /**\n * Normal data - changes occasionally\n * Good for:
|
|
1
|
+
{"version":3,"file":"query-client-Bh3TyB8W.js","names":["DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>>","error: unknown","returnUrl?: string","config: QueryClientConfig","cachePresets: CachePresets","module: T","filters?: Record<string, unknown>","id: string","queryClient: QueryClient","module: string","queryKey: readonly unknown[]"],"sources":["../src/query-client/query-config.ts"],"sourcesContent":["/**\n * Query Client Configuration\n * \n * Optimized caching strategy for different types of data:\n * - Static data (rarely changes): Long staleTime and gcTime\n * - Dynamic data (changes frequently): Short staleTime\n * - Critical data (needs to be fresh): Very short staleTime with prefetching\n */\n\nimport { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';\nimport type { QueryClientConfig, CachePresets } from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>> = {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n maxRetries: 3,\n maxMutationRetries: 1,\n refetchOnWindowFocus: true,\n refetchOnReconnect: false,\n refetchOnMount: false,\n};\n\n/**\n * Check if error is a 401 Unauthorized error\n */\nfunction is401Error(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status === 401;\n }\n\n if (error && typeof error === 'object' && 'status' in error) {\n return (error as any).status === 401;\n }\n\n return false;\n}\n\n/**\n * Check if error requires logout (refresh token expired)\n */\nfunction shouldLogout(error: unknown): boolean {\n if (error && typeof error === 'object') {\n // Check for shouldLogout flag from server\n if ('shouldLogout' in error && (error as any).shouldLogout === true) {\n return true;\n }\n\n // Check for SESSION_EXPIRED code\n if ('code' in error && (error as any).code === 'SESSION_EXPIRED') {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default logout handler\n */\nfunction createDefaultLogoutHandler(): (returnUrl?: string) => void {\n return (returnUrl?: string) => {\n if (typeof window !== 'undefined') {\n const currentUrl = returnUrl || window.location.pathname + window.location.search;\n window.location.href = `/auth/logout?logout=local&returnUrl=${encodeURIComponent(currentUrl)}`;\n }\n };\n}\n\n/**\n * Create optimized QueryClient instance\n * \n * @param config - Configuration options\n * @returns Configured QueryClient with optimized defaults\n */\nexport function createQueryClient(config: QueryClientConfig = {}): QueryClient {\n const {\n staleTime,\n gcTime,\n maxRetries,\n maxMutationRetries,\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n logger,\n onLogout,\n } = { ...DEFAULT_CONFIG, ...config };\n\n const handleLogout = onLogout || createDefaultLogoutHandler();\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime,\n gcTime,\n retry: (failureCount, error) => {\n // Don't retry 401 errors - the fetch client handles refresh flows separately\n if (is401Error(error)) {\n logger?.debug?.('Query failed with 401, not retrying');\n return false;\n }\n return failureCount < maxRetries;\n },\n retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n },\n mutations: {\n retry: (failureCount, error) => {\n if (is401Error(error)) {\n logger?.debug?.('Mutation failed with 401, not retrying');\n return false;\n }\n return failureCount < maxMutationRetries;\n },\n retryDelay: 1000,\n },\n },\n queryCache: new QueryCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Query error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n mutationCache: new MutationCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Mutation error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n });\n\n return queryClient;\n}\n\n/**\n * Cache Presets for different data types\n * \n * Use these presets for consistent caching across the application.\n */\nexport const cachePresets: CachePresets = {\n /**\n * Static data - rarely changes\n * Good for: permissions, roles, system settings\n */\n static: {\n staleTime: 30 * 60 * 1000, // 30 minutes\n gcTime: 60 * 60 * 1000, // 1 hour\n refetchOnWindowFocus: false,\n },\n\n /**\n * Normal data - changes occasionally\n * Good for: resources, users, tenant data\n */\n normal: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Dynamic data - changes frequently\n * Good for: transactions, activities, notifications\n */\n dynamic: {\n staleTime: 1 * 60 * 1000, // 1 minute\n gcTime: 5 * 60 * 1000, // 5 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Realtime data - always fresh\n * Good for: live status, counters, dashboards\n */\n realtime: {\n staleTime: 0, // Always stale\n gcTime: 1 * 60 * 1000, // 1 minute\n refetchOnWindowFocus: true,\n },\n};\n\n/**\n * Create base query keys for a module\n */\nexport function createQueryKeyFactory<T extends string>(\n module: T\n): {\n all: readonly [T];\n lists: () => readonly [T, 'list'];\n list: (filters?: Record<string, unknown>) => readonly [T, 'list', Record<string, unknown>?];\n details: () => readonly [T, 'detail'];\n detail: (id: string) => readonly [T, 'detail', string];\n} {\n return {\n all: [module] as const,\n lists: () => [module, 'list'] as const,\n list: (filters?: Record<string, unknown>) => [module, 'list', filters] as const,\n details: () => [module, 'detail'] as const,\n detail: (id: string) => [module, 'detail', id] as const,\n };\n}\n\n/**\n * Invalidation helpers for common patterns\n */\nexport const invalidationHelpers = {\n /**\n * Invalidate all queries for a module\n */\n invalidateModule: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module] });\n },\n\n /**\n * Invalidate all list queries for a module\n */\n invalidateLists: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'list'] });\n },\n\n /**\n * Invalidate a specific detail query\n */\n invalidateDetail: async (\n queryClient: QueryClient,\n module: string,\n id: string\n ): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'detail', id] });\n },\n\n /**\n * Invalidate and refetch (useful for critical data)\n */\n invalidateAndRefetch: async (queryClient: QueryClient, queryKey: readonly unknown[]): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey });\n await queryClient.refetchQueries({ queryKey });\n },\n};\n"],"mappings":"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"}
|