@neohm/nestend 2.0.0 → 2.0.3

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 (96) hide show
  1. package/README.md +169 -14
  2. package/dist/admin/es6.classes.d.ts +2 -1
  3. package/dist/admin/es6.classes.d.ts.map +1 -1
  4. package/dist/admin/libraries/scheduled.task.list.processor.d.ts +3 -0
  5. package/dist/admin/libraries/scheduled.task.list.processor.d.ts.map +1 -1
  6. package/dist/admin/libraries/scheduled.task.list.processor.js +4 -0
  7. package/dist/admin/libraries/scheduled.task.list.processor.js.map +1 -1
  8. package/dist/admin/libraries/tenant.list.processor.d.ts +3 -0
  9. package/dist/admin/libraries/tenant.list.processor.d.ts.map +1 -1
  10. package/dist/admin/libraries/tenant.list.processor.js +4 -0
  11. package/dist/admin/libraries/tenant.list.processor.js.map +1 -1
  12. package/dist/auth/jobs/system.refresh.token.job.js +1 -1
  13. package/dist/auth/jobs/system.refresh.token.job.js.map +1 -1
  14. package/dist/auth/jobs/system.role.job.js +1 -1
  15. package/dist/auth/jobs/system.role.job.js.map +1 -1
  16. package/dist/auth/jobs/user.details.job.js +1 -1
  17. package/dist/auth/jobs/user.details.job.js.map +1 -1
  18. package/dist/auth/jobs/user.job.js +1 -1
  19. package/dist/auth/jobs/user.job.js.map +1 -1
  20. package/dist/auth/jobs/user.role.job.js +1 -1
  21. package/dist/auth/jobs/user.role.job.js.map +1 -1
  22. package/dist/common/consumers/job.consumer.d.ts +6 -2
  23. package/dist/common/consumers/job.consumer.d.ts.map +1 -1
  24. package/dist/common/consumers/job.consumer.js +41 -11
  25. package/dist/common/consumers/job.consumer.js.map +1 -1
  26. package/dist/common/enums/filter.value.type.enum.d.ts +1 -0
  27. package/dist/common/enums/filter.value.type.enum.d.ts.map +1 -1
  28. package/dist/common/enums/filter.value.type.enum.js +1 -0
  29. package/dist/common/enums/filter.value.type.enum.js.map +1 -1
  30. package/dist/common/es6.classes.d.ts +1 -1
  31. package/dist/common/jobs/activity.log.job.js +1 -1
  32. package/dist/common/jobs/activity.log.job.js.map +1 -1
  33. package/dist/common/jobs/common.job.d.ts.map +1 -1
  34. package/dist/common/jobs/common.job.js +2 -2
  35. package/dist/common/jobs/common.job.js.map +1 -1
  36. package/dist/common/jobs/dispatch.scheduled.event.job.js +1 -1
  37. package/dist/common/jobs/dispatch.scheduled.event.job.js.map +1 -1
  38. package/dist/common/jobs/document.job.js +1 -1
  39. package/dist/common/jobs/document.job.js.map +1 -1
  40. package/dist/common/jobs/scheduled.event.job.d.ts.map +1 -1
  41. package/dist/common/jobs/scheduled.event.job.js +2 -3
  42. package/dist/common/jobs/scheduled.event.job.js.map +1 -1
  43. package/dist/common/jobs/set.scheduled.event.job.js +1 -1
  44. package/dist/common/jobs/set.scheduled.event.job.js.map +1 -1
  45. package/dist/common/jobs/system.audit.log.job.js +1 -1
  46. package/dist/common/jobs/system.audit.log.job.js.map +1 -1
  47. package/dist/common/jobs/system.constant.job.js +1 -1
  48. package/dist/common/jobs/system.constant.job.js.map +1 -1
  49. package/dist/common/jobs/system.constant.type.job.js +1 -1
  50. package/dist/common/jobs/system.constant.type.job.js.map +1 -1
  51. package/dist/common/jobs/system.failed.job.job.js +1 -1
  52. package/dist/common/jobs/system.failed.job.job.js.map +1 -1
  53. package/dist/common/jobs/system.notification.job.js +1 -1
  54. package/dist/common/jobs/system.notification.job.js.map +1 -1
  55. package/dist/common/jobs/system.property.job.js +1 -1
  56. package/dist/common/jobs/system.property.job.js.map +1 -1
  57. package/dist/common/jobs/system.scheduled.task.job.js +1 -1
  58. package/dist/common/jobs/system.scheduled.task.job.js.map +1 -1
  59. package/dist/common/libraries/common.list.processor.d.ts +0 -1
  60. package/dist/common/libraries/common.list.processor.d.ts.map +1 -1
  61. package/dist/common/libraries/common.list.processor.js +9 -2
  62. package/dist/common/libraries/common.list.processor.js.map +1 -1
  63. package/dist/common/queue.module.d.ts.map +1 -1
  64. package/dist/common/queue.module.js +5 -3
  65. package/dist/common/queue.module.js.map +1 -1
  66. package/dist/common/services/queue.service.d.ts +9 -4
  67. package/dist/common/services/queue.service.d.ts.map +1 -1
  68. package/dist/common/services/queue.service.js +73 -5
  69. package/dist/common/services/queue.service.js.map +1 -1
  70. package/dist/common/types/queue.types.d.ts +11 -0
  71. package/dist/common/types/queue.types.d.ts.map +1 -1
  72. package/dist/constants/entity.constants.d.ts +5 -1
  73. package/dist/constants/entity.constants.d.ts.map +1 -1
  74. package/dist/constants/entity.constants.js +6 -2
  75. package/dist/constants/entity.constants.js.map +1 -1
  76. package/dist/constants/entity.maps.d.ts +43 -0
  77. package/dist/constants/entity.maps.d.ts.map +1 -0
  78. package/dist/constants/entity.maps.js +47 -0
  79. package/dist/constants/entity.maps.js.map +1 -0
  80. package/dist/constants/job.constants.d.ts +20 -20
  81. package/dist/constants/job.constants.d.ts.map +1 -1
  82. package/dist/constants/job.constants.js +2 -2
  83. package/dist/constants/job.constants.js.map +1 -1
  84. package/dist/constants/job.maps.d.ts +41 -0
  85. package/dist/constants/job.maps.d.ts.map +1 -0
  86. package/dist/constants/job.maps.js +45 -0
  87. package/dist/constants/job.maps.js.map +1 -0
  88. package/dist/index.d.ts +4 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +7 -3
  91. package/dist/index.js.map +1 -1
  92. package/dist/notification/jobs/email.notification.log.job.js +1 -1
  93. package/dist/notification/jobs/email.notification.log.job.js.map +1 -1
  94. package/dist/notification/jobs/email.template.job.js +1 -1
  95. package/dist/notification/jobs/email.template.job.js.map +1 -1
  96. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @neohm/nestjs-utilities
