@@ -5,11 +5,14 @@ import {
5
5
} from 'node:assert/strict'
6
6
import omit from 'lodash/omit.js'
7
7
import maxBy from 'lodash/maxBy.js'
8
+ import {
9
+ PREFIX as REDIS_PREFIX ,
10
+ } from './caching.js'
8
11
import {
9
12
mergeButPreferNonNull ,
10
13
unixTimestampFromIso8601 ,
11
14
} from './util.js'
12
- import { createCache } from './caching .js'
15
+ import { connectToRedis } from './redis .js'
13
16
14
17
// The logic in this file uses three sources of information:
15
18
// 1. In almost all cases, a REF-AUS SollFahrt is sent ahead of time.
@@ -20,9 +23,14 @@ import {createCache} from './caching.js'
20
23
21
24
// ---
22
25
26
+ const VDV_STORAGE_TTL = process . env . VDV_STORAGE_TTL
27
+ ? parseInt ( process . env . VDV_STORAGE_TTL ) * 1000
28
+ : 32 * 60 * 60 * 1000 // 32 hours
29
+
23
30
const KIND_SOLLFAHRT = Symbol ( 'REF-AUS SollFahrt' )
24
31
const KIND_ISTFAHRT = Symbol ( 'AUS IstFahrt' )
25
32
33
+ const STORAGE_KEY_PREFIX = REDIS_PREFIX + 'vdv:'
26
34
const STORAGE_KEY_REF_AUS_SOLLFAHRT = 'ref_aus_soll'
27
35
const STORAGE_KEY_AUS_ISTFAHRT_KOMPLETTFAHRT = 'aus_komplett'
28
36
const STORAGE_KEY_AUS_ISTFAHRT_PARTIAL = 'aus_partial'
@@ -214,9 +222,12 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
214
222
logger,
215
223
} = cfg
216
224
217
- const storage = await createCache ( {
218
- prefix : 'vdv:' ,
219
- } )
225
+ const storage = await connectToRedis ( )
226
+ // As of [email protected] , it doesn't support the HSETEX command, so we patch our client.
227
+ // https://redis.io/docs/latest/commands/?group=hash
228
+ if ( typeof storage . hsetex !== 'function' ) {
229
+ storage . addBuiltinCommand ( 'hsetex' )
230
+ }
220
231
221
232
const _storeVdvFahrt = async ( vdvFahrt , kind ) => {
222
233
if ( kind !== KIND_SOLLFAHRT && kind !== KIND_ISTFAHRT ) {
@@ -231,10 +242,11 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
231
242
if ( ! fahrtId ) {
232
243
return null
233
244
}
245
+ const storageKey = STORAGE_KEY_PREFIX + fahrtId
234
246
235
247
const isKomplettfahrt = vdvFahrt . Komplettfahrt === 'true'
236
248
if ( kind === KIND_ISTFAHRT && ! isKomplettfahrt ) {
237
- const entries = halts . map ( ( istHalt , i ) => {
249
+ const fieldsArgs = halts . flatMap ( ( istHalt , i ) => {
238
250
ok ( istHalt . HaltID , `vdvFahrt.${ haltsKey } [${ i } ].HaltID must not be missing` )
239
251
let depOrArrPrefix = null
240
252
let depOrArr = null
@@ -255,32 +267,41 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
255
267
256
268
// todo: what if there are >1 IstHalts stopping at the same HaltID within the same minute?
257
269
// todo: what if an IstHalt is first seen only with an Ankunftszeit and later with an Abfahrtszeit? β it will be stored & read twice!
258
- const storageKey = [
259
- fahrtId ,
270
+ const hashField = [
260
271
STORAGE_KEY_AUS_ISTFAHRT_PARTIAL ,
261
272
istHalt . HaltID ,
262
273
depOrArrPrefix ,
263
274
depOrArr ,
264
275
] . join ( ':' )
265
276
return [
266
- storageKey ,
267
- {
277
+ hashField ,
278
+ JSON . stringify ( {
268
279
...istHalt ,
269
280
IstFahrt : sparseIstFahrt ,
270
- } ,
281
+ } ) ,
271
282
]
272
283
} )
273
- await storage . putMany ( entries )
284
+
285
+ // todo: support Valkey once they have a "Hash set + expiration" command
286
+ await storage . hsetex (
287
+ storageKey , // key of Redis Hash
288
+ 'PX' , VDV_STORAGE_TTL , // expiration time in milliseconds
289
+ 'FIELDS' , fieldsArgs . length / 2 , // number of Hash fields to set
290
+ ...fieldsArgs ,
291
+ )
274
292
} else {
275
- const storageKeySuffix = kind === KIND_SOLLFAHRT
293
+ const field = kind === KIND_SOLLFAHRT
276
294
? STORAGE_KEY_REF_AUS_SOLLFAHRT
277
295
: STORAGE_KEY_AUS_ISTFAHRT_KOMPLETTFAHRT
278
- const storageKey = [
279
- fahrtId ,
280
- storageKeySuffix ,
281
- ] . join ( ':' )
282
296
283
- await storage . put ( storageKey , vdvFahrt )
297
+ // todo: support Valkey once they have a "Hash set + expiration" command
298
+ await storage . hsetex (
299
+ storageKey , // key of Redis Hash
300
+ 'PX' , VDV_STORAGE_TTL , // expiration time in milliseconds
301
+ 'FIELDS' , 1 , // number of Hash fields to set
302
+ field ,
303
+ JSON . stringify ( vdvFahrt ) , // value
304
+ )
284
305
}
285
306
}
286
307
const storeRefAusSollFahrt = async ( sollFahrt ) => {
@@ -300,7 +321,7 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
300
321
if ( ! fahrtId ) {
301
322
return [ ]
302
323
}
303
- const storageKeyPrefix = fahrtId + ':'
324
+ const storageKey = STORAGE_KEY_PREFIX + fahrtId
304
325
305
326
const res = {
306
327
refAusSollFahrt : null ,
@@ -309,8 +330,10 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
309
330
}
310
331
311
332
// load from storage
312
- for ( const [ key , item ] of await storage . getMany ( storageKeyPrefix ) ) {
313
- const [ kindPart ] = key . slice ( storageKeyPrefix . length ) . split ( ':' )
333
+ const hash = await storage . hgetall ( storageKey )
334
+ for ( const [ key , val ] of Object . entries ( hash ) ) {
335
+ const [ kindPart ] = key . split ( ':' )
336
+ const item = JSON . parse ( val )
314
337
if ( kindPart === STORAGE_KEY_REF_AUS_SOLLFAHRT ) {
315
338
res . refAusSollFahrt = item
316
339
} else if ( kindPart === STORAGE_KEY_AUS_ISTFAHRT_KOMPLETTFAHRT ) {
@@ -430,7 +453,7 @@ const createMergeVdvFahrtWithRefAusSollFahrtAndAusIstFahrts = async (cfg) => {
430
453
}
431
454
432
455
const stop = async ( ) => {
433
- await storage . stop ( )
456
+ await storage . quit ( )
434
457
}
435
458
436
459
const res = {
0 commit comments