@kaikybrofc/omnizap-system 2.2.3 → 2.2.5

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 (55) hide show
  1. package/.env.example +13 -0
  2. package/README.md +29 -85
  3. package/app/controllers/messageController.js +133 -1
  4. package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
  5. package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
  6. package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
  7. package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
  8. package/app/modules/stickerPackModule/catalogRouter.js +79 -0
  9. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
  10. package/app/modules/stickerPackModule/domainEvents.js +61 -0
  11. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
  12. package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
  13. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
  14. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
  15. package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
  16. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
  17. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  18. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +1090 -659
  19. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +19 -1
  20. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
  21. package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
  22. package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
  23. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
  24. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
  25. package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
  26. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
  27. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
  28. package/app/observability/metrics.js +169 -0
  29. package/app/services/featureFlagService.js +137 -0
  30. package/app/services/lidMapService.js +4 -1
  31. package/app/services/whatsappLoginLinkService.js +232 -0
  32. package/database/index.js +5 -0
  33. package/database/migrations/20260228_0021_sticker_web_google_owner_phone.sql +83 -0
  34. package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
  35. package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
  36. package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
  37. package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
  38. package/database/migrations/20260228_0026_feature_flags.sql +21 -0
  39. package/ecosystem.prod.config.cjs +70 -9
  40. package/index.js +26 -0
  41. package/package.json +5 -1
  42. package/public/index.html +128 -10
  43. package/public/js/apps/createPackApp.js +59 -272
  44. package/public/js/apps/homeApp.js +106 -0
  45. package/public/js/apps/loginApp.js +459 -0
  46. package/public/js/apps/stickersApp.js +34 -37
  47. package/public/js/apps/userApp.js +244 -0
  48. package/public/js/runtime/react-runtime.js +1 -0
  49. package/public/login/index.html +333 -0
  50. package/public/stickers/create/index.html +2 -1
  51. package/public/stickers/index.html +2 -1
  52. package/public/user/index.html +367 -0
  53. package/scripts/cache-bust.mjs +65 -11
  54. package/scripts/sticker-catalog-loadtest.mjs +208 -0
  55. package/scripts/sticker-worker-task.mjs +122 -0
