@application-kit/authorization 0.3.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @application-kit/authorization
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9411eb: Спеки-гейты продукта переехали в пакеты: спека теперь — вызов помощника с параметрами продукта, а нарушение приходит ошибкой со списком.
8
+
9
+ `ui/testing`: `assertTokensDeclared`. `kafka/testing` (новый вход): `assertConsumerTopicsConsistent`. `authorization/testing` (новый вход): `assertAbilityRegistryConsistent`. `nestjs-vite/static-assets-gate` (новый вход, отдельно от `/testing`, чтобы спеки с фейком рендерера не грузили `vite`): `assertStaticAssetsLayout`. `lint-config` разрешает импорт нового входа `nestjs-vite/static-assets-gate`.
10
+
11
+ ## 0.4.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 92d5b04: Вход `/vue`: `installAuthorization(app, { accessDenied, grants, resolveActor, router })` — ability на приложение, `<Can>`,
16
+ `useAbility()` и гвард маршрутов по `meta.can`; `canAccessRoute` и расширение `RouteMeta.can`. Гранты, источник актора и маршрут
17
+ отказа приходят параметрами. `@casl/vue`, `vue`, `vue-router` — опциональные peer, корневой вход их не тянет.
18
+
3
19
  ## 0.3.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -10,7 +10,8 @@
10
10
  yarn add @application-kit/authorization
