@sunboyoo/supabase-rbac 1.0.2

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.
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client.tsx
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ RBACProvider: () => RBACProvider,
24
+ useRBAC: () => useRBAC
25
+ });
26
+ module.exports = __toCommonJS(client_exports);
27
+ var import_react = require("react");
28
+
29
+ // src/shared.ts
30
+ var import_jose = require("jose");
31
+ var IS_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
32
+ function isUuid(v) {
33
+ return typeof v === "string" && IS_UUID.test(v);
34
+ }
35
+ function uniqueStable(arr) {
36
+ const seen = /* @__PURE__ */ new Set();
37
+ const out = [];
38
+ for (const x of arr) {
39
+ if (seen.has(x)) continue;
40
+ seen.add(x);
41
+ out.push(x);
42
+ }
43
+ return out;
44
+ }
45
+ function decodeClaims(token) {
46
+ if (!token) return null;
47
+ try {
48
+ return (0, import_jose.decodeJwt)(token);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function getMergedRoleIds(params) {
54
+ const { token, appId, includeGlobal = true, globalAppId = "global" } = params;
55
+ const claims = decodeClaims(token);
56
+ const roleMap = claims?.app_metadata?.role_ids ?? {};
57
+ const appRoles = Array.isArray(roleMap[appId]) ? roleMap[appId] : [];
58
+ const gRoles = includeGlobal && Array.isArray(roleMap[globalAppId]) ? roleMap[globalAppId] : [];
59
+ const merged = uniqueStable([...appRoles, ...gRoles]).filter(isUuid);
60
+ const userId = isUuid(claims?.sub) ? claims.sub : null;
61
+ const exp = typeof claims?.exp === "number" ? claims.exp : null;
62
+ return { roleIds: merged, claims, userId, exp };
63
+ }
64
+ function hasRole(userRoleIds, required, mode = "any") {
65
+ if (!required || required.length === 0) return true;
66
+ if (!userRoleIds || userRoleIds.length === 0) return false;
67
+ const set = new Set(userRoleIds);
68
+ if (mode === "all") {
69
+ for (const r of required) if (!set.has(r)) return false;
70
+ return true;
71
+ }
72
+ for (const r of required) if (set.has(r)) return true;
73
+ return false;
74
+ }
75
+
76
+ // src/client.tsx
77
+ var import_jsx_runtime = require("react/jsx-runtime");
78
+ var permissionCache = /* @__PURE__ */ new Map();
79
+ function cacheKey(appId, userId) {
80
+ return `${appId}|${userId ?? "anon"}`;
81
+ }
82
+ function log(logger, level, msg, meta) {
83
+ const fn = logger?.[level];
84
+ if (typeof fn === "function") fn(msg, meta);
85
+ }
86
+ var RBACContext = (0, import_react.createContext)(null);
87
+ function RBACProvider(props) {
88
+ const {
89
+ client,
90
+ appId,
91
+ includeGlobal = true,
92
+ globalAppId = "global",
93
+ permissions: permOptsRaw,
94
+ logger,
95
+ children
96
+ } = props;
97
+ const permOpts = {
98
+ enabled: permOptsRaw?.enabled ?? false,
99
+ rpcName: permOptsRaw?.rpcName ?? "get_my_permissions",
100
+ ttlMs: permOptsRaw?.ttlMs ?? 6e4,
101
+ nonBlocking: permOptsRaw?.nonBlocking ?? true
102
+ };
103
+ const sb = client;
104
+ const [isAuthenticated, setIsAuthenticated] = (0, import_react.useState)(false);
105
+ const [userId, setUserId] = (0, import_react.useState)(null);
106
+ const [roleIds, setRoleIds] = (0, import_react.useState)([]);
107
+ const [permissionNames, setPermissionNames] = (0, import_react.useState)([]);
108
+ const [isRBACLoading, setIsRBACLoading] = (0, import_react.useState)(true);
109
+ const [isPermissionLoading, setIsPermissionLoading] = (0, import_react.useState)(false);
110
+ const isRBACReady = !isRBACLoading;
111
+ const isPermissionReady = permOpts.enabled ? !isPermissionLoading : true;
112
+ const syncLock = (0, import_react.useRef)(null);
113
+ const permissionRequestId = (0, import_react.useRef)(0);
114
+ const sessionEpoch = (0, import_react.useRef)(0);
115
+ const pendingSync = (0, import_react.useRef)(null);
116
+ const fetchPermissionNames = (0, import_react.useCallback)(async (params) => {
117
+ if (!permOpts.enabled) return [];
118
+ if (!params.userId) return [];
119
+ const key = cacheKey(params.appId, params.userId);
120
+ const now = Date.now();
121
+ const existing = permissionCache.get(key);
122
+ const fresh = existing && existing.inflight == null && now - existing.fetchedAt < permOpts.ttlMs && // EN: If token exp changed, treat cache as stale to avoid wrong UI
123
+ // 中文:token exp 变化则视为过期,避免 UI 错判
124
+ existing.tokenExp === params.tokenExp;
125
+ if (fresh) return existing.names;
126
+ if (existing?.inflight) return existing.inflight;
127
+ const inflight = (async () => {
128
+ setIsPermissionLoading(true);
129
+ try {
130
+ const { data, error } = await sb.rpc(permOpts.rpcName, { target_app_id: params.appId });
131
+ if (error) throw error;
132
+ const names = Array.isArray(data) ? data.map((x) => String(x?.permission_name ?? "")).filter((s) => s.length > 0) : [];
133
+ permissionCache.set(key, { fetchedAt: Date.now(), names, inflight: null, tokenExp: params.tokenExp });
134
+ return names;
135
+ } catch (e) {
136
+ permissionCache.set(key, {
137
+ fetchedAt: existing?.fetchedAt ?? 0,
138
+ names: existing?.names ?? [],
139
+ inflight: null,
140
+ tokenExp: params.tokenExp
141
+ });
142
+ throw e;
143
+ } finally {
144
+ setIsPermissionLoading(false);
145
+ }
146
+ })();
147
+ permissionCache.set(key, { fetchedAt: existing?.fetchedAt ?? 0, names: existing?.names ?? [], inflight, tokenExp: params.tokenExp });
148
+ return inflight;
149
+ }, [permOpts.enabled, permOpts.rpcName, permOpts.ttlMs, sb]);
150
+ const applySession = (0, import_react.useCallback)(async (session, epoch) => {
151
+ const accessToken = session?.access_token;
152
+ const authed = Boolean(session?.user?.id);
153
+ const { roleIds: merged, userId: parsedUid, exp } = getMergedRoleIds({
154
+ token: accessToken,
155
+ appId,
156
+ includeGlobal,
157
+ globalAppId
158
+ });
159
+ const uid = session?.user?.id && typeof session.user.id === "string" ? session.user.id : parsedUid;
160
+ if (epoch !== sessionEpoch.current) return;
161
+ setIsAuthenticated(authed);
162
+ setUserId(uid ?? null);
163
+ setRoleIds(merged);
164
+ const requestId = ++permissionRequestId.current;
165
+ if (!permOpts.enabled) {
166
+ setPermissionNames([]);
167
+ return;
168
+ }
169
+ const load = async () => {
170
+ try {
171
+ if (epoch !== sessionEpoch.current) return;
172
+ const names = await fetchPermissionNames({ appId, userId: uid ?? null, tokenExp: exp });
173
+ if (epoch !== sessionEpoch.current || requestId !== permissionRequestId.current) return;
174
+ setPermissionNames(names);
175
+ } catch (e) {
176
+ if (epoch !== sessionEpoch.current || requestId !== permissionRequestId.current) return;
177
+ setPermissionNames([]);
178
+ log(logger, "warn", "[RBAC] permissions RPC failed; degraded to role-only gating.", e);
179
+ }
180
+ };
181
+ if (permOpts.nonBlocking) void load();
182
+ else await load();
183
+ }, [appId, includeGlobal, globalAppId, permOpts.enabled, permOpts.nonBlocking, fetchPermissionNames, logger]);
184
+ const runSync = (0, import_react.useCallback)((forceSessionUpdate = false, sessionHint) => {
185
+ const promise = (async () => {
186
+ setIsRBACLoading(true);
187
+ try {
188
+ if (forceSessionUpdate && sb.auth.refreshSession) {
189
+ await sb.auth.refreshSession();
190
+ }
191
+ const session = sessionHint ? sessionHint : (await sb.auth.getSession()).data.session;
192
+ const epoch = sessionEpoch.current;
193
+ await applySession(session, epoch);
194
+ } finally {
195
+ setIsRBACLoading(false);
196
+ }
197
+ })();
198
+ syncLock.current = promise;
199
+ promise.finally(() => {
200
+ const pending = pendingSync.current;
201
+ if (pending) {
202
+ pendingSync.current = null;
203
+ const next = runSync(pending.forceSessionUpdate, pending.sessionHint);
204
+ next.then(pending.resolve, pending.reject);
205
+ return;
206
+ }
207
+ if (syncLock.current === promise) {
208
+ syncLock.current = null;
209
+ }
210
+ });
211
+ return promise;
212
+ }, [applySession, sb]);
213
+ const sync = (0, import_react.useCallback)((forceSessionUpdate = false, sessionHint) => {
214
+ if (syncLock.current) {
215
+ if (!pendingSync.current) {
216
+ let resolve;
217
+ let reject;
218
+ const promise = new Promise((res, rej) => {
219
+ resolve = res;
220
+ reject = rej;
221
+ });
222
+ pendingSync.current = { forceSessionUpdate, sessionHint, promise, resolve, reject };
223
+ } else {
224
+ pendingSync.current.forceSessionUpdate = pendingSync.current.forceSessionUpdate || forceSessionUpdate;
225
+ if (sessionHint) {
226
+ pendingSync.current.sessionHint = sessionHint;
227
+ } else if (forceSessionUpdate) {
228
+ pendingSync.current.sessionHint = void 0;
229
+ }
230
+ }
231
+ return pendingSync.current.promise;
232
+ }
233
+ return runSync(forceSessionUpdate, sessionHint);
234
+ }, [runSync]);
235
+ (0, import_react.useEffect)(() => {
236
+ void sync(false);
237
+ const { data: { subscription } } = sb.auth.onAuthStateChange((event, session) => {
238
+ if (event === "SIGNED_OUT") {
239
+ sessionEpoch.current++;
240
+ const pending = pendingSync.current;
241
+ pendingSync.current = null;
242
+ if (pending) pending.resolve();
243
+ permissionRequestId.current++;
244
+ setIsAuthenticated(false);
245
+ setUserId(null);
246
+ setRoleIds([]);
247
+ setPermissionNames([]);
248
+ setIsRBACLoading(false);
249
+ setIsPermissionLoading(false);
250
+ return;
251
+ }
252
+ if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED" || event === "USER_UPDATED") {
253
+ void sync(false, session);
254
+ }
255
+ });
256
+ return () => subscription.unsubscribe();
257
+ }, [sb, sync]);
258
+ const refresh = (0, import_react.useCallback)(async (forceSessionUpdate = true) => {
259
+ await sync(forceSessionUpdate);
260
+ }, [sync]);
261
+ const hasRole2 = (0, import_react.useCallback)((required, mode = "any") => {
262
+ return hasRole(roleIds, required, mode);
263
+ }, [roleIds]);
264
+ const hasPermission = (0, import_react.useCallback)((permissionName) => {
265
+ if (!permOpts.enabled) return false;
266
+ if (!permissionName) return false;
267
+ return permissionNames.includes(permissionName);
268
+ }, [permOpts.enabled, permissionNames]);
269
+ const value = (0, import_react.useMemo)(() => ({
270
+ appId,
271
+ isAuthenticated,
272
+ userId,
273
+ roleIds,
274
+ permissionNames,
275
+ isRBACLoading,
276
+ isPermissionLoading,
277
+ isRBACReady,
278
+ isPermissionReady,
279
+ refresh,
280
+ hasRole: hasRole2,
281
+ hasPermission
282
+ }), [
283
+ appId,
284
+ isAuthenticated,
285
+ userId,
286
+ roleIds,
287
+ permissionNames,
288
+ isRBACLoading,
289
+ isPermissionLoading,
290
+ isRBACReady,
291
+ isPermissionReady,
292
+ refresh,
293
+ hasRole2,
294
+ hasPermission
295
+ ]);
296
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RBACContext.Provider, { value, children });
297
+ }
298
+ function useRBAC() {
299
+ const ctx = (0, import_react.useContext)(RBACContext);
300
+ if (!ctx) throw new Error("useRBAC must be used within <RBACProvider>.");
301
+ return ctx;
302
+ }
303
+ // Annotate the CommonJS export names for ESM import in node:
304
+ 0 && (module.exports = {
305
+ RBACProvider,
306
+ useRBAC
307
+ });
@@ -0,0 +1,7 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { c as RBACProviderProps, d as RBACState } from './types-BayIl-Ha.cjs';
3
+
4
+ declare function RBACProvider(props: RBACProviderProps): react_jsx_runtime.JSX.Element;
5
+ declare function useRBAC(): RBACState;
6
+
7
+ export { RBACProvider, useRBAC };
@@ -0,0 +1,7 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { c as RBACProviderProps, d as RBACState } from './types-BayIl-Ha.js';
3
+
4
+ declare function RBACProvider(props: RBACProviderProps): react_jsx_runtime.JSX.Element;
5
+ declare function useRBAC(): RBACState;
6
+
7
+ export { RBACProvider, useRBAC };
package/dist/client.js ADDED
@@ -0,0 +1,237 @@
1
+ import {
2
+ getMergedRoleIds,
3
+ hasRole
4
+ } from "./chunk-ZFY3OHWO.js";
5
+
6
+ // src/client.tsx
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var permissionCache = /* @__PURE__ */ new Map();
10
+ function cacheKey(appId, userId) {
11
+ return `${appId}|${userId ?? "anon"}`;
12
+ }
13
+ function log(logger, level, msg, meta) {
14
+ const fn = logger?.[level];
15
+ if (typeof fn === "function") fn(msg, meta);
16
+ }
17
+ var RBACContext = createContext(null);
18
+ function RBACProvider(props) {
19
+ const {
20
+ client,
21
+ appId,
22
+ includeGlobal = true,
23
+ globalAppId = "global",
24
+ permissions: permOptsRaw,
25
+ logger,
26
+ children
27
+ } = props;
28
+ const permOpts = {
29
+ enabled: permOptsRaw?.enabled ?? false,
30
+ rpcName: permOptsRaw?.rpcName ?? "get_my_permissions",
31
+ ttlMs: permOptsRaw?.ttlMs ?? 6e4,
32
+ nonBlocking: permOptsRaw?.nonBlocking ?? true
33
+ };
34
+ const sb = client;
35
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
36
+ const [userId, setUserId] = useState(null);
37
+ const [roleIds, setRoleIds] = useState([]);
38
+ const [permissionNames, setPermissionNames] = useState([]);
39
+ const [isRBACLoading, setIsRBACLoading] = useState(true);
40
+ const [isPermissionLoading, setIsPermissionLoading] = useState(false);
41
+ const isRBACReady = !isRBACLoading;
42
+ const isPermissionReady = permOpts.enabled ? !isPermissionLoading : true;
43
+ const syncLock = useRef(null);
44
+ const permissionRequestId = useRef(0);
45
+ const sessionEpoch = useRef(0);
46
+ const pendingSync = useRef(null);
47
+ const fetchPermissionNames = useCallback(async (params) => {
48
+ if (!permOpts.enabled) return [];
49
+ if (!params.userId) return [];
50
+ const key = cacheKey(params.appId, params.userId);
51
+ const now = Date.now();
52
+ const existing = permissionCache.get(key);
53
+ const fresh = existing && existing.inflight == null && now - existing.fetchedAt < permOpts.ttlMs && // EN: If token exp changed, treat cache as stale to avoid wrong UI
54
+ // 中文:token exp 变化则视为过期,避免 UI 错判
55
+ existing.tokenExp === params.tokenExp;
56
+ if (fresh) return existing.names;
57
+ if (existing?.inflight) return existing.inflight;
58
+ const inflight = (async () => {
59
+ setIsPermissionLoading(true);
60
+ try {
61
+ const { data, error } = await sb.rpc(permOpts.rpcName, { target_app_id: params.appId });
62
+ if (error) throw error;
63
+ const names = Array.isArray(data) ? data.map((x) => String(x?.permission_name ?? "")).filter((s) => s.length > 0) : [];
64
+ permissionCache.set(key, { fetchedAt: Date.now(), names, inflight: null, tokenExp: params.tokenExp });
65
+ return names;
66
+ } catch (e) {
67
+ permissionCache.set(key, {
68
+ fetchedAt: existing?.fetchedAt ?? 0,
69
+ names: existing?.names ?? [],
70
+ inflight: null,
71
+ tokenExp: params.tokenExp
72
+ });
73
+ throw e;
74
+ } finally {
75
+ setIsPermissionLoading(false);
76
+ }
77
+ })();
78
+ permissionCache.set(key, { fetchedAt: existing?.fetchedAt ?? 0, names: existing?.names ?? [], inflight, tokenExp: params.tokenExp });
79
+ return inflight;
80
+ }, [permOpts.enabled, permOpts.rpcName, permOpts.ttlMs, sb]);
81
+ const applySession = useCallback(async (session, epoch) => {
82
+ const accessToken = session?.access_token;
83
+ const authed = Boolean(session?.user?.id);
84
+ const { roleIds: merged, userId: parsedUid, exp } = getMergedRoleIds({
85
+ token: accessToken,
86
+ appId,
87
+ includeGlobal,
88
+ globalAppId
89
+ });
90
+ const uid = session?.user?.id && typeof session.user.id === "string" ? session.user.id : parsedUid;
91
+ if (epoch !== sessionEpoch.current) return;
92
+ setIsAuthenticated(authed);
93
+ setUserId(uid ?? null);
94
+ setRoleIds(merged);
95
+ const requestId = ++permissionRequestId.current;
96
+ if (!permOpts.enabled) {
97
+ setPermissionNames([]);
98
+ return;
99
+ }
100
+ const load = async () => {
101
+ try {
102
+ if (epoch !== sessionEpoch.current) return;
103
+ const names = await fetchPermissionNames({ appId, userId: uid ?? null, tokenExp: exp });
104
+ if (epoch !== sessionEpoch.current || requestId !== permissionRequestId.current) return;
105
+ setPermissionNames(names);
106
+ } catch (e) {
107
+ if (epoch !== sessionEpoch.current || requestId !== permissionRequestId.current) return;
108
+ setPermissionNames([]);
109
+ log(logger, "warn", "[RBAC] permissions RPC failed; degraded to role-only gating.", e);
110
+ }
111
+ };
112
+ if (permOpts.nonBlocking) void load();
113
+ else await load();
114
+ }, [appId, includeGlobal, globalAppId, permOpts.enabled, permOpts.nonBlocking, fetchPermissionNames, logger]);
115
+ const runSync = useCallback((forceSessionUpdate = false, sessionHint) => {
116
+ const promise = (async () => {
117
+ setIsRBACLoading(true);
118
+ try {
119
+ if (forceSessionUpdate && sb.auth.refreshSession) {
120
+ await sb.auth.refreshSession();
121
+ }
122
+ const session = sessionHint ? sessionHint : (await sb.auth.getSession()).data.session;
123
+ const epoch = sessionEpoch.current;
124
+ await applySession(session, epoch);
125
+ } finally {
126
+ setIsRBACLoading(false);
127
+ }
128
+ })();
129
+ syncLock.current = promise;
130
+ promise.finally(() => {
131
+ const pending = pendingSync.current;
132
+ if (pending) {
133
+ pendingSync.current = null;
134
+ const next = runSync(pending.forceSessionUpdate, pending.sessionHint);
135
+ next.then(pending.resolve, pending.reject);
136
+ return;
137
+ }
138
+ if (syncLock.current === promise) {
139
+ syncLock.current = null;
140
+ }
141
+ });
142
+ return promise;
143
+ }, [applySession, sb]);
144
+ const sync = useCallback((forceSessionUpdate = false, sessionHint) => {
145
+ if (syncLock.current) {
146
+ if (!pendingSync.current) {
147
+ let resolve;
148
+ let reject;
149
+ const promise = new Promise((res, rej) => {
150
+ resolve = res;
151
+ reject = rej;
152
+ });
153
+ pendingSync.current = { forceSessionUpdate, sessionHint, promise, resolve, reject };
154
+ } else {
155
+ pendingSync.current.forceSessionUpdate = pendingSync.current.forceSessionUpdate || forceSessionUpdate;
156
+ if (sessionHint) {
157
+ pendingSync.current.sessionHint = sessionHint;
158
+ } else if (forceSessionUpdate) {
159
+ pendingSync.current.sessionHint = void 0;
160
+ }
161
+ }
162
+ return pendingSync.current.promise;
163
+ }
164
+ return runSync(forceSessionUpdate, sessionHint);
165
+ }, [runSync]);
166
+ useEffect(() => {
167
+ void sync(false);
168
+ const { data: { subscription } } = sb.auth.onAuthStateChange((event, session) => {
169
+ if (event === "SIGNED_OUT") {
170
+ sessionEpoch.current++;
171
+ const pending = pendingSync.current;
172
+ pendingSync.current = null;
173
+ if (pending) pending.resolve();
174
+ permissionRequestId.current++;
175
+ setIsAuthenticated(false);
176
+ setUserId(null);
177
+ setRoleIds([]);
178
+ setPermissionNames([]);
179
+ setIsRBACLoading(false);
180
+ setIsPermissionLoading(false);
181
+ return;
182
+ }
183
+ if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED" || event === "USER_UPDATED") {
184
+ void sync(false, session);
185
+ }
186
+ });
187
+ return () => subscription.unsubscribe();
188
+ }, [sb, sync]);
189
+ const refresh = useCallback(async (forceSessionUpdate = true) => {
190
+ await sync(forceSessionUpdate);
191
+ }, [sync]);
192
+ const hasRole2 = useCallback((required, mode = "any") => {
193
+ return hasRole(roleIds, required, mode);
194
+ }, [roleIds]);
195
+ const hasPermission = useCallback((permissionName) => {
196
+ if (!permOpts.enabled) return false;
197
+ if (!permissionName) return false;
198
+ return permissionNames.includes(permissionName);
199
+ }, [permOpts.enabled, permissionNames]);
200
+ const value = useMemo(() => ({
201
+ appId,
202
+ isAuthenticated,
203
+ userId,
204
+ roleIds,
205
+ permissionNames,
206
+ isRBACLoading,
207
+ isPermissionLoading,
208
+ isRBACReady,
209
+ isPermissionReady,
210
+ refresh,
211
+ hasRole: hasRole2,
212
+ hasPermission
213
+ }), [
214
+ appId,
215
+ isAuthenticated,
216
+ userId,
217
+ roleIds,
218
+ permissionNames,
219
+ isRBACLoading,
220
+ isPermissionLoading,
221
+ isRBACReady,
222
+ isPermissionReady,
223
+ refresh,
224
+ hasRole2,
225
+ hasPermission
226
+ ]);
227
+ return /* @__PURE__ */ jsx(RBACContext.Provider, { value, children });
228
+ }
229
+ function useRBAC() {
230
+ const ctx = useContext(RBACContext);
231
+ if (!ctx) throw new Error("useRBAC must be used within <RBACProvider>.");
232
+ return ctx;
233
+ }
234
+ export {
235
+ RBACProvider,
236
+ useRBAC
237
+ };