2
2
 
3
- Reusable NestJS building blocks for SaaS backends: entities, migrations, JWT auth,
4
- BullMQ queueing, Redis caching, encryption, system properties, document storage
5
- (S3 / MinIO), logging and monitoring interceptors, and more — all behind one flat import.
3
+ Reusable NestJS building blocks for SaaS backends: entities, migrations, JWT auth
4
+ (+ Google SSO), BullMQ queueing, Redis caching, encryption, system properties,
5
+ document storage (S3 / MinIO), raw SQL on a read replica, outbound HTTP, templated
6
+ SES email, logging and monitoring interceptors, and more — all behind one flat import.
6
7
 
7
8
  ```ts
8
9
  import {
@@ -87,11 +88,12 @@ app.useGlobalInterceptors(
87
88
  | Migrations | `CreateSysUsersTable...` (16 files), `MigrationUtility`, `SeederUtility` | fluent `this.string()`, `this.numeric()`, `addRecord()` seeding |
88
89
  | Queue | `QueueModule`, `QueueService`, `CommonJob`, `JobConsumer` | BullMQ `@Processor`/`WorkerHost` consumer; auto-discovered jobs, duplicate prevention, recurring schedulers, 1-day TTL, retries, failures persisted to `sys_failed_jobs` |
89
90
  | Scheduled events | `SystemScheduledTaskEntity`, `ScheduledEventEntity`, `SetScheduledEventJob`, `DispatchScheduledEventJob` | schedule definitions in `sys_scheduled_tasks` → events in `sys_scheduled_events`; worker-driven materialisation + dispatch via `SCHEDULED_EVENT_*` env |
90
- | Auth | `AuthModule`, `AuthService.validateAccess()`, `AuthContextService`, `AuthContextMiddleware` | imperative auth (open by default); bearer token via AsyncLocalStorage → user + roles; access + rotating refresh tokens, Redis JTI blocklist |
91
+ | Auth | `AuthModule`, `AuthService.validateAccess()`, `AuthContextService`, `AuthContextMiddleware`, `GoogleAuthService` | imperative auth (open by default); bearer token via AsyncLocalStorage → user + roles; access + rotating refresh tokens, Redis JTI blocklist; Google SSO via ID token at `POST /auth/google` |
91
92
  | API keys | `ApiKeyService`, `ApiKeyGuard`, `@ApiKeyScopes()` | hashed keys in `sys_api_keys`, scopes, expiry/revocation, `x-api-key` header |
92
93
  | Multi-tenancy | `TenantModule`, `TenantEntity`, `TenantCommonEntity`, `TenantContextService`, `TenantContextMiddleware` | AsyncLocalStorage tenant context; resolve by header, slug, or domain |
93
94
  | Admin | `AdminModule`, `AdminAccessService`, `TenantDataProcessor`, `ClientUserDataProcessor`, `BulkUserDataProcessor`, `AdminUserDataProcessor`, list processors | back-office APIs: tenant onboarding, client/admin user registration (single + bulk), tenant + scheduled-task listing; 4-tier `GlobalAdminRoles` gate; `UserType` discriminator on `sys_users`; Swagger at `/docs` (hidden in production) |
94
95
  | Notifications | `NotificationModule`, `NotificationService`, `FcmPushNotificationService` | in-app rows + FCM push (optional `firebase-admin`); device token registry |
96
+ | Email (SES) | `EmailNotificationService`, `ProcessEmailNotification`, `EmailTemplateEntity`, `EmailNotificationLogEntity` | templates in `sys_email_templates` rendered by a remote service, dispatched via SES per recipient (to/cc/bcc); logged to `sys_email_notification_logs` outside production; dispatch skipped when `AWS_SES_FROM_ADDRESS` unset |
95
97
  | Audit | `AuditService` | `saveAuditLog(old, new)` with changed-value diffing into `sys_audit_logs` |
96
98
  | Feature flags | `FeatureFlagService` | on/off, percentage rollouts, allow lists — stored as `feature.*` system properties |
97
99
  | Cache | `CacheService` | Redis, TTL objects (`{days, hours, min}`), transparent encryption |
@@ -99,8 +101,12 @@ app.useGlobalInterceptors(
99
101
  | System properties | `SystemPropertyModule`, `SystemPropertyService` | cached + optionally encrypted key/value settings in `sys_properties` |
100
102
  | Documents | `DocumentModule`, `DocumentService`, `S3BucketDocumentService`, `MinioBucketDocumentService` | `uploadDocument(multerFiles) → links[]`; driver chosen via `DOCUMENT_STORAGE_DRIVER` |
101
103
  | HTTP | `CommonController`, `GlobalExceptionFilter`, common DTOs | health endpoint base controller, uniform error envelope |
104
+ | Raw SQL | `SqlService`, `SqlTarget` | named `:key` placeholders always bound as driver params, `:...key` array expansion; runs on the read replica (`DB_READ_*`) with transparent primary fallback; `fetchOne`/`fetchScalar`/`exists`/`count`/`paginate` helpers |
105
+ | List endpoints | `CommonListProcessor`, `CommonListFilterDto`, `FilterDto`, `FILTER_OPERATORS` | allowlisted columns, parameterised operators (`eq`…`isNull`), typed value casting (`str`/`num`/`date`/`bool`/`null`), multi-sort + paginated on the read replica |
106
+ | Outbound HTTP | `RemoteRequestService`, `ApiRequestTypeEnum`, `ApiContentTypeEnum` | injectable `fetch` wrapper (global via `CommonModule`); `getApiResponse<T>()` parses JSON/text and throws `OperationException` on non-2xx, `getApiRawResponse()` returns the raw `Response`; JSON + form-urlencoded body serialisation |
102
107
  | Observability | `LoggingInterceptor`, `MonitoringInterceptor`, `MonitoringService` | Sentry auto-registers when `@sentry/node` + `SENTRY_DSN` present; pluggable `MonitoringAdapter` |
103
108
  | Jobs/Events | `CommonJob`, `JobConsumer`, `CommonSubscriber`, `CommonDataProcessor` | dispatch/handle separation, discovery-based registration, worker-event lifecycle, column-change detection, field error collection |
109
+ | Constants & maps | `baseEntityConstants`, `baseJobConstants`, `baseEntityMaps`, `baseJobMaps` | centralised dotted identifiers (`as const`) plus identifier → class maps of every concrete entity and job (abstract bases excluded); map keys are computed from the constants, so renames propagate at compile time |
104
110
 
105
111
  ## Environment Variables
106
112
 
@@ -124,6 +130,19 @@ unless marked **required**.
124
130
  | `DB_PASSWORD` | `postgres` | Postgres password |
125
131
  | `DB_NAME` | `myapp_db` | Database name |
126
132
 
133
+ **Read replica** (optional, used by `SqlService` and the list processors for read
134
+ queries). Comma-separate `DB_READ_HOST` for multiple replicas; unset `DB_READ_*`
135
+ values fall back to the primary's. Leave `DB_READ_HOST` empty to route replica
136
+ queries to the primary.
137
+
138
+ | Variable | Default | Description |
139
+ | ------------------ | -------------- | ---------------------- |
140
+ | `DB_READ_HOST` | _(empty)_ | Replica host(s) |
141
+ | `DB_READ_PORT` | `DB_PORT` | Replica port |
142
+ | `DB_READ_USERNAME` | `DB_USERNAME` | Replica user |
143
+ | `DB_READ_PASSWORD` | `DB_PASSWORD` | Replica password |
144
+ | `DB_READ_NAME` | `DB_NAME` | Replica database name |
145
+
127
146
  ### JWT / Auth
128
147
 
129
148
  | Variable | Default | Description |
@@ -132,6 +151,7 @@ unless marked **required**.
132
151
  | `REFRESH_TOKEN_SECRET` | — | **Required.** Secret for signing refresh tokens. Min 32 chars |
133
152
  | `ACCESS_TOKEN_EXPIRY` | `1h` | Access token lifetime (`60s`, `15m`, `1h`, `7d`, …) |
134
153
  | `REFRESH_TOKEN_EXPIRY` | `30d` | Refresh token lifetime |
154
+ | `GOOGLE_CLIENT_ID` | _(empty)_ | OAuth client id whose ID tokens are accepted at `POST /auth/google`. Empty disables Google SSO |
135
155
 
136
156
  #### Authentication (imperative)
137
157
 
@@ -143,10 +163,17 @@ rejects revoked tokens, and returns the user row with its roles — throwing
143
163
  your business logic, so layering tenant/plan/role checks is just more code.
144
164
 
145
165
  `AuthController` (mounted at `/auth`) exposes the ready-made flows: `POST
