@flusys/nestjs-shared 9.1.1 → 9.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.
Files changed (46) hide show
  1. package/README.md +119 -18
  2. package/classes/api-controller.class.d.ts +1 -0
  3. package/classes/api-service.class.d.ts +7 -2
  4. package/classes/hybrid-cache.class.d.ts +17 -4
  5. package/constants/index.d.ts +1 -0
  6. package/constants/permissions.d.ts +9 -0
  7. package/fesm/183.js +7 -7
  8. package/fesm/47.js +1520 -0
  9. package/fesm/470.js +333 -382
  10. package/fesm/667.js +17 -17
  11. package/fesm/681.js +326 -129
  12. package/fesm/{948.js → 997.js} +45 -23
  13. package/fesm/chunks/123.js +17 -9
  14. package/fesm/chunks/37.js +5 -5
  15. package/fesm/classes/index.js +4 -3
  16. package/fesm/constants/index.js +18 -7
  17. package/fesm/decorators/index.js +120 -39
  18. package/fesm/dtos/index.js +1 -1
  19. package/fesm/entities/index.js +8 -8
  20. package/fesm/enums/index.js +2 -2
  21. package/fesm/exceptions/index.js +12 -12
  22. package/fesm/guards/index.js +201 -68
  23. package/fesm/index.js +95 -42
  24. package/fesm/interceptors/index.js +290 -590
  25. package/fesm/interfaces/index.js +97 -8
  26. package/fesm/middlewares/index.js +4 -4
  27. package/fesm/modules/index.js +2 -2
  28. package/fesm/pipes/index.js +8 -8
  29. package/fesm/utils/index.js +150 -478
  30. package/interceptors/idempotency.interceptor.d.ts +14 -2
  31. package/interceptors/slug.interceptor.d.ts +1 -0
  32. package/interfaces/expression.interface.d.ts +45 -0
  33. package/interfaces/index.d.ts +1 -0
  34. package/interfaces/permission-action-registry.interface.d.ts +1 -0
  35. package/interfaces/permission.interface.d.ts +1 -0
  36. package/modules/shared-permission-cache/shared-permission-cache.service.d.ts +3 -1
  37. package/modules/utils/utils.service.d.ts +6 -6
  38. package/package.json +3 -2
  39. package/utils/date-time.util.d.ts +0 -1
  40. package/utils/event-pattern.util.d.ts +1 -0
  41. package/utils/expression.util.d.ts +35 -0
  42. package/utils/index.d.ts +2 -0
  43. package/utils/permission-cache-key.util.d.ts +3 -0
  44. package/utils/query-helpers.util.d.ts +2 -2
  45. package/utils/safe-regex.util.d.ts +10 -0
  46. package/fesm/270.js +0 -659
@@ -6,7 +6,7 @@ import * as __rspack_external_keyv from "keyv";
6
6
  export const __rspack_esm_id = 333;
7
7
  export const __rspack_esm_ids = [333];