11
11
  ```
12
12
 
13
- Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`, `nestjs-pino`. Все обязательные.
13
+ Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`, `nestjs-pino` — обязательные; `@casl/vue`, `vue`,
14
+ `vue-router` — опциональные, нужны только входу `/vue`.
14
15
  `@nestjs/graphql` пакету не нужен: guard узнаёт GraphQL-запрос по типу контекста и работает и в приложении без GraphQL.
15
16
 
16
17
  ## Входы
@@ -19,6 +20,8 @@ Peer-зависимости: `@casl/ability`, `@nestjs/common`, `@nestjs/core`,
19
20
  |------|----------|-------|
20
21
  | `@application-kit/authorization` | сервер и браузер: реестры, `buildAbilityFor`, `composeAbilityGrants` | только `@casl/ability` |
21
22
  | `@application-kit/authorization/nest` | сервер: guard, декораторы, фабрика, логгер | `@nestjs/*`, `nestjs-pino` |
23
+ | `@application-kit/authorization/vue` | клиент и SSR: ability на приложение, `<Can>`, гвард маршрутов по `meta.can` | `vue`, `vue-router`, `@casl/vue` |
24
+ | `@application-kit/authorization/testing` | спека продукта: гейт реестра abilities | `node:fs`, `@casl/ability` |
22
25
 
23
26
  Корневой вход не импортирует Nest — его можно собирать в браузерный бандл.
24
27
 
@@ -46,6 +49,28 @@ const grants = composeAbilityGrants(['EDITOR', 'VIEWER'], articleGrants, comment
46
49
  const ability = buildAbilityFor({ role: 'EDITOR' }, grants)
47
50
  ```
48
51
 
52
+ ## Vue
53
+
54
+ `installAuthorization(app, options)` ставит ability (свой экземпляр на приложение, поэтому и на запрос SSR), `<Can>`,
55
+ `useAbility()` и гвард: маршрут с `meta.can` без нужного права уводит на `accessDenied`. Решение только о показе
56
+ интерфейса: источник истины — бэкенд. `RouteMeta.can` объявляет сам вход.
57
+
58
+ ```ts
59
+ installAuthorization(app, {
60
+ accessDenied: { name: 'ACCESS_DENIED' }, // сам не несёт meta.can: отказ на нём зациклился бы
61
+ grants,
62
+ resolveActor: async () => meRepository.loadMe(), // null — гость; исполняется в контексте приложения
63
+ router,
64
+ })
65
+
66
+ const routes = [{ path: '/console', component: Console, meta: { can: { action: 'read', subject: 'Console' } } }]
67
+ ```
68
+
69
+ Актор запрашивается лениво, на первом маршруте с `meta.can`: маршруты без него запроса не ждут. Ошибка `resolveActor`
70
+ читается как гость, и следующий такой переход спросит снова — это и покрывает вход без перезагрузки страницы. Актор,
71
+ приехавший снапшотом SSR, `resolveActor` возвращает сам, без запроса. `canAccessRoute(ability, route)` — то же решение
72
+ отдельно от гварда.
73
+
49
74
  ## Nest
50
75
 
51
76
  - `PoliciesGuard` — глобальный guard (`APP_GUARD`): обработчик с `@Public()` пропускается, с `@CheckPolicies(...)` —
@@ -72,3 +97,18 @@ export class AppModule {}
72
97
 
73
98
  `AuthorizationLogger` — pino-логгер пакета с контекстом `AuthorizationLogger`; собирается из глобального
74
99
  `LoggerModule` `nestjs-pino`.
100
+
101
+ ## Гейт реестра
102
+
103
+ Реестр subject'ов живёт только в типах: забытая регистрация не падает в рантайме. `assertAbilityRegistryConsistent` из
104
+ входа `@application-kit/authorization/testing` читает блоки `declare module` файлов грантов и падает, если запись не
105
+ выдана грантом продукта или её subject не проверяется в prod-коде:
106
+
107
+ ```ts
108
+ await assertAbilityRegistryConsistent({
109
+ grants: abilityGrants,
110
+ grantsFiles: 'modules/*/module.grants.ts',
111
+ roles: Object.values(ROLE_VALUE),
112
+ sourceRoot: path.resolve(import.meta.dirname, '..'),
113
+ })
114
+ ```
@@ -0,0 +1,9 @@
1
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
2
+ import type { Role } from '../types/roles.js';
3
+ export interface AbilityRegistryOptionsInterface {
4
+ readonly grants: AbilityGrantMap;
5
+ readonly grantsFiles: string;
6
+ readonly roles: readonly Role[];
7
+ readonly sourceRoot: string;
8
+ }
9
+ export declare const assertAbilityRegistryConsistent: (options: AbilityRegistryOptionsInterface) => Promise<void>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertAbilityRegistryConsistent = void 0;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const build_ability_js_1 = require("../build-ability.js");
10
+ const REGISTRY_MARKER = 'interface SubjectRegistryInterface {';
11
+ const PRODUCTION_EXTENSIONS = new Set(['.ts', '.vue']);
12
+ const actionsOf = (declaration) => declaration
13
+ .split('|')
14
+ .map((action) => action.replaceAll(/[^\w-]/gu, ''))
15
+ .filter((action) => action.length > 0 && action !== 'never');
16
+ const withoutComments = (source) => source
17
+ .split('\n')
18
+ .filter((line) => !(/^\s*(?:\/\/|\*|\/\*)/u).test(line))
19
+ .join('\n');
20
+ const registryEntries = (code) => {
21
+ const start = code.indexOf(REGISTRY_MARKER) + REGISTRY_MARKER.length;
22
+ return code.slice(start, code.indexOf('}', start))
23
+ .split('\n')
24
+ .map((line) => line.trim())
25
+ .filter((line) => line.includes(':'))
26
+ .map((line) => [line.slice(0, line.indexOf(':')).trim(), actionsOf(line.slice(line.indexOf(':') + 1))]);
27
+ };
28
+ const grantedPairs = (roles, grants) => {
29
+ const rules = roles.flatMap((role) => (0, build_ability_js_1.buildAbilityFor)({ role }, grants).rules);
30
+ return new Set(rules.flatMap((rule) => [rule.action].flat().flatMap((action) => [rule.subject].flat().map((subject) => `${action} ${subject}`))));
31
+ };
32
+ const productionSources = async (sourceRoot, producers) => {
33
+ const files = await (0, promises_1.readdir)(sourceRoot, { recursive: true });
34
+ const sources = files.filter((file) => PRODUCTION_EXTENSIONS.has(node_path_1.default.extname(file))
35
+ && !file.endsWith('.spec.ts')
36
+ && !file.split(node_path_1.default.sep).includes('tests')
37
+ && !producers.includes(file));
38
+ return Promise.all(sources.map(async (file) => (0, promises_1.readFile)(node_path_1.default.join(sourceRoot, file), 'utf8')));
39
+ };
40
+ const assertAbilityRegistryConsistent = async (options) => {
41
+ const { grants, grantsFiles, roles, sourceRoot } = options;
42
+ const producers = await Array.fromAsync((0, promises_1.glob)(grantsFiles, { cwd: sourceRoot }));
43
+ const [codes, sources] = await Promise.all([
44
+ Promise.all(producers.map(async (file) => withoutComments(await (0, promises_1.readFile)(node_path_1.default.join(sourceRoot, file), 'utf8')))),
45
+ productionSources(sourceRoot, producers),
46
+ ]);
47
+ const entries = codes.filter((code) => code.includes(REGISTRY_MARKER)).flatMap((code) => registryEntries(code));
48
+ const granted = grantedPairs(roles, grants);
49
+ const violations = [
50
+ ...entries.length === 0 ? [`шаблон ${grantsFiles} не нашёл записей реестра`] : [],
51
+ ...entries.flatMap(([subject, actions]) => actions
52
+ .map((action) => `${action} ${subject}`)
53
+ .filter((pair) => !granted.has(pair))
54
+ .map((pair) => `запись реестра «${pair}» не выдана ни одним грантом`)),
55
+ ...entries
56
+ .map(([subject]) => subject)
57
+ .filter((subject) => sources.every((source) => !source.includes(`'${subject}'`)))
58
+ .map((subject) => `subject ${subject} не проверяется в prod-коде`),
59
+ ];
60
+ if (violations.length > 0) {
61
+ throw new Error(`Гейт реестра abilities:\n${violations.join('\n')}`);
62
+ }
63
+ };
64
+ exports.assertAbilityRegistryConsistent = assertAbilityRegistryConsistent;
@@ -0,0 +1 @@
1
+ export * from './assert-ability-registry-consistent.js';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./assert-ability-registry-consistent.js"), exports);
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./install-authorization.js"), exports);
18
+ __exportStar(require("./route-ability.js"), exports);
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vue';
2
+ import type { RouteLocationRaw, Router } from 'vue-router';
3
+ import type { AbilityActorInterface } from '../build-ability.js';
4
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
5
+ export interface InstallAuthorizationOptionsInterface {
6
+ accessDenied: RouteLocationRaw;
7
+ grants: AbilityGrantMap;
8
+ resolveActor(): Promise<AbilityActorInterface | null>;
9
+ router: Router;
10
+ }
11
+ export declare const installAuthorization: (app: App, options: InstallAuthorizationOptionsInterface) => void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installAuthorization = void 0;
4
+ const ability_1 = require("@casl/ability");
5
+ const vue_1 = require("@casl/vue");
6
+ const build_ability_js_1 = require("../build-ability.js");
7
+ const route_ability_js_1 = require("./route-ability.js");
8
+ const installAuthorization = (app, options) => {
9
+ const ability = (0, ability_1.createMongoAbility)();
10
+ app.use(vue_1.abilitiesPlugin, ability);
11
+ app.component('Can', vue_1.Can);
12
+ const hydrateAbility = async () => {
13
+ try {
14
+ const actor = await app.runWithContext(async () => options.resolveActor());
15
+ ability.update((0, build_ability_js_1.buildAbilityFor)(actor, options.grants).rules);
16
+ }
17
+ catch {
18
+ }
19
+ };
20
+ options.router.beforeEach(async (to) => {
21
+ if (to.meta.can !== undefined && ability.rules.length === 0) {
22
+ await hydrateAbility();
23
+ }
24
+ return (0, route_ability_js_1.canAccessRoute)(ability, to) || options.accessDenied;
25
+ });
26
+ };
27
+ exports.installAuthorization = installAuthorization;
@@ -0,0 +1,14 @@
1
+ import type { AnyAbility } from '@casl/ability';
2
+ import type { RouteLocationNormalized } from 'vue-router';
3
+ import type { Action } from '../types/actions.js';
4
+ import type { Subject } from '../types/subjects.js';
5
+ export interface RouteAbilityRequirementInterface {
6
+ action: Action;
7
+ subject: Subject;
8
+ }
9
+ declare module 'vue-router' {
10
+ interface RouteMeta {
11
+ can?: RouteAbilityRequirementInterface;
12
+ }
13
+ }
14
+ export declare const canAccessRoute: (ability: AnyAbility, route: Pick<RouteLocationNormalized, "meta">) => boolean;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canAccessRoute = void 0;
4
+ const canAccessRoute = (ability, route) => {
5
+ const requirement = route.meta.can;
6
+ return requirement === undefined || ability.can(requirement.action, requirement.subject);
7
+ };
8
+ exports.canAccessRoute = canAccessRoute;
@@ -0,0 +1,9 @@
1
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
2
+ import type { Role } from '../types/roles.js';
3
+ export interface AbilityRegistryOptionsInterface {
4
+ readonly grants: AbilityGrantMap;
5
+ readonly grantsFiles: string;
6
+ readonly roles: readonly Role[];
7
+ readonly sourceRoot: string;
8
+ }
9
+ export declare const assertAbilityRegistryConsistent: (options: AbilityRegistryOptionsInterface) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ import { glob, readdir, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { buildAbilityFor } from '../build-ability.js';
4
+ const REGISTRY_MARKER = 'interface SubjectRegistryInterface {';
5
+ const PRODUCTION_EXTENSIONS = new Set(['.ts', '.vue']);
6
+ const actionsOf = (declaration) => declaration
7
+ .split('|')
8
+ .map((action) => action.replaceAll(/[^\w-]/gu, ''))
9
+ .filter((action) => action.length > 0 && action !== 'never');
10
+ const withoutComments = (source) => source
11
+ .split('\n')
12
+ .filter((line) => !(/^\s*(?:\/\/|\*|\/\*)/u).test(line))
13
+ .join('\n');
14
+ const registryEntries = (code) => {
15
+ const start = code.indexOf(REGISTRY_MARKER) + REGISTRY_MARKER.length;
16
+ return code.slice(start, code.indexOf('}', start))
17
+ .split('\n')
18
+ .map((line) => line.trim())
19
+ .filter((line) => line.includes(':'))
20
+ .map((line) => [line.slice(0, line.indexOf(':')).trim(), actionsOf(line.slice(line.indexOf(':') + 1))]);
21
+ };
22
+ const grantedPairs = (roles, grants) => {
23
+ const rules = roles.flatMap((role) => buildAbilityFor({ role }, grants).rules);
24
+ return new Set(rules.flatMap((rule) => [rule.action].flat().flatMap((action) => [rule.subject].flat().map((subject) => `${action} ${subject}`))));
25
+ };
26
+ const productionSources = async (sourceRoot, producers) => {
27
+ const files = await readdir(sourceRoot, { recursive: true });
28
+ const sources = files.filter((file) => PRODUCTION_EXTENSIONS.has(path.extname(file))
29
+ && !file.endsWith('.spec.ts')
30
+ && !file.split(path.sep).includes('tests')
31
+ && !producers.includes(file));
32
+ return Promise.all(sources.map(async (file) => readFile(path.join(sourceRoot, file), 'utf8')));
33
+ };
34
+ export const assertAbilityRegistryConsistent = async (options) => {
35
+ const { grants, grantsFiles, roles, sourceRoot } = options;
36
+ const producers = await Array.fromAsync(glob(grantsFiles, { cwd: sourceRoot }));
37
+ const [codes, sources] = await Promise.all([
38
+ Promise.all(producers.map(async (file) => withoutComments(await readFile(path.join(sourceRoot, file), 'utf8')))),
39
+ productionSources(sourceRoot, producers),
40
+ ]);
41
+ const entries = codes.filter((code) => code.includes(REGISTRY_MARKER)).flatMap((code) => registryEntries(code));
42
+ const granted = grantedPairs(roles, grants);
43
+ const violations = [
44
+ ...entries.length === 0 ? [`шаблон ${grantsFiles} не нашёл записей реестра`] : [],
45
+ ...entries.flatMap(([subject, actions]) => actions
46
+ .map((action) => `${action} ${subject}`)
47
+ .filter((pair) => !granted.has(pair))
48
+ .map((pair) => `запись реестра «${pair}» не выдана ни одним грантом`)),
49
+ ...entries
50
+ .map(([subject]) => subject)
51
+ .filter((subject) => sources.every((source) => !source.includes(`'${subject}'`)))
52
+ .map((subject) => `subject ${subject} не проверяется в prod-коде`),
53
+ ];
54
+ if (violations.length > 0) {
55
+ throw new Error(`Гейт реестра abilities:\n${violations.join('\n')}`);
56
+ }
57
+ };
@@ -0,0 +1 @@
1
+ export * from './assert-ability-registry-consistent.js';
@@ -0,0 +1 @@
1
+ export * from './assert-ability-registry-consistent.js';
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,2 @@
1
+ export * from './install-authorization.js';
2
+ export * from './route-ability.js';
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vue';
2
+ import type { RouteLocationRaw, Router } from 'vue-router';
3
+ import type { AbilityActorInterface } from '../build-ability.js';
4
+ import type { AbilityGrantMap } from '../types/ability-grant.js';
5
+ export interface InstallAuthorizationOptionsInterface {
6
+ accessDenied: RouteLocationRaw;
7
+ grants: AbilityGrantMap;
8
+ resolveActor(): Promise<AbilityActorInterface | null>;
9
+ router: Router;
10
+ }
11
+ export declare const installAuthorization: (app: App, options: InstallAuthorizationOptionsInterface) => void;
@@ -0,0 +1,23 @@
1
+ import { createMongoAbility } from '@casl/ability';
2
+ import { abilitiesPlugin, Can } from '@casl/vue';
3
+ import { buildAbilityFor } from '../build-ability.js';
4
+ import { canAccessRoute } from './route-ability.js';
5
+ export const installAuthorization = (app, options) => {
6
+ const ability = createMongoAbility();
7
+ app.use(abilitiesPlugin, ability);
8
+ app.component('Can', Can);
9
+ const hydrateAbility = async () => {
10
+ try {
11
+ const actor = await app.runWithContext(async () => options.resolveActor());
12
+ ability.update(buildAbilityFor(actor, options.grants).rules);
13
+ }
14
+ catch {
15
+ }
16
+ };
17
+ options.router.beforeEach(async (to) => {
18
+ if (to.meta.can !== undefined && ability.rules.length === 0) {
19
+ await hydrateAbility();
20
+ }
21
+ return canAccessRoute(ability, to) || options.accessDenied;
22
+ });
23
+ };
@@ -0,0 +1,14 @@
1
+ import type { AnyAbility } from '@casl/ability';
2
+ import type { RouteLocationNormalized } from 'vue-router';
3
+ import type { Action } from '../types/actions.js';
4
+ import type { Subject } from '../types/subjects.js';
5
+ export interface RouteAbilityRequirementInterface {
6
+ action: Action;
7
+ subject: Subject;
8
+ }
9
+ declare module 'vue-router' {
10
+ interface RouteMeta {
11
+ can?: RouteAbilityRequirementInterface;
12
+ }
13
+ }
14
+ export declare const canAccessRoute: (ability: AnyAbility, route: Pick<RouteLocationNormalized, "meta">) => boolean;
@@ -0,0 +1,4 @@
1
+ export const canAccessRoute = (ability, route) => {
2
+ const requirement = route.meta.can;
3
+ return requirement === undefined || ability.can(requirement.action, requirement.subject);
4
+ };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@application-kit/authorization",
3
- "version": "0.3.0",
4
- "description": "Авторизация на CASL для NestJS и браузера: реестры subject'ов и ролей, ability из грантов модулей, глобальный guard политик",
3
+ "version": "0.5.0",
4
+ "description": "Авторизация на CASL для NestJS и браузера: реестры subject'ов и ролей, ability из грантов модулей, глобальный guard политик, гвард маршрутов Vue",
5
5
  "keywords": [
6
6
  "authorization",
7
7
  "casl",
8
8
  "nestjs",
9
- "policies"
9
+ "policies",
10
+ "vue"
10
11
  ],
11
12
  "homepage": "https://gitlab.com/application-kit/application-kit/-/tree/main/packages/authorization",
12
13
  "bugs": "https://gitlab.com/application-kit/application-kit/-/issues",
@@ -42,7 +43,27 @@
42
43
  "default": "./dist/cjs/nest/index.js"
43
44
  }
44
45
  },
45
- "./package.json": "./package.json"
46
+ "./package.json": "./package.json",
47
+ "./testing": {
48
+ "import": {
49
+ "types": "./dist/esm/testing/index.d.ts",
50
+ "default": "./dist/esm/testing/index.js"
51
+ },
52
+ "require": {
53
+ "types": "./dist/cjs/testing/index.d.ts",
54
+ "default": "./dist/cjs/testing/index.js"
55
+ }
56
+ },
57
+ "./vue": {
58
+ "import": {
59
+ "types": "./dist/esm/vue/index.d.ts",
60
+ "default": "./dist/esm/vue/index.js"
61
+ },
62
+ "require": {
63
+ "types": "./dist/cjs/vue/index.d.ts",
64
+ "default": "./dist/cjs/vue/index.js"
65
+ }
66
+ }
46
67
  },
47
68
  "main": "./dist/cjs/index.js",
48
69
  "module": "./dist/esm/index.js",
@@ -51,13 +72,14 @@
51
72
  "dist"
52
73
  ],
53
74
  "scripts": {
54
- "build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=nest,internal && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
75
+ "build": "rm -rf ./dist && node ../../scripts/barrels.mjs create ./src --entry-only=nest,internal,testing,vue && tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json && node ../../scripts/barrels.mjs clean ./src && node ../../scripts/mark-cjs.mjs ./dist/cjs",
55
76
  "lint:tsc": "tsc --noEmit --pretty --project .",
56
77
  "test:mutation": "stryker run",
57
78
  "test:unit": "vitest run"
58
79
  },
59
80
  "devDependencies": {
60
81
  "@casl/ability": "^7.0.1",
82
+ "@casl/vue": "^3.0.1",
61
83
  "@nestjs/common": "^11.2.7",
62
84
  "@nestjs/core": "^11.2.7",
63
85
  "@nestjs/testing": "^11.2.7",
@@ -70,13 +92,29 @@
70
92
  "reflect-metadata": "^0.2.2",
71
93
  "rxjs": "^7.8.2",
72
94
  "typescript": "^6.0.3",
73
- "vitest": "^4.1.11"
95
+ "vitest": "^4.1.11",
96
+ "vue": "^3.5.41",
97
+ "vue-router": "^5.2.0"
74
98
  },
75
99
  "peerDependencies": {
76
100
  "@casl/ability": "^7.0.0",
101
+ "@casl/vue": "^3.0.0",
77
102
  "@nestjs/common": "^11.0.0",
78
103
  "@nestjs/core": "^11.0.0",
79
- "nestjs-pino": "^4.6.1"
104
+ "nestjs-pino": "^4.6.1",
105
+ "vue": "^3.5.0",
106
+ "vue-router": "^5.0.0"
107
+ },
108
+ "peerDependenciesMeta": {
109
+ "@casl/vue": {
110
+ "optional": true
111
+ },
112
+ "vue": {
113
+ "optional": true
114
+ },
115
+ "vue-router": {
116
+ "optional": true
117
+ }
80
118
  },
81
119
  "engines": {
82
120
  "node": ">=24"