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 +21 -0
- package/README.md +276 -0
- package/dist/index.cjs +110 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +156 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +119 -0
- package/dist/react/index.d.ts +119 -0
- package/dist/react/index.js +134 -0
- package/dist/react/index.js.map +1 -0
- package/dist/vue/index.cjs +241 -0
- package/dist/vue/index.cjs.map +1 -0
- package/dist/vue/index.d.cts +173 -0
- package/dist/vue/index.d.ts +173 -0
- package/dist/vue/index.js +213 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +67 -0
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":";;;AAUA,IAAM,aAAA,GAAiC;AAAA,EACrC,aAAa,EAAC;AAAA,EACd,OAAO,EAAC;AAAA,EACR,YAAA,EAAc;AAChB,CAAA;AAEA,SAAS,QAAW,KAAA,EAAqB;AACvC,EAAA,OAAO,MAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAC,KAAK,CAAA;AAC9C;AAEA,SAAS,UAAA,CACP,KAAA,EACA,QAAA,EACA,IAAA,EACA,YAAA,EACS;AACT,EAAA,IAAI,cAAc,OAAO,IAAA;AACzB,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAElC,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,OAAO,SAAS,KAAA,CAAM,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,EACjD;AAEA,EAAA,OAAO,SAAS,IAAA,CAAK,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AAChD;AAMO,IAAM,kBAAN,MAAmD;AAAA,EAMxD,YAAY,OAAA,EAAoC;AAFhD,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAwB;AAG9C,IAAA,IAAA,CAAK,KAAA,GAAQ;AAAA,MACX,GAAG,aAAA;AAAA,MACH,GAAG,OAAA;AAAA,MACH,WAAA,EAAa,OAAA,EAAS,WAAA,IAAe,EAAC;AAAA,MACtC,KAAA,EAAO,OAAA,EAAS,KAAA,IAAS;AAAC,KAC5B;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAI,GAAA,CAAI,IAAA,CAAK,MAAM,WAAW,CAAA;AACnD,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,GAAA,CAAI,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,EACzC;AAAA,EAEA,QAAA,GAA4B;AAC1B,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,CAAC,GAAG,IAAA,CAAK,MAAM,WAAW,CAAA;AAAA,MACvC,KAAA,EAAO,CAAC,GAAG,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,MAC3B,YAAA,EAAc,KAAK,KAAA,CAAM;AAAA,KAC3B;AAAA,EACF;AAAA,EAEA,SAAS,IAAA,EAAsC;AAC7C,IAAA,IAAA,CAAK,KAAA,GAAQ;AAAA,MACX,WAAA,EAAa,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,KAAA,CAAM,WAAA;AAAA,MAC5C,KAAA,EAAO,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,KAAA;AAAA,MAChC,YAAA,EAAc,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,KAAA,CAAM;AAAA,KAChD;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAI,GAAA,CAAI,IAAA,CAAK,MAAM,WAAW,CAAA;AACnD,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,GAAA,CAAI,IAAA,CAAK,MAAM,KAAK,CAAA;AACvC,IAAA,IAAA,CAAK,IAAA,EAAK;AAAA,EACZ;AAAA,EAEA,eAAe,WAAA,EAAqC;AAClD,IAAA,IAAA,CAAK,QAAA,CAAS,EAAE,WAAA,EAAa,CAAA;AAAA,EAC/B;AAAA,EAEA,SAAS,KAAA,EAAyB;AAChC,IAAA,IAAA,CAAK,QAAA,CAAS,EAAE,KAAA,EAAO,CAAA;AAAA,EACzB;AAAA,EAEA,cAAc,YAAA,EAA6B;AACzC,IAAA,IAAA,CAAK,QAAA,CAAS,EAAE,YAAA,EAAc,CAAA;AAAA,EAChC;AAAA;AAAA,EAGA,QAAQ,OAAA,EAAyC;AAC/C,IAAA,IAAA,CAAK,QAAA,CAAS;AAAA,MACZ,WAAA,EAAa,OAAA,CAAQ,WAAA,IAAe,EAAC;AAAA,MACrC,KAAA,EAAO,OAAA,CAAQ,KAAA,IAAS,EAAC;AAAA,MACzB,YAAA,EAAc,QAAQ,YAAA,IAAgB;AAAA,KACvC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,OAAA,CAAQ,EAAE,CAAA;AAAA,EACjB;AAAA,EAEA,aAAA,CACE,KAAA,EACA,OAAA,GAAwB,EAAC,EAChB;AACT,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,KAAA;AAC7B,IAAA,OAAO,UAAA;AAAA,MACL,IAAA,CAAK,aAAA;AAAA,MACL,OAAA,CAAQ,KAAK,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AAAA,MAC7B,IAAA;AAAA,MACA,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,YAAY;AAAA,KACjC;AAAA,EACF;AAAA,EAEA,OAAA,CAAQ,KAAA,EAA8B,OAAA,GAAwB,EAAC,EAAY;AACzE,IAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,KAAA;AAC7B,IAAA,OAAO,UAAA;AAAA,MACL,IAAA,CAAK,OAAA;AAAA,MACL,OAAA,CAAQ,KAAK,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AAAA,MAC7B,IAAA;AAAA,MACA,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,YAAY;AAAA,KACjC;AAAA,EACF;AAAA;AAAA,EAGA,GAAA,CAAI,OAA0C,OAAA,EAAiC;AAC7E,IAAA,OAAO,IAAA,CAAK,aAAA,CAAc,KAAA,EAAO,OAAO,CAAA;AAAA,EAC1C;AAAA,EAEA,UAAU,QAAA,EAA0C;AAClD,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,IAChC,CAAA;AAAA,EACF;AAAA,EAEQ,IAAA,GAAa;AACnB,IAAA,MAAM,QAAA,GAAW,KAAK,QAAA,EAAS;AAC/B,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,CAAC,QAAA,KAAa,QAAA,CAAS,QAAQ,CAAC,CAAA;AAAA,EACzD;AACF;AAEO,SAAS,sBACd,OAAA,EACiB;AACjB,EAAA,OAAO,IAAI,gBAAgB,OAAO,CAAA;AACpC","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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"]}
|