146
- /auth/login`, `/auth/register`, `/auth/refresh`, `/auth/logout`,
166
+ /auth/login`, `/auth/google`, `/auth/register`, `/auth/refresh`, `/auth/logout`,
147
167
  `/auth/change-password`, and `GET /auth/me`. Passwords are hashed/verified via
148
168
  `AuthUtility` (bcrypt); the write flows use the processor lifecycle.
149
169
 
170
+ **Google SSO** — the client posts a Google ID token as `{ id_token }` to
171
+ `POST /auth/google`. `GoogleAuthService` verifies it against Google's tokeninfo
172
+ endpoint (audience must match `GOOGLE_CLIENT_ID`, issuer `accounts.google.com`,
173
+ email verified), then `GoogleUserDataProcessor` reuses the matching user or
174
+ provisions a tenant user (no password — password login stays disabled until one
175
+ is set) and the standard `issueTokens()` flow returns the app's own token pair.
176
+
150
177
  ```ts
151
178
  @Controller('reports')
152
179
  export class ReportController {
@@ -230,6 +257,11 @@ export class WelcomeEmailJob extends CommonJob {
230
257
  `CommonJob` has **no `onModuleInit`**. At boot `QueueService` uses Nest's
231
258
  `DiscoveryService` to find every `CommonJob` provider and register its handler
232
259
  by identifier — so simply providing the job is enough; nothing self-registers.
260
+ Registration is guarded: two different classes claiming the same identifier
261
+ abort boot (no silent shadowing), and the discovered registry is validated
262
+ against `baseJobMaps` (merged with `QueueModule.register({ jobMaps })` for app
263
+ jobs) — a mapped job with no provider fails fast at startup instead of
264
+ surfacing as a consume-time "not registered" error on the worker.
233
265
 
234
266
  For database-event jobs (dispatched by a `CommonSubscriber`), `CommonJob`
235
267
  exposes `isNewRecord(event)`, `isColumnUpdated(event, col)` and
@@ -241,7 +273,16 @@ on the plain event object after the queue round-trip).
241
273
  processes every job — routing each to the handler registered under
242
274
  `job.data.job` — tracks queue stats via worker lifecycle events
243
275
  (`active`/`completed`/`failed`), and persists _final_ failures (after
244
- `MAX_JOB_RETRIES` attempts) to `sys_failed_jobs`. API instances leave
276
+ `MAX_JOB_RETRIES` attempts) to `sys_failed_jobs`. The worker is created with
277
+ `autorun: false` and only starts consuming on `onApplicationBootstrap`, after
278
+ the job registry is populated and validated — so a restart against a non-empty
279
+ queue can never pick up jobs before their handlers exist. Failed chained
280
+ dispatches (`nextQueuePayload` links) are persisted to `sys_failed_jobs` too.
281
+ Anything in that table can be re-dispatched with
282
+ `queueService.retryFailedJob(id)` (records are stamped once re-dispatched;
283
+ pass `force` to repeat) — this is also the recovery path for
284
+ duplicate-prevented jobs, which are removed from Redis on failure and are
285
+ therefore invisible to `retryJob(jobId)`. API instances leave
245
286
  `BULL_QUEUE_WORKER=false` so no worker spawns; they can still `dispatch()`.
246
287
 
247
288
  #### Scheduled events
@@ -254,7 +295,8 @@ loop. Two tables back it:
254
295
  | `sys_scheduled_tasks` | `SystemScheduledTaskEntity` | a schedule definition (`identifier`, `name`, `start_from`, `schedule_interval`, `enabled`, `next_upsert_at`) |
255
296
  | `sys_scheduled_events` | `ScheduledEventEntity` | one row per fire time (`job_id` → task, `scheduled_at`, `started_at`, `failed_at`, `total_attempts`, `active`); one task → many events |
256
297
 
257
- When the worker emits `ready`, `JobConsumer` enrols `SetScheduledEventJob` as a
298
+ At application bootstrap (once the job registry is populated), `JobConsumer`
299
+ enrols `SetScheduledEventJob` as a
258
300
  recurring scheduler whose cadence comes from `SCHEDULED_EVENT_{HOUR,MIN,SEC}`
259
301
  (summed to an interval; all-zero disables it). BullMQ keys the scheduler on the
260
302
  job identifier, so re-running on each boot **upserts** rather than duplicates.
@@ -328,6 +370,17 @@ into that job's `handle()`.
328
370
  | --------------------- | ----------- | ----------------------------------- |
329
371
  | `API_KEY_HEADER_NAME` | `x-api-key` | Request header carrying the API key |
330
372
 
373
+ ### Email Notifications (AWS SES)
374
+
375
+ Credentials resolve through the standard AWS SDK chain. When
376
+ `AWS_SES_FROM_ADDRESS` is unset, dispatch is skipped with a warning — safe by
377
+ default in development.
378
+
379
+ | Variable | Default | Description |
380
+ | ---------------------- | ----------- | ------------------------------------------- |
381
+ | `AWS_SES_FROM_ADDRESS` | _(empty)_ | Sender address; empty skips actual dispatch |
382
+ | `AWS_SES_REGION` | `us-east-1` | SES region (falls back to `AWS_REGION`) |
383
+
331
384
  ### Push Notifications (FCM)
332
385
 
333
386
  Requires the optional peer dependency `firebase-admin`. Provide **one** of:
@@ -359,6 +412,105 @@ async upload(@UploadedFiles() files: Express.Multer.File[], @CurrentUser() user:
359
412
  Set `DOCUMENT_STORAGE_DRIVER=minio` for a local MinIO bucket (auto-created) or
360
413
  `DOCUMENT_STORAGE_DRIVER=s3` for Amazon S3. Switching drivers requires no code change.
361
414
 
415
+ ## Raw SQL (SqlService)
416
+
417
+ `SqlService` (global via `CommonModule`) runs raw SQL with named `:key`
418
+ placeholders that are **always bound as driver parameters** — never
419
+ interpolated. `:...key` expands an array for `IN` clauses; a placeholder without
420
+ a matching param key throws `OperationException` before anything reaches the
421
+ database. Queries run on the read replica by default (`DB_READ_*` env), falling
422
+ back transparently to the primary when no replica is configured; use
423
+ `executeSqlOnPrimary()` for writes or lag-sensitive reads.
424
+
425
+ ```ts
426
+ const users = await this.sqlService.executeSql<{ id: string; name: string }>(
427
+ `select u.id, u.full_name name from sys_users u
428
+ where u.business_id = :business_id and u.status in (:...statuses)`,
429
+ { business_id: 42, statuses: ['active', 'invited'] },
430
+ );
431
+ ```
432
+
433
+ Helpers: `fetchOne`, `fetchScalar`, `fetchColumn`, `exists`, `count`,
434
+ `paginate(sql, params, { page, limit })` (bound LIMIT/OFFSET + parallel count →
435
+ `{ data, total, page, limit, total_pages }`), and `ping`.
436
+
437
+ ## Outbound HTTP (RemoteRequestService)
438
+
439
+ A thin injectable `fetch` wrapper (global via `CommonModule`). Requests are
440
+ described by one options object; `getApiResponse<T>()` returns the parsed body
441
+ (JSON or text) and throws `OperationException` on non-2xx, while
442
+ `getApiRawResponse()` hands back the raw `Response`. Bodies are serialised per
443
+ `ApiContentTypeEnum` (JSON or form-urlencoded); `GET` never sends a body.
444
+
445
+ ```ts
446
+ const data = await this.remoteRequestService.getApiResponse<MyDto>({
447
+ url: 'https://api.example.com/v1/items',
448
+ requestType: ApiRequestTypeEnum.PATCH,
449
+ body: { active: true },
450
+ headers: { Authorization: `Bearer ${token}` },
451
+ });
452
+ ```
453
+
454
+ ## Templated Email (AWS SES)
455
+
456
+ `ProcessEmailNotification` sends transactional email from templates registered
457
+ in `sys_email_templates`: the body is rendered by a remote template service
458
+ (base URL from the `email.template.base.url` system property, called through
459
+ `RemoteRequestService`), then dispatched via one SES `SendEmailCommand` per
460
+ recipient entry (to / cc / bcc). Outside production every send is logged to
461
+ `sys_email_notification_logs` first, and when `AWS_SES_FROM_ADDRESS` is unset
462
+ dispatch is skipped entirely — so development stays send-free by default.
463
+
464
+ ```ts
465
+ await new ProcessEmailNotification(emailNotificationService).process({
466
+ identifier: 'user.welcome',
467
+ data: { name: 'Jane' }, // payload for the remote template renderer
468
+ emailRecepients: [{ recepient: 'jane@example.com', cc: ['ops@example.com'] }],
469
+ });
470
+ ```
471
+
472
+ ## List Endpoints & Filtering
473
+
474
+ List endpoints are `POST .../list` routes backed by `CommonListProcessor`: a
475
+ subclass declares a base query plus an **allowlist of filterable/sortable
476
+ columns**, and the request body (`CommonListFilterDto` — always `@Body`, never
477
+ query params) carries pagination, `filters` and `sorts`. Column names
478
+ never come from the caller, operators resolve through the `FILTER_OPERATORS`
479
+ registry, and every value is bound as a parameter — nothing is interpolated
480
+ into the SQL.
481
+
482
+ Each `FilterDto` is `{ identifier, operator, value, value_type }`. The raw
483
+ string `value` is cast per its declared `value_type` before binding:
484
+
485
+ | `value_type` | Cast |
486
+ | ------------ | ------------------------------------------------------------------------ |
487
+ | `str` | used as-is |
488
+ | `num` | `Number(value)` — rejects empty / non-numeric input |
489
+ | `date` | `new Date(value)` — rejects invalid dates |
490
+ | `bool` | case-insensitive `'true'` / `'false'` → real boolean, rejects everything else |
491
+ | `null` | bound as SQL `NULL` |
492
+
493
+ Operators: `eq`, `ne`, `in`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`,
494
+ `between`, `isNull`. Multi-value operators (`in`, `between`) take
495
+ comma-separated values and cast each part; `isNull` reads its value truthily
496
+ (`false`/`0`/empty → `IS NOT NULL`).
497
+
498
+ ```jsonc
499
+ {
500
+ "page": 1,
501
+ "limit": 10,
502
+ "filters": [
503
+ { "identifier": "tenant_name", "operator": "ilike", "value": "acme", "value_type": "str" },
504
+ { "identifier": "is_active", "operator": "eq", "value": "true", "value_type": "bool" },
505
+ { "identifier": "created_at", "operator": "between", "value": "2026-01-01, 2026-06-30", "value_type": "date" }
506
+ ],
507
+ "sorts": [{ "identifier": "created_at", "direction": "DESC" }]
508
+ }
509
+ ```
510
+
511
+ Invalid identifiers, operators, or values raise `OperationException` before any
512
+ SQL runs. Results come back as `{ records, total, page, limit, total_pages }`.
513
+
362
514
  ## Admin Module