8
8
  export const __webpack_modules__ = {
9
- 375(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
+ 5375(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
10
10
 
11
11
  // EXPORTS
12
12
  __webpack_require__.d(__webpack_exports__, {
@@ -14,25 +14,41 @@ __webpack_require__.d(__webpack_exports__, {
14
14
  });
15
15
 
16
16
  // EXTERNAL MODULE: external "@flusys/nestjs-core/config"
17
- var config_ = __webpack_require__(307);
17
+ var config_ = __webpack_require__(8307);
18
18
  ;// CONCATENATED MODULE: external "@keyv/redis"
19
19
 
20
20
  ;// CONCATENATED MODULE: external "cacheable"
21
21
 
22
22
  ;// CONCATENATED MODULE: external "keyv"
23
23
 
24
+ // EXTERNAL MODULE: external "node:crypto"
25
+ var external_node_crypto_ = __webpack_require__(2561);
24
26
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/classes/hybrid-cache.class.ts
25
27
 
26
28
 
27
29
 
28
30
 
29
- class HybridCache {
31
+
32
+ const RAW_KEY_PREFIX = 'flusys';
33
+ const STAMP_TTL_MS = 30 * 24 * 60 * 60 * 1000;
34
+ /**
35
+ * L1 in-process memory + L2 Redis, chosen by `USE_CACHE_LABEL` (memory | redis | hybrid).
36
+ *
37
+ * Plain `get`/`set` may be served by L1, which other instances cannot invalidate, so a value
38
+ * read that way must be immutable for its key - which is what version stamps give: data keys
39
+ * embed the current stamp, and invalidation moves the stamp instead of deleting entries.
40
+ * Stamps and claims live outside Keyv so they can be set atomically (Redis `SET NX`).
41
+ */ class HybridCache {
42
+ mode;
43
+ defaultTtl;
30
44
  memory;
31
45
  redis;
46
+ redisStore;
47
+ localAtomic;
32
48
  constructor(memoryTtl = 60 * 1000, memorySize = 5000){
33
- // Default to 'memory' if not specified
34
- const useCacheLabel = config_.envConfig.tryGetValue('USE_CACHE_LABEL', false) || 'memory';
35
- if (useCacheLabel === 'memory' || useCacheLabel === 'hybrid') {
49
+ this.mode = config_.envConfig.tryGetValue('USE_CACHE_LABEL', false) || 'memory';
50
+ this.defaultTtl = memoryTtl;
51
+ if (this.mode === 'memory' || this.mode === 'hybrid') {
36
52
  this.memory = new __rspack_external_keyv["default"]({
37
53
  store: new __rspack_external_cacheable.CacheableMemory({
38
54
  ttl: memoryTtl,
@@ -40,35 +56,51 @@ class HybridCache {
40
56
  })
41
57
  });
42
58
  }
43
- if (useCacheLabel === 'redis' || useCacheLabel === 'hybrid') {
59
+ if (this.mode === 'redis' || this.mode === 'hybrid') {
60
+ this.redisStore = new __rspack_external__keyv_redis_139d03ca["default"](config_.envConfig.getRedisUrl());
44
61
  this.redis = new __rspack_external_keyv["default"]({
45
- store: new __rspack_external__keyv_redis_139d03ca["default"](config_.envConfig.getRedisUrl())
62
+ store: this.redisStore
63
+ });
64
+ } else {
65
+ // Stamps and claims must never be LRU-evicted while still referenced
66
+ this.localAtomic = new __rspack_external_cacheable.CacheableMemory({
67
+ ttl: STAMP_TTL_MS,
68
+ lruSize: 0,
69
+ checkInterval: 60_000
46
70
  });
47
71
  }
48
72
  }
49
- async get(key) {
50
- // Prefer memory first if available
51
- if (this.memory) {
73
+ getMode() {
74
+ return this.mode;
75
+ }
76
+ async get(key, options = {}) {
77
+ const useMemory = this.memory && !(options.skipMemory && this.redis);
78
+ if (useMemory) {
52
79
  const value = await this.memory.get(key);
53
80
  if (value !== undefined) return value;
54
81
  }
55
- // Fallback to redis
56
82
  if (this.redis) {
57
- const value = await this.redis.get(key);
58
- if (value !== undefined && this.memory) {
59
- // backfill L1 memory cache
60
- await this.memory.set(key, value);
83
+ const raw = await this.redis.get(key, {
84
+ raw: true
85
+ });
86
+ if (raw === undefined) return undefined;
87
+ if (useMemory) {
88
+ // Never let the L1 copy outlive the Redis entry (time-bound values such as permissions)
89
+ const remaining = raw.expires ? raw.expires - Date.now() : this.defaultTtl;
90
+ if (remaining > 0) await this.memory.set(key, raw.value, Math.min(remaining, this.defaultTtl));
61
91
  }
62
- return value;
92
+ return raw.value;
63
93
  }
64
94
  return undefined;
65
95
  }
66
- async set(key, value, ttl) {
67
- if (this.memory) {
68
- await this.memory.set(key, value, ttl);
96
+ async set(key, value, ttl, options = {}) {
97
+ const effectiveTtl = ttl ?? this.defaultTtl;
98
+ const useMemory = this.memory && !(options.skipMemory && this.redis);
99
+ if (useMemory) {
100
+ await this.memory.set(key, value, effectiveTtl);
69
101
  }
70
102
  if (this.redis) {
71
- await this.redis.set(key, value, ttl);
103
+ await this.redis.set(key, value, effectiveTtl);
72
104
  }
73
105
  }
74
106
  async del(key) {
@@ -79,23 +111,88 @@ class HybridCache {
79
111
  await this.redis.delete(key);
80
112
  }
81
113
  }
82
- async reset() {
83
- if (this.memory) {
84
- await this.memory.clear();
114
+ /**
115
+ * The current version stamp for `name`, creating one when absent. A missing stamp is always
116
+ * replaced by a fresh random one - never a fixed default - so entries cached under a stamp that
117
+ * expired or was lost can never become current again.
118
+ */ async getStamp(name) {
119
+ const key = this.rawKey('stamp', name);
120
+ if (this.localAtomic) {
121
+ let stamp = this.localAtomic.get(key);
122
+ if (!stamp) {
123
+ stamp = (0,external_node_crypto_.randomUUID)();
124
+ this.localAtomic.set(key, stamp);
125
+ }
126
+ return stamp;
85
127
  }
128
+ const client = await this.redisStore.getClient();
129
+ const existing = await client.get(key);
130
+ if (existing) return String(existing);
131
+ const candidate = (0,external_node_crypto_.randomUUID)();
132
+ await client.set(key, candidate, {
133
+ NX: true,
134
+ PX: STAMP_TTL_MS
135
+ });
136
+ // A key evicted between SET NX and GET must not turn into the fixed stamp "null"
137
+ return String(await client.get(key) ?? candidate);
138
+ }
139
+ /** Moves the stamp for `name`, orphaning every entry cached under the previous one on all instances */ async bumpStamp(name) {
140
+ const key = this.rawKey('stamp', name);
141
+ if (this.localAtomic) {
142
+ this.localAtomic.set(key, (0,external_node_crypto_.randomUUID)());
143
+ return;
144
+ }
145
+ const client = await this.redisStore.getClient();
146
+ await client.set(key, (0,external_node_crypto_.randomUUID)(), {
147
+ PX: STAMP_TTL_MS
148
+ });
86
149
  }
87
- async resetL2() {
88
- if (this.redis) {
89
- await this.redis.clear();
150
+ /** Atomically claims `name` for `ttl` ms; true only for the first caller while the claim lives */ async claim(name, ttl) {
151
+ const key = this.rawKey('claim', name);
152
+ if (this.localAtomic) {
153
+ if (this.localAtomic.has(key)) return false;
154
+ this.localAtomic.set(key, true, ttl);
155
+ return true;
156
+ }
157
+ const client = await this.redisStore.getClient();
158
+ return await client.set(key, '1', {
159
+ NX: true,
160
+ PX: ttl
161
+ }) !== null;
162
+ }
163
+ async releaseClaim(name) {
164
+ const key = this.rawKey('claim', name);
165
+ if (this.localAtomic) {
166
+ this.localAtomic.delete(key);
167
+ return;
90
168
  }
169
+ const client = await this.redisStore.getClient();
170
+ await client.del(key);
171
+ }
172
+ /**
173
+ * Reads and removes a one-time value; only one caller across all instances ever receives it.
174
+ * The claim is per value, so a fresh value later stored under the same key (a resent code)
175
+ * can be taken again while the claim on the consumed one is still alive.
176
+ */ async take(key, ttl) {
177
+ const value = await this.get(key, {
178
+ skipMemory: true
179
+ });
180
+ if (value === undefined) return undefined;
181
+ const digest = (0,external_node_crypto_.createHash)('sha256').update(JSON.stringify(value) ?? '').digest('hex');
182
+ if (!await this.claim(`take:${key}:${digest}`, ttl)) return undefined;
183
+ await this.del(key);
184
+ return value;
185
+ }
186
+ rawKey(kind, name) {
187
+ return `${RAW_KEY_PREFIX}:${kind}:${name}`;
91
188
  }
92
189
  }
93
190
 
94
191
 
95
192
  },
96
- 374(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
97
- /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(157);
98
- /* import */ var _constants_message_keys_js__rspack_import_1 = __webpack_require__(389);
193
+ 8374(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
194
+ /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
195
+ /* import */ var _constants_message_keys_js__rspack_import_1 = __webpack_require__(4389);
99
196
 
100
197
 
101
198
  class PermissionSystemUnavailableException extends _nestjs_common__rspack_import_0.InternalServerErrorException {
@@ -140,7 +237,7 @@ __webpack_require__.d(__webpack_exports__, {
140
237
 
141
238
 
142
239
  },
143
- 510(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
240
+ 3510(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
144
241
 
145
242
  // EXPORTS
146
243
  __webpack_require__.d(__webpack_exports__, {
@@ -150,13 +247,13 @@ __webpack_require__.d(__webpack_exports__, {
150
247
  });
151
248
 
152
249
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/index.ts
153
- var constants = __webpack_require__(43);
154
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/middlewares/logger.middleware.ts + 2 modules
155
- var logger_middleware = __webpack_require__(591);
250
+ var constants = __webpack_require__(6043);
251
+ // EXTERNAL MODULE: ./projects/nestjs-shared/src/middlewares/logger.middleware.ts + 1 modules
252
+ var logger_middleware = __webpack_require__(8244);
156
253
  // EXTERNAL MODULE: external "@nestjs/common"
157
- var common_ = __webpack_require__(157);
254
+ var common_ = __webpack_require__(6157);
158
255
  // EXTERNAL MODULE: external "@nestjs/core"
159
- var core_ = __webpack_require__(75);
256
+ var core_ = __webpack_require__(3075);
160
257
  ;// CONCATENATED MODULE: external "@nestjs/passport"
161
258
 
162
259
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/guards/jwt-auth.guard.ts
@@ -284,9 +381,9 @@ OptionalJwtGuard = optional_jwt_auth_guard_ts_decorate([
284
381
  ], OptionalJwtGuard);
285
382
 
286
383
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/exceptions/permission.exception.ts
287
- var permission_exception = __webpack_require__(374);
384
+ var permission_exception = __webpack_require__(8374);
288
385
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/modules/shared-permission-cache/shared-permission-cache.service.ts
289
- var shared_permission_cache_service = __webpack_require__(657);
386
+ var shared_permission_cache_service = __webpack_require__(4657);
290
387
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/permission-match.util.ts
291
388
  var permission_match_util = __webpack_require__(788);
292
389
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/guards/permission.guard.ts
@@ -336,13 +433,15 @@ class PermissionGuard {
336
433
  context.getHandler(),
337
434
  context.getClass()
338
435
  ]);
339
- if (!permissionConfig) return true;
436
+ if (permissionConfig === undefined || permissionConfig === null) return true;
340
437
  const request = context.switchToHttp().getRequest();
341
438
  const user = request.user;
342
439
  if (!user) throw new common_.UnauthorizedException({
343
440
  message: 'Authentication required',
344
441
  messageKey: constants/* .AUTH_MESSAGES.TOKEN_REQUIRED */.mK.TOKEN_REQUIRED
345
442
  });
443
+ const logicNode = this.normalizeToLogicNode(permissionConfig);
444
+ if (!logicNode) throw new permission_exception/* .InsufficientPermissionsException */.z8([], 'AND');
346
445
  if (!this.permissionCacheService?.isCacheConfigured()) {
347
446
  throw new permission_exception/* .PermissionSystemUnavailableException */.GS();
348
447
  }
@@ -354,8 +453,6 @@ class PermissionGuard {
354
453
  if (!userPermissions || userPermissions.length === 0) {
355
454
  throw new permission_exception/* .NoPermissionsFoundException */.d$();
356
455
  }
357
- const logicNode = this.normalizeToLogicNode(permissionConfig);
358
- if (!logicNode) return true;
359
456
  const result = (0,permission_match_util/* .evaluatePermissionLogic */.Ng)(logicNode, userPermissions);
360
457
  if (!result.passed) {
361
458
  throw new permission_exception/* .InsufficientPermissionsException */.z8(result.missingPermissions, result.operator);
@@ -363,14 +460,12 @@ class PermissionGuard {
363
460
  return true;
364
461
  }
365
462
  normalizeToLogicNode(config) {
366
- // string - single permission
367
463
  if (typeof config === 'string') {
368
464
  return config ? {
369
465
  type: 'action',
370
466
  actionId: config
371
467
  } : null;
372
468
  }
373
- // string[] - treat as AND of multiple permissions
374
469
  if (Array.isArray(config)) {
375
470
  if (config.length === 0) return null;
376
471
  if (config.length === 1) return {
@@ -386,7 +481,6 @@ class PermissionGuard {
386
481
  }))
387
482
  };
388
483
  }
389
- // SimplePermissionConfig - { permissions: string[], operator: 'AND'|'OR' }
390
484
  if ('permissions' in config && Array.isArray(config.permissions)) {
391
485
  const simple = config;
392
486
  if (simple.permissions.length === 0) return null;
@@ -403,7 +497,6 @@ class PermissionGuard {
403
497
  }))
404
498
  };
405
499
  }
406
- // ILogicNode
407
500
  return config;
408
501
  }
409
502
  }
@@ -426,12 +519,23 @@ PermissionGuard = permission_guard_ts_decorate([
426
519
 
427
520
 
428
521
  },
429
- 657(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
430
- /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(157);
431
- /* import */ var _classes_hybrid_cache_class_js__rspack_import_1 = __webpack_require__(375);
432
- /* import */ var _constants_index_js__rspack_import_2 = __webpack_require__(43);
433
- /* import */ var _exceptions_permission_exception_js__rspack_import_3 = __webpack_require__(374);
434
- /* import */ var _utils_permission_cache_key_util_js__rspack_import_4 = __webpack_require__(199);
522
+ 1647(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
523
+ const PERMISSION_RESOLVER = 'PERMISSION_RESOLVER';
524
+
525
+ __webpack_require__.d(__webpack_exports__, {
526
+ }, {
527
+ j: PERMISSION_RESOLVER
528
+ });
529
+
530
+
531
+ },
532
+ 4657(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
533
+ /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
534
+ /* import */ var _classes_hybrid_cache_class_js__rspack_import_1 = __webpack_require__(5375);
535
+ /* import */ var _constants_index_js__rspack_import_2 = __webpack_require__(6043);
536
+ /* import */ var _exceptions_permission_exception_js__rspack_import_3 = __webpack_require__(8374);
537
+ /* import */ var _interfaces_permission_resolver_interface_js__rspack_import_6 = __webpack_require__(1647);
538
+ /* import */ var _utils_permission_cache_key_util_js__rspack_import_4 = __webpack_require__(3199);
435
539
  /* import */ var _utils_permission_match_util_js__rspack_import_5 = __webpack_require__(788);
436
540
  function _ts_decorate(decorators, target, key, desc) {
437
541
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -462,11 +566,14 @@ function _ts_param(paramIndex, decorator) {
462
566
 
463
567
 
464
568
 
569
+
465
570
  class SharedPermissionCacheService {
466
571
  cache;
572
+ resolver;
467
573
  config;
468
- constructor(cache, config){
574
+ constructor(cache, config, resolver){
469
575
  this.cache = cache;
576
+ this.resolver = resolver;
470
577
  this.config = {
471
578
  enableCompanyFeature: false,
472
579
  userPermissionKeyFormat: _utils_permission_cache_key_util_js__rspack_import_4/* .DEFAULT_USER_PERMISSION_KEY_FORMAT */.q6,
@@ -474,22 +581,21 @@ class SharedPermissionCacheService {
474
581
  ...config
475
582
  };
476
583
  }
477
- /** Read the cached permission codes for a user; empty array when cache is unavailable or unset */ async getPermissions(options) {
584
+ /** The user's permission codes in a scope: cached, else rebuilt by the resolver; empty when neither has them */ async getPermissions(options) {
478
585
  if (!this.cache) return [];
479
- const cacheKey = (0,_utils_permission_cache_key_util_js__rspack_import_4/* .buildPermissionCacheKey */.nt)(options, this.config);
586
+ const cacheKey = await (0,_utils_permission_cache_key_util_js__rspack_import_4/* .resolvePermissionCacheKey */.Rw)(this.cache, options, this.config);
480
587
  const cached = await this.cache.get(cacheKey);
481
- if (!cached) return [];
482
- return Array.isArray(cached) ? cached : cached.backendCodes || [];
588
+ if (cached) return Array.isArray(cached) ? cached : cached.backendCodes || [];
589
+ return this.resolver ? this.resolver.getPermissionCodes(options) : [];
483
590
  }
484
591
  /** Overwrite the cached permission codes for a user with a fixed list */ async setPermissions(options, permissions, ttl) {
485
592
  if (!this.cache) return;
486
- const cacheKey = (0,_utils_permission_cache_key_util_js__rspack_import_4/* .buildPermissionCacheKey */.nt)(options, this.config);
593
+ const cacheKey = await (0,_utils_permission_cache_key_util_js__rspack_import_4/* .resolvePermissionCacheKey */.Rw)(this.cache, options, this.config);
487
594
  await this.cache.set(cacheKey, permissions, ttl);
488
595
  }
489
- /** Clear the cached permission codes for a user */ async clearPermissions(options) {
596
+ /** Clear the cached permission codes of a user - every company and branch scope */ async clearPermissions(options) {
490
597
  if (!this.cache) return;
491
- const cacheKey = (0,_utils_permission_cache_key_util_js__rspack_import_4/* .buildPermissionCacheKey */.nt)(options, this.config);
492
- await this.cache.del(cacheKey);
598
+ await (0,_utils_permission_cache_key_util_js__rspack_import_4/* .invalidatePermissionCache */.xp)(this.cache, options.userId, this.config);
493
599
  }
494
600
  /** Check a single permission against the cached list, honoring `*` and `prefix.*` wildcards */ async hasPermission(options, requiredPermission) {
495
601
  const permissions = await this.getPermissions(options);
@@ -529,10 +635,13 @@ SharedPermissionCacheService = _ts_decorate([
529
635
  _ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_constants_index_js__rspack_import_2/* .CACHE_INSTANCE */.zl)),
530
636
  _ts_param(1, (0,_nestjs_common__rspack_import_0.Optional)()),
531
637
  _ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_constants_index_js__rspack_import_2/* .PERMISSION_GUARD_CONFIG */.Lw)),
638
+ _ts_param(2, (0,_nestjs_common__rspack_import_0.Optional)()),
639
+ _ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_interfaces_permission_resolver_interface_js__rspack_import_6/* .PERMISSION_RESOLVER */.j)),
532
640
  _ts_metadata("design:type", Function),
533
641
  _ts_metadata("design:paramtypes", [
534
642
  typeof _classes_hybrid_cache_class_js__rspack_import_1/* .HybridCache */.l === "undefined" ? Object : _classes_hybrid_cache_class_js__rspack_import_1/* .HybridCache */.l,
535
- typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
643
+ typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig,
644
+ typeof IPermissionResolver === "undefined" ? Object : IPermissionResolver
536
645
  ])
537
646
  ], SharedPermissionCacheService);
538
647
 
@@ -542,8 +651,10 @@ __webpack_require__.d(__webpack_exports__, {
542
651
 
543
652
 
544
653
  },
545
- 199(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
546
- /* import */ var _constants_index_js__rspack_import_0 = __webpack_require__(43);
654
+ 3199(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
655
+ /* import */ var _constants_index_js__rspack_import_0 = __webpack_require__(6043);
656
+ /* import */ var _middlewares_logger_middleware_js__rspack_import_1 = __webpack_require__(8244);
657
+
547
658
 
548
659
  const DEFAULT_USER_PERMISSION_KEY_FORMAT = `${_constants_index_js__rspack_import_0/* .PERMISSIONS_CACHE_PREFIX */.fG}:user:{userId}`;
549
660
  const DEFAULT_COMPANY_PERMISSION_KEY_FORMAT = `${_constants_index_js__rspack_import_0/* .PERMISSIONS_CACHE_PREFIX */.fG}:company:{companyId}:branch:{branchId}:user:{userId}`;
@@ -554,9 +665,31 @@ const DEFAULT_COMPANY_PERMISSION_KEY_FORMAT = `${_constants_index_js__rspack_imp
554
665
  }
555
666
  return (config?.userPermissionKeyFormat || DEFAULT_USER_PERMISSION_KEY_FORMAT).replace('{userId}', options.userId);
556
667
  }
668
+ /**
669
+ * Tenant databases can share user ids (they are cloned from one template), so multi-tenant keys
670
+ * carry the request's tenant. A single database never does: a stray tenant header must not move
671
+ * a user onto a cache entry that invalidation never reaches.
672
+ */ function permissionTenant(config) {
673
+ return config?.multiTenant ? `tenant:${(0,_middlewares_logger_middleware_js__rspack_import_1/* .getTenantId */.EO)() ?? 'none'}:` : '';
674
+ }
675
+ function permissionStampName(userId, config) {
676
+ return `${_constants_index_js__rspack_import_0/* .PERMISSIONS_CACHE_PREFIX */.fG}:${permissionTenant(config)}user:${userId}`;
677
+ }
678
+ /**
679
+ * The key a user's permissions are cached under right now: tenant + scope + the user's version
680
+ * stamp. Resolve it once, BEFORE reading grants from the database, and write under that same key.
681
+ */ async function resolvePermissionCacheKey(cache, options, config) {
682
+ const stamp = await cache.getStamp(permissionStampName(options.userId, config));
683
+ return `${permissionTenant(config)}${buildPermissionCacheKey(options, config)}:v:${stamp}`;
684
+ }
685
+ /** Drops every cached permission scope (each company and branch) of the user, on every instance */ async function invalidatePermissionCache(cache, userId, config) {
686
+ await cache.bumpStamp(permissionStampName(userId, config));
687
+ }
557
688
 
558
689
  __webpack_require__.d(__webpack_exports__, {
559
- nt: () => (buildPermissionCacheKey)
690
+ Rw: () => (resolvePermissionCacheKey),
691
+ nt: () => (buildPermissionCacheKey),
692
+ xp: () => (invalidatePermissionCache)
560
693
  }, {
561
694
  q6: DEFAULT_USER_PERMISSION_KEY_FORMAT,
562
695
  s8: DEFAULT_COMPANY_PERMISSION_KEY_FORMAT
@@ -565,7 +698,7 @@ __webpack_require__.d(__webpack_exports__, {
565
698
 
566
699
  },
567
700
  788(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
568
- /* import */ var _exceptions_permission_exception_js__rspack_import_0 = __webpack_require__(374);
701
+ /* import */ var _exceptions_permission_exception_js__rspack_import_0 = __webpack_require__(8374);
569
702
 
570
703
  /** Whether `granted` covers `required`, honoring `*` (everything) and `prefix.*` (everything under prefix) wildcards */ function matchesPermission(granted, required) {
571
704
  if (granted.includes(required)) return true;
@@ -646,7 +779,7 @@ __webpack_require__.d(__webpack_exports__, {
646
779
 
647
780
 
648
781
  },
649
- 75(module) {
782
+ 3075(module) {
650
783
 
651
784
  module.exports = __rspack_external__nestjs_core_d7b00a1a;
652
785
 
@@ -656,11 +789,11 @@ module.exports = __rspack_external__nestjs_core_d7b00a1a;
656
789
  };
657
790
  import { __webpack_require__ } from '../runtime.js';
658
791
  var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
659
- import * as __rspack_chunk_1 from '../948.js';
792
+ import * as __rspack_chunk_1 from '../997.js';
660
793
  __webpack_require__.C(__rspack_chunk_1);
661
794
  import * as __rspack_chunk_2 from './index.js';
662
795
  __webpack_require__.C(__rspack_chunk_2);
663
- var __webpack_exports__ = __webpack_exec__(510);var __webpack_exports__JwtAuthGuard = __webpack_exports__.TD;
796
+ var __webpack_exports__ = __webpack_exec__(3510);var __webpack_exports__JwtAuthGuard = __webpack_exports__.TD;
664
797
  var __webpack_exports__OptionalJwtGuard = __webpack_exports__.Zb;
665
798
  var __webpack_exports__PermissionGuard = __webpack_exports__.LQ;
666
799
  export { __webpack_exports__JwtAuthGuard as JwtAuthGuard, __webpack_exports__OptionalJwtGuard as OptionalJwtGuard, __webpack_exports__PermissionGuard as PermissionGuard };