@flusys/nestjs-shared 8.1.5 → 9.1.0-rc.1

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 (39) hide show
  1. package/README.md +48 -2
  2. package/classes/api-service.class.d.ts +3 -0
  3. package/constants/permissions.d.ts +46 -0
  4. package/exceptions/base-app.exception.d.ts +3 -2
  5. package/fesm/{509.js → 183.js} +43 -37
  6. package/fesm/470.js +228 -105
  7. package/fesm/667.js +28 -10
  8. package/fesm/{67.js → 681.js} +97 -19
  9. package/fesm/{720.js → 948.js} +60 -7
  10. package/fesm/chunks/123.js +9 -10
  11. package/fesm/chunks/37.js +7 -8
  12. package/fesm/classes/index.js +10 -4
  13. package/fesm/constants/index.js +53 -1
  14. package/fesm/decorators/index.js +4 -4
  15. package/fesm/dtos/index.js +2 -2
  16. package/fesm/guards/index.js +48 -426
  17. package/fesm/index.js +422 -341
  18. package/fesm/interceptors/index.js +62 -23
  19. package/fesm/interfaces/index.js +16 -3
  20. package/fesm/middlewares/index.js +5 -3
  21. package/fesm/modules/index.js +6 -414
  22. package/fesm/pipes/index.js +196 -0
  23. package/fesm/utils/index.js +125 -10
  24. package/index.d.ts +1 -0
  25. package/interfaces/datasource.interface.d.ts +2 -0
  26. package/interfaces/index.d.ts +3 -0
  27. package/interfaces/message-ref.interface.d.ts +4 -0
  28. package/interfaces/permission-action-registry.interface.d.ts +12 -0
  29. package/interfaces/permission-resolver.interface.d.ts +9 -0
  30. package/middlewares/logger.middleware.d.ts +4 -0
  31. package/modules/shared-permission-cache/shared-permission-cache.service.d.ts +5 -0
  32. package/package.json +8 -2
  33. package/pipes/dto-body-validation.pipe.d.ts +18 -0
  34. package/pipes/index.d.ts +1 -0
  35. package/utils/cache-tenant.util.d.ts +2 -0
  36. package/utils/index.d.ts +3 -0
  37. package/utils/message-ref.util.d.ts +2 -0
  38. package/utils/permission-match.util.d.ts +1 -0
  39. package/utils/query-helpers.util.d.ts +2 -1
