@govish/shared-services 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ import { SharedServicesDependencies } from '../types/dependencies';
2
+ export declare class DeviceCacheService {
3
+ private redisClient;
4
+ private logger;
5
+ constructor(deps: SharedServicesDependencies);
6
+ /**
7
+ * Store individual device as hash with indexes
8
+ */
9
+ storeDevice(device: any): Promise<void>;
10
+ /**
11
+ * Get device by ID - O(1) lookup
12
+ */
13
+ getDeviceById(id: number): Promise<any | null>;
14
+ /**
15
+ * Get device by device_id
16
+ */
17
+ getDeviceByDeviceId(device_id: string): Promise<any | null>;
18
+ /**
19
+ * Get device by serial number
20
+ */
21
+ getDeviceBySerialNumber(serial_number: string): Promise<any | null>;
22
+ /**
23
+ * Get multiple devices efficiently using pipeline
24
+ */
25
+ getDevicesByIds(ids: number[]): Promise<any[]>;
26
+ /**
27
+ * Query by police station
28
+ */
29
+ getDevicesByStation(stationId: number): Promise<any[]>;
30
+ /**
31
+ * Get active devices
32
+ */
33
+ getActiveDevices(): Promise<any[]>;
34
+ /**
35
+ * Get all devices from cache
36
+ */
37
+ getAllDevices(): Promise<any[]>;
38
+ /**
39
+ * Clear all device cache
40
+ */
41
+ clearCache(): Promise<void>;
42
+ /**
43
+ * Remove single device from cache
44
+ */
45
+ removeDevice(id: number): Promise<void>;
46
+ }
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DeviceCacheService = void 0;
13
+ class DeviceCacheService {
14
+ constructor(deps) {
15
+ this.redisClient = deps.redisClient;
16
+ this.logger = deps.logger;
17
+ }
18
+ /**
19
+ * Store individual device as hash with indexes
20
+ */
21
+ storeDevice(device) {
22
+ return __awaiter(this, void 0, void 0, function* () {
23
+ var _a, _b;
24
+ try {
25
+ if (!device || !device.id) {
26
+ throw new Error('Invalid device: missing id');
27
+ }
28
+ // Check if Redis is connected
29
+ if (!this.redisClient.isOpen) {
30
+ this.logger.warn('Redis client not connected, attempting to connect...');
31
+ yield this.redisClient.connect();
32
+ }
33
+ const key = `device:${device.id}`;
34
+ // Check if key exists and delete it if it's the wrong type (from old cache implementation)
35
+ try {
36
+ const keyType = yield this.redisClient.type(key);
37
+ if (keyType !== 'hash' && keyType !== 'none') {
38
+ this.logger.warn(`Deleting key ${key} with wrong type: ${keyType}`);
39
+ yield this.redisClient.del(key);
40
+ }
41
+ }
42
+ catch (typeError) {
43
+ // If type check fails, try to delete the key anyway
44
+ this.logger.warn(`Error checking key type for ${key}, attempting to delete`, {
45
+ error: typeError instanceof Error ? typeError.message : String(typeError)
46
+ });
47
+ try {
48
+ yield this.redisClient.del(key);
49
+ }
50
+ catch (delError) {
51
+ // Ignore delete errors, continue with store
52
+ }
53
+ }
54
+ // Helper function to safely convert dates
55
+ const safeDateToString = (date) => {
56
+ if (!date)
57
+ return '';
58
+ if (date instanceof Date) {
59
+ return isNaN(date.getTime()) ? '' : date.toISOString();
60
+ }
61
+ if (typeof date === 'string') {
62
+ return date;
63
+ }
64
+ return '';
65
+ };
66
+ // Prepare hash data - ensure all values are strings
67
+ const hashData = {
68
+ id: device.id.toString(),
69
+ device_id: (device.device_id || '').toString(),
70
+ device_name: (device.device_name || '').toString(),
71
+ device_type: (device.device_type || '').toString(),
72
+ serial_number: (device.serial_number || '').toString(),
73
+ imei: (device.imei || '').toString(),
74
+ is_active: (((_a = device.is_active) === null || _a === void 0 ? void 0 : _a.toString()) || 'false').toString(),
75
+ policeStationId: (((_b = device.policeStationId) === null || _b === void 0 ? void 0 : _b.toString()) || '').toString(),
76
+ created_at: safeDateToString(device.created_at),
77
+ updated_at: safeDateToString(device.updated_at),
78
+ };
79
+ // Stringify nested objects safely
80
+ try {
81
+ hashData.policeStation = JSON.stringify(device.policeStation || null);
82
+ }
83
+ catch (jsonError) {
84
+ this.logger.warn('Failed to stringify policeStation', { deviceId: device.id });
85
+ hashData.policeStation = 'null';
86
+ }
87
+ // Store as hash with JSON-stringified complex fields
88
+ yield this.redisClient.hSet(key, hashData);
89
+ // Create indexes for common queries
90
+ // Helper to safely add to set (delete if wrong type first)
91
+ const safeSAdd = (setKey, value) => __awaiter(this, void 0, void 0, function* () {
92
+ try {
93
+ const keyType = yield this.redisClient.type(setKey);
94
+ if (keyType !== 'set' && keyType !== 'none') {
95
+ yield this.redisClient.del(setKey);
96
+ }
97
+ yield this.redisClient.sAdd(setKey, value);
98
+ }
99
+ catch (error) {
100
+ // If it fails, try to delete and retry
101
+ try {
102
+ yield this.redisClient.del(setKey);
103
+ yield this.redisClient.sAdd(setKey, value);
104
+ }
105
+ catch (retryError) {
106
+ this.logger.warn(`Failed to add to set ${setKey}`, {
107
+ error: retryError instanceof Error ? retryError.message : String(retryError)
108
+ });
109
+ }
110
+ }
111
+ });
112
+ // Helper to safely set string value (delete if wrong type first)
113
+ const safeSet = (key, value) => __awaiter(this, void 0, void 0, function* () {
114
+ try {
115
+ const keyType = yield this.redisClient.type(key);
116
+ if (keyType !== 'string' && keyType !== 'none') {
117
+ yield this.redisClient.del(key);
118
+ }
119
+ yield this.redisClient.set(key, value);
120
+ }
121
+ catch (error) {
122
+ // If it fails, try to delete and retry
123
+ try {
124
+ yield this.redisClient.del(key);
125
+ yield this.redisClient.set(key, value);
126
+ }
127
+ catch (retryError) {
128
+ this.logger.warn(`Failed to set key ${key}`, {
129
+ error: retryError instanceof Error ? retryError.message : String(retryError)
130
+ });
131
+ }
132
+ }
133
+ });
134
+ if (device.policeStationId) {
135
+ yield safeSAdd(`devices:station:${device.policeStationId}`, device.id.toString());
136
+ }
137
+ if (device.device_id) {
138
+ yield safeSet(`device:by_device_id:${device.device_id}`, device.id.toString());
139
+ }
140
+ if (device.serial_number) {
141
+ yield safeSet(`device:by_serial:${device.serial_number}`, device.id.toString());
142
+ }
143
+ if (device.is_active) {
144
+ yield safeSAdd('devices:active', device.id.toString());
145
+ }
146
+ else {
147
+ yield safeSAdd('devices:inactive', device.id.toString());
148
+ }
149
+ // Maintain list of all device IDs
150
+ yield safeSAdd('devices:all', device.id.toString());
151
+ }
152
+ catch (error) {
153
+ const errorMessage = error instanceof Error ? error.message : String(error);
154
+ const errorStack = error instanceof Error ? error.stack : undefined;
155
+ const errorDetails = {
156
+ error: errorMessage,
157
+ errorStack: errorStack,
158
+ deviceId: device === null || device === void 0 ? void 0 : device.id,
159
+ deviceKeys: device ? Object.keys(device) : [],
160
+ device: device ? JSON.stringify(device, (key, value) => {
161
+ // Handle circular references and Date objects
162
+ if (value instanceof Date) {
163
+ return value.toISOString();
164
+ }
165
+ if (typeof value === 'object' && value !== null) {
166
+ try {
167
+ return value;
168
+ }
169
+ catch (e) {
170
+ return '[Circular]';
171
+ }
172
+ }
173
+ return value;
174
+ }, 2) : 'No device data'
175
+ };
176
+ // Log error (logger always logs, console only in dev)
177
+ this.logger.error('Error storing device in cache', errorDetails);
178
+ throw error;
179
+ }
180
+ });
181
+ }
182
+ /**
183
+ * Get device by ID - O(1) lookup
184
+ */
185
+ getDeviceById(id) {
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ try {
188
+ const data = yield this.redisClient.hGetAll(`device:${id}`);
189
+ if (!data || !data.id)
190
+ return null;
191
+ // Parse JSON fields
192
+ return Object.assign(Object.assign({}, data), { id: parseInt(data.id), policeStationId: data.policeStationId ? parseInt(data.policeStationId) : null, is_active: data.is_active === 'true', created_at: data.created_at ? new Date(data.created_at) : null, updated_at: data.updated_at ? new Date(data.updated_at) : null, policeStation: data.policeStation ? JSON.parse(data.policeStation) : null });
193
+ }
194
+ catch (error) {
195
+ this.logger.error('Error getting device from cache', {
196
+ error: error instanceof Error ? error.message : String(error),
197
+ deviceId: id
198
+ });
199
+ return null;
200
+ }
201
+ });
202
+ }
203
+ /**
204
+ * Get device by device_id
205
+ */
206
+ getDeviceByDeviceId(device_id) {
207
+ return __awaiter(this, void 0, void 0, function* () {
208
+ try {
209
+ const idStr = yield this.redisClient.get(`device:by_device_id:${device_id}`);
210
+ if (!idStr)
211
+ return null;
212
+ const id = parseInt(String(idStr));
213
+ return this.getDeviceById(id);
214
+ }
215
+ catch (error) {
216
+ this.logger.error('Error getting device by device_id from cache', {
217
+ error: error instanceof Error ? error.message : String(error),
218
+ device_id
219
+ });
220
+ return null;
221
+ }
222
+ });
223
+ }
224
+ /**
225
+ * Get device by serial number
226
+ */
227
+ getDeviceBySerialNumber(serial_number) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ try {
230
+ const idStr = yield this.redisClient.get(`device:by_serial:${serial_number}`);
231
+ if (!idStr)
232
+ return null;
233
+ const id = parseInt(String(idStr));
234
+ return this.getDeviceById(id);
235
+ }
236
+ catch (error) {
237
+ this.logger.error('Error getting device by serial from cache', {
238
+ error: error instanceof Error ? error.message : String(error),
239
+ serial_number
240
+ });
241
+ return null;
242
+ }
243
+ });
244
+ }
245
+ /**
246
+ * Get multiple devices efficiently using pipeline
247
+ */
248
+ getDevicesByIds(ids) {
249
+ return __awaiter(this, void 0, void 0, function* () {
250
+ if (ids.length === 0)
251
+ return [];
252
+ try {
253
+ const pipeline = this.redisClient.multi();
254
+ ids.forEach(id => pipeline.hGetAll(`device:${id}`));
255
+ const results = yield pipeline.exec();
256
+ return results
257
+ .map((result) => {
258
+ if (result[0])
259
+ return null; // Skip errors
260
+ const data = result[1];
261
+ if (!data || !data.id)
262
+ return null;
263
+ return Object.assign(Object.assign({}, data), { id: parseInt(data.id), policeStationId: data.policeStationId ? parseInt(data.policeStationId) : null, is_active: data.is_active === 'true', created_at: data.created_at ? new Date(data.created_at) : null, updated_at: data.updated_at ? new Date(data.updated_at) : null, policeStation: data.policeStation ? JSON.parse(data.policeStation) : null });
264
+ })
265
+ .filter(Boolean);
266
+ }
267
+ catch (error) {
268
+ this.logger.error('Error getting devices from cache', {
269
+ error: error instanceof Error ? error.message : String(error)
270
+ });
271
+ return [];
272
+ }
273
+ });
274
+ }
275
+ /**
276
+ * Query by police station
277
+ */
278
+ getDevicesByStation(stationId) {
279
+ return __awaiter(this, void 0, void 0, function* () {
280
+ try {
281
+ const deviceIds = yield this.redisClient.sMembers(`devices:station:${stationId}`);
282
+ const ids = Array.isArray(deviceIds)
283
+ ? deviceIds.map((id) => parseInt(String(id)))
284
+ : [];
285
+ return this.getDevicesByIds(ids);
286
+ }
287
+ catch (error) {
288
+ this.logger.error('Error getting devices by station from cache', {
289
+ error: error instanceof Error ? error.message : String(error),
290
+ stationId
291
+ });
292
+ return [];
293
+ }
294
+ });
295
+ }
296
+ /**
297
+ * Get active devices
298
+ */
299
+ getActiveDevices() {
300
+ return __awaiter(this, void 0, void 0, function* () {
301
+ try {
302
+ const deviceIds = yield this.redisClient.sMembers('devices:active');
303
+ const ids = Array.isArray(deviceIds)
304
+ ? deviceIds.map((id) => parseInt(String(id)))
305
+ : [];
306
+ return this.getDevicesByIds(ids);
307
+ }
308
+ catch (error) {
309
+ this.logger.error('Error getting active devices from cache', {
310
+ error: error instanceof Error ? error.message : String(error)
311
+ });
312
+ return [];
313
+ }
314
+ });
315
+ }
316
+ /**
317
+ * Get all devices from cache
318
+ */
319
+ getAllDevices() {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ try {
322
+ const deviceIds = yield this.redisClient.sMembers('devices:all');
323
+ const ids = Array.isArray(deviceIds)
324
+ ? deviceIds.map((id) => parseInt(String(id)))
325
+ : [];
326
+ const devices = yield this.getDevicesByIds(ids);
327
+ // Sort by ID descending to match original behavior
328
+ return devices.sort((a, b) => b.id - a.id);
329
+ }
330
+ catch (error) {
331
+ this.logger.error('Error getting all devices from cache', {
332
+ error: error instanceof Error ? error.message : String(error)
333
+ });
334
+ return [];
335
+ }
336
+ });
337
+ }
338
+ /**
339
+ * Clear all device cache
340
+ */
341
+ clearCache() {
342
+ return __awaiter(this, void 0, void 0, function* () {
343
+ try {
344
+ // Delete old cache key if it exists (from previous implementation)
345
+ try {
346
+ yield this.redisClient.del('devices');
347
+ }
348
+ catch (e) {
349
+ // Ignore if key doesn't exist
350
+ }
351
+ const deviceIds = yield this.redisClient.sMembers('devices:all');
352
+ const ids = Array.isArray(deviceIds) ? deviceIds : [];
353
+ if (ids.length > 0) {
354
+ const pipeline = this.redisClient.multi();
355
+ ids.forEach((id) => {
356
+ const idStr = String(id);
357
+ pipeline.del(`device:${idStr}`);
358
+ });
359
+ yield pipeline.exec();
360
+ }
361
+ // Clear indexes
362
+ const keys = yield this.redisClient.keys('devices:*');
363
+ if (keys.length > 0) {
364
+ yield this.redisClient.del(keys);
365
+ }
366
+ // Clear lookup keys
367
+ const lookupKeys = yield this.redisClient.keys('device:by_*');
368
+ if (lookupKeys.length > 0) {
369
+ yield this.redisClient.del(lookupKeys);
370
+ }
371
+ // Clear individual device hash keys (in case they exist from previous attempts)
372
+ const deviceKeys = yield this.redisClient.keys('device:[0-9]*');
373
+ const keysArray = Array.isArray(deviceKeys) ? deviceKeys : [];
374
+ if (keysArray.length > 0) {
375
+ // Filter to only get device:ID pattern (not device:by_*)
376
+ const hashKeys = keysArray
377
+ .map((key) => typeof key === 'string' ? key : key.toString())
378
+ .filter((key) => /^device:\d+$/.test(key));
379
+ if (hashKeys.length > 0) {
380
+ yield this.redisClient.del(hashKeys);
381
+ }
382
+ }
383
+ }
384
+ catch (error) {
385
+ this.logger.error('Error clearing device cache', {
386
+ error: error instanceof Error ? error.message : String(error)
387
+ });
388
+ }
389
+ });
390
+ }
391
+ /**
392
+ * Remove single device from cache
393
+ */
394
+ removeDevice(id) {
395
+ return __awaiter(this, void 0, void 0, function* () {
396
+ try {
397
+ const device = yield this.getDeviceById(id);
398
+ if (device) {
399
+ // Remove from indexes
400
+ if (device.policeStationId) {
401
+ yield this.redisClient.sRem(`devices:station:${device.policeStationId}`, id.toString());
402
+ }
403
+ if (device.device_id) {
404
+ const deviceIdStr = typeof device.device_id === 'string' ? device.device_id : device.device_id.toString();
405
+ yield this.redisClient.del(`device:by_device_id:${deviceIdStr}`);
406
+ }
407
+ if (device.serial_number) {
408
+ const serialStr = typeof device.serial_number === 'string' ? device.serial_number : device.serial_number.toString();
409
+ yield this.redisClient.del(`device:by_serial:${serialStr}`);
410
+ }
411
+ if (device.is_active) {
412
+ yield this.redisClient.sRem('devices:active', id.toString());
413
+ }
414
+ else {
415
+ yield this.redisClient.sRem('devices:inactive', id.toString());
416
+ }
417
+ // Remove from all list
418
+ yield this.redisClient.sRem('devices:all', id.toString());
419
+ // Remove hash
420
+ yield this.redisClient.del(`device:${id}`);
421
+ }
422
+ }
423
+ catch (error) {
424
+ this.logger.error('Error removing device from cache', {
425
+ error: error instanceof Error ? error.message : String(error),
426
+ deviceId: id
427
+ });
428
+ }
429
+ });
430
+ }
431
+ }
432
+ exports.DeviceCacheService = DeviceCacheService;
@@ -0,0 +1,21 @@
1
+ import { SharedServicesDependencies } from '../types/dependencies';
2
+ /**
3
+ * Device Service - Handles device retrieval with cache and auth service fallback
4
+ *
5
+ * This service:
6
+ * 1. First tries Redis cache (via DeviceCacheService)
7
+ * 2. Falls back to auth service API if cache miss
8
+ * 3. Stores fetched device in cache for future use
9
+ */
10
+ export declare class DeviceService {
11
+ private deviceCacheService;
12
+ private logger;
13
+ private authHost?;
14
+ private authPort?;
15
+ constructor(deps: SharedServicesDependencies);
16
+ /**
17
+ * Get device by ID from Redis cache, fallback to auth service
18
+ * First tries Redis cache, then falls back to auth service API
19
+ */
20
+ getDeviceById(deviceId: string | number): Promise<any | null>;
21
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DeviceService = void 0;
13
+ const deviceCacheService_1 = require("./deviceCacheService");
14
+ /**
15
+ * Device Service - Handles device retrieval with cache and auth service fallback
16
+ *
17
+ * This service:
18
+ * 1. First tries Redis cache (via DeviceCacheService)
19
+ * 2. Falls back to auth service API if cache miss
20
+ * 3. Stores fetched device in cache for future use
21
+ */
22
+ class DeviceService {
23
+ constructor(deps) {
24
+ this.deviceCacheService = new deviceCacheService_1.DeviceCacheService(deps);
25
+ this.logger = deps.logger;
26
+ this.authHost = deps.authHost || process.env.AUTH_HOST;
27
+ this.authPort = deps.authPort || process.env.AUTH_PORT;
28
+ }
29
+ /**
30
+ * Get device by ID from Redis cache, fallback to auth service
31
+ * First tries Redis cache, then falls back to auth service API
32
+ */
33
+ getDeviceById(deviceId) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ try {
36
+ const deviceIdNum = typeof deviceId === 'string' ? parseInt(deviceId) : deviceId;
37
+ if (isNaN(deviceIdNum)) {
38
+ this.logger.warn('Invalid device ID', { deviceId });
39
+ return null;
40
+ }
41
+ // Try cache first
42
+ try {
43
+ const cachedDevice = yield this.deviceCacheService.getDeviceById(deviceIdNum);
44
+ if (cachedDevice) {
45
+ this.logger.debug(`Device ${deviceIdNum} retrieved from Redis cache`);
46
+ return cachedDevice;
47
+ }
48
+ }
49
+ catch (cacheError) {
50
+ this.logger.warn('Cache read error, falling back to auth service', {
51
+ error: cacheError instanceof Error ? cacheError.message : String(cacheError),
52
+ deviceId: deviceIdNum
53
+ });
54
+ }
55
+ // Fallback to auth service if host is configured
56
+ if (!this.authHost) {
57
+ this.logger.warn('Auth host not configured, cannot fetch device from auth service', {
58
+ deviceId: deviceIdNum
59
+ });
60
+ return null;
61
+ }
62
+ const authUrl = this.authPort
63
+ ? `http://${this.authHost}:${this.authPort}/api/v1/device/cache/${deviceIdNum}`
64
+ : `http://${this.authHost}/api/v1/device/cache/${deviceIdNum}`;
65
+ this.logger.debug(`Cache miss for device ${deviceIdNum}, fetching from auth service`);
66
+ const response = yield fetch(authUrl);
67
+ if (!response.ok) {
68
+ if (response.status === 404) {
69
+ this.logger.warn(`Device ${deviceIdNum} not found in auth service`);
70
+ return null;
71
+ }
72
+ throw new Error(`Failed to fetch device: ${response.status} ${response.statusText}`);
73
+ }
74
+ const result = yield response.json();
75
+ const device = result.data || result;
76
+ // Store in cache for future use
77
+ if (device && device.id) {
78
+ try {
79
+ yield this.deviceCacheService.storeDevice(device);
80
+ this.logger.debug(`Device ${deviceIdNum} stored in cache after fetch from auth service`);
81
+ }
82
+ catch (cacheStoreError) {
83
+ // Don't fail if cache store fails, just log
84
+ this.logger.warn('Failed to store device in cache after fetch', {
85
+ error: cacheStoreError instanceof Error ? cacheStoreError.message : String(cacheStoreError),
86
+ deviceId: deviceIdNum
87
+ });
88
+ }
89
+ }
90
+ this.logger.debug(`Device ${deviceIdNum} fetched from auth service`);
91
+ return device;
92
+ }
93
+ catch (error) {
94
+ this.logger.error('Error fetching device by ID', {
95
+ error: error instanceof Error ? error.message : 'Unknown error',
96
+ deviceId: deviceId.toString()
97
+ });
98
+ return null;
99
+ }
100
+ });
101
+ }
102
+ }
103
+ exports.DeviceService = DeviceService;
@@ -0,0 +1,50 @@
1
+ import { SharedServicesDependencies } from '../types/dependencies';
2
+ export declare class OfficerCacheService {
3
+ private redisClient;
4
+ private logger;
5
+ constructor(deps: SharedServicesDependencies);
6
+ /**
7
+ * Store individual officer as hash with indexes
8
+ */
9
+ storeOfficer(officer: any): Promise<void>;
10
+ /**
11
+ * Get officer by ID - O(1) lookup
12
+ */
13
+ getOfficerById(id: number): Promise<any | null>;
14
+ /**
15
+ * Get multiple officers efficiently using pipeline
16
+ */
17
+ getOfficersByIds(ids: number[]): Promise<any[]>;
18
+ /**
19
+ * Query by station
20
+ */
21
+ getOfficersByStation(stationId: number): Promise<any[]>;
22
+ /**
23
+ * Query by rank
24
+ */
25
+ getOfficersByRank(rankId: number): Promise<any[]>;
26
+ /**
27
+ * Query by role
28
+ */
29
+ getOfficersByRole(roleId: number): Promise<any[]>;
30
+ /**
31
+ * Get all officers from cache
32
+ */
33
+ getAllOfficers(): Promise<any[]>;
34
+ /**
35
+ * Get officer by service_number
36
+ */
37
+ getOfficerByServiceNumber(service_number: string): Promise<any | null>;
38
+ /**
39
+ * Cache all officers from auth service
40
+ */
41
+ cacheAllOfficers(): Promise<any[]>;
42
+ /**
43
+ * Clear all officer cache
44
+ */
45
+ clearCache(): Promise<void>;
46
+ /**
47
+ * Remove single officer from cache
48
+ */
49
+ removeOfficer(id: number): Promise<void>;
50
+ }