@@ -0,0 +1,238 @@
1
+ import logger from '../../utils/logger/loggerModule.js';
2
+ import { setQueueDepth } from '../../observability/metrics.js';
3
+ import { isFeatureEnabled } from '../../services/featureFlagService.js';
4
+ import { runStickerClassificationCycle } from './stickerClassificationBackgroundRuntime.js';
5
+ import { runStickerAutoPackByTagsCycle } from './stickerAutoPackByTagsRuntime.js';
6
+ import {
7
+ claimWorkerTask,
8
+ completeWorkerTask,
9
+ countWorkerTasksByStatus,
10
+ failWorkerTask,
11
+ } from './stickerWorkerTaskQueueRepository.js';
12
+
13
+ const parseEnvBool = (value, fallback) => {
14
+ if (value === undefined || value === null || value === '') return fallback;
15
+ const normalized = String(value).trim().toLowerCase();
16
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
17
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
18
+ return fallback;
19
+ };
20
+
21
+ const clampInt = (value, fallback, min, max) => {
22
+ const numeric = Number(value);
23
+ if (!Number.isFinite(numeric)) return fallback;
24
+ return Math.max(min, Math.min(max, Math.floor(numeric)));
25
+ };
26
+
27
+ const DEDICATED_WORKERS_ENABLED = parseEnvBool(process.env.STICKER_DEDICATED_WORKERS_ENABLED, true);
28
+ const DEDICATED_WORKERS_FORCE_ENABLED = parseEnvBool(process.env.STICKER_DEDICATED_WORKERS_FORCE_ENABLED, false);
29
+ const DEDICATED_WORKER_RETRY_DELAY_SECONDS = clampInt(
30
+ process.env.STICKER_DEDICATED_WORKER_RETRY_DELAY_SECONDS,
31
+ 60,
32
+ 5,
33
+ 3600,
34
+ );
35
+ const DEDICATED_WORKER_POLL_INTERVAL_MS = clampInt(
36
+ process.env.STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS,
37
+ 2500,
38
+ 250,
39
+ 60_000,
40
+ );
41
+ const DEDICATED_WORKER_MAX_TASKS_PER_TICK = clampInt(
42
+ process.env.STICKER_DEDICATED_WORKER_MAX_TASKS_PER_TICK,
43
+ 1,
44
+ 1,
45
+ 25,
46
+ );
47
+ const DEDICATED_WORKER_COHORT_KEY =
48
+ String(process.env.STICKER_DEDICATED_WORKER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim()
49
+ || 'worker';
50
+
51
+ const SUPPORTED_TASK_TYPES = new Set([
52
+ 'classification_cycle',
53
+ 'curation_cycle',
54
+ 'rebuild_cycle',
55
+ ]);
56
+
57
+ const normalizeTaskType = (value) => {
58
+ const normalized = String(value || '').trim().toLowerCase();
59
+ return SUPPORTED_TASK_TYPES.has(normalized) ? normalized : null;
60
+ };
61
+
62
+ const runTaskHandler = async (taskType, payload = {}) => {
63
+ if (taskType === 'classification_cycle') {
64
+ return runStickerClassificationCycle({
65
+ processPending: true,
66
+ processReprocess: true,
67
+ processDeterministic: true,
68
+ ...payload,
69
+ });
70
+ }
71
+ if (taskType === 'curation_cycle') {
72
+ return runStickerAutoPackByTagsCycle({
73
+ enableAdditions: true,
74
+ enableRebuild: false,
75
+ ...payload,
76
+ });
77
+ }
78
+ if (taskType === 'rebuild_cycle') {
79
+ return runStickerAutoPackByTagsCycle({
80
+ enableAdditions: false,
81
+ enableRebuild: true,
82
+ ...payload,
83
+ });
84
+ }
85
+ throw new Error(`unsupported_task_type:${taskType}`);
86
+ };
87
+
88
+ const refreshQueueDepthMetrics = async () => {
89
+ const [pending, processing, failed] = await Promise.all([
90
+ countWorkerTasksByStatus('pending'),
91
+ countWorkerTasksByStatus('processing'),
92
+ countWorkerTasksByStatus('failed'),
93
+ ]);
94
+ setQueueDepth('sticker_worker_tasks_pending', pending);
95
+ setQueueDepth('sticker_worker_tasks_processing', processing);
96
+ setQueueDepth('sticker_worker_tasks_failed', failed);
97
+ };
98
+
99
+ const canRunDedicatedWorkers = async (taskType) => {
100
+ if (!DEDICATED_WORKERS_ENABLED) return false;
101
+ if (DEDICATED_WORKERS_FORCE_ENABLED) return true;
102
+ return isFeatureEnabled('enable_worker_dedicated_processes', {
103
+ fallback: false,
104
+ subjectKey: `worker:${taskType}:${DEDICATED_WORKER_COHORT_KEY}`,
105
+ });
106
+ };
107
+
108
+ export const isSupportedStickerWorkerTaskType = (taskType) => Boolean(normalizeTaskType(taskType));
109
+
110
+ export const runDedicatedStickerWorkerTick = async (
111
+ {
112
+ taskType,
113
+ maxTasks = DEDICATED_WORKER_MAX_TASKS_PER_TICK,
114
+ retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS,
115
+ } = {},
116
+ ) => {
117
+ const normalizedTaskType = normalizeTaskType(taskType);
118
+ if (!normalizedTaskType) {
119
+ return { executed: false, reason: 'invalid_task_type', task_type: taskType || null };
120
+ }
121
+
122
+ const enabled = await canRunDedicatedWorkers(normalizedTaskType);
123
+ if (!enabled) {
124
+ return { executed: false, reason: 'feature_disabled', task_type: normalizedTaskType };
125
+ }
126
+
127
+ const safeMaxTasks = clampInt(maxTasks, DEDICATED_WORKER_MAX_TASKS_PER_TICK, 1, 25);
128
+ const stats = {
129
+ executed: true,
130
+ task_type: normalizedTaskType,
131
+ claimed: 0,
132
+ completed: 0,
133
+ failed: 0,
134
+ };
135
+
136
+ for (let i = 0; i < safeMaxTasks; i += 1) {
137
+ const task = await claimWorkerTask({ taskType: normalizedTaskType });
138
+ if (!task) break;
139
+
140
+ stats.claimed += 1;
141
+
142
+ try {
143
+ await runTaskHandler(normalizedTaskType, task.payload || {});
144
+ await completeWorkerTask(task.id);
145
+ stats.completed += 1;
146
+ } catch (error) {
147
+ stats.failed += 1;
148
+ await failWorkerTask(task.id, {
149
+ error: error?.message || 'dedicated_worker_task_failed',
150
+ retryDelaySeconds,
151
+ });
152
+
153
+ logger.warn('Task falhou no worker dedicado.', {
154
+ action: 'sticker_dedicated_worker_task_failed',
155
+ task_type: normalizedTaskType,
156
+ task_id: task.id,
157
+ attempts: task.attempts,
158
+ error: error?.message,
159
+ });
160
+ }
161
+ }
162
+
163
+ if (stats.claimed > 0) {
164
+ await refreshQueueDepthMetrics().catch(() => null);
165
+ }
166
+
167
+ return stats;
168
+ };
169
+
170
+ export const startDedicatedStickerWorker = ({
171
+ taskType,
172
+ pollIntervalMs = DEDICATED_WORKER_POLL_INTERVAL_MS,
173
+ maxTasksPerTick = DEDICATED_WORKER_MAX_TASKS_PER_TICK,
174
+ retryDelaySeconds = DEDICATED_WORKER_RETRY_DELAY_SECONDS,
175
+ label = '',
176
+ } = {}) => {
177
+ const normalizedTaskType = normalizeTaskType(taskType);
178
+ if (!normalizedTaskType) {
179
+ throw new Error(`invalid_task_type:${taskType}`);
180
+ }
181
+
182
+ const safePollIntervalMs = clampInt(pollIntervalMs, DEDICATED_WORKER_POLL_INTERVAL_MS, 250, 60_000);
183
+ let tickInFlight = false;
184
+ let stopped = false;
185
+
186
+ const runTick = async () => {
187
+ if (stopped || tickInFlight) return;
188
+ tickInFlight = true;
189
+ try {
190
+ await runDedicatedStickerWorkerTick({
191
+ taskType: normalizedTaskType,
192
+ maxTasks: maxTasksPerTick,
193
+ retryDelaySeconds,
194
+ });
195
+ } catch (error) {
196
+ if (error?.code !== 'ER_NO_SUCH_TABLE') {
197
+ logger.error('Falha no worker dedicado de sticker.', {
198
+ action: 'sticker_dedicated_worker_tick_failed',
199
+ task_type: normalizedTaskType,
200
+ error: error?.message,
201
+ });
202
+ }
203
+ } finally {
204
+ tickInFlight = false;
205
+ }
206
+ };
207
+
208
+ const intervalHandle = setInterval(() => {
209
+ void runTick();
210
+ }, safePollIntervalMs);
211
+ if (typeof intervalHandle?.unref === 'function') {
212
+ intervalHandle.unref();
213
+ }
214
+
215
+ void runTick();
216
+
217
+ logger.info('Worker dedicado de sticker iniciado.', {
218
+ action: 'sticker_dedicated_worker_started',
219
+ task_type: normalizedTaskType,
220
+ poll_interval_ms: safePollIntervalMs,
221
+ max_tasks_per_tick: maxTasksPerTick,
222
+ label: label || null,
223
+ });
224
+
225
+ return {
226
+ taskType: normalizedTaskType,
227
+ stop: () => {
228
+ if (stopped) return;
229
+ stopped = true;
230
+ clearInterval(intervalHandle);
231
+ logger.info('Worker dedicado de sticker encerrado.', {
232
+ action: 'sticker_dedicated_worker_stopped',
233
+ task_type: normalizedTaskType,
234
+ label: label || null,
235
+ });
236
+ },
237
+ };
238
+ };
@@ -0,0 +1,71 @@
1
+ import logger from '../../utils/logger/loggerModule.js';
2
+ import { isFeatureEnabled } from '../../services/featureFlagService.js';
3
+ import { enqueueDomainEvent } from './domainEventOutboxRepository.js';
4
+
5
+ const resolveDefaultIdempotencyKey = ({
6
+ eventType,
7
+ aggregateType,
8
+ aggregateId,
9
+ payload = null,
10
+ }) => {
11
+ const payloadKey = payload && typeof payload === 'object' ? JSON.stringify(payload).slice(0, 80) : '';
12
+ return `${eventType}:${aggregateType}:${aggregateId}:${payloadKey}`.slice(0, 180);
13
+ };
14
+
15
+ export const publishStickerDomainEvent = async (
16
+ eventPayload,
17
+ { connection = null, force = false } = {},
18
+ ) => {
19
+ const eventType = String(eventPayload?.eventType || '').trim();
20
+ const aggregateType = String(eventPayload?.aggregateType || '').trim();
21
+ const aggregateId = String(eventPayload?.aggregateId || '').trim();
22
+
23
+ if (!eventType || !aggregateType || !aggregateId) return false;
24
+
25
+ const enabled = force ? true : await isFeatureEnabled('enable_domain_event_outbox', {
26
+ fallback: true,
27
+ subjectKey: `${aggregateType}:${aggregateId}`,
28
+ });
29
+ if (!enabled) return false;
30
+
31
+ try {
32
+ const idempotencyKey =
33
+ String(eventPayload?.idempotencyKey || '').trim()
34
+ || resolveDefaultIdempotencyKey({
35
+ eventType,
36
+ aggregateType,
37
+ aggregateId,
38
+ payload: eventPayload?.payload || null,
39
+ });
40
+
41
+ return await enqueueDomainEvent(
42
+ {
43
+ eventType,
44
+ aggregateType,
45
+ aggregateId,
46
+ payload: eventPayload?.payload || null,
47
+ priority: eventPayload?.priority ?? 50,
48
+ availableAt: eventPayload?.availableAt || null,
49
+ maxAttempts: eventPayload?.maxAttempts ?? 10,
50
+ idempotencyKey,
51
+ },
52
+ connection,
53
+ );
54
+ } catch (error) {
55
+ if (error?.code === 'ER_NO_SUCH_TABLE') {
56
+ logger.warn('Outbox indisponível; evento de domínio descartado.', {
57
+ action: 'sticker_domain_event_outbox_unavailable',
58
+ event_type: eventType,
59
+ });
60
+ return false;
61
+ }
62
+ logger.warn('Falha ao publicar evento de domínio.', {
63
+ action: 'sticker_domain_event_publish_failed',
64
+ event_type: eventType,
65
+ aggregate_type: aggregateType,
66
+ aggregate_id: aggregateId,
67
+ error: error?.message,
68
+ });
69
+ return false;
70
+ }
71
+ };
@@ -0,0 +1,198 @@
1
+ import logger from '../../utils/logger/loggerModule.js';
2
+ import { setQueueDepth } from '../../observability/metrics.js';
3
+ import { isFeatureEnabled } from '../../services/featureFlagService.js';
4
+ import {
5
+ claimDomainEvent,
6
+ completeDomainEvent,
7
+ countDomainEventsByStatus,
8
+ failDomainEvent,
9
+ } from './domainEventOutboxRepository.js';
10
+ import { STICKER_DOMAIN_EVENTS } from './domainEvents.js';
11
+ import { enqueueWorkerTask } from './stickerWorkerTaskQueueRepository.js';
12
+ import { enqueuePackScoreSnapshotRefresh } from './stickerPackScoreSnapshotRuntime.js';
13
+ import { listPackIdsByStickerId } from './stickerPackItemRepository.js';
14
+
15
+ const parseEnvBool = (value, fallback) => {
16
+ if (value === undefined || value === null || value === '') return fallback;
17
+ const normalized = String(value).trim().toLowerCase();
18
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
19
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
20
+ return fallback;
21
+ };
22
+
23
+ const CONSUMER_ENABLED = parseEnvBool(process.env.STICKER_DOMAIN_EVENT_CONSUMER_ENABLED, true);
24
+ const STARTUP_DELAY_MS = Math.max(
25
+ 1_000,
26
+ Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_STARTUP_DELAY_MS) || 8_000,
27
+ );
28
+ const POLLER_INTERVAL_MS = Math.max(
29
+ 1_000,
30
+ Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_POLLER_INTERVAL_MS) || 2_000,
31
+ );
32
+ const RETRY_DELAY_SECONDS = Math.max(
33
+ 5,
34
+ Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_RETRY_DELAY_SECONDS) || 45),
35
+ );
36
+ const CONSUMER_COHORT_KEY =
37
+ String(process.env.STICKER_DOMAIN_EVENT_CONSUMER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim()
38
+ || 'consumer';
39
+
40
+ let startupHandle = null;
41
+ let pollerHandle = null;
42
+ let running = false;
43
+
44
+ const refreshOutboxDepthMetrics = async () => {
45
+ const [pending, processing, failed] = await Promise.all([
46
+ countDomainEventsByStatus('pending'),
47
+ countDomainEventsByStatus('processing'),
48
+ countDomainEventsByStatus('failed'),
49
+ ]);
50
+ setQueueDepth('domain_event_outbox_pending', pending);
51
+ setQueueDepth('domain_event_outbox_processing', processing);
52
+ setQueueDepth('domain_event_outbox_failed', failed);
53
+ };
54
+
55
+ const canRunConsumer = async () =>
56
+ isFeatureEnabled('enable_domain_event_outbox', {
57
+ fallback: true,
58
+ subjectKey: `domain_event_consumer:${CONSUMER_COHORT_KEY}`,
59
+ });
60
+
61
+ const enqueueTaskSafely = async ({ taskType, payload, priority, idempotencyKey }) => {
62
+ await enqueueWorkerTask({
63
+ taskType,
64
+ payload,
65
+ priority,
66
+ idempotencyKey,
67
+ });
68
+ };
69
+
70
+ const handleDomainEvent = async (event) => {
71
+ const eventType = String(event?.event_type || '').trim().toUpperCase();
72
+ const aggregateId = String(event?.aggregate_id || '').trim();
73
+ const payload = event?.payload && typeof event.payload === 'object' ? event.payload : {};
74
+
75
+ if (eventType === STICKER_DOMAIN_EVENTS.STICKER_ASSET_CREATED) {
76
+ await enqueueTaskSafely({
77
+ taskType: 'classification_cycle',
78
+ payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId },
79
+ priority: 80,
80
+ idempotencyKey: `evt:${event.id}:classification_cycle`,
81
+ });
82
+ return;
83
+ }
84
+
85
+ if (eventType === STICKER_DOMAIN_EVENTS.STICKER_CLASSIFIED) {
86
+ const assetId = String(payload?.asset_id || aggregateId || '').trim();
87
+ const relatedPackIds = assetId ? await listPackIdsByStickerId(assetId).catch(() => []) : [];
88
+ if (relatedPackIds.length) {
89
+ enqueuePackScoreSnapshotRefresh(relatedPackIds);
90
+ }
91
+ await enqueueTaskSafely({
92
+ taskType: 'curation_cycle',
93
+ payload: {
94
+ reason: 'domain_event',
95
+ event_type: eventType,
96
+ aggregate_id: aggregateId,
97
+ related_pack_ids: relatedPackIds,
98
+ },
99
+ priority: 65,
100
+ idempotencyKey: `evt:${event.id}:curation_cycle`,
101
+ });
102
+ return;
103
+ }
104
+
105
+ if (eventType === STICKER_DOMAIN_EVENTS.PACK_UPDATED) {
106
+ const packId = String(payload?.pack_id || aggregateId || '').trim();
107
+ if (packId) {
108
+ enqueuePackScoreSnapshotRefresh([packId]);
109
+ }
110
+ await enqueueTaskSafely({
111
+ taskType: 'rebuild_cycle',
112
+ payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null },
113
+ priority: 60,
114
+ idempotencyKey: `evt:${event.id}:rebuild_cycle`,
115
+ });
116
+ return;
117
+ }
118
+
119
+ if (eventType === STICKER_DOMAIN_EVENTS.ENGAGEMENT_RECORDED) {
120
+ const packId = String(payload?.pack_id || aggregateId || '').trim();
121
+ if (packId) {
122
+ enqueuePackScoreSnapshotRefresh([packId]);
123
+ }
124
+ return;
125
+ }
126
+ };
127
+
128
+ const pollOnce = async () => {
129
+ if (running || !CONSUMER_ENABLED) return;
130
+ running = true;
131
+ try {
132
+ if (!(await canRunConsumer())) return;
133
+
134
+ const event = await claimDomainEvent();
135
+ if (!event) {
136
+ await refreshOutboxDepthMetrics();
137
+ return;
138
+ }
139
+
140
+ try {
141
+ await handleDomainEvent(event);
142
+ await completeDomainEvent(event.id);
143
+ } catch (error) {
144
+ await failDomainEvent(
145
+ event.id,
146
+ {
147
+ error: error?.message || 'domain_event_consumer_failed',
148
+ retryDelaySeconds: RETRY_DELAY_SECONDS,
149
+ },
150
+ );
151
+ logger.warn('Evento de domínio falhou no consumidor interno.', {
152
+ action: 'sticker_domain_event_consumer_event_failed',
153
+ event_id: event.id,
154
+ event_type: event.event_type,
155
+ aggregate_type: event.aggregate_type,
156
+ aggregate_id: event.aggregate_id,
157
+ error: error?.message,
158
+ });
159
+ }
160
+
161
+ await refreshOutboxDepthMetrics();
162
+ } catch (error) {
163
+ if (error?.code !== 'ER_NO_SUCH_TABLE') {
164
+ logger.error('Falha no poller do consumidor de eventos de domínio.', {
165
+ action: 'sticker_domain_event_consumer_poll_failed',
166
+ error: error?.message,
167
+ });
168
+ }
169
+ } finally {
170
+ running = false;
171
+ }
172
+ };
173
+
174
+ export const startStickerDomainEventConsumer = () => {
175
+ if (!CONSUMER_ENABLED) return;
176
+ if (startupHandle || pollerHandle) return;
177
+
178
+ startupHandle = setTimeout(() => {
179
+ startupHandle = null;
180
+ void pollOnce();
181
+ pollerHandle = setInterval(() => {
182
+ void pollOnce();
183
+ }, POLLER_INTERVAL_MS);
184
+ if (typeof pollerHandle?.unref === 'function') pollerHandle.unref();
185
+ }, STARTUP_DELAY_MS);
186
+ if (typeof startupHandle?.unref === 'function') startupHandle.unref();
187
+ };
188
+
189
+ export const stopStickerDomainEventConsumer = () => {
190
+ if (startupHandle) {
191
+ clearTimeout(startupHandle);
192
+ startupHandle = null;
193
+ }
194
+ if (pollerHandle) {
195
+ clearInterval(pollerHandle);
196
+ pollerHandle = null;
197
+ }
198
+ };