363
515
 
364
516
  `AdminModule` is the back-office surface — tenant onboarding, client/admin user
@@ -388,13 +540,16 @@ processor.
388
540
 
389
541
  | Route | Min. role | Processor |
390
542
  | -------------------------------- | --------------- | ---------------------------- |
391
- | `POST /admin/tenants` | super admin | `TenantDataProcessor` |
392
- | `GET /admin/tenants` | admin | `TenantListProcessor` |
393
- | `POST /admin/users/clients` | super admin | `ClientUserDataProcessor` |
394
- | `POST /admin/users/clients/bulk` | super admin | `BulkUserDataProcessor` |
395
- | `POST /admin/users/admins` | admin | `AdminUserDataProcessor` |
396
- | `POST /admin/scheduled-tasks` | content manager | `ScheduledTaskDataProcessor` |
397
- | `GET /admin/scheduled-tasks` | content reader | `ScheduledTaskListProcessor` |
543
+ | `POST /admin/tenants` | super admin | `TenantDataProcessor` |
544
+ | `POST /admin/tenants/list` | admin | `TenantListProcessor` |
545
+ | `POST /admin/users/clients` | super admin | `ClientUserDataProcessor` |
546
+ | `POST /admin/users/clients/bulk` | super admin | `BulkUserDataProcessor` |
547
+ | `POST /admin/users/admins` | admin | `AdminUserDataProcessor` |
548
+ | `POST /admin/scheduled-tasks` | content manager | `ScheduledTaskDataProcessor` |
549
+ | `POST /admin/scheduled-tasks/list` | content reader | `ScheduledTaskListProcessor` |
550
+
551
+ List endpoints are `POST .../list` with the filter payload in the body (a
552
+ `CommonListFilterDto`) — see [List Endpoints & Filtering](#list-endpoints--filtering).
398
553
 
399
554
  ### Flow
400
555
 
@@ -8,6 +8,7 @@ import { ClientUserDataProcessor } from './libraries/client.user.data.processor'
8
8
  import { ScheduledTaskDataProcessor } from './libraries/scheduled.task.data.processor';
9
9
  import { ScheduledTaskListProcessor } from './libraries/scheduled.task.list.processor';
10
10
  import { TenantDataProcessor } from './libraries/tenant.data.processor';
11
+ import { TenantListProcessor } from './libraries/tenant.list.processor';
11
12
  import { ScheduleIntervalDto } from './dtos/add.scheduled.task.dto';
12
13
  import { AddTenantDto } from './dtos/add.tenant.dto';
13
14
  import { BaseUserDto } from './dtos/base.user.dto';
@@ -15,7 +16,7 @@ import { ScheduledTaskListDto } from './dtos/scheduled.task.list.dto';
15
16
  export declare const es6Classes: {
16
17
  controllers: (typeof AdminScheduledTaskController | typeof AdminTenantController | typeof AdminUserController)[];
17
18
  services: readonly [typeof AdminAccessService];
18
- libraries: (typeof ScheduledTaskDataProcessor | typeof ScheduledTaskListProcessor | typeof TenantDataProcessor | typeof ClientUserDataProcessor | typeof BulkUserDataProcessor | typeof AdminUserDataProcessor)[];
19
+ libraries: (typeof ScheduledTaskDataProcessor | typeof ScheduledTaskListProcessor | typeof TenantDataProcessor | typeof TenantListProcessor | typeof ClientUserDataProcessor | typeof BulkUserDataProcessor | typeof AdminUserDataProcessor)[];
19
20
  dtos: (typeof ScheduleIntervalDto | typeof ScheduledTaskListDto | typeof AddTenantDto | typeof BaseUserDto)[];
20
21
  jobs: any[];
21
22
  subscribers: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"es6.classes.d.ts","sourceRoot":"","sources":["../../src/admin/es6.classes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAKxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAGtE,eAAO,MAAM,UAAU;;;;;;;CAOtB,CAAC"}
1
+ {"version":3,"file":"es6.classes.d.ts","sourceRoot":"","sources":["../../src/admin/es6.classes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAIxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAGtE,eAAO,MAAM,UAAU;;;;;;;CAOtB,CAAC"}
@@ -1,6 +1,9 @@
1
1
  import { CommonListProcessor } from '../../common/libraries/common.list.processor';
2
+ import { SqlService } from '../../common/services/sql.service';
2
3
  import { ScheduledTaskListDto } from '../dtos/scheduled.task.list.dto';
3
4
  export declare class ScheduledTaskListProcessor extends CommonListProcessor {
5
+ protected readonly sqlService: SqlService;
6
+ constructor(sqlService: SqlService);
4
7
  process(payload: ScheduledTaskListDto): Promise<{
5
8
  records: import("../..").SqlRow[];
6
9
  total: number;
@@ -1 +1 @@
1
- {"version":3,"file":"scheduled.task.list.processor.d.ts","sourceRoot":"","sources":["../../../src/admin/libraries/scheduled.task.list.processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAGvE,qBAAa,0BAA2B,SAAQ,mBAAmB;IAC3D,OAAO,CAAC,OAAO,EAAE,oBAAoB;;;;;;;CAkB5C"}
1
+ {"version":3,"file":"scheduled.task.list.processor.d.ts","sourceRoot":"","sources":["../../../src/admin/libraries/scheduled.task.list.processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAGvE,qBAAa,0BAA2B,SAAQ,mBAAmB;IACrD,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU;gBAAtB,UAAU,EAAE,UAAU;IAG/C,OAAO,CAAC,OAAO,EAAE,oBAAoB;;;;;;;CAkB5C"}
@@ -4,6 +4,10 @@ exports.ScheduledTaskListProcessor = void 0;
4
4
  const sort_direction_enum_1 = require("../../common/enums/sort.direction.enum");
5
5
  const common_list_processor_1 = require("../../common/libraries/common.list.processor");
6
6
  class ScheduledTaskListProcessor extends common_list_processor_1.CommonListProcessor {
7
+ constructor(sqlService) {
8
+ super();
9
+ this.sqlService = sqlService;
10
+ }
7
11
  async process(payload) {
8
12
  this.config = {
9
13
  query: 'sys_scheduled_tasks task',
@@ -1 +1 @@
1
- {"version":3,"file":"scheduled.task.list.processor.js","sourceRoot":"","sources":["../../../src/admin/libraries/scheduled.task.list.processor.ts"],"names":[],"mappings":";;;AAAA,gFAAuE;AACvE,wFAAmF;AAInF,MAAa,0BAA2B,SAAQ,2CAAmB;IACjE,KAAK,CAAC,OAAO,CAAC,OAA6B;QACzC,IAAI,CAAC,MAAM,GAAG;YACZ,KAAK,EAAE,0BAA0B;YACjC,OAAO,EAAE;gBACP,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE;gBACpC,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE;gBACxC,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE;gBAC9C,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,qBAAqB,EAAE,UAAU,EAAE,gBAAgB,EAAE;gBAC5D,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;aACrD;YACD,WAAW,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,mCAAa,CAAC,IAAI,EAAE;SACpE,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF;AAnBD,gEAmBC"}
1
+ {"version":3,"file":"scheduled.task.list.processor.js","sourceRoot":"","sources":["../../../src/admin/libraries/scheduled.task.list.processor.ts"],"names":[],"mappings":";;;AAAA,gFAAuE;AACvE,wFAAmF;AAKnF,MAAa,0BAA2B,SAAQ,2CAAmB;IACjE,YAA+B,UAAsB;QACnD,KAAK,EAAE,CAAC;QADqB,eAAU,GAAV,UAAU,CAAY;IAErD,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,OAA6B;QACzC,IAAI,CAAC,MAAM,GAAG;YACZ,KAAK,EAAE,0BAA0B;YACjC,OAAO,EAAE;gBACP,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE;gBACpC,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE;gBACxC,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE;gBAC9C,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,qBAAqB,EAAE,UAAU,EAAE,gBAAgB,EAAE;gBAC5D,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;gBACpD,EAAE,GAAG,EAAE,iBAAiB,EAAE,UAAU,EAAE,YAAY,EAAE;aACrD;YACD,WAAW,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,mCAAa,CAAC,IAAI,EAAE;SACpE,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF;AAtBD,gEAsBC"}
@@ -1,6 +1,9 @@
1
1
  import { CommonListProcessor } from '../../common/libraries/common.list.processor';
2
+ import { SqlService } from '../../common/services/sql.service';
2
3
  import { TenantListDto } from '../dtos/tenant.list.dto';
3
4
  export declare class TenantListProcessor extends CommonListProcessor {
5
+ protected readonly sqlService: SqlService;
6
+ constructor(sqlService: SqlService);
4
7
  process(payload: TenantListDto): Promise<{
5
8
  records: import("../..").SqlRow[];
6
9
  total: number;
@@ -1 +1 @@
1
- {"version":3,"file":"tenant.list.processor.d.ts","sourceRoot":"","sources":["../../../src/admin/libraries/tenant.list.processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,qBAAa,mBAAoB,SAAQ,mBAAmB;IACpD,OAAO,CAAC,OAAO,EAAE,aAAa;;;;;;;CAsBrC"}
1
+ {"version":3,"file":"tenant.list.processor.d.ts","sourceRoot":"","sources":["../../../src/admin/libraries/tenant.list.processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC9C,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU;gBAAtB,UAAU,EAAE,UAAU;IAG/C,OAAO,CAAC,OAAO,EAAE,aAAa;;;;;;;CAsBrC"}
@@ -4,6 +4,10 @@ exports.TenantListProcessor = void 0;
4
4
  const sort_direction_enum_1 = require("../../common/enums/sort.direction.enum");
5
5
  const common_list_processor_1 = require("../../common/libraries/common.list.processor");
6
6
  class TenantListProcessor extends common_list_processor_1.CommonListProcessor {
7
+ constructor(sqlService) {
8
+ super();
9
+ this.sqlService = sqlService;
10
+ }
7
11
  async process(payload) {
8
12
  this.config = {
9
13
  query: 'sys_tenants t',
@@ -1 +1 @@
1
- {"version":3,"file":"tenant.list.processor.js","sourceRoot":"","sources":["../../../src/admin/libraries/tenant.list.processor.ts"],"names":[],"mappings":";;;AAAA,gFAAuE;AACvE,wFAAmF;AAInF,MAAa,mBAAoB,SAAQ,2CAAmB;IAC1D,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,IAAI,CAAC,MAAM,GAAG;YACZ,KAAK,EAAE,eAAe;YACtB,OAAO,EAAE;gBACP,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;gBACjC,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE;gBACrC,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE;gBACrC,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;gBACzC,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE;gBAC/C,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE;gBAC3C,EAAE,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC7C,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE;gBAC/C,EAAE,GAAG,EAAE,gBAAgB,EAAE,UAAU,EAAE,cAAc,EAAE;gBACrD,EAAE,GAAG,EAAE,uBAAuB,EAAE,UAAU,EAAE,qBAAqB,EAAE;gBACnE,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE;gBACjD,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE;aAClD;YACD,WAAW,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,mCAAa,CAAC,IAAI,EAAE;SACpE,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF;AAvBD,kDAuBC"}
1
+ {"version":3,"file":"tenant.list.processor.js","sourceRoot":"","sources":["../../../src/admin/libraries/tenant.list.processor.ts"],"names":[],"mappings":";;;AAAA,gFAAuE;AACvE,wFAAmF;AAKnF,MAAa,mBAAoB,SAAQ,2CAAmB;IAC1D,YAA+B,UAAsB;QACnD,KAAK,EAAE,CAAC;QADqB,eAAU,GAAV,UAAU,CAAY;IAErD,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,IAAI,CAAC,MAAM,GAAG;YACZ,KAAK,EAAE,eAAe;YACtB,OAAO,EAAE;gBACP,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;gBACjC,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE;gBACrC,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE;gBACrC,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;gBACzC,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE;gBAC/C,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE;gBAC3C,EAAE,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC7C,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE;gBAC/C,EAAE,GAAG,EAAE,gBAAgB,EAAE,UAAU,EAAE,cAAc,EAAE;gBACrD,EAAE,GAAG,EAAE,uBAAuB,EAAE,UAAU,EAAE,qBAAqB,EAAE;gBACnE,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE;gBACjD,EAAE,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE;aAClD;YACD,WAAW,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,mCAAa,CAAC,IAAI,EAAE;SACpE,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF;AA1BD,kDA0BC"}
@@ -16,7 +16,7 @@ const queue_service_1 = require("../../common/services/queue.service");
16
16
  const job_constants_1 = require("../../constants/job.constants");
17
17
  let SystemRefreshTokenJob = class SystemRefreshTokenJob extends common_job_1.CommonJob {
18
18
  constructor(queueService) {
19
- super(job_constants_1.baseJobMap.systemRefreshTokenJob);
19
+ super(job_constants_1.baseJobConstants.systemRefreshTokenJob);
20
20
  this.queueService = queueService;
21
21
  }
22
22
  async handle(_data) {
@@ -1 +1 @@
1
- {"version":3,"file":"system.refresh.token.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/system.refresh.token.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAA2D;AAGpD,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,sBAAS;IAClD,YAA+B,YAA0B;QACvD,KAAK,CAAC,0BAAU,CAAC,qBAAqB,CAAC,CAAC;QADX,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,qBAAqB,CAQjC"}
1
+ {"version":3,"file":"system.refresh.token.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/system.refresh.token.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAAiE;AAG1D,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,sBAAS;IAClD,YAA+B,YAA0B;QACvD,KAAK,CAAC,gCAAgB,CAAC,qBAAqB,CAAC,CAAC;QADjB,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,qBAAqB,CAQjC"}
@@ -16,7 +16,7 @@ const queue_service_1 = require("../../common/services/queue.service");
16
16
  const job_constants_1 = require("../../constants/job.constants");
17
17
  let SystemRoleJob = class SystemRoleJob extends common_job_1.CommonJob {
18
18
  constructor(queueService) {
19
- super(job_constants_1.baseJobMap.systemRoleJob);
19
+ super(job_constants_1.baseJobConstants.systemRoleJob);
20
20
  this.queueService = queueService;
21
21
  }
22
22
  async handle(_data) {
@@ -1 +1 @@
1
- {"version":3,"file":"system.role.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/system.role.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAA2D;AAGpD,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,sBAAS;IAC1C,YAA+B,YAA0B;QACvD,KAAK,CAAC,0BAAU,CAAC,aAAa,CAAC,CAAC;QADH,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,aAAa,CAQzB"}
1
+ {"version":3,"file":"system.role.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/system.role.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAAiE;AAG1D,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,sBAAS;IAC1C,YAA+B,YAA0B;QACvD,KAAK,CAAC,gCAAgB,CAAC,aAAa,CAAC,CAAC;QADT,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,aAAa,CAQzB"}
@@ -16,7 +16,7 @@ const queue_service_1 = require("../../common/services/queue.service");
16
16
  const job_constants_1 = require("../../constants/job.constants");
17
17
  let UserDetailsJob = class UserDetailsJob extends common_job_1.CommonJob {
18
18
  constructor(queueService) {
19
- super(job_constants_1.baseJobMap.userDetailsJob);
19
+ super(job_constants_1.baseJobConstants.userDetailsJob);
20
20
  this.queueService = queueService;
21
21
  }
22
22
  async handle(_data) {
@@ -1 +1 @@
1
- {"version":3,"file":"user.details.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.details.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAA2D;AAGpD,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,sBAAS;IAC3C,YAA+B,YAA0B;QACvD,KAAK,CAAC,0BAAU,CAAC,cAAc,CAAC,CAAC;QADJ,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,cAAc,CAQ1B"}
1
+ {"version":3,"file":"user.details.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.details.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAAiE;AAG1D,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,sBAAS;IAC3C,YAA+B,YAA0B;QACvD,KAAK,CAAC,gCAAgB,CAAC,cAAc,CAAC,CAAC;QADV,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,cAAc,CAQ1B"}
@@ -16,7 +16,7 @@ const queue_service_1 = require("../../common/services/queue.service");
16
16
  const job_constants_1 = require("../../constants/job.constants");
17
17
  let UserJob = class UserJob extends common_job_1.CommonJob {
18
18
  constructor(queueService) {
19
- super(job_constants_1.baseJobMap.userJob);
19
+ super(job_constants_1.baseJobConstants.userJob);
20
20
  this.queueService = queueService;
21
21
  }
22
22
  async handle(_data) {
@@ -1 +1 @@
1
- {"version":3,"file":"user.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAA2D;AAGpD,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,sBAAS;IACpC,YAA+B,YAA0B;QACvD,KAAK,CAAC,0BAAU,CAAC,OAAO,CAAC,CAAC;QADG,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,0BAAO;kBAAP,OAAO;IADnB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,OAAO,CAQnB"}
1
+ {"version":3,"file":"user.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAAiE;AAG1D,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,sBAAS;IACpC,YAA+B,YAA0B;QACvD,KAAK,CAAC,gCAAgB,CAAC,OAAO,CAAC,CAAC;QADH,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,0BAAO;kBAAP,OAAO;IADnB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,OAAO,CAQnB"}
@@ -16,7 +16,7 @@ const queue_service_1 = require("../../common/services/queue.service");
16
16
  const job_constants_1 = require("../../constants/job.constants");
17
17
  let UserRoleJob = class UserRoleJob extends common_job_1.CommonJob {
18
18
  constructor(queueService) {
19
- super(job_constants_1.baseJobMap.userRoleJob);
19
+ super(job_constants_1.baseJobConstants.userRoleJob);
20
20
  this.queueService = queueService;
21
21
  }
22
22
  async handle(_data) {
@@ -1 +1 @@
1
- {"version":3,"file":"user.role.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.role.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAA2D;AAGpD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,sBAAS;IACxC,YAA+B,YAA0B;QACvD,KAAK,CAAC,0BAAU,CAAC,WAAW,CAAC,CAAC;QADD,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,WAAW,CAQvB"}
1
+ {"version":3,"file":"user.role.job.js","sourceRoot":"","sources":["../../../src/auth/jobs/user.role.job.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,uEAAmE;AACnE,iEAAiE;AAG1D,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,sBAAS;IACxC,YAA+B,YAA0B;QACvD,KAAK,CAAC,gCAAgB,CAAC,WAAW,CAAC,CAAC;QADP,iBAAY,GAAZ,YAAY,CAAc;IAEzD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAU;QACrB,OAAO;IACT,CAAC;CACF,CAAA;AARY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEkC,4BAAY;GAD9C,WAAW,CAQvB"}
@@ -1,10 +1,12 @@
1
1
  import { WorkerHost } from '@nestjs/bullmq';
2
+ import { OnApplicationBootstrap } from '@nestjs/common';
2
3
  import { Job } from 'bullmq';
3
4
  import { QueueService } from '../services/queue.service';
4
- export declare class JobConsumer extends WorkerHost {
5
+ export declare class JobConsumer extends WorkerHost implements OnApplicationBootstrap {
5
6
  private readonly queueService;
6
7
  private readonly logger;
7
8
  constructor(queueService: QueueService);
9
+ onApplicationBootstrap(): Promise<void>;
8
10
  process(job: Job): Promise<any>;
9
11
  private setWorker;
10
12
  private handleNextJob;
@@ -13,8 +15,10 @@ export declare class JobConsumer extends WorkerHost {
13
15
  onFailed(job: Job): Promise<void>;
14
16
  onPaused(): void;
15
17
  onResumed(): void;
16
- onReady(): Promise<void>;
18
+ onReady(): void;
19
+ private enrolScheduledEventDriver;
17
20
  private persistFinalFailure;
21
+ private persistFailure;
18
22
  private jobName;
19
23
  }
20
24
  //# sourceMappingURL=job.consumer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"job.consumer.d.ts","sourceRoot":"","sources":["../../../src/common/consumers/job.consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAiBzD,qBAGa,WAAY,SAAQ,UAAU;IAG7B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;gBAEvB,YAAY,EAAE,YAAY;IAIjD,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAsBrC,OAAO,CAAC,SAAS;YAKH,aAAa;IAmB3B,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAMxB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAUrB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,QAAQ,IAAI,IAAI;IAKhB,SAAS,IAAI,IAAI;IASX,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YA4BhB,mBAAmB;IAuBjC,OAAO,CAAC,OAAO;CAKhB"}
1
+ {"version":3,"file":"job.consumer.d.ts","sourceRoot":"","sources":["../../../src/common/consumers/job.consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAU,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAyBzD,qBAIa,WAAY,SAAQ,UAAW,YAAW,sBAAsB;IAG/D,OAAO,CAAC,QAAQ,CAAC,YAAY;IAFzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;gBAEvB,YAAY,EAAE,YAAY;IASjD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAavC,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAsBrC,OAAO,CAAC,SAAS;YAUH,aAAa;IAmC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAMxB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAUrB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,QAAQ,IAAI,IAAI;IAKhB,SAAS,IAAI,IAAI;IAKjB,OAAO,IAAI,IAAI;YAUD,yBAAyB;YAyBzB,mBAAmB;YAanB,cAAc;IAqB5B,OAAO,CAAC,OAAO;CAKhB"}
@@ -25,6 +25,14 @@ let JobConsumer = class JobConsumer extends bullmq_1.WorkerHost {
25
25
  this.queueService = queueService;
26
26
  this.logger = new common_1.Logger('JobConsumer');
27
27
  }
28
+ async onApplicationBootstrap() {
29
+ this.setWorker();
30
+ await this.enrolScheduledEventDriver();
31
+ if (!this.worker.isRunning())
32
+ this.worker.run().catch((error) => {
33
+ this.logger.error(`Worker run loop ended with error: ${error instanceof Error ? error.message : String(error)}`);
34
+ });
35
+ }
28
36
  async process(job) {
29
37
  this.setWorker();
30
38
  const data = job.data;
@@ -45,11 +53,25 @@ let JobConsumer = class JobConsumer extends bullmq_1.WorkerHost {
45
53
  }
46
54
  async handleNextJob(items) {
47
55
  for (const item of items) {
56
+ const identifier = item.job ?? item.hash;
48
57
  try {
49
- await this.queueService.addJob(item.job ?? item.hash, item.payload, item.options || {});
58
+ await this.queueService.addJob(identifier, item.payload, item.options || {});
50
59
  }
51
60
  catch (error) {
52
- this.logger.error(`Failed to chain next job (${item.job ?? item.hash}): ${error instanceof Error ? error.message : String(error)}`);
61
+ this.logger.error(`Failed to chain next job (${identifier}): ${error instanceof Error ? error.message : String(error)}`);
62
+ await this.persistFailure({
63
+ job_identifier: identifier ?? '',
64
+ job_name: identifier ?? 'chained job',
65
+ payload: JSON.stringify({
66
+ job: identifier,
67
+ payload: item.payload,
68
+ name: identifier,
69
+ props: item.options?.props || {},
70
+ nextQueuePayload: item.options?.nextQueuePayload || [],
71
+ }),
72
+ reason: `Chained dispatch failed: ${error instanceof Error ? error.message : String(error)}`,
73
+ stack_trace: error instanceof Error ? (error.stack ?? '') : '',
74
+ });
53
75
  }
54
76
  }
55
77
  }
@@ -74,33 +96,40 @@ let JobConsumer = class JobConsumer extends bullmq_1.WorkerHost {
74
96
  onResumed() {
75
97
  this.logger.debug('worker resumed');
76
98
  }
77
- async onReady() {
99
+ onReady() {
78
100
  this.setWorker();
79
101
  this.logger.log(`Worker ready for queue: ${this.queueService.queueName}`);
102
+ }
103
+ async enrolScheduledEventDriver() {
80
104
  const every = schedule_utility_1.ScheduleUtility.intervalToMs(schedule_utility_1.ScheduleUtility.envInterval());
81
105
  if (every <= 0) {
82
106
  this.logger.warn('SetScheduledEventJob not scheduled: SCHEDULED_EVENT_{HOUR,MIN,SEC} resolve to 0ms.');
83
107
  return;
84
108
  }
85
- if (!this.queueService.hasJob(job_constants_1.baseJobMap.setScheduledEventJob)) {
109
+ if (!this.queueService.hasJob(job_constants_1.baseJobConstants.setScheduledEventJob)) {
86
110
  this.logger.warn('SetScheduledEventJob is not registered as a provider; skipping schedule.');
87
111
  return;
88
112
  }
89
- await this.queueService.scheduleRecurring(job_constants_1.baseJobMap.setScheduledEventJob, every);
113
+ await this.queueService.scheduleRecurring(job_constants_1.baseJobConstants.setScheduledEventJob, every);
90
114
  this.logger.log(`SetScheduledEventJob enrolled to run every ${every}ms.`);
91
115
  }
92
116
  async persistFinalFailure(job) {
93
117
  const maxAttempts = job?.opts?.attempts ?? 1;
94
118
  if ((job?.attemptsMade ?? 0) < maxAttempts)
95
119
  return;
120
+ await this.persistFailure({
121
+ job_identifier: job.data?.job ?? '',
122
+ job_name: job.data?.name ?? job.name,
123
+ payload: JSON.stringify(job.data ?? {}),
124
+ reason: job.failedReason ?? '',
125
+ stack_trace: (job.stacktrace ?? []).join('\n'),
126
+ });
127
+ }
128
+ async persistFailure(record) {
96
129
  try {
97
130
  await system_failed_job_entity_1.SystemFailedJobEntity.create({
98
- job_identifier: job.data?.job ?? '',
99
- job_name: job.data?.name ?? job.name,
131
+ ...record,
100
132
  queue_name: this.queueService.queueName,
101
- payload: JSON.stringify(job.data ?? {}),
102
- reason: job.failedReason ?? '',
103
- stack_trace: (job.stacktrace ?? []).join('\n'),
104
133
  attributes: {},
105
134
  }).save();
106
135
  }
@@ -148,11 +177,12 @@ __decorate([
148
177
  (0, bullmq_1.OnWorkerEvent)('ready'),
149
178
  __metadata("design:type", Function),
150
179
  __metadata("design:paramtypes", []),
151
- __metadata("design:returntype", Promise)
180
+ __metadata("design:returntype", void 0)
152
181
  ], JobConsumer.prototype, "onReady", null);
153
182
  exports.JobConsumer = JobConsumer = __decorate([
154
183
  (0, bullmq_1.Processor)(process.env.BULL_QUEUE_NAME || queue_enum_1.QueueDefault.NAME, {
155
184
  concurrency: parseInt(process.env.BULL_WORKERS, 10) || 2,
185
+ autorun: false,
156
186
  }),
157
187
  __metadata("design:paramtypes", [queue_service_1.QueueService])
158
188
  ], JobConsumer);