com-angel-authorization 1.0.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 Angel
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 ADDED
@@ -0,0 +1,276 @@
1
+ # com-angel-authorization
2
+
3
+ 框架无关的用户权限配置 npm 包,可在 **React** 与 **Vue 3** 项目中使用。
4
+
5
+ ## 特性
6
+
7
+ - 核心层与 UI 框架解耦,也可直接在非前端环境使用
8
+ - 支持权限码(permission)与角色(role)
9
+ - `any` / `all` 匹配模式
10
+ - 超级管理员旁路(`isSuperAdmin`)
11
+ - React:`Provider` + Hooks + `<Can>`
12
+ - Vue 3:Plugin + Composables + `v-permission` + `<Can>`
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install com-angel-authorization
18
+ # 或
19
+ pnpm add com-angel-authorization
20
+ ```
21
+
22
+ 按需安装 peer 依赖:
23
+
24
+ ```bash
25
+ # React 项目
26
+ npm install react
27
+
28
+ # Vue 项目
29
+ npm install vue
30
+ ```
31
+
32
+ ## 包入口
33
+
34
+ | 入口 | 用途 |
35
+ |------|------|
36
+ | `com-angel-authorization` | 框架无关核心 |
37
+ | `com-angel-authorization/react` | React 适配 |
38
+ | `com-angel-authorization/vue` | Vue 3 适配 |
39
+
40
+ ---
41
+
42
+ ## 核心 API(框架无关)
43
+
44
+ ```ts
45
+ import { createPermissionStore } from 'com-angel-authorization';
46
+
47
+ const store = createPermissionStore({
48
+ permissions: ['user:view', 'user:edit'],
49
+ roles: ['editor'],
50
+ isSuperAdmin: false,
51
+ });
52
+
53
+ store.hasPermission('user:edit'); // true
54
+ store.hasPermission(['user:edit', 'user:delete'], { mode: 'all' }); // false
55
+ store.hasRole('editor'); // true
56
+ store.can('user:view'); // true(hasPermission 别名)
57
+
58
+ // 登录后写入
59
+ store.hydrate({
60
+ permissions: ['user:view', 'user:edit', 'user:delete'],
61
+ roles: ['admin'],
62
+ });
63
+
64
+ // 登出清空
65
+ store.clear();
66
+ ```
67
+
68
+ ---
69
+
70
+ ## React 用法
71
+
72
+ ```tsx
73
+ import {
74
+ PermissionProvider,
75
+ usePermission,
76
+ useHasPermission,
77
+ Can,
78
+ } from 'com-angel-authorization/react';
79
+
80
+ function App() {
81
+ return (
82
+ <PermissionProvider
83
+ initialState={{
84
+ permissions: ['user:view', 'user:edit'],
85
+ roles: ['editor'],
86
+ }}
87
+ >
88
+ <Dashboard />
89
+ </PermissionProvider>
90
+ );
91
+ }
92
+
93
+ function Dashboard() {
94
+ const { can, hydrate, clear } = usePermission();
95
+ const canEdit = useHasPermission('user:edit');
96
+
97
+ // 登录成功后
98
+ // hydrate({ permissions: data.perms, roles: data.roles });
99
+
100
+ // 登出
101
+ // clear();
102
+
103
+ return (
104
+ <div>
105
+ {canEdit && <button>编辑</button>}
106
+
107
+ <Can permission="user:delete" fallback={<span>无删除权限</span>}>
108
+ <button>删除</button>
109
+ </Can>
110
+
111
+ <Can permission={['user:edit', 'user:delete']} mode="all">
112
+ <AdminTools />
113
+ </Can>
114
+
115
+ <Can role="admin" permission="user:edit" combine="or">
116
+ <button>管理入口</button>
117
+ </Can>
118
+
119
+ {can('user:view') && <UserList />}
120
+ </div>
121
+ );
122
+ }
123
+ ```
124
+
125
+ 也可传入外部 store,便于跨树或与路由守卫共享:
126
+
127
+ ```tsx
128
+ import { createPermissionStore } from 'com-angel-authorization';
129
+ import { PermissionProvider } from 'com-angel-authorization/react';
130
+
131
+ export const permissionStore = createPermissionStore();
132
+
133
+ <PermissionProvider store={permissionStore}>
134
+ <App />
135
+ </PermissionProvider>
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Vue 3 用法
141
+
142
+ ### 安装插件
143
+
144
+ ```ts
145
+ import { createApp } from 'vue';
146
+ import { createPermissionPlugin, Can } from 'com-angel-authorization/vue';
147
+ import App from './App.vue';
148
+
149
+ const permission = createPermissionPlugin({
150
+ initialState: {
151
+ permissions: ['user:view', 'user:edit'],
152
+ roles: ['editor'],
153
+ },
154
+ });
155
+
156
+ const app = createApp(App);
157
+ app.use(permission);
158
+ app.component('Can', Can); // 可选:全局注册
159
+ app.mount('#app');
160
+
161
+ // 需要时在模块外访问
162
+ // permission.store.hydrate({ ... })
163
+ ```
164
+
165
+ ### Composition API
166
+
167
+ ```vue
168
+ <script setup lang="ts">
169
+ import { usePermission, useHasPermission, Can } from 'com-angel-authorization/vue';
170
+
171
+ const { can, hydrate, clear } = usePermission();
172
+ const canEdit = useHasPermission('user:edit');
173
+
174
+ async function onLogin() {
175
+ const data = await fetchUserAuth();
176
+ hydrate({
177
+ permissions: data.permissions,
178
+ roles: data.roles,
179
+ isSuperAdmin: data.isSuperAdmin,
180
+ });
181
+ }
182
+ </script>
183
+
184
+ <template>
185
+ <button v-if="canEdit">编辑</button>
186
+
187
+ <Can permission="user:delete">
188
+ <button>删除</button>
189
+ <template #fallback>无删除权限</template>
190
+ </Can>
191
+
192
+ <!-- 指令:无权限时隐藏元素 -->
193
+ <button v-permission="'user:edit'">编辑</button>
194
+ <button v-permission:all="['user:edit', 'user:delete']">危险操作</button>
195
+ <button v-permission:role="'admin'">管理员</button>
196
+ <button
197
+ v-permission="{
198
+ permission: 'user:edit',
199
+ role: 'admin',
200
+ combine: 'or',
201
+ }"
202
+ >
203
+ 管理入口
204
+ </button>
205
+ </template>
206
+ ```
207
+
208
+ ### 选项式 / 模板全局方法
209
+
210
+ 插件安装后可用:
211
+
212
+ ```vue
213
+ <template>
214
+ <button v-if="$can('user:edit')">编辑</button>
215
+ <button v-if="$hasRole('admin')">管理</button>
216
+ </template>
217
+ ```
218
+
219
+ ---
220
+
221
+ ## API 速查
222
+
223
+ ### PermissionStore
224
+
225
+ | 方法 | 说明 |
226
+ |------|------|
227
+ | `hydrate(state)` | 一次性写入权限 / 角色 / 超管 |
228
+ | `setPermissions(list)` | 设置权限码 |
229
+ | `setRoles(list)` | 设置角色 |
230
+ | `setSuperAdmin(bool)` | 设置超管 |
231
+ | `clear()` | 清空 |
232
+ | `hasPermission(codes, { mode })` | 校验权限,`mode`: `any` \| `all` |
233
+ | `hasRole(codes, { mode })` | 校验角色 |
234
+ | `can(codes, options)` | `hasPermission` 别名 |
235
+ | `subscribe(listener)` | 订阅变更,返回取消函数 |
236
+ | `getState()` | 获取当前快照 |
237
+
238
+ ### `<Can>` 组件(React / Vue)
239
+
240
+ | Prop | 说明 |
241
+ |------|------|
242
+ | `permission` | 权限码或数组 |
243
+ | `role` | 角色码或数组 |
244
+ | `mode` | `any`(默认)/ `all` |
245
+ | `combine` | 同时传 permission 与 role 时:`and`(默认)/ `or` |
246
+ | `fallback` | React:无权限占位;Vue:使用 `#fallback` 插槽 |
247
+
248
+ ---
249
+
250
+ ## 典型接入流程
251
+
252
+ 1. 应用启动时创建 store / 安装 Provider 或 Plugin
253
+ 2. 登录成功后调用 `hydrate` 写入后端返回的权限与角色
254
+ 3. 页面用 `<Can>` / `v-permission` / `can()` 控制按钮与菜单
255
+ 4. 路由守卫中直接读同一个 store 做页面级鉴权
256
+ 5. 登出时调用 `clear()`
257
+
258
+ ```ts
259
+ // 路由守卫示例(与框架无关)
260
+ if (!permissionStore.hasPermission('order:view')) {
261
+ return next('/403');
262
+ }
263
+ ```
264
+
265
+ ## 构建
266
+
267
+ ```bash
268
+ npm install
269
+ npm run build
270
+ ```
271
+
272
+ 产物在 `dist/`,支持 ESM / CJS 与类型声明。
273
+
274
+ ## License
275
+
276
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ // src/core/store.ts
4
+ var DEFAULT_STATE = {
5
+ permissions: [],
6
+ roles: [],
7
+ isSuperAdmin: false
8
+ };
9
+ function toArray(value) {
10
+ return Array.isArray(value) ? value : [value];
11
+ }
12
+ function matchCodes(owned, required, mode, isSuperAdmin) {
13
+ if (isSuperAdmin) return true;
14
+ if (required.length === 0) return true;
15
+ if (mode === "all") {
16
+ return required.every((code) => owned.has(code));
17
+ }
18
+ return required.some((code) => owned.has(code));
19
+ }
20
+ var PermissionStore = class {
21
+ constructor(initial) {
22
+ this.listeners = /* @__PURE__ */ new Set();
23
+ this.state = {
24
+ ...DEFAULT_STATE,
25
+ ...initial,
26
+ permissions: initial?.permissions ?? [],
27
+ roles: initial?.roles ?? []
28
+ };
29
+ this.permissionSet = new Set(this.state.permissions);
30
+ this.roleSet = new Set(this.state.roles);
31
+ }
32
+ getState() {
33
+ return {
34
+ permissions: [...this.state.permissions],
35
+ roles: [...this.state.roles],
36
+ isSuperAdmin: this.state.isSuperAdmin
37
+ };
38
+ }
39
+ setState(next) {
40
+ this.state = {
41
+ permissions: next.permissions ?? this.state.permissions,
42
+ roles: next.roles ?? this.state.roles,
43
+ isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin
44
+ };
45
+ this.permissionSet = new Set(this.state.permissions);
46
+ this.roleSet = new Set(this.state.roles);
47
+ this.emit();
48
+ }
49
+ setPermissions(permissions) {
50
+ this.setState({ permissions });
51
+ }
52
+ setRoles(roles) {
53
+ this.setState({ roles });
54
+ }
55
+ setSuperAdmin(isSuperAdmin) {
56
+ this.setState({ isSuperAdmin });
57
+ }
58
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
59
+ hydrate(payload) {
60
+ this.setState({
61
+ permissions: payload.permissions ?? [],
62
+ roles: payload.roles ?? [],
63
+ isSuperAdmin: payload.isSuperAdmin ?? false
64
+ });
65
+ }
66
+ /** Clear all authz data (typical after logout). */
67
+ clear() {
68
+ this.hydrate({});
69
+ }
70
+ hasPermission(codes, options = {}) {
71
+ const mode = options.mode ?? "any";
72
+ return matchCodes(
73
+ this.permissionSet,
74
+ toArray(codes).filter(Boolean),
75
+ mode,
76
+ Boolean(this.state.isSuperAdmin)
77
+ );
78
+ }
79
+ hasRole(codes, options = {}) {
80
+ const mode = options.mode ?? "any";
81
+ return matchCodes(
82
+ this.roleSet,
83
+ toArray(codes).filter(Boolean),
84
+ mode,
85
+ Boolean(this.state.isSuperAdmin)
86
+ );
87
+ }
88
+ /** Alias of `hasPermission` for template-friendly APIs. */
89
+ can(codes, options) {
90
+ return this.hasPermission(codes, options);
91
+ }
92
+ subscribe(listener) {
93
+ this.listeners.add(listener);
94
+ return () => {
95
+ this.listeners.delete(listener);
96
+ };
97
+ }
98
+ emit() {
99
+ const snapshot = this.getState();
100
+ this.listeners.forEach((listener) => listener(snapshot));
101
+ }
102
+ };
103
+ function createPermissionStore(initial) {
104
+ return new PermissionStore(initial);
105
+ }
106
+
107
+ exports.PermissionStore = PermissionStore;
108
+ exports.createPermissionStore = createPermissionStore;
109
+ //# sourceMappingURL=index.cjs.map
110
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/store.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n CheckOptions,\n MatchMode,\n PermissionChecker,\n PermissionCode,\n PermissionListener,\n PermissionState,\n RoleCode,\n} from './types';\n\nconst DEFAULT_STATE: PermissionState = {\n permissions: [],\n roles: [],\n isSuperAdmin: false,\n};\n\nfunction toArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n\nfunction matchCodes(\n owned: Set<string>,\n required: string[],\n mode: MatchMode,\n isSuperAdmin: boolean,\n): boolean {\n if (isSuperAdmin) return true;\n if (required.length === 0) return true;\n\n if (mode === 'all') {\n return required.every((code) => owned.has(code));\n }\n\n return required.some((code) => owned.has(code));\n}\n\n/**\n * Mutable permission store shared by React / Vue adapters.\n * Framework-agnostic — safe to use in Node, browser, or any UI lib.\n */\nexport class PermissionStore implements PermissionChecker {\n private state: PermissionState;\n private permissionSet: Set<string>;\n private roleSet: Set<string>;\n private listeners = new Set<PermissionListener>();\n\n constructor(initial?: Partial<PermissionState>) {\n this.state = {\n ...DEFAULT_STATE,\n ...initial,\n permissions: initial?.permissions ?? [],\n roles: initial?.roles ?? [],\n };\n this.permissionSet = new Set(this.state.permissions);\n this.roleSet = new Set(this.state.roles);\n }\n\n getState(): PermissionState {\n return {\n permissions: [...this.state.permissions],\n roles: [...this.state.roles],\n isSuperAdmin: this.state.isSuperAdmin,\n };\n }\n\n setState(next: Partial<PermissionState>): void {\n this.state = {\n permissions: next.permissions ?? this.state.permissions,\n roles: next.roles ?? this.state.roles,\n isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin,\n };\n this.permissionSet = new Set(this.state.permissions);\n this.roleSet = new Set(this.state.roles);\n this.emit();\n }\n\n setPermissions(permissions: PermissionCode[]): void {\n this.setState({ permissions });\n }\n\n setRoles(roles: RoleCode[]): void {\n this.setState({ roles });\n }\n\n setSuperAdmin(isSuperAdmin: boolean): void {\n this.setState({ isSuperAdmin });\n }\n\n /** Replace permissions, roles and super-admin flag in one shot (typical after login). */\n hydrate(payload: Partial<PermissionState>): void {\n this.setState({\n permissions: payload.permissions ?? [],\n roles: payload.roles ?? [],\n isSuperAdmin: payload.isSuperAdmin ?? false,\n });\n }\n\n /** Clear all authz data (typical after logout). */\n clear(): void {\n this.hydrate({});\n }\n\n hasPermission(\n codes: PermissionCode | PermissionCode[],\n options: CheckOptions = {},\n ): boolean {\n const mode = options.mode ?? 'any';\n return matchCodes(\n this.permissionSet,\n toArray(codes).filter(Boolean),\n mode,\n Boolean(this.state.isSuperAdmin),\n );\n }\n\n hasRole(codes: RoleCode | RoleCode[], options: CheckOptions = {}): boolean {\n const mode = options.mode ?? 'any';\n return matchCodes(\n this.roleSet,\n toArray(codes).filter(Boolean),\n mode,\n Boolean(this.state.isSuperAdmin),\n );\n }\n\n /** Alias of `hasPermission` for template-friendly APIs. */\n can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean {\n return this.hasPermission(codes, options);\n }\n\n subscribe(listener: PermissionListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private emit(): void {\n const snapshot = this.getState();\n this.listeners.forEach((listener) => listener(snapshot));\n }\n}\n\nexport function createPermissionStore(\n initial?: Partial<PermissionState>,\n): PermissionStore {\n return new PermissionStore(initial);\n}\n"]}
@@ -0,0 +1,55 @@
1
+ type PermissionCode = string;
2
+ type RoleCode = string;
3
+ interface PermissionState {
4
+ /** Permission codes owned by the current user */
5
+ permissions: PermissionCode[];
6
+ /** Roles owned by the current user */
7
+ roles: RoleCode[];
8
+ /** Super admin bypasses all checks when true */
9
+ isSuperAdmin?: boolean;
10
+ }
11
+ type MatchMode = 'all' | 'any';
12
+ interface CheckOptions {
13
+ /**
14
+ * `any` — pass when at least one code matches (default)
15
+ * `all` — pass only when every code matches
16
+ */
17
+ mode?: MatchMode;
18
+ }
19
+ type PermissionListener = (state: PermissionState) => void;
20
+ interface PermissionChecker {
21
+ hasPermission(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
22
+ hasRole(codes: RoleCode | RoleCode[], options?: CheckOptions): boolean;
23
+ can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
24
+ getState(): PermissionState;
25
+ }
26
+
27
+ /**
28
+ * Mutable permission store shared by React / Vue adapters.
29
+ * Framework-agnostic — safe to use in Node, browser, or any UI lib.
30
+ */
31
+ declare class PermissionStore implements PermissionChecker {
32
+ private state;
33
+ private permissionSet;
34
+ private roleSet;
35
+ private listeners;
36
+ constructor(initial?: Partial<PermissionState>);
37
+ getState(): PermissionState;
38
+ setState(next: Partial<PermissionState>): void;
39
+ setPermissions(permissions: PermissionCode[]): void;
40
+ setRoles(roles: RoleCode[]): void;
41
+ setSuperAdmin(isSuperAdmin: boolean): void;
42
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
43
+ hydrate(payload: Partial<PermissionState>): void;
44
+ /** Clear all authz data (typical after logout). */
45
+ clear(): void;
46
+ hasPermission(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
47
+ hasRole(codes: RoleCode | RoleCode[], options?: CheckOptions): boolean;
48
+ /** Alias of `hasPermission` for template-friendly APIs. */
49
+ can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
50
+ subscribe(listener: PermissionListener): () => void;
51
+ private emit;
52
+ }
53
+ declare function createPermissionStore(initial?: Partial<PermissionState>): PermissionStore;
54
+
55
+ export { type CheckOptions, type MatchMode, type PermissionChecker, type PermissionCode, type PermissionListener, type PermissionState, PermissionStore, type RoleCode, createPermissionStore };
@@ -0,0 +1,55 @@
1
+ type PermissionCode = string;
2
+ type RoleCode = string;
3
+ interface PermissionState {
4
+ /** Permission codes owned by the current user */
5
+ permissions: PermissionCode[];
6
+ /** Roles owned by the current user */
7
+ roles: RoleCode[];
8
+ /** Super admin bypasses all checks when true */
9
+ isSuperAdmin?: boolean;
10
+ }
11
+ type MatchMode = 'all' | 'any';
12
+ interface CheckOptions {
13
+ /**
14
+ * `any` — pass when at least one code matches (default)
15
+ * `all` — pass only when every code matches
16
+ */
17
+ mode?: MatchMode;
18
+ }
19
+ type PermissionListener = (state: PermissionState) => void;
20
+ interface PermissionChecker {
21
+ hasPermission(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
22
+ hasRole(codes: RoleCode | RoleCode[], options?: CheckOptions): boolean;
23
+ can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
24
+ getState(): PermissionState;
25
+ }
26
+
27
+ /**
28
+ * Mutable permission store shared by React / Vue adapters.
29
+ * Framework-agnostic — safe to use in Node, browser, or any UI lib.
30
+ */
31
+ declare class PermissionStore implements PermissionChecker {
32
+ private state;
33
+ private permissionSet;
34
+ private roleSet;
35
+ private listeners;
36
+ constructor(initial?: Partial<PermissionState>);
37
+ getState(): PermissionState;
38
+ setState(next: Partial<PermissionState>): void;
39
+ setPermissions(permissions: PermissionCode[]): void;
40
+ setRoles(roles: RoleCode[]): void;
41
+ setSuperAdmin(isSuperAdmin: boolean): void;
42
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
43
+ hydrate(payload: Partial<PermissionState>): void;
44
+ /** Clear all authz data (typical after logout). */
45
+ clear(): void;
46
+ hasPermission(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
47
+ hasRole(codes: RoleCode | RoleCode[], options?: CheckOptions): boolean;
48
+ /** Alias of `hasPermission` for template-friendly APIs. */
49
+ can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean;
50
+ subscribe(listener: PermissionListener): () => void;
51
+ private emit;
52
+ }
53
+ declare function createPermissionStore(initial?: Partial<PermissionState>): PermissionStore;
54
+
55
+ export { type CheckOptions, type MatchMode, type PermissionChecker, type PermissionCode, type PermissionListener, type PermissionState, PermissionStore, type RoleCode, createPermissionStore };
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ // src/core/store.ts
2
+ var DEFAULT_STATE = {
3
+ permissions: [],
4
+ roles: [],
5
+ isSuperAdmin: false
6
+ };
7
+ function toArray(value) {
8
+ return Array.isArray(value) ? value : [value];
9
+ }
10
+ function matchCodes(owned, required, mode, isSuperAdmin) {
11
+ if (isSuperAdmin) return true;
12
+ if (required.length === 0) return true;
13
+ if (mode === "all") {
14
+ return required.every((code) => owned.has(code));
15
+ }
16
+ return required.some((code) => owned.has(code));
17
+ }
18
+ var PermissionStore = class {
19
+ constructor(initial) {
20
+ this.listeners = /* @__PURE__ */ new Set();
21
+ this.state = {
22
+ ...DEFAULT_STATE,
23
+ ...initial,
24
+ permissions: initial?.permissions ?? [],
25
+ roles: initial?.roles ?? []
26
+ };
27
+ this.permissionSet = new Set(this.state.permissions);
28
+ this.roleSet = new Set(this.state.roles);
29
+ }
30
+ getState() {
31
+ return {
32
+ permissions: [...this.state.permissions],
33
+ roles: [...this.state.roles],
34
+ isSuperAdmin: this.state.isSuperAdmin
35
+ };
36
+ }
37
+ setState(next) {
38
+ this.state = {
39
+ permissions: next.permissions ?? this.state.permissions,
40
+ roles: next.roles ?? this.state.roles,
41
+ isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin
42
+ };
43
+ this.permissionSet = new Set(this.state.permissions);
44
+ this.roleSet = new Set(this.state.roles);
45
+ this.emit();
46
+ }
47
+ setPermissions(permissions) {
48
+ this.setState({ permissions });
49
+ }
50
+ setRoles(roles) {
51
+ this.setState({ roles });
52
+ }
53
+ setSuperAdmin(isSuperAdmin) {
54
+ this.setState({ isSuperAdmin });
55
+ }
56
+ /** Replace permissions, roles and super-admin flag in one shot (typical after login). */
57
+ hydrate(payload) {
58
+ this.setState({
59
+ permissions: payload.permissions ?? [],
60
+ roles: payload.roles ?? [],
61
+ isSuperAdmin: payload.isSuperAdmin ?? false
62
+ });
63
+ }
64
+ /** Clear all authz data (typical after logout). */
65
+ clear() {
66
+ this.hydrate({});
67
+ }
68
+ hasPermission(codes, options = {}) {
69
+ const mode = options.mode ?? "any";
70
+ return matchCodes(
71
+ this.permissionSet,
72
+ toArray(codes).filter(Boolean),
73
+ mode,
74
+ Boolean(this.state.isSuperAdmin)
75
+ );
76
+ }
77
+ hasRole(codes, options = {}) {
78
+ const mode = options.mode ?? "any";
79
+ return matchCodes(
80
+ this.roleSet,
81
+ toArray(codes).filter(Boolean),
82
+ mode,
83
+ Boolean(this.state.isSuperAdmin)
84
+ );
85
+ }
86
+ /** Alias of `hasPermission` for template-friendly APIs. */
87
+ can(codes, options) {
88
+ return this.hasPermission(codes, options);
89
+ }
90
+ subscribe(listener) {
91
+ this.listeners.add(listener);
92
+ return () => {
93
+ this.listeners.delete(listener);
94
+ };
95
+ }
96
+ emit() {
97
+ const snapshot = this.getState();
98
+ this.listeners.forEach((listener) => listener(snapshot));
99
+ }
100
+ };
101
+ function createPermissionStore(initial) {
102
+ return new PermissionStore(initial);
103
+ }
104
+
105
+ export { PermissionStore, createPermissionStore };
106
+ //# sourceMappingURL=index.js.map
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/store.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n CheckOptions,\n MatchMode,\n PermissionChecker,\n PermissionCode,\n PermissionListener,\n PermissionState,\n RoleCode,\n} from './types';\n\nconst DEFAULT_STATE: PermissionState = {\n permissions: [],\n roles: [],\n isSuperAdmin: false,\n};\n\nfunction toArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value];\n}\n\nfunction matchCodes(\n owned: Set<string>,\n required: string[],\n mode: MatchMode,\n isSuperAdmin: boolean,\n): boolean {\n if (isSuperAdmin) return true;\n if (required.length === 0) return true;\n\n if (mode === 'all') {\n return required.every((code) => owned.has(code));\n }\n\n return required.some((code) => owned.has(code));\n}\n\n/**\n * Mutable permission store shared by React / Vue adapters.\n * Framework-agnostic — safe to use in Node, browser, or any UI lib.\n */\nexport class PermissionStore implements PermissionChecker {\n private state: PermissionState;\n private permissionSet: Set<string>;\n private roleSet: Set<string>;\n private listeners = new Set<PermissionListener>();\n\n constructor(initial?: Partial<PermissionState>) {\n this.state = {\n ...DEFAULT_STATE,\n ...initial,\n permissions: initial?.permissions ?? [],\n roles: initial?.roles ?? [],\n };\n this.permissionSet = new Set(this.state.permissions);\n this.roleSet = new Set(this.state.roles);\n }\n\n getState(): PermissionState {\n return {\n permissions: [...this.state.permissions],\n roles: [...this.state.roles],\n isSuperAdmin: this.state.isSuperAdmin,\n };\n }\n\n setState(next: Partial<PermissionState>): void {\n this.state = {\n permissions: next.permissions ?? this.state.permissions,\n roles: next.roles ?? this.state.roles,\n isSuperAdmin: next.isSuperAdmin ?? this.state.isSuperAdmin,\n };\n this.permissionSet = new Set(this.state.permissions);\n this.roleSet = new Set(this.state.roles);\n this.emit();\n }\n\n setPermissions(permissions: PermissionCode[]): void {\n this.setState({ permissions });\n }\n\n setRoles(roles: RoleCode[]): void {\n this.setState({ roles });\n }\n\n setSuperAdmin(isSuperAdmin: boolean): void {\n this.setState({ isSuperAdmin });\n }\n\n /** Replace permissions, roles and super-admin flag in one shot (typical after login). */\n hydrate(payload: Partial<PermissionState>): void {\n this.setState({\n permissions: payload.permissions ?? [],\n roles: payload.roles ?? [],\n isSuperAdmin: payload.isSuperAdmin ?? false,\n });\n }\n\n /** Clear all authz data (typical after logout). */\n clear(): void {\n this.hydrate({});\n }\n\n hasPermission(\n codes: PermissionCode | PermissionCode[],\n options: CheckOptions = {},\n ): boolean {\n const mode = options.mode ?? 'any';\n return matchCodes(\n this.permissionSet,\n toArray(codes).filter(Boolean),\n mode,\n Boolean(this.state.isSuperAdmin),\n );\n }\n\n hasRole(codes: RoleCode | RoleCode[], options: CheckOptions = {}): boolean {\n const mode = options.mode ?? 'any';\n return matchCodes(\n this.roleSet,\n toArray(codes).filter(Boolean),\n mode,\n Boolean(this.state.isSuperAdmin),\n );\n }\n\n /** Alias of `hasPermission` for template-friendly APIs. */\n can(codes: PermissionCode | PermissionCode[], options?: CheckOptions): boolean {\n return this.hasPermission(codes, options);\n }\n\n subscribe(listener: PermissionListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private emit(): void {\n const snapshot = this.getState();\n this.listeners.forEach((listener) => listener(snapshot));\n }\n}\n\nexport function createPermissionStore(\n initial?: Partial<PermissionState>,\n): PermissionStore {\n return new PermissionStore(initial);\n}\n"]}