@@ -0,0 +1,196 @@
1
+ import * as __rspack_external__nestjs_common_a04939da from "@nestjs/common";
2
+ import * as __rspack_external_class_validator_7b99ef4d from "class-validator";
3
+ export const __rspack_esm_id = 360;
4
+ export const __rspack_esm_ids = [360];
5
+ export const __webpack_modules__ = {
6
+ 389(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
7
+ // ==================== AUTH (Shared across guards/interceptors) ====================
8
+ // These are duplicated in nestjs-auth but needed here to avoid circular dependencies
9
+ const AUTH_MESSAGES = {
10
+ TOKEN_REQUIRED: 'auth.token.required',
11
+ TOKEN_INVALID: 'auth.token.invalid',
12
+ ENTITY_BELOG_ANOTHER_COMPANY: 'entity.belongs.another.company',
13
+ COMPANY_NO_ACCESS: 'auth.company.no.access'
14
+ };
15
+ // ==================== ERROR (HTTP Exceptions) ====================
16
+ const ERROR_MESSAGES = {
17
+ NOT_FOUND: 'error.not.found',
18
+ VALIDATION: 'error.validation',
19
+ UNAUTHORIZED: 'error.unauthorized',
20
+ FORBIDDEN: 'error.forbidden',
21
+ CONFLICT: 'error.conflict',
22
+ DB_FOREIGN_KEY: 'error.db.foreign.key',
23
+ DB_NOT_NULL: 'error.db.not.null',
24
+ DB_INVALID_INPUT: 'error.db.invalid.input',
25
+ DB_VALUE_TOO_LONG: 'error.db.value.too.long',
26
+ DB_CHECK_CONSTRAINT: 'error.db.check.constraint',
27
+ DB_DEADLOCK: 'error.db.deadlock',
28
+ INTERNAL: 'error.internal',
29
+ SERVICE_UNAVAILABLE: 'error.service.unavailable',
30
+ UNKNOWN: 'error.unknown',
31
+ HTTP: 'error.http',
32
+ GENERIC: 'error.generic',
33
+ PERMISSION_SYSTEM_UNAVAILABLE: 'error.permission.system.unavailable',
34
+ INSUFFICIENT_PERMISSIONS: 'error.insufficient.permissions',
35
+ INSUFFICIENT_PERMISSIONS_OR: 'error.insufficient.permissions.or',
36
+ NO_PERMISSIONS_FOUND: 'error.no.permissions.found',
37
+ ENDPOINT_DISABLED: 'error.endpoint.disabled'
38
+ };
39
+ // ==================== SYSTEM (Infrastructure) ====================
40
+ const SYSTEM_MESSAGES = {
41
+ REPOSITORY_NOT_AVAILABLE: 'system.repository.not.available',
42
+ DATASOURCE_NOT_AVAILABLE: 'system.datasource.not.available',
43
+ DATABASE_CONFIG_NOT_AVAILABLE: 'system.database.config.not.available',
44
+ SERVICE_NOT_AVAILABLE: 'system.service.not.available',
45
+ CONFIG_REQUIRED: 'system.config.required',
46
+ INTERNAL_ERROR: 'system.internal.error',
47
+ DUPLICATE_REQUEST: 'system.duplicate.request',
48
+ INVALID_TENANT_ID: 'system.invalid.tenant.id',
49
+ TENANT_NOT_FOUND: 'system.tenant.not.found',
50
+ TENANT_HEADER_REQUIRED: 'system.tenant.header.required',
51
+ MISSING_PARAMETER: 'system.missing.parameter',
52
+ SDK_NOT_INSTALLED: 'system.sdk.not.installed',
53
+ PATH_TRAVERSAL_DETECTED: 'system.path.traversal.detected',
54
+ INVALID_FILE_KEY: 'system.invalid.file.key'
55
+ };
56
+
57
+ __webpack_require__.d(__webpack_exports__, {
58
+ }, {
59
+ UU: ERROR_MESSAGES,
60
+ hg: SYSTEM_MESSAGES,
61
+ mK: AUTH_MESSAGES
62
+ });
63
+
64
+
65
+ },
66
+ 928(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
67
+
68
+ // EXPORTS
69
+ __webpack_require__.d(__webpack_exports__, {
70
+ gG: () => (/* reexport */ AUDIT_BODY_KEYS),
71
+ d0: () => (/* reexport */ DEFAULT_VALIDATION_OPTIONS),
72
+ zT: () => (/* reexport */ DtoBodyValidationPipe)
73
+ });
74
+
75
+ // EXTERNAL MODULE: external "@nestjs/common"
76
+ var common_ = __webpack_require__(157);
77
+ // EXTERNAL MODULE: external "class-validator"
78
+ var external_class_validator_ = __webpack_require__(421);
79
+ // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/message-keys.ts
80
+ var message_keys = __webpack_require__(389);
81
+ ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/pipes/dto-body-validation.pipe.ts
82
+
83
+
84
+
85
+ const DEFAULT_VALIDATION_OPTIONS = {
86
+ transform: true,
87
+ whitelist: true,
88
+ forbidNonWhitelisted: true,
89
+ transformOptions: {
90
+ enableImplicitConversion: false
91
+ }
92
+ };
93
+ const AUDIT_BODY_KEYS = [
94
+ 'createdById',
95
+ 'updatedById',
96
+ 'deletedById'
97
+ ];
98
+ /**
99
+ * Validates a generic controller body against its concrete DTO class: a generic
100
+ * `@Body() dto: T` reflects as `Object`, so the global ValidationPipe skips it.
101
+ * Audit keys are never taken from the client: `stampedKeys` (written by the endpoint's
102
+ * SetCreatedByOnBody / SetUpdateByOnBody interceptors, which run first) are kept, any other
103
+ * audit key is dropped. A `slug` the DTO does not declare is the Slug interceptor's and is kept.
104
+ */ class DtoBodyValidationPipe {
105
+ resolveDto;
106
+ stampedKeys;
107
+ isArray;
108
+ options;
109
+ pipes = new Map();
110
+ constructor(resolveDto, stampedKeys, isArray = false, options = DEFAULT_VALIDATION_OPTIONS){
111
+ this.resolveDto = resolveDto;
112
+ this.stampedKeys = stampedKeys;
113
+ this.isArray = isArray;
114
+ this.options = options;
115
+ }
116
+ static for(dto, stampedKeys, isArray = false) {
117
+ return new DtoBodyValidationPipe(()=>dto, stampedKeys, isArray);
118
+ }
119
+ async transform(value, metadata) {
120
+ if (!this.isArray) return this.validateItem(value, metadata);
121
+ if (!Array.isArray(value)) throw new common_.BadRequestException({
122
+ message: 'Request body must be an array',
123
+ messageKey: message_keys/* .ERROR_MESSAGES.VALIDATION */.UU.VALIDATION
124
+ });
125
+ return Promise.all(value.map((item)=>this.validateItem(item, metadata)));
126
+ }
127
+ async validateItem(item, metadata) {
128
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
129
+ throw new common_.BadRequestException({
130
+ message: 'Request body item must be an object',
131
+ messageKey: message_keys/* .ERROR_MESSAGES.VALIDATION */.UU.VALIDATION
132
+ });
133
+ }
134
+ const dto = this.resolveDto(item);
135
+ const rest = {
136
+ ...item
137
+ };
138
+ const injected = {};
139
+ for (const key of AUDIT_BODY_KEYS){
140
+ if (!(key in rest)) continue;
141
+ if (this.stampedKeys.includes(key)) injected[key] = rest[key];
142
+ delete rest[key];
143
+ }
144
+ if ('slug' in rest && !declaresProperty(dto, 'slug')) {
145
+ injected['slug'] = rest['slug'];
146
+ delete rest['slug'];
147
+ }
148
+ const validated = await this.pipeFor(dto).transform(rest, {
149
+ ...metadata,
150
+ metatype: dto
151
+ });
152
+ return Object.assign(validated, injected);
153
+ }
154
+ pipeFor(dto) {
155
+ let pipe = this.pipes.get(dto);
156
+ if (!pipe) {
157
+ pipe = new common_.ValidationPipe({
158
+ ...this.options,
159
+ expectedType: dto
160
+ });
161
+ this.pipes.set(dto, pipe);
162
+ }
163
+ return pipe;
164
+ }
165
+ }
166
+ function declaresProperty(dto, key) {
167
+ return (0,external_class_validator_.getMetadataStorage)().getTargetValidationMetadatas(dto, '', true, false).some((m)=>m.propertyName === key);
168
+ }
169
+
170
+ ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/pipes/index.ts
171
+
172
+
173
+
174
+ },
175
+ 157(module) {
176
+
177
+ module.exports = __rspack_external__nestjs_common_a04939da;
178
+
179
+
180
+ },
181
+ 421(module) {
182
+
183
+ module.exports = __rspack_external_class_validator_7b99ef4d;
184
+
185
+
186
+ },
187
+
188
+ };
189
+ import { __webpack_require__ } from '../runtime.js';
190
+ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
191
+ import * as __rspack_chunk_1 from './index.js';
192
+ __webpack_require__.C(__rspack_chunk_1);
193
+ var __webpack_exports__ = __webpack_exec__(928);var __webpack_exports__AUDIT_BODY_KEYS = __webpack_exports__.gG;
194
+ var __webpack_exports__DEFAULT_VALIDATION_OPTIONS = __webpack_exports__.d0;
195
+ var __webpack_exports__DtoBodyValidationPipe = __webpack_exports__.zT;
196
+ export { __webpack_exports__AUDIT_BODY_KEYS as AUDIT_BODY_KEYS, __webpack_exports__DEFAULT_VALIDATION_OPTIONS as DEFAULT_VALIDATION_OPTIONS, __webpack_exports__DtoBodyValidationPipe as DtoBodyValidationPipe };
@@ -35,7 +35,10 @@ const DEFAULT_EVENT_SERVICE_NAME = 'flusys';
35
35
  __webpack_require__.d(__webpack_exports__, {
36
36
  BC: () => (/* reexport safe */ _permissions_js__rspack_import_0.BC),
37
37
  Bz: () => (/* reexport safe */ _permissions_js__rspack_import_0.Bz),
38
+ DP: () => (/* reexport safe */ _permissions_js__rspack_import_0.DP),
38
39
  Em: () => (/* reexport safe */ _permissions_js__rspack_import_0.Em),
40
+ G0: () => (/* reexport safe */ _permissions_js__rspack_import_0.G0),
41
+ GH: () => (/* reexport safe */ _permissions_js__rspack_import_0.GH),
39
42
  GT: () => (/* reexport safe */ _permissions_js__rspack_import_0.GT),
40
43
  Jj: () => (/* reexport safe */ _permissions_js__rspack_import_0.Jj),
41
44
  N3: () => (/* reexport safe */ _permissions_js__rspack_import_0.N3),
@@ -54,8 +57,11 @@ __webpack_require__.d(__webpack_exports__, {
54
57
  Y7: () => (/* reexport safe */ _permissions_js__rspack_import_0.Y7),
55
58
  _X: () => (/* reexport safe */ _permissions_js__rspack_import_0._X),
56
59
  aO: () => (/* reexport safe */ _permissions_js__rspack_import_0.aO),
60
+ aT: () => (/* reexport safe */ _permissions_js__rspack_import_0.aT),
57
61
  fS: () => (/* reexport safe */ _permissions_js__rspack_import_0.fS),
58
62
  hg: () => (/* reexport safe */ _message_keys_js__rspack_import_1.hg),
63
+ kA: () => (/* reexport safe */ _permissions_js__rspack_import_0.kA),
64
+ kd: () => (/* reexport safe */ _permissions_js__rspack_import_0.kd),
59
65
  l3: () => (/* reexport safe */ _permissions_js__rspack_import_0.l3),
60
66
  mK: () => (/* reexport safe */ _message_keys_js__rspack_import_1.mK),
61
67
  mV: () => (/* reexport safe */ _permissions_js__rspack_import_0.mV),
@@ -250,6 +256,35 @@ const FORM_RESULT_PERMISSIONS = {
250
256
  UPDATE: 'form-result.update',
251
257
  DELETE: 'form-result.delete'
252
258
  };
259
+ // ==================== ENTITY BUILDER MODULE ====================
260
+ // Static metadata permissions only. Per-dynamic-entity record permissions
261
+ // (`entity_builder.entity.<entityCode>.<action>`) are checked by flows running as the caller;
262
+ // ENTITY_BUILDER_ALL_DYNAMIC is the wildcard that grants all of them (and nothing else).
263
+ const ENTITY_DEFINITION_PERMISSIONS = {
264
+ CREATE: 'entity_builder.entity_definition.create',
265
+ READ: 'entity_builder.entity_definition.read',
266
+ UPDATE: 'entity_builder.entity_definition.update',
267
+ DELETE: 'entity_builder.entity_definition.delete'
268
+ };
269
+ const FIELD_DEFINITION_PERMISSIONS = {
270
+ READ: 'entity_builder.field_definition.read',
271
+ UPDATE: 'entity_builder.field_definition.update'
272
+ };
273
+ const FLOW_DEFINITION_PERMISSIONS = {
274
+ CREATE: 'entity_builder.flow_definition.create',
275
+ READ: 'entity_builder.flow_definition.read',
276
+ UPDATE: 'entity_builder.flow_definition.update',
277
+ DELETE: 'entity_builder.flow_definition.delete',
278
+ TEST: 'entity_builder.flow_definition.test',
279
+ /** Needed to save a flow that runs as the system (bypassing per-entity permissions). */ SYSTEM: 'entity_builder.flow_definition.system',
280
+ /** Needed to save a flow containing a code node (sandboxed JavaScript). */ CODE: 'entity_builder.flow_definition.code'
281
+ };
282
+ const ENTITY_BUILDER_DYNAMIC_PREFIX = 'entity_builder.entity';
283
+ const ENTITY_BUILDER_ALL_DYNAMIC = {
284
+ ALL: `${ENTITY_BUILDER_DYNAMIC_PREFIX}.*`
285
+ };
286
+ const dynamicEntityPermissionParent = (entityCode)=>`${ENTITY_BUILDER_DYNAMIC_PREFIX}.${entityCode}`;
287
+ const dynamicEntityPermission = (entityCode, action)=>`${dynamicEntityPermissionParent(entityCode)}.${action}`;
253
288
  // ==================== EVENT MANAGER MODULE ====================
254
289
  const EVENT_PERMISSIONS = {
255
290
  CREATE: 'event.create',
@@ -344,6 +379,11 @@ const PERMISSIONS = {
344
379
  // Form Builder
345
380
  FORM: FORM_PERMISSIONS,
346
381
  FORM_RESULT: FORM_RESULT_PERMISSIONS,
382
+ // Entity Builder
383
+ ENTITY_DEFINITION: ENTITY_DEFINITION_PERMISSIONS,
384
+ FIELD_DEFINITION: FIELD_DEFINITION_PERMISSIONS,
385
+ FLOW_DEFINITION: FLOW_DEFINITION_PERMISSIONS,
386
+ ENTITY_BUILDER_ALL_DYNAMIC: ENTITY_BUILDER_ALL_DYNAMIC,
347
387
  // Event Manager
348
388
  EVENT: EVENT_PERMISSIONS,
349
389
  // Notification
@@ -366,7 +406,10 @@ __webpack_require__.d(__webpack_exports__, {
366
406
  }, {
367
407
  BC: USER_PERMISSIONS,
368
408
  Bz: AI_ASSISTANT_PERMISSIONS,
409
+ DP: ENTITY_DEFINITION_PERMISSIONS,
369
410
  Em: STORAGE_CONFIG_PERMISSIONS,
411
+ G0: ENTITY_BUILDER_ALL_DYNAMIC,
412
+ GH: dynamicEntityPermissionParent,
370
413
  GT: FORM_RESULT_PERMISSIONS,
371
414
  Jj: PERMISSIONS,
372
415
  N3: TRANSLATION_KEY_PERMISSIONS,
@@ -384,7 +427,10 @@ __webpack_require__.d(__webpack_exports__, {
384
427
  Y7: ACTION_PERMISSIONS,
385
428
  _X: USER_ACTION_PERMISSIONS,
386
429
  aO: COMPANY_ACTION_PERMISSIONS,
430
+ aT: dynamicEntityPermission,
387
431
  fS: TRANSLATION_PERMISSIONS,
432
+ kA: FIELD_DEFINITION_PERMISSIONS,
433
+ kd: FLOW_DEFINITION_PERMISSIONS,
388
434
  l3: SOCIAL_AUTH_CONFIG_PERMISSIONS,
389
435
  mV: ROLE_PERMISSIONS,
390
436
  rH: NOTIFICATION_PERMISSIONS,
@@ -397,6 +443,21 @@ __webpack_require__.d(__webpack_exports__, {
397
443
  });
398
444
 
399
445
 
446
+ },
447
+ 154(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
448
+ /**
449
+ * The tenant a cache key belongs to: the provider's current tenant in multi-tenant mode, else
450
+ * undefined. A single database never prefixes, so a stray tenant header cannot split its cache.
451
+ */ function cacheTenantId(provider) {
452
+ if (!provider?.isMultiTenant?.()) return undefined;
453
+ return provider.getCurrentTenantId?.() ?? undefined;
454
+ }
455
+
456
+ __webpack_require__.d(__webpack_exports__, {
457
+ q: () => (cacheTenantId)
458
+ });
459
+
460
+
400
461
  },
401
462
  456(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
402
463
  const SENSITIVE_KEYS = [
@@ -499,7 +560,7 @@ __webpack_require__.d(__webpack_exports__, {
499
560
 
500
561
 
501
562
  },
502
- 712(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
563
+ 528(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
503
564
 
504
565
  // EXPORTS
505
566
  __webpack_require__.d(__webpack_exports__, {
@@ -510,6 +571,7 @@ __webpack_require__.d(__webpack_exports__, {
510
571
  fJ: () => (/* reexport */ buildCookieOptions),
511
572
  oV: () => (/* reexport */ buildFrontendUrl),
512
573
  nt: () => (/* reexport */ permission_cache_key_util/* .buildPermissionCacheKey */.nt),
574
+ qW: () => (/* reexport */ cache_tenant_util/* .cacheTenantId */.q),
513
575
  ZD: () => (/* reexport */ escapeHtml),
514
576
  qo: () => (/* reexport */ escapeHtmlVariables),
515
577
  hX: () => (/* reexport */ extractFrontendUrl),
@@ -526,12 +588,17 @@ __webpack_require__.d(__webpack_exports__, {
526
588
  lk: () => (/* reexport */ optional_module_util/* .loadOptionalModule */.l),
527
589
  vf: () => (/* reexport */ localToUtc),
528
590
  qe: () => (/* reexport */ event_pattern_util/* .matchEventPattern */.qe),
591
+ xY: () => (/* reexport */ permission_match_util/* .matchesPermission */.x),
592
+ RG: () => (/* reexport */ messageRef),
529
593
  Ju: () => (/* reexport */ parseDurationToMs),
530
594
  IN: () => (/* reexport */ event_pattern_util/* .sanitizeEventPayload */.IN),
531
595
  v4: () => (/* reexport */ utcToLocal),
532
- bE: () => (/* reexport */ validateCompanyOwnership)
596
+ bE: () => (/* reexport */ validateCompanyOwnership),
597
+ o0: () => (/* reexport */ withSavepoint)
533
598
  });
534
599
 
600
+ // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/cache-tenant.util.ts
601
+ var cache_tenant_util = __webpack_require__(154);
535
602
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/date-time.util.ts
536
603
  function getOffsetMinutes(utcDate, timezone) {
537
604
  const extract = (tz)=>{
@@ -677,14 +744,26 @@ var event_pattern_util = __webpack_require__(456);
677
744
  return escaped;
678
745
  }
679
746
 
747
+ ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/message-ref.util.ts
748
+ function messageRef(messageKey, messageVariables) {
749
+ return messageVariables ? {
750
+ messageKey,
751
+ messageVariables
752
+ } : {
753
+ messageKey
754
+ };
755
+ }
756
+
680
757
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/optional-module.util.ts
681
758
  var optional_module_util = __webpack_require__(368);
682
759
  // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/permission-cache-key.util.ts
683
760
  var permission_cache_key_util = __webpack_require__(199);
761
+ // EXTERNAL MODULE: ./projects/nestjs-shared/src/utils/permission-match.util.ts
762
+ var permission_match_util = __webpack_require__(788);
684
763
  // EXTERNAL MODULE: external "@nestjs/common"
685
764
  var common_ = __webpack_require__(157);
686
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/message-keys.ts
687
- var message_keys = __webpack_require__(389);
765
+ // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/index.ts
766
+ var constants = __webpack_require__(43);
688
767
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/query-helpers.util.ts
689
768
 
690
769
 
@@ -763,7 +842,7 @@ var message_keys = __webpack_require__(389);
763
842
  if (entity.companyId && entity.companyId !== user.companyId) {
764
843
  throw new common_.BadRequestException({
765
844
  message: `${entityName} belongs to another company`,
766
- messageKey: message_keys/* .AUTH_MESSAGES.ENTITY_BELOG_ANOTHER_COMPANY */.mK.ENTITY_BELOG_ANOTHER_COMPANY,
845
+ messageKey: constants/* .AUTH_MESSAGES.ENTITY_BELOG_ANOTHER_COMPANY */.mK.ENTITY_BELOG_ANOTHER_COMPANY,
767
846
  messageVariables: {
768
847
  entity: entityName
769
848
  }
@@ -771,11 +850,24 @@ var message_keys = __webpack_require__(389);
771
850
  }
772
851
  }
773
852
  }
853
+ /**
854
+ * Runs `fn` inside a savepoint of an already-open transaction. A failure rolls back only what
855
+ * `fn` did, so the caller can swallow the error and keep using the transaction - without it,
856
+ * PostgreSQL aborts the whole transaction after any failed statement.
857
+ */ async function withSavepoint(queryRunner, fn) {
858
+ await queryRunner.startTransaction();
859
+ try {
860
+ const result = await fn();
861
+ await queryRunner.commitTransaction();
862
+ return result;
863
+ } catch (error) {
864
+ await queryRunner.rollbackTransaction();
865
+ throw error;
866
+ }
867
+ }
774
868
 
775
869
  // EXTERNAL MODULE: external "@flusys/nestjs-core/config"
776
870
  var config_ = __webpack_require__(307);
777
- // EXTERNAL MODULE: ./projects/nestjs-shared/src/constants/index.ts
778
- var constants = __webpack_require__(43);
779
871
  ;// CONCATENATED MODULE: ./projects/nestjs-shared/src/utils/request.util.ts
780
872
 
781
873
 
@@ -856,7 +948,7 @@ var constants = __webpack_require__(43);
856
948
  }
857
949
  throw new common_.BadRequestException({
858
950
  message: 'Unable to determine frontend URL from request',
859
- messageKey: message_keys/* .ERROR_MESSAGES.VALIDATION */.UU.VALIDATION
951
+ messageKey: constants/* .ERROR_MESSAGES.VALIDATION */.UU.VALIDATION
860
952
  });
861
953
  }
862
954
  function buildFrontendUrl(req, path) {
@@ -963,6 +1055,9 @@ function isExpired(expiresAt) {
963
1055
 
964
1056
 
965
1057
 
1058
+
1059
+
1060
+
966
1061
  },
967
1062
  368(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
968
1063
  /**
@@ -1018,6 +1113,22 @@ __webpack_require__.d(__webpack_exports__, {
1018
1113
  });
1019
1114
 
1020
1115
 
1116
+ },
1117
+ 788(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1118
+ /** Whether `granted` covers `required`, honoring `*` (everything) and `prefix.*` (everything under prefix) wildcards */ function matchesPermission(granted, required) {
1119
+ if (granted.includes(required)) return true;
1120
+ for (const permission of granted){
1121
+ if (permission === '*') return true;
1122
+ if (permission.endsWith('.*') && required.startsWith(permission.slice(0, -1))) return true;
1123
+ }
1124
+ return false;
1125
+ }
1126
+
1127
+ __webpack_require__.d(__webpack_exports__, {
1128
+ x: () => (matchesPermission)
1129
+ });
1130
+
1131
+
1021
1132
  },
1022
1133
  307(module) {
1023
1134
 
@@ -1043,13 +1154,14 @@ import { __webpack_require__ } from '../runtime.js';
1043
1154
  var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
1044
1155
  import * as __rspack_chunk_1 from './index.js';
1045
1156
  __webpack_require__.C(__rspack_chunk_1);
1046
- var __webpack_exports__ = __webpack_exec__(712);var __webpack_exports__DEFAULT_COMPANY_PERMISSION_KEY_FORMAT = __webpack_exports__.s8;
1157
+ var __webpack_exports__ = __webpack_exec__(528);var __webpack_exports__DEFAULT_COMPANY_PERMISSION_KEY_FORMAT = __webpack_exports__.s8;
1047
1158
  var __webpack_exports__DEFAULT_USER_PERMISSION_KEY_FORMAT = __webpack_exports__.q6;
1048
1159
  var __webpack_exports__applyCompanyFilter = __webpack_exports__.wf;
1049
1160
  var __webpack_exports__buildCompanyWhereCondition = __webpack_exports__.kQ;
1050
1161
  var __webpack_exports__buildCookieOptions = __webpack_exports__.fJ;
1051
1162
  var __webpack_exports__buildFrontendUrl = __webpack_exports__.oV;
1052
1163
  var __webpack_exports__buildPermissionCacheKey = __webpack_exports__.nt;
1164
+ var __webpack_exports__cacheTenantId = __webpack_exports__.qW;
1053
1165
  var __webpack_exports__escapeHtml = __webpack_exports__.ZD;
1054
1166
  var __webpack_exports__escapeHtmlVariables = __webpack_exports__.qo;
1055
1167
  var __webpack_exports__extractFrontendUrl = __webpack_exports__.hX;
@@ -1066,8 +1178,11 @@ var __webpack_exports__isMultiWildcard = __webpack_exports__._c;
1066
1178
  var __webpack_exports__loadOptionalModule = __webpack_exports__.lk;
1067
1179
  var __webpack_exports__localToUtc = __webpack_exports__.vf;
1068
1180
  var __webpack_exports__matchEventPattern = __webpack_exports__.qe;
1181
+ var __webpack_exports__matchesPermission = __webpack_exports__.xY;
1182
+ var __webpack_exports__messageRef = __webpack_exports__.RG;
1069
1183
  var __webpack_exports__parseDurationToMs = __webpack_exports__.Ju;
1070
1184
  var __webpack_exports__sanitizeEventPayload = __webpack_exports__.IN;
1071
1185
  var __webpack_exports__utcToLocal = __webpack_exports__.v4;
1072
1186
  var __webpack_exports__validateCompanyOwnership = __webpack_exports__.bE;
1073
- export { __webpack_exports__DEFAULT_COMPANY_PERMISSION_KEY_FORMAT as DEFAULT_COMPANY_PERMISSION_KEY_FORMAT, __webpack_exports__DEFAULT_USER_PERMISSION_KEY_FORMAT as DEFAULT_USER_PERMISSION_KEY_FORMAT, __webpack_exports__applyCompanyFilter as applyCompanyFilter, __webpack_exports__buildCompanyWhereCondition as buildCompanyWhereCondition, __webpack_exports__buildCookieOptions as buildCookieOptions, __webpack_exports__buildFrontendUrl as buildFrontendUrl, __webpack_exports__buildPermissionCacheKey as buildPermissionCacheKey, __webpack_exports__escapeHtml as escapeHtml, __webpack_exports__escapeHtmlVariables as escapeHtmlVariables, __webpack_exports__extractFrontendUrl as extractFrontendUrl, __webpack_exports__formatInTimezone as formatInTimezone, __webpack_exports__generateSecureToken as generateSecureToken, __webpack_exports__generateSlug as generateSlug, __webpack_exports__generateUniqueSlug as generateUniqueSlug, __webpack_exports__getTodayInTimezone as getTodayInTimezone, __webpack_exports__hasCompanyId as hasCompanyId, __webpack_exports__hashToken as hashToken, __webpack_exports__isBrowserRequest as isBrowserRequest, __webpack_exports__isExpired as isExpired, __webpack_exports__isMultiWildcard as isMultiWildcard, __webpack_exports__loadOptionalModule as loadOptionalModule, __webpack_exports__localToUtc as localToUtc, __webpack_exports__matchEventPattern as matchEventPattern, __webpack_exports__parseDurationToMs as parseDurationToMs, __webpack_exports__sanitizeEventPayload as sanitizeEventPayload, __webpack_exports__utcToLocal as utcToLocal, __webpack_exports__validateCompanyOwnership as validateCompanyOwnership };
1187
+ var __webpack_exports__withSavepoint = __webpack_exports__.o0;
1188
+ export { __webpack_exports__DEFAULT_COMPANY_PERMISSION_KEY_FORMAT as DEFAULT_COMPANY_PERMISSION_KEY_FORMAT, __webpack_exports__DEFAULT_USER_PERMISSION_KEY_FORMAT as DEFAULT_USER_PERMISSION_KEY_FORMAT, __webpack_exports__applyCompanyFilter as applyCompanyFilter, __webpack_exports__buildCompanyWhereCondition as buildCompanyWhereCondition, __webpack_exports__buildCookieOptions as buildCookieOptions, __webpack_exports__buildFrontendUrl as buildFrontendUrl, __webpack_exports__buildPermissionCacheKey as buildPermissionCacheKey, __webpack_exports__cacheTenantId as cacheTenantId, __webpack_exports__escapeHtml as escapeHtml, __webpack_exports__escapeHtmlVariables as escapeHtmlVariables, __webpack_exports__extractFrontendUrl as extractFrontendUrl, __webpack_exports__formatInTimezone as formatInTimezone, __webpack_exports__generateSecureToken as generateSecureToken, __webpack_exports__generateSlug as generateSlug, __webpack_exports__generateUniqueSlug as generateUniqueSlug, __webpack_exports__getTodayInTimezone as getTodayInTimezone, __webpack_exports__hasCompanyId as hasCompanyId, __webpack_exports__hashToken as hashToken, __webpack_exports__isBrowserRequest as isBrowserRequest, __webpack_exports__isExpired as isExpired, __webpack_exports__isMultiWildcard as isMultiWildcard, __webpack_exports__loadOptionalModule as loadOptionalModule, __webpack_exports__localToUtc as localToUtc, __webpack_exports__matchEventPattern as matchEventPattern, __webpack_exports__matchesPermission as matchesPermission, __webpack_exports__messageRef as messageRef, __webpack_exports__parseDurationToMs as parseDurationToMs, __webpack_exports__sanitizeEventPayload as sanitizeEventPayload, __webpack_exports__utcToLocal as utcToLocal, __webpack_exports__validateCompanyOwnership as validateCompanyOwnership, __webpack_exports__withSavepoint as withSavepoint };
package/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export * from './interfaces/index.js';
12
12
  export * from './middlewares/index.js';
13
13
  export * from './modules/index.js';
14
14
  export * from './utils/index.js';
15
+ export * from './pipes/index.js';
@@ -2,4 +2,6 @@ import { DataSource, type EntityTarget, type ObjectLiteral, Repository } from 't
2
2
  export interface IDataSourceProvider {
3
3
  getRepository<T extends ObjectLiteral>(entity: EntityTarget<T>): Promise<Repository<T>>;
4
4
  getDataSource(): Promise<DataSource>;
5
+ isMultiTenant?(): boolean;
6
+ getCurrentTenantId?(): string | null;
5
7
  }
@@ -5,7 +5,10 @@ export * from './event-bus.interface.js';
5
5
  export * from './event-manager-adapter.interface.js';
6
6
  export * from './identity.interface.js';
7
7
  export * from './logged-user-info.interface.js';
8
+ export * from './message-ref.interface.js';
8
9
  export * from './module-config.interface.js';
9
10
  export * from './notification-adapter.interface.js';
11
+ export * from './permission-action-registry.interface.js';
12
+ export * from './permission-resolver.interface.js';
10
13
  export * from './permission.interface.js';
11
14
  export * from './permission-sync-adapter.interface.js';
@@ -0,0 +1,4 @@
1
+ export interface IMessageRef {
2
+ messageKey: string;
3
+ messageVariables?: Record<string, unknown>;
4
+ }
@@ -0,0 +1,12 @@
1
+ export declare const PERMISSION_ACTION_REGISTRY = "PERMISSION_ACTION_REGISTRY";
2
+ export interface IPermissionActionInput {
3
+ code: string;
4
+ name: string;
5
+ description?: string | null;
6
+ parentCode?: string | null;
7
+ actionType?: 'BACKEND' | 'FRONTEND' | 'BOTH';
8
+ }
9
+ export interface IPermissionActionRegistry {
10
+ registerActions(actions: IPermissionActionInput[]): Promise<void>;
11
+ removeActionsByCodes(codes: string[]): Promise<void>;
12
+ }
@@ -0,0 +1,9 @@
1
+ export declare const PERMISSION_RESOLVER = "PERMISSION_RESOLVER";
2
+ export interface IPermissionScope {
3
+ userId: string;
4
+ companyId?: string | null;
5
+ branchId?: string | null;
6
+ }
7
+ export interface IPermissionResolver {
8
+ getPermissionCodes(scope: IPermissionScope): Promise<string[]>;
9
+ }
@@ -13,6 +13,10 @@ export declare const getRequestId: () => string | undefined;
13
13
  export declare const getTenantId: () => string | undefined;
14
14
  export declare const getUserId: () => string | undefined;
15
15
  export declare const getCompanyId: () => string | undefined;
16
+ export declare function setRequestUser(user: {
17
+ id?: string;
18
+ companyId?: string;
19
+ } | undefined): void;
16
20
  export declare class LoggerMiddleware implements NestMiddleware {
17
21
  private readonly logger;
18
22
  use(req: Request, res: Response, next: NextFunction): void;
@@ -9,6 +9,11 @@ export declare class SharedPermissionCacheService {
9
9
  setPermissions(options: PermissionCacheKeyOptions, permissions: string[], ttl?: number): Promise<void>;
10
10
  clearPermissions(options: PermissionCacheKeyOptions): Promise<void>;
11
11
  hasPermission(options: PermissionCacheKeyOptions, requiredPermission: string): Promise<boolean>;
12
+ assertPermission(user: {
13
+ id?: string | null;
14
+ companyId?: string | null;
15
+ branchId?: string | null;
16
+ } | null | undefined, requiredPermission: string): Promise<void>;
12
17
  isCacheConfigured(): boolean;
13
18
  matchPermission(userPermissions: string[], requiredPermission: string): boolean;
14
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-shared",
3
- "version": "8.1.5",
3
+ "version": "9.1.0-rc.1",
4
4
  "description": "Common shared utilities for Flusys NestJS applications",
5
5
  "type": "module",
6
6
  "main": "./fesm/index.js",
@@ -69,6 +69,10 @@
69
69
  "types": "./modules/index.d.ts",
70
70
  "import": "./fesm/modules/index.js"
71
71
  },
72
+ "./pipes": {
73
+ "types": "./pipes/index.d.ts",
74
+ "import": "./fesm/pipes/index.js"
75
+ },
72
76
  "./utils": {
73
77
  "types": "./utils/index.d.ts",
74
78
  "import": "./fesm/utils/index.js"
@@ -92,12 +96,14 @@
92
96
  "class-validator": "^0.15.0",
93
97
  "keyv": "^5.0.0",
94
98
  "typeorm": "^0.3.0",
99
+ "typeorm-naming-strategies": "^4.1.0",
95
100
  "winston": "^3.0.0",
101
+ "winston-transport": "^4.9.0",
96
102
  "winston-daily-rotate-file": "^5.0.0",
97
103
  "express": "^5.0.0",
98
104
  "rxjs": "^7.8.0"
99
105
  },
100
106
  "dependencies": {
101
- "@flusys/nestjs-core": "8.1.5"
107
+ "@flusys/nestjs-core": "9.1.0-rc.1"
102
108
  }
103
109
  }
@@ -0,0 +1,18 @@
1
+ import { type ArgumentMetadata, type PipeTransform, type Type, type ValidationPipeOptions } from '@nestjs/common';
2
+ export declare const DEFAULT_VALIDATION_OPTIONS: ValidationPipeOptions;
3
+ export declare const AUDIT_BODY_KEYS: readonly ["createdById", "updatedById", "deletedById"];
4
+ export type AuditBodyKey = (typeof AUDIT_BODY_KEYS)[number];
5
+ type BodyItem = Record<string, unknown>;
6
+ export declare class DtoBodyValidationPipe implements PipeTransform {
7
+ private readonly resolveDto;
8
+ private readonly stampedKeys;
9
+ private readonly isArray;
10
+ private readonly options;
11
+ private readonly pipes;
12
+ constructor(resolveDto: (item: BodyItem) => Type<unknown>, stampedKeys: readonly AuditBodyKey[], isArray?: boolean, options?: ValidationPipeOptions);
13
+ static for(dto: Type<unknown>, stampedKeys: readonly AuditBodyKey[], isArray?: boolean): DtoBodyValidationPipe;
14
+ transform(value: unknown, metadata: ArgumentMetadata): Promise<unknown>;
15
+ private validateItem;
16
+ private pipeFor;
17
+ }
18
+ export {};
@@ -0,0 +1 @@
1
+ export * from './dto-body-validation.pipe.js';
@@ -0,0 +1,2 @@
1
+ import { type IDataSourceProvider } from '../interfaces/datasource.interface.js';
2
+ export declare function cacheTenantId(provider: Pick<IDataSourceProvider, 'isMultiTenant' | 'getCurrentTenantId'> | null | undefined): string | undefined;
package/utils/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ export * from './cache-tenant.util.js';
1
2
  export * from './date-time.util.js';
2
3
  export * from './event-pattern.util.js';
3
4
  export * from './html-sanitizer.util.js';
5
+ export * from './message-ref.util.js';
4
6
  export * from './optional-module.util.js';
5
7
  export * from './permission-cache-key.util.js';
8
+ export * from './permission-match.util.js';
6
9
  export * from './query-helpers.util.js';
7
10
  export * from './request.util.js';
8
11
  export * from './string.util.js';
@@ -0,0 +1,2 @@
1
+ import { type IMessageRef } from '../interfaces/message-ref.interface.js';
2
+ export declare function messageRef(messageKey: string, messageVariables?: Record<string, unknown>): IMessageRef;
@@ -0,0 +1 @@
1
+ export declare function matchesPermission(granted: readonly string[], required: string): boolean;
@@ -1,4 +1,4 @@
1
- import { type ObjectLiteral, SelectQueryBuilder } from 'typeorm';
1
+ import { type ObjectLiteral, type QueryRunner, SelectQueryBuilder } from 'typeorm';
2
2
  import { type ILoggedUserInfo } from '../interfaces/index.js';
3
3
  export interface ICompanyFilterConfig {
4
4
  isCompanyFeatureEnabled: boolean;
@@ -14,3 +14,4 @@ export interface ICompanyEnabled {
14
14
  }
15
15
  export declare function hasCompanyId<T>(entity: T): entity is T & ICompanyEnabled;
16
16
  export declare function validateCompanyOwnership<T>(entity: T, user: ILoggedUserInfo | null | undefined, isCompanyFeatureEnabled: boolean, entityName: string): void;
17
+ export declare function withSavepoint<T>(queryRunner: QueryRunner, fn: () => Promise<T>): Promise<T>;