@ahmed109/gatekeeper-react 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ahmed Tamer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,76 @@
1
- # Temporary Holding Version
1
+ # @ahmed109/gatekeeper-react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Declarative React authorization and feature-flag toolkit with fail-safe loading state.
4
+
5
+ Part of the **Gatekeeper** toolkit. Built on top of [`@ahmed109/gatekeeper-core`](https://www.npmjs.com/package/@ahmed109/gatekeeper-core).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @ahmed109/gatekeeper-react @ahmed109/gatekeeper-core
11
+ # or
12
+ pnpm add @ahmed109/gatekeeper-react @ahmed109/gatekeeper-core
13
+ ```
14
+
15
+ ### Peer Dependencies
16
+
17
+ - `react`: `>=18.0.0`
18
+ - `react-dom`: `>=18.0.0`
19
+
20
+ ## Features
21
+
22
+ - **Fail-Safe Loading**: Authorization decisions default to closed (`false`) during `isLoading: true`, eliminating UI flashes of restricted content.
23
+ - **Declarative Guards**: `<Can>` and `<Feature>` components with customizable `fallback` slots.
24
+ - **Reactive Hooks**: `usePermission()`, `useFeatureFlag()`, `useCanAll()`, `useCanAny()`, and `useGatekeeper()`.
25
+ - **Permission Memoization**: Automatically caches effective permission sets to minimize evaluation overhead.
26
+
27
+ ## Quickstart
28
+
29
+ ```tsx
30
+ import React from 'react';
31
+ import { GatekeeperProvider, Can, Feature, usePermission } from '@ahmed109/gatekeeper-react';
32
+ import type { GatekeeperUser } from '@ahmed109/gatekeeper-core';
33
+
34
+ const currentUser: GatekeeperUser = {
35
+ id: 'usr_123',
36
+ name: 'Alice',
37
+ role: 'manager',
38
+ permissions: [],
39
+ };
40
+
41
+ const activeFlags = {
42
+ 'beta-analytics': true,
43
+ };
44
+
45
+ export function App() {
46
+ return (
47
+ <GatekeeperProvider user={currentUser} flags={activeFlags} isLoading={false}>
48
+ <Dashboard />
49
+ </GatekeeperProvider>
50
+ );
51
+ }
52
+
53
+ function Dashboard() {
54
+ const canManage = usePermission('settings.manage');
55
+
56
+ return (
57
+ <div>
58
+ <h1>Operations Dashboard</h1>
59
+
60
+ {/* Declarative permission guard */}
61
+ <Can permission="users.read" fallback={<p>Access denied to users list.</p>}>
62
+ <UsersList />
63
+ </Can>
64
+
65
+ {/* Declarative feature flag guard */}
66
+ <Feature flag="beta-analytics" fallback={<p>Analytics coming soon.</p>}>
67
+ <AnalyticsPanel />
68
+ </Feature>
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT © [Ahmed Tamer](https://github.com/Ahmed1092002)
package/dist/index.cjs ADDED
@@ -0,0 +1,180 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Can: () => Can,
24
+ Feature: () => Feature,
25
+ GatekeeperContext: () => GatekeeperContext,
26
+ GatekeeperProvider: () => GatekeeperProvider,
27
+ useCanAll: () => useCanAll,
28
+ useCanAny: () => useCanAny,
29
+ useFeatureFlag: () => useFeatureFlag,
30
+ useGatekeeper: () => useGatekeeper,
31
+ usePermission: () => usePermission
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/context.tsx
36
+ var import_react = require("react");
37
+ var import_gatekeeper_core = require("@ahmed109/gatekeeper-core");
38
+ var import_jsx_runtime = require("react/jsx-runtime");
39
+ var GatekeeperContext = (0, import_react.createContext)(null);
40
+ var GatekeeperProvider = ({
41
+ user,
42
+ currentUser,
43
+ subject,
44
+ flags,
45
+ featureFlags,
46
+ roleMap = import_gatekeeper_core.DEFAULT_ROLE_PERMISSIONS,
47
+ policy,
48
+ context,
49
+ isLoading = false,
50
+ children,
51
+ fallback = null
52
+ }) => {
53
+ const activeUser = subject ?? user ?? currentUser ?? null;
54
+ const rawFlags = flags ?? featureFlags;
55
+ const normalizedFlags = (0, import_react.useMemo)(() => (0, import_gatekeeper_core.normalizeFeatureFlags)(rawFlags), [rawFlags]);
56
+ const effectivePermissions = (0, import_react.useMemo)(() => {
57
+ if (isLoading) return [];
58
+ return Array.from((0, import_gatekeeper_core.getUserEffectivePermissions)(activeUser, roleMap));
59
+ }, [activeUser, roleMap, isLoading]);
60
+ const contextValue = (0, import_react.useMemo)(() => {
61
+ return {
62
+ user: activeUser,
63
+ subject: activeUser,
64
+ flags: normalizedFlags,
65
+ roleMap,
66
+ policy,
67
+ context,
68
+ isLoading,
69
+ can: (permission, canOptions) => {
70
+ if (isLoading) return false;
71
+ const combinedOptions = {
72
+ roleMap: canOptions?.roleMap ?? roleMap,
73
+ policy: canOptions?.policy ?? policy,
74
+ context: canOptions?.context ?? context
75
+ };
76
+ return (0, import_gatekeeper_core.can)(activeUser, permission, combinedOptions);
77
+ },
78
+ canAll: (permissions, canOptions) => {
79
+ if (isLoading) return false;
80
+ const combinedOptions = {
81
+ roleMap: canOptions?.roleMap ?? roleMap,
82
+ policy: canOptions?.policy ?? policy,
83
+ context: canOptions?.context ?? context
84
+ };
85
+ return (0, import_gatekeeper_core.canAll)(activeUser, permissions, combinedOptions);
86
+ },
87
+ canAny: (permissions, canOptions) => {
88
+ if (isLoading) return false;
89
+ const combinedOptions = {
90
+ roleMap: canOptions?.roleMap ?? roleMap,
91
+ policy: canOptions?.policy ?? policy,
92
+ context: canOptions?.context ?? context
93
+ };
94
+ return (0, import_gatekeeper_core.canAny)(activeUser, permissions, combinedOptions);
95
+ },
96
+ isFeatureEnabled: (flagKey) => {
97
+ if (isLoading) return false;
98
+ return (0, import_gatekeeper_core.isFeatureEnabled)(normalizedFlags, flagKey);
99
+ },
100
+ effectivePermissions
101
+ };
102
+ }, [activeUser, normalizedFlags, roleMap, policy, context, isLoading, effectivePermissions]);
103
+ if (isLoading && fallback) {
104
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GatekeeperContext.Provider, { value: contextValue, children: fallback });
105
+ }
106
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GatekeeperContext.Provider, { value: contextValue, children });
107
+ };
108
+
109
+ // src/hooks.ts
110
+ var import_react2 = require("react");
111
+ function useGatekeeper() {
112
+ const context = (0, import_react2.useContext)(GatekeeperContext);
113
+ if (!context) {
114
+ throw new Error(
115
+ "useGatekeeper must be used within a <GatekeeperProvider>. Make sure to wrap your application with <GatekeeperProvider>."
116
+ );
117
+ }
118
+ return context;
119
+ }
120
+ function usePermission(permission, options) {
121
+ const { can } = useGatekeeper();
122
+ return can(permission, options);
123
+ }
124
+ function useFeatureFlag(flagKey) {
125
+ const { isFeatureEnabled } = useGatekeeper();
126
+ return isFeatureEnabled(flagKey);
127
+ }
128
+ function useCanAll(permissions, options) {
129
+ const { canAll } = useGatekeeper();
130
+ return canAll(permissions, options);
131
+ }
132
+ function useCanAny(permissions, options) {
133
+ const { canAny } = useGatekeeper();
134
+ return canAny(permissions, options);
135
+ }
136
+
137
+ // src/components.tsx
138
+ var import_jsx_runtime2 = require("react/jsx-runtime");
139
+ var Can = ({
140
+ permission,
141
+ permissions,
142
+ requireAll = true,
143
+ context,
144
+ policy,
145
+ fallback = null,
146
+ children
147
+ }) => {
148
+ const { can, canAll, canAny } = useGatekeeper();
149
+ let isAllowed = false;
150
+ const options = context || policy ? { context, policy } : void 0;
151
+ if (permission) {
152
+ isAllowed = can(permission, options);
153
+ } else if (permissions && permissions.length > 0) {
154
+ isAllowed = requireAll ? canAll(permissions, options) : canAny(permissions, options);
155
+ }
156
+ if (!isAllowed) {
157
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
158
+ }
159
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
160
+ };
161
+ var Feature = ({ flag, fallback = null, children }) => {
162
+ const { isFeatureEnabled } = useGatekeeper();
163
+ const enabled = isFeatureEnabled(flag);
164
+ if (!enabled) {
165
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
166
+ }
167
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
168
+ };
169
+ // Annotate the CommonJS export names for ESM import in node:
170
+ 0 && (module.exports = {
171
+ Can,
172
+ Feature,
173
+ GatekeeperContext,
174
+ GatekeeperProvider,
175
+ useCanAll,
176
+ useCanAny,
177
+ useFeatureFlag,
178
+ useGatekeeper,
179
+ usePermission
180
+ });
@@ -0,0 +1,110 @@
1
+ import { Permission, AuthorizationPolicy, AuthorizationSubject, RolePermissionsMap, EvaluateOptions, FeatureFlag } from '@ahmed109/gatekeeper-core';
2
+ export { AuthorizationResult, FeatureFlag, GatekeeperUser, Permission, Role, RolePermissionsMap } from '@ahmed109/gatekeeper-core';
3
+ import React, { ReactNode } from 'react';
4
+
5
+ interface GatekeeperContextValue {
6
+ user: AuthorizationSubject | null;
7
+ subject: AuthorizationSubject | null;
8
+ flags: Record<string, boolean>;
9
+ roleMap: RolePermissionsMap;
10
+ policy?: AuthorizationPolicy;
11
+ context?: Record<string, unknown>;
12
+ isLoading: boolean;
13
+ can: (permission: Permission, options?: EvaluateOptions) => boolean;
14
+ canAll: (permissions: Permission[], options?: EvaluateOptions) => boolean;
15
+ canAny: (permissions: Permission[], options?: EvaluateOptions) => boolean;
16
+ isFeatureEnabled: (flagKey: string) => boolean;
17
+ effectivePermissions: string[];
18
+ }
19
+ interface GatekeeperProviderProps {
20
+ user?: AuthorizationSubject | null;
21
+ currentUser?: AuthorizationSubject | null;
22
+ subject?: AuthorizationSubject | null;
23
+ flags?: Record<string, boolean> | FeatureFlag[];
24
+ featureFlags?: Record<string, boolean> | FeatureFlag[];
25
+ roleMap?: RolePermissionsMap;
26
+ policy?: AuthorizationPolicy;
27
+ context?: Record<string, unknown>;
28
+ isLoading?: boolean;
29
+ children: ReactNode;
30
+ fallback?: ReactNode;
31
+ }
32
+ interface CanProps {
33
+ /** Single permission to check */
34
+ permission?: Permission;
35
+ /** Multiple permissions to check */
36
+ permissions?: Permission[];
37
+ /** If true when multiple permissions given, user must have all. Default is true */
38
+ requireAll?: boolean;
39
+ /** Optional scoped context for policy evaluation */
40
+ context?: Record<string, unknown>;
41
+ /** Optional policy callback overriding or supplementing provider policy */
42
+ policy?: AuthorizationPolicy;
43
+ /** Custom fallback component if unauthorized */
44
+ fallback?: ReactNode;
45
+ /** Content to render when authorized */
46
+ children: ReactNode;
47
+ }
48
+ interface FeatureProps {
49
+ /** Feature flag key to check */
50
+ flag: string;
51
+ /** Custom fallback component if flag is disabled */
52
+ fallback?: ReactNode;
53
+ /** Content to render when flag is active */
54
+ children: ReactNode;
55
+ }
56
+
57
+ declare const GatekeeperContext: React.Context<GatekeeperContextValue | null>;
58
+ /**
59
+ * Top-level provider for Gatekeeper authorization and feature flags in React.
60
+ */
61
+ declare const GatekeeperProvider: React.FC<GatekeeperProviderProps>;
62
+
63
+ /**
64
+ * Access the full Gatekeeper context.
65
+ */
66
+ declare function useGatekeeper(): GatekeeperContextValue;
67
+ /**
68
+ * React hook to check if the current user has a specific permission.
69
+ *
70
+ * @example
71
+ * const canDelete = usePermission('users.delete');
72
+ * const canEdit = usePermission('projects.update', { context: { projectOwnerId: 'u1' } });
73
+ */
74
+ declare function usePermission(permission: Permission, options?: EvaluateOptions): boolean;
75
+ /**
76
+ * React hook to check if a feature flag is enabled.
77
+ *
78
+ * @example
79
+ * const hasBeta = useFeatureFlag('beta-analytics');
80
+ */
81
+ declare function useFeatureFlag(flagKey: string): boolean;
82
+ /**
83
+ * React hook to check if the current user has all specified permissions.
84
+ */
85
+ declare function useCanAll(permissions: Permission[], options?: EvaluateOptions): boolean;
86
+ /**
87
+ * React hook to check if the current user has at least one of the specified permissions.
88
+ */
89
+ declare function useCanAny(permissions: Permission[], options?: EvaluateOptions): boolean;
90
+
91
+ /**
92
+ * Conditionally renders children if the authenticated user has the required permission(s).
93
+ *
94
+ * @example
95
+ * <Can permission="users.delete" fallback={<p>Not allowed</p>}>
96
+ * <DeleteUserButton />
97
+ * </Can>
98
+ */
99
+ declare const Can: React.FC<CanProps>;
100
+ /**
101
+ * Conditionally renders children if a specific feature flag is enabled.
102
+ *
103
+ * @example
104
+ * <Feature flag="new-dashboard" fallback={<LegacyDashboard />}>
105
+ * <NewDashboard />
106
+ * </Feature>
107
+ */
108
+ declare const Feature: React.FC<FeatureProps>;
109
+
110
+ export { Can, type CanProps, Feature, type FeatureProps, GatekeeperContext, type GatekeeperContextValue, GatekeeperProvider, type GatekeeperProviderProps, useCanAll, useCanAny, useFeatureFlag, useGatekeeper, usePermission };
@@ -0,0 +1,110 @@
1
+ import { Permission, AuthorizationPolicy, AuthorizationSubject, RolePermissionsMap, EvaluateOptions, FeatureFlag } from '@ahmed109/gatekeeper-core';
2
+ export { AuthorizationResult, FeatureFlag, GatekeeperUser, Permission, Role, RolePermissionsMap } from '@ahmed109/gatekeeper-core';
3
+ import React, { ReactNode } from 'react';
4
+
5
+ interface GatekeeperContextValue {
6
+ user: AuthorizationSubject | null;
7
+ subject: AuthorizationSubject | null;
8
+ flags: Record<string, boolean>;
9
+ roleMap: RolePermissionsMap;
10
+ policy?: AuthorizationPolicy;
11
+ context?: Record<string, unknown>;
12
+ isLoading: boolean;
13
+ can: (permission: Permission, options?: EvaluateOptions) => boolean;
14
+ canAll: (permissions: Permission[], options?: EvaluateOptions) => boolean;
15
+ canAny: (permissions: Permission[], options?: EvaluateOptions) => boolean;
16
+ isFeatureEnabled: (flagKey: string) => boolean;
17
+ effectivePermissions: string[];
18
+ }
19
+ interface GatekeeperProviderProps {
20
+ user?: AuthorizationSubject | null;
21
+ currentUser?: AuthorizationSubject | null;
22
+ subject?: AuthorizationSubject | null;
23
+ flags?: Record<string, boolean> | FeatureFlag[];
24
+ featureFlags?: Record<string, boolean> | FeatureFlag[];
25
+ roleMap?: RolePermissionsMap;
26
+ policy?: AuthorizationPolicy;
27
+ context?: Record<string, unknown>;
28
+ isLoading?: boolean;
29
+ children: ReactNode;
30
+ fallback?: ReactNode;
31
+ }
32
+ interface CanProps {
33
+ /** Single permission to check */
34
+ permission?: Permission;
35
+ /** Multiple permissions to check */
36
+ permissions?: Permission[];
37
+ /** If true when multiple permissions given, user must have all. Default is true */
38
+ requireAll?: boolean;
39
+ /** Optional scoped context for policy evaluation */
40
+ context?: Record<string, unknown>;
41
+ /** Optional policy callback overriding or supplementing provider policy */
42
+ policy?: AuthorizationPolicy;
43
+ /** Custom fallback component if unauthorized */
44
+ fallback?: ReactNode;
45
+ /** Content to render when authorized */
46
+ children: ReactNode;
47
+ }
48
+ interface FeatureProps {
49
+ /** Feature flag key to check */
50
+ flag: string;
51
+ /** Custom fallback component if flag is disabled */
52
+ fallback?: ReactNode;
53
+ /** Content to render when flag is active */
54
+ children: ReactNode;
55
+ }
56
+
57
+ declare const GatekeeperContext: React.Context<GatekeeperContextValue | null>;
58
+ /**
59
+ * Top-level provider for Gatekeeper authorization and feature flags in React.
60
+ */
61
+ declare const GatekeeperProvider: React.FC<GatekeeperProviderProps>;
62
+
63
+ /**
64
+ * Access the full Gatekeeper context.
65
+ */
66
+ declare function useGatekeeper(): GatekeeperContextValue;
67
+ /**
68
+ * React hook to check if the current user has a specific permission.
69
+ *
70
+ * @example
71
+ * const canDelete = usePermission('users.delete');
72
+ * const canEdit = usePermission('projects.update', { context: { projectOwnerId: 'u1' } });
73
+ */
74
+ declare function usePermission(permission: Permission, options?: EvaluateOptions): boolean;
75
+ /**
76
+ * React hook to check if a feature flag is enabled.
77
+ *
78
+ * @example
79
+ * const hasBeta = useFeatureFlag('beta-analytics');
80
+ */
81
+ declare function useFeatureFlag(flagKey: string): boolean;
82
+ /**
83
+ * React hook to check if the current user has all specified permissions.
84
+ */
85
+ declare function useCanAll(permissions: Permission[], options?: EvaluateOptions): boolean;
86
+ /**
87
+ * React hook to check if the current user has at least one of the specified permissions.
88
+ */
89
+ declare function useCanAny(permissions: Permission[], options?: EvaluateOptions): boolean;
90
+
91
+ /**
92
+ * Conditionally renders children if the authenticated user has the required permission(s).
93
+ *
94
+ * @example
95
+ * <Can permission="users.delete" fallback={<p>Not allowed</p>}>
96
+ * <DeleteUserButton />
97
+ * </Can>
98
+ */
99
+ declare const Can: React.FC<CanProps>;
100
+ /**
101
+ * Conditionally renders children if a specific feature flag is enabled.
102
+ *
103
+ * @example
104
+ * <Feature flag="new-dashboard" fallback={<LegacyDashboard />}>
105
+ * <NewDashboard />
106
+ * </Feature>
107
+ */
108
+ declare const Feature: React.FC<FeatureProps>;
109
+
110
+ export { Can, type CanProps, Feature, type FeatureProps, GatekeeperContext, type GatekeeperContextValue, GatekeeperProvider, type GatekeeperProviderProps, useCanAll, useCanAny, useFeatureFlag, useGatekeeper, usePermission };
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ // src/context.tsx
2
+ import { createContext, useMemo } from "react";
3
+ import {
4
+ DEFAULT_ROLE_PERMISSIONS,
5
+ can as coreCan,
6
+ canAll as coreCanAll,
7
+ canAny as coreCanAny,
8
+ getUserEffectivePermissions,
9
+ isFeatureEnabled as coreIsFeatureEnabled,
10
+ normalizeFeatureFlags
11
+ } from "@ahmed109/gatekeeper-core";
12
+ import { jsx } from "react/jsx-runtime";
13
+ var GatekeeperContext = createContext(null);
14
+ var GatekeeperProvider = ({
15
+ user,
16
+ currentUser,
17
+ subject,
18
+ flags,
19
+ featureFlags,
20
+ roleMap = DEFAULT_ROLE_PERMISSIONS,
21
+ policy,
22
+ context,
23
+ isLoading = false,
24
+ children,
25
+ fallback = null
26
+ }) => {
27
+ const activeUser = subject ?? user ?? currentUser ?? null;
28
+ const rawFlags = flags ?? featureFlags;
29
+ const normalizedFlags = useMemo(() => normalizeFeatureFlags(rawFlags), [rawFlags]);
30
+ const effectivePermissions = useMemo(() => {
31
+ if (isLoading) return [];
32
+ return Array.from(getUserEffectivePermissions(activeUser, roleMap));
33
+ }, [activeUser, roleMap, isLoading]);
34
+ const contextValue = useMemo(() => {
35
+ return {
36
+ user: activeUser,
37
+ subject: activeUser,
38
+ flags: normalizedFlags,
39
+ roleMap,
40
+ policy,
41
+ context,
42
+ isLoading,
43
+ can: (permission, canOptions) => {
44
+ if (isLoading) return false;
45
+ const combinedOptions = {
46
+ roleMap: canOptions?.roleMap ?? roleMap,
47
+ policy: canOptions?.policy ?? policy,
48
+ context: canOptions?.context ?? context
49
+ };
50
+ return coreCan(activeUser, permission, combinedOptions);
51
+ },
52
+ canAll: (permissions, canOptions) => {
53
+ if (isLoading) return false;
54
+ const combinedOptions = {
55
+ roleMap: canOptions?.roleMap ?? roleMap,
56
+ policy: canOptions?.policy ?? policy,
57
+ context: canOptions?.context ?? context
58
+ };
59
+ return coreCanAll(activeUser, permissions, combinedOptions);
60
+ },
61
+ canAny: (permissions, canOptions) => {
62
+ if (isLoading) return false;
63
+ const combinedOptions = {
64
+ roleMap: canOptions?.roleMap ?? roleMap,
65
+ policy: canOptions?.policy ?? policy,
66
+ context: canOptions?.context ?? context
67
+ };
68
+ return coreCanAny(activeUser, permissions, combinedOptions);
69
+ },
70
+ isFeatureEnabled: (flagKey) => {
71
+ if (isLoading) return false;
72
+ return coreIsFeatureEnabled(normalizedFlags, flagKey);
73
+ },
74
+ effectivePermissions
75
+ };
76
+ }, [activeUser, normalizedFlags, roleMap, policy, context, isLoading, effectivePermissions]);
77
+ if (isLoading && fallback) {
78
+ return /* @__PURE__ */ jsx(GatekeeperContext.Provider, { value: contextValue, children: fallback });
79
+ }
80
+ return /* @__PURE__ */ jsx(GatekeeperContext.Provider, { value: contextValue, children });
81
+ };
82
+
83
+ // src/hooks.ts
84
+ import { useContext } from "react";
85
+ function useGatekeeper() {
86
+ const context = useContext(GatekeeperContext);
87
+ if (!context) {
88
+ throw new Error(
89
+ "useGatekeeper must be used within a <GatekeeperProvider>. Make sure to wrap your application with <GatekeeperProvider>."
90
+ );
91
+ }
92
+ return context;
93
+ }
94
+ function usePermission(permission, options) {
95
+ const { can } = useGatekeeper();
96
+ return can(permission, options);
97
+ }
98
+ function useFeatureFlag(flagKey) {
99
+ const { isFeatureEnabled } = useGatekeeper();
100
+ return isFeatureEnabled(flagKey);
101
+ }
102
+ function useCanAll(permissions, options) {
103
+ const { canAll } = useGatekeeper();
104
+ return canAll(permissions, options);
105
+ }
106
+ function useCanAny(permissions, options) {
107
+ const { canAny } = useGatekeeper();
108
+ return canAny(permissions, options);
109
+ }
110
+
111
+ // src/components.tsx
112
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
113
+ var Can = ({
114
+ permission,
115
+ permissions,
116
+ requireAll = true,
117
+ context,
118
+ policy,
119
+ fallback = null,
120
+ children
121
+ }) => {
122
+ const { can, canAll, canAny } = useGatekeeper();
123
+ let isAllowed = false;
124
+ const options = context || policy ? { context, policy } : void 0;
125
+ if (permission) {
126
+ isAllowed = can(permission, options);
127
+ } else if (permissions && permissions.length > 0) {
128
+ isAllowed = requireAll ? canAll(permissions, options) : canAny(permissions, options);
129
+ }
130
+ if (!isAllowed) {
131
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
132
+ }
133
+ return /* @__PURE__ */ jsx2(Fragment, { children });
134
+ };
135
+ var Feature = ({ flag, fallback = null, children }) => {
136
+ const { isFeatureEnabled } = useGatekeeper();
137
+ const enabled = isFeatureEnabled(flag);
138
+ if (!enabled) {
139
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
140
+ }
141
+ return /* @__PURE__ */ jsx2(Fragment, { children });
142
+ };
143
+ export {
144
+ Can,
145
+ Feature,
146
+ GatekeeperContext,
147
+ GatekeeperProvider,
148
+ useCanAll,
149
+ useCanAny,
150
+ useFeatureFlag,
151
+ useGatekeeper,
152
+ usePermission
153
+ };
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "@ahmed109/gatekeeper-react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "React authorization and feature-flag toolkit with fail-safe loading state powered by Gatekeeper Core",
5
+ "keywords": [
6
+ "react",
7
+ "authorization",
8
+ "permissions",
9
+ "feature-flags",
10
+ "rbac",
11
+ "hooks",
12
+ "components"
13
+ ],
14
+ "author": "Ahmed Tamer",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Ahmed1092002/npm-packages.git",
19
+ "directory": "gatekeeper/packages/react"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/Ahmed1092002/npm-packages/issues"
23
+ },
24
+ "homepage": "https://github.com/Ahmed1092002/npm-packages/tree/main/gatekeeper#readme",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "type": "module",
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js",
36
+ "require": "./dist/index.cjs"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "peerDependencies": {
45
+ "react": ">=18.0.0",
46
+ "react-dom": ">=18.0.0"
47
+ },
48
+ "dependencies": {
49
+ "@ahmed109/gatekeeper-core": "^0.2.0"
50
+ },
51
+ "devDependencies": {
52
+ "@testing-library/jest-dom": "^6.6.3",
53
+ "@testing-library/react": "^16.2.0",
54
+ "@types/react": "^19.0.8",
55
+ "@types/react-dom": "^19.0.3",
56
+ "jsdom": "^26.0.0",
57
+ "react": "^19.0.0",
58
+ "react-dom": "^19.0.0",
59
+ "tsup": "^8.3.6",
60
+ "typescript": "^5.7.3",
61
+ "vitest": "^3.0.5"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react,react-dom,@ahmed109/gatekeeper-core",
65
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch --external react,react-dom,@ahmed109/gatekeeper-core",
66
+ "test": "vitest run",
67
+ "lint": "eslint src"
68
+ }
6
69
  }