@live-change/timer-service 0.9.43 → 0.9.44
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.
- package/config.js +12 -0
- package/definition.js +8 -0
- package/index.js +4 -416
- package/loop.js +242 -0
- package/model.js +113 -0
- package/package.json +3 -3
package/config.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
queueDuration = 1 * 60 * 1000,
|
|
8
|
+
} = definition.config
|
|
9
|
+
|
|
10
|
+
const loadMoreAfter = Math.floor(queueDuration / 2)
|
|
11
|
+
|
|
12
|
+
export { queueDuration, loadMoreAfter }
|
package/definition.js
ADDED
package/index.js
CHANGED
|
@@ -1,302 +1,11 @@
|
|
|
1
1
|
import App from '@live-change/framework'
|
|
2
2
|
const app = App.app()
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
name: "timer"
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
let queueDuration = 1 * 60 * 1000
|
|
9
|
-
let loadMoreAfter = Math.floor(queueDuration / 2)
|
|
10
|
-
|
|
11
|
-
let timersQueue = []
|
|
12
|
-
let timersById = new Map()
|
|
13
|
-
let timersLoopStarted = false
|
|
14
|
-
let timersLoopTimeout = false
|
|
15
|
-
let lastLoadTime = 0
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const Timer = definition.model({
|
|
19
|
-
name: "Timer",
|
|
20
|
-
properties: {
|
|
21
|
-
timestamp: {
|
|
22
|
-
type: Number
|
|
23
|
-
},
|
|
24
|
-
time: {
|
|
25
|
-
type: Date,
|
|
26
|
-
},
|
|
27
|
-
loops: {
|
|
28
|
-
type: Number
|
|
29
|
-
},
|
|
30
|
-
interval: {
|
|
31
|
-
type: Number
|
|
32
|
-
},
|
|
33
|
-
maxRetries: {
|
|
34
|
-
type: Number
|
|
35
|
-
},
|
|
36
|
-
retryDelay: {
|
|
37
|
-
type: Number
|
|
38
|
-
},
|
|
39
|
-
retries: {
|
|
40
|
-
type: Number
|
|
41
|
-
},
|
|
42
|
-
service: {
|
|
43
|
-
type: String
|
|
44
|
-
},
|
|
45
|
-
command: {
|
|
46
|
-
type: Object
|
|
47
|
-
},
|
|
48
|
-
trigger: {
|
|
49
|
-
type: Object
|
|
50
|
-
},
|
|
51
|
-
origin: {
|
|
52
|
-
type: Object
|
|
53
|
-
},
|
|
54
|
-
causeType: {
|
|
55
|
-
type: String
|
|
56
|
-
},
|
|
57
|
-
cause: {
|
|
58
|
-
type: String
|
|
59
|
-
},
|
|
60
|
-
createdAt: {
|
|
61
|
-
type: Date,
|
|
62
|
-
default: () => new Date()
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
indexes: {
|
|
66
|
-
timestamp: {
|
|
67
|
-
property: 'timestamp',
|
|
68
|
-
function: async function(input, output) {
|
|
69
|
-
const mapper = (obj) => ({ id: (''+obj.timestamp).padStart(16, '0') + '_' + obj.id, to: obj.id })
|
|
70
|
-
await input.table('timer_Timer').onChange(
|
|
71
|
-
(obj, oldObj) => output.change(obj && mapper(obj), oldObj && mapper(oldObj))
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
function fireTimer(timer) {
|
|
80
|
-
runTimerAction(timer).catch(error => {
|
|
81
|
-
console.error("TIMER ACTION ERROR", error)
|
|
82
|
-
let timestamp = Date.now() + timer.retryDelay
|
|
83
|
-
timer.retries ++
|
|
84
|
-
if(timer.retries > timer.maxRetries) {
|
|
85
|
-
app.emitEvents("timer", [{
|
|
86
|
-
type: "timerFinished",
|
|
87
|
-
timer: timer.id,
|
|
88
|
-
error
|
|
89
|
-
}], { ...(timer.origin || {}), through: 'timer' })
|
|
90
|
-
timersById.delete(timer.id)
|
|
91
|
-
} else { // Retry
|
|
92
|
-
app.emitEvents("timer", [{
|
|
93
|
-
type: "timerFailed",
|
|
94
|
-
timer: timer.id,
|
|
95
|
-
error, timestamp,
|
|
96
|
-
retries: timer.retries
|
|
97
|
-
}], { ...(timer.origin || {}), through: 'timer' })
|
|
98
|
-
timer.timestamp = timestamp
|
|
99
|
-
insertTimer(timer)
|
|
100
|
-
}
|
|
101
|
-
}).then(done => {
|
|
102
|
-
console.error("TIMER ACTION FINISHED", done)
|
|
103
|
-
timer.loops --
|
|
104
|
-
if(timer.loops < 0) {
|
|
105
|
-
console.log("TIMER FINISHED")
|
|
106
|
-
app.emitEvents("timer",[{
|
|
107
|
-
type: "timerFinished",
|
|
108
|
-
timer: timer.id
|
|
109
|
-
}], { ...(timer.origin || {}), through: 'timer' })
|
|
110
|
-
timersById.delete(timer.id)
|
|
111
|
-
} else {
|
|
112
|
-
let timestamp = timer.timestamp + timer.interval
|
|
113
|
-
console.log("TIMER FIRED")
|
|
114
|
-
app.emitEvents("timer",[{
|
|
115
|
-
type: "timerFired",
|
|
116
|
-
timer: timer.id,
|
|
117
|
-
timestamp,
|
|
118
|
-
loops: timer.loops
|
|
119
|
-
}], { ...(timer.origin || {}), through: 'timer' })
|
|
120
|
-
timer.timestamp = timestamp
|
|
121
|
-
insertTimer(timer)
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function timersLoop() {
|
|
127
|
-
//console.log("TL", timersQueue.length, timersQueue[0] && (timersQueue[0].timestamp - Date.now()))
|
|
128
|
-
timersLoopTimeout = false
|
|
129
|
-
if(timersQueue.length === 0) {
|
|
130
|
-
timersLoopStarted = false
|
|
131
|
-
setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
let nextTs = timersQueue[0].timestamp
|
|
135
|
-
let now = Date.now()
|
|
136
|
-
//console.log("NEXT TS", nextTs, '<', now, '-->', nextTs < now, "INQ", timersQueue)
|
|
137
|
-
while(nextTs < now) {
|
|
138
|
-
fireTimer(timersQueue.shift())
|
|
139
|
-
if(timersQueue.length === 0) {
|
|
140
|
-
timersLoopStarted = false
|
|
141
|
-
setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
nextTs = timersQueue[0].timestamp
|
|
145
|
-
}
|
|
146
|
-
let delay = nextTs - Date.now()
|
|
147
|
-
if(delay > 1000) delay = 1000
|
|
148
|
-
await maybeLoadMore()
|
|
149
|
-
if(timersLoopTimeout === false) {
|
|
150
|
-
timersLoopTimeout = setTimeout(timersLoop, delay)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function startTimersLoop() {
|
|
155
|
-
timersLoopStarted = true
|
|
156
|
-
timersLoop()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function resetTimersLoop() {
|
|
160
|
-
if(timersLoopTimeout !== false) clearTimeout(timersLoopTimeout)
|
|
161
|
-
timersLoop()
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function appendTimers(timers) {
|
|
165
|
-
for(let timer of timers) {
|
|
166
|
-
if(!timersById.has(timer.id)) {
|
|
167
|
-
timersQueue.push(timer)
|
|
168
|
-
timersById.set(timer.id, timer)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function maybeLoadMore() {
|
|
174
|
-
if(!(lastLoadTime - Date.now() < loadMoreAfter)) return
|
|
175
|
-
let loadTime = Date.now() + queueDuration
|
|
176
|
-
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
177
|
-
async (input, output, { encodedFrom, encodedTo }) => {
|
|
178
|
-
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
179
|
-
await input.index('timer_Timer_timestamp').range({
|
|
180
|
-
gte: encodedFrom,
|
|
181
|
-
lt: encodedTo
|
|
182
|
-
}).onChange(async (obj, oldObj) => {
|
|
183
|
-
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
})`, { encodedFrom: (''+lastLoadTime).padStart(16, '0')+'_', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
187
|
-
lastLoadTime = loadTime
|
|
188
|
-
for(let timer of timers) {
|
|
189
|
-
insertTimer(timer)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function checkIfThereIsMore() {
|
|
194
|
-
if(timersLoopStarted) return // loop started
|
|
195
|
-
let loadTime = Date.now() + queueDuration
|
|
196
|
-
//console.log("CHECK IF THERE IS MORE?", loadTime)
|
|
197
|
-
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
198
|
-
async (input, output, { encodedFrom, encodedTo }) => {
|
|
199
|
-
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
200
|
-
await input.index('timer_Timer_timestamp').range({
|
|
201
|
-
gte: encodedFrom,
|
|
202
|
-
lt: encodedTo
|
|
203
|
-
}).onChange(async (obj, oldObj) => {
|
|
204
|
-
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
})`, { encodedFrom: '', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
208
|
-
if(timers.length === 0) {
|
|
209
|
-
//console.log("NO MORE")
|
|
210
|
-
if(!timersLoopStarted) setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
lastLoadTime = loadTime
|
|
214
|
-
appendTimers(timers)
|
|
215
|
-
if(!timersLoopStarted) startTimersLoop()
|
|
216
|
-
}
|
|
4
|
+
import definition from './definition.js'
|
|
217
5
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
let loadTime = Date.now() + queueDuration
|
|
223
|
-
|
|
224
|
-
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
225
|
-
async (input, output, { encodedFrom, encodedTo }) => {
|
|
226
|
-
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
227
|
-
await input.index('timer_Timer_timestamp').range({
|
|
228
|
-
gte: encodedFrom,
|
|
229
|
-
lt: encodedTo
|
|
230
|
-
}).onChange(async (obj, oldObj) => {
|
|
231
|
-
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
})`, { encodedFrom: '', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
235
|
-
console.error("NEXT TIMERS", timers)
|
|
236
|
-
lastLoadTime = loadTime
|
|
237
|
-
appendTimers(timers)
|
|
238
|
-
if(!timersLoopStarted) startTimersLoop()
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function runTimerAction(timer) {
|
|
242
|
-
/* console.error("RUN ACTION", timer)
|
|
243
|
-
console.trace("RUN ACTION")*/
|
|
244
|
-
if(timer.command) {
|
|
245
|
-
if(!timer.command.service) timer.command.service = timer.service
|
|
246
|
-
return app.command({
|
|
247
|
-
...timer.command,
|
|
248
|
-
origin: { ...(timer.origin || {}), through: "timer" }
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
|
-
if(timer.trigger) {
|
|
252
|
-
if(timer.service) {
|
|
253
|
-
return app.triggerService({
|
|
254
|
-
...timer.trigger, service: timer.service,
|
|
255
|
-
causeType: 'timer',
|
|
256
|
-
cause: timer.id
|
|
257
|
-
}, {
|
|
258
|
-
...timer.trigger.data,
|
|
259
|
-
origin: { ...(timer.origin || {}), through: "timer" }
|
|
260
|
-
})
|
|
261
|
-
} else {
|
|
262
|
-
return app.trigger({ ...timer.trigger }, {
|
|
263
|
-
...timer.trigger.data,
|
|
264
|
-
origin: { ...(timer.origin || {}), through: "timer" }
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return new Promise((resolve, reject) => resolve(true))
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function insertTimer(timer) {
|
|
272
|
-
//console.log("INSERT TIMER", timer, "TO", timersQueue)
|
|
273
|
-
if(timersById.has(timer.id)) return //throw new Error("INSERT TIMER DUPLICATED!!!")
|
|
274
|
-
timersById.set(timer.id, timer)
|
|
275
|
-
for(let i = 0; i < timersQueue.length; i++) {
|
|
276
|
-
if(timer.timestamp < timersQueue[i].timestamp) {
|
|
277
|
-
timersQueue.splice(i, 0, timer)
|
|
278
|
-
if(i === 0) { // reset timers loop
|
|
279
|
-
resetTimersLoop()
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
timersQueue.push(timer)
|
|
285
|
-
if(!timersLoopStarted) {
|
|
286
|
-
startTimersLoop()
|
|
287
|
-
} else if(timer.timestamp - Date.now() < 1000) {
|
|
288
|
-
resetTimersLoop()
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function removeTimer(timerId) {
|
|
293
|
-
for(let i = 0; i < timersQueue; i++) {
|
|
294
|
-
if(timersQueue[i].id === timerId) {
|
|
295
|
-
timersQueue.splice(i, 1)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
timersById.delete(timerId)
|
|
299
|
-
}
|
|
6
|
+
import { Timer } from './model.js'
|
|
7
|
+
import { insertTimer, removeTimer, startTimers } from './loop.js'
|
|
8
|
+
import { queueDuration, loadMoreAfter } from './config.js'
|
|
300
9
|
|
|
301
10
|
definition.trigger({
|
|
302
11
|
name: "createTimer",
|
|
@@ -377,127 +86,6 @@ definition.trigger({
|
|
|
377
86
|
}
|
|
378
87
|
})
|
|
379
88
|
|
|
380
|
-
|
|
381
|
-
/*
|
|
382
|
-
definition.action({
|
|
383
|
-
name: "create",
|
|
384
|
-
properties: {
|
|
385
|
-
timer: {
|
|
386
|
-
type: Object
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
async execute({ timer }, { client, service }, emit) {
|
|
390
|
-
if(!timer) throw new Error("timer is required")
|
|
391
|
-
let timestamp = timer.timestamp
|
|
392
|
-
let loops = timer.loops || 0
|
|
393
|
-
let timerId = timer.id || app.generateUid()
|
|
394
|
-
let maxRetries = timer.maxRetries || 0
|
|
395
|
-
let retryDelay = timer.retryDelay || 5 * 1000
|
|
396
|
-
let interval = timer.interval || 0
|
|
397
|
-
if(loops > 0 && interval == 0) throw new Error("impossibleTimer")
|
|
398
|
-
const props = {
|
|
399
|
-
...timer, timestamp, loops, interval, timerId, maxRetries, retryDelay, retries: 0
|
|
400
|
-
}
|
|
401
|
-
emit({
|
|
402
|
-
type: "timerCreated",
|
|
403
|
-
timer: timerId,
|
|
404
|
-
data: props
|
|
405
|
-
})
|
|
406
|
-
if(timestamp < Date.now() + queueDuration) {
|
|
407
|
-
insertTimer({ ...props , id: timerId })
|
|
408
|
-
}
|
|
409
|
-
return timerId
|
|
410
|
-
}
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
definition.action({
|
|
415
|
-
name: "cancel",
|
|
416
|
-
properties: {
|
|
417
|
-
timer: {
|
|
418
|
-
type: Timer
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
async execute({ timer }, { client, service }, emit) {
|
|
422
|
-
if(!timer) throw new Error("timer is required")
|
|
423
|
-
let timerRow = await Timer.get(timer)
|
|
424
|
-
if(!timerRow) throw 'notFound'
|
|
425
|
-
emit({
|
|
426
|
-
type: "timerCanceled", timer
|
|
427
|
-
})
|
|
428
|
-
removeTimer(timer)
|
|
429
|
-
return true
|
|
430
|
-
}
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
definition.action({
|
|
434
|
-
name: "cancelIfExists",
|
|
435
|
-
properties: {
|
|
436
|
-
timer: {
|
|
437
|
-
type: Timer
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
async execute({ timer }, { client, service }, emit) {
|
|
441
|
-
if(!timer) throw new Error("timer is required")
|
|
442
|
-
let timerRow = await Timer.get(timer)
|
|
443
|
-
if(!timerRow) return false
|
|
444
|
-
emit({
|
|
445
|
-
type: "timerCanceled", timer
|
|
446
|
-
})
|
|
447
|
-
removeTimer(timer)
|
|
448
|
-
return true
|
|
449
|
-
}
|
|
450
|
-
})
|
|
451
|
-
*/
|
|
452
|
-
|
|
453
|
-
definition.event({
|
|
454
|
-
queuedBy: 'timer',
|
|
455
|
-
name: "timerCreated",
|
|
456
|
-
async execute({ timer, data, origin }) {
|
|
457
|
-
return Timer.create({
|
|
458
|
-
id: timer,
|
|
459
|
-
...data,
|
|
460
|
-
origin: { ...origin, ...( data.origin || {} ) }
|
|
461
|
-
})
|
|
462
|
-
}
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
definition.event({
|
|
466
|
-
queuedBy: 'timer',
|
|
467
|
-
name: "timerCanceled",
|
|
468
|
-
async execute({ timer }) {
|
|
469
|
-
return Timer.delete(timer)
|
|
470
|
-
}
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
definition.event({
|
|
474
|
-
queuedBy: 'timer',
|
|
475
|
-
name: "timerFinished",
|
|
476
|
-
async execute({ timer }) {
|
|
477
|
-
return Timer.delete(timer)
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
definition.event({
|
|
482
|
-
queuedBy: 'timer',
|
|
483
|
-
name: "timerFired",
|
|
484
|
-
async execute({ timer, timestamp, loops }) {
|
|
485
|
-
return Timer.update(timer, {
|
|
486
|
-
loops, timestamp
|
|
487
|
-
})
|
|
488
|
-
}
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
definition.event({
|
|
492
|
-
queuedBy: 'timer',
|
|
493
|
-
name: "timerFailed",
|
|
494
|
-
async execute({ timer, timestamp, retries }) {
|
|
495
|
-
return Timer.update(timer, {
|
|
496
|
-
retries, timestamp
|
|
497
|
-
})
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
|
|
501
89
|
definition.afterStart(async () => {
|
|
502
90
|
await startTimers()
|
|
503
91
|
})
|
package/loop.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
|
|
6
|
+
import { queueDuration, loadMoreAfter } from './config.js'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
let timersQueue = []
|
|
10
|
+
let timersById = new Map()
|
|
11
|
+
let timersLoopStarted = false
|
|
12
|
+
let timersLoopTimeout = false
|
|
13
|
+
let lastLoadTime = 0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export function runTimerAction(timer) {
|
|
17
|
+
/* console.error("RUN ACTION", timer)
|
|
18
|
+
console.trace("RUN ACTION")*/
|
|
19
|
+
if(timer.command) {
|
|
20
|
+
if(!timer.command.service) timer.command.service = timer.service
|
|
21
|
+
return app.command({
|
|
22
|
+
...timer.command,
|
|
23
|
+
origin: { ...(timer.origin || {}), through: "timer" }
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
if(timer.trigger) {
|
|
27
|
+
if(timer.service) {
|
|
28
|
+
return app.triggerService({
|
|
29
|
+
...timer.trigger, service: timer.service,
|
|
30
|
+
causeType: 'timer',
|
|
31
|
+
cause: timer.id
|
|
32
|
+
}, {
|
|
33
|
+
...timer.trigger.data,
|
|
34
|
+
origin: { ...(timer.origin || {}), through: "timer" }
|
|
35
|
+
})
|
|
36
|
+
} else {
|
|
37
|
+
return app.trigger({ ...timer.trigger }, {
|
|
38
|
+
...timer.trigger.data,
|
|
39
|
+
origin: { ...(timer.origin || {}), through: "timer" }
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return new Promise((resolve, reject) => resolve(true))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function fireTimer(timer) {
|
|
47
|
+
runTimerAction(timer).catch(error => {
|
|
48
|
+
console.error("TIMER ACTION ERROR", error)
|
|
49
|
+
let timestamp = Date.now() + timer.retryDelay
|
|
50
|
+
timer.retries ++
|
|
51
|
+
if(timer.retries > timer.maxRetries) {
|
|
52
|
+
app.emitEvents("timer", [{
|
|
53
|
+
type: "timerFinished",
|
|
54
|
+
timer: timer.id,
|
|
55
|
+
error
|
|
56
|
+
}], { ...(timer.origin || {}), through: 'timer' })
|
|
57
|
+
timersById.delete(timer.id)
|
|
58
|
+
} else { // Retry
|
|
59
|
+
app.emitEvents("timer", [{
|
|
60
|
+
type: "timerFailed",
|
|
61
|
+
timer: timer.id,
|
|
62
|
+
error, timestamp,
|
|
63
|
+
retries: timer.retries
|
|
64
|
+
}], { ...(timer.origin || {}), through: 'timer' })
|
|
65
|
+
timer.timestamp = timestamp
|
|
66
|
+
insertTimer(timer)
|
|
67
|
+
}
|
|
68
|
+
}).then(done => {
|
|
69
|
+
console.error("TIMER ACTION FINISHED", done)
|
|
70
|
+
timer.loops --
|
|
71
|
+
if(timer.loops < 0) {
|
|
72
|
+
console.log("TIMER FINISHED")
|
|
73
|
+
app.emitEvents("timer",[{
|
|
74
|
+
type: "timerFinished",
|
|
75
|
+
timer: timer.id
|
|
76
|
+
}], { ...(timer.origin || {}), through: 'timer' })
|
|
77
|
+
timersById.delete(timer.id)
|
|
78
|
+
} else {
|
|
79
|
+
let timestamp = timer.timestamp + timer.interval
|
|
80
|
+
console.log("TIMER FIRED")
|
|
81
|
+
app.emitEvents("timer",[{
|
|
82
|
+
type: "timerFired",
|
|
83
|
+
timer: timer.id,
|
|
84
|
+
timestamp,
|
|
85
|
+
loops: timer.loops
|
|
86
|
+
}], { ...(timer.origin || {}), through: 'timer' })
|
|
87
|
+
timer.timestamp = timestamp
|
|
88
|
+
insertTimer(timer)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async function timersLoop() {
|
|
96
|
+
//console.log("TL", timersQueue.length, timersQueue[0] && (timersQueue[0].timestamp - Date.now()))
|
|
97
|
+
timersLoopTimeout = false
|
|
98
|
+
if(timersQueue.length === 0) {
|
|
99
|
+
timersLoopStarted = false
|
|
100
|
+
setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
let nextTs = timersQueue[0].timestamp
|
|
104
|
+
let now = Date.now()
|
|
105
|
+
//console.log("NEXT TS", nextTs, '<', now, '-->', nextTs < now, "INQ", timersQueue)
|
|
106
|
+
while(nextTs < now) {
|
|
107
|
+
fireTimer(timersQueue.shift())
|
|
108
|
+
if(timersQueue.length === 0) {
|
|
109
|
+
timersLoopStarted = false
|
|
110
|
+
setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
nextTs = timersQueue[0].timestamp
|
|
114
|
+
}
|
|
115
|
+
let delay = nextTs - Date.now()
|
|
116
|
+
if(delay > 1000) delay = 1000
|
|
117
|
+
await maybeLoadMore()
|
|
118
|
+
if(timersLoopTimeout === false) {
|
|
119
|
+
timersLoopTimeout = setTimeout(timersLoop, delay)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function startTimersLoop() {
|
|
124
|
+
timersLoopStarted = true
|
|
125
|
+
timersLoop()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resetTimersLoop() {
|
|
129
|
+
if(timersLoopTimeout !== false) clearTimeout(timersLoopTimeout)
|
|
130
|
+
timersLoop()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function appendTimers(timers) {
|
|
134
|
+
for(let timer of timers) {
|
|
135
|
+
if(!timersById.has(timer.id)) {
|
|
136
|
+
timersQueue.push(timer)
|
|
137
|
+
timersById.set(timer.id, timer)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function maybeLoadMore() {
|
|
143
|
+
if(!(lastLoadTime - Date.now() < loadMoreAfter)) return
|
|
144
|
+
let loadTime = Date.now() + queueDuration
|
|
145
|
+
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
146
|
+
async (input, output, { encodedFrom, encodedTo }) => {
|
|
147
|
+
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
148
|
+
await input.index('timer_Timer_timestamp').range({
|
|
149
|
+
gte: encodedFrom,
|
|
150
|
+
lt: encodedTo
|
|
151
|
+
}).onChange(async (obj, oldObj) => {
|
|
152
|
+
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
})`, { encodedFrom: (''+lastLoadTime).padStart(16, '0')+'_', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
156
|
+
lastLoadTime = loadTime
|
|
157
|
+
for(let timer of timers) {
|
|
158
|
+
insertTimer(timer)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function checkIfThereIsMore() {
|
|
163
|
+
if(timersLoopStarted) return // loop started
|
|
164
|
+
let loadTime = Date.now() + queueDuration
|
|
165
|
+
//console.log("CHECK IF THERE IS MORE?", loadTime)
|
|
166
|
+
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
167
|
+
async (input, output, { encodedFrom, encodedTo }) => {
|
|
168
|
+
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
169
|
+
await input.index('timer_Timer_timestamp').range({
|
|
170
|
+
gte: encodedFrom,
|
|
171
|
+
lt: encodedTo
|
|
172
|
+
}).onChange(async (obj, oldObj) => {
|
|
173
|
+
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
})`, { encodedFrom: '', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
177
|
+
if(timers.length === 0) {
|
|
178
|
+
//console.log("NO MORE")
|
|
179
|
+
if(!timersLoopStarted) setTimeout(checkIfThereIsMore, loadMoreAfter)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
lastLoadTime = loadTime
|
|
183
|
+
appendTimers(timers)
|
|
184
|
+
if(!timersLoopStarted) startTimersLoop()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
export async function startTimers() {
|
|
189
|
+
console.error("START TIMERS")
|
|
190
|
+
|
|
191
|
+
let loadTime = Date.now() + queueDuration
|
|
192
|
+
|
|
193
|
+
let timers = await app.dao.get(['database', 'query', app.databaseName, `(${
|
|
194
|
+
async (input, output, { encodedFrom, encodedTo }) => {
|
|
195
|
+
const mapper = async (res) => input.table('timer_Timer').object(res.to).get()
|
|
196
|
+
await input.index('timer_Timer_timestamp').range({
|
|
197
|
+
gte: encodedFrom,
|
|
198
|
+
lt: encodedTo
|
|
199
|
+
}).onChange(async (obj, oldObj) => {
|
|
200
|
+
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
})`, { encodedFrom: '', encodedTo: (''+loadTime).padStart(16, '0')+'_' }])
|
|
204
|
+
console.error("NEXT TIMERS", timers)
|
|
205
|
+
lastLoadTime = loadTime
|
|
206
|
+
appendTimers(timers)
|
|
207
|
+
if(!timersLoopStarted) startTimersLoop()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function insertTimer(timer) {
|
|
211
|
+
//console.log("INSERT TIMER", timer, "TO", timersQueue)
|
|
212
|
+
if(timersById.has(timer.id)) return //throw new Error("INSERT TIMER DUPLICATED!!!")
|
|
213
|
+
timersById.set(timer.id, timer)
|
|
214
|
+
for(let i = 0; i < timersQueue.length; i++) {
|
|
215
|
+
if(timer.timestamp < timersQueue[i].timestamp) {
|
|
216
|
+
timersQueue.splice(i, 0, timer)
|
|
217
|
+
if(i === 0) { // reset timers loop
|
|
218
|
+
resetTimersLoop()
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
timersQueue.push(timer)
|
|
224
|
+
if(!timersLoopStarted) {
|
|
225
|
+
startTimersLoop()
|
|
226
|
+
} else if(timer.timestamp - Date.now() < 1000) {
|
|
227
|
+
resetTimersLoop()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function removeTimer(timerId) {
|
|
232
|
+
for(let i = 0; i < timersQueue; i++) {
|
|
233
|
+
if(timersQueue[i].id === timerId) {
|
|
234
|
+
timersQueue.splice(i, 1)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
timersById.delete(timerId)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
definition.afterStart(async () => {
|
|
241
|
+
await startTimers()
|
|
242
|
+
})
|
package/model.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
|
|
6
|
+
export const Timer = definition.model({
|
|
7
|
+
name: "Timer",
|
|
8
|
+
properties: {
|
|
9
|
+
timestamp: {
|
|
10
|
+
type: Number
|
|
11
|
+
},
|
|
12
|
+
time: {
|
|
13
|
+
type: Date,
|
|
14
|
+
},
|
|
15
|
+
loops: {
|
|
16
|
+
type: Number
|
|
17
|
+
},
|
|
18
|
+
interval: {
|
|
19
|
+
type: Number
|
|
20
|
+
},
|
|
21
|
+
maxRetries: {
|
|
22
|
+
type: Number
|
|
23
|
+
},
|
|
24
|
+
retryDelay: {
|
|
25
|
+
type: Number
|
|
26
|
+
},
|
|
27
|
+
retries: {
|
|
28
|
+
type: Number
|
|
29
|
+
},
|
|
30
|
+
service: {
|
|
31
|
+
type: String
|
|
32
|
+
},
|
|
33
|
+
command: {
|
|
34
|
+
type: Object
|
|
35
|
+
},
|
|
36
|
+
trigger: {
|
|
37
|
+
type: Object
|
|
38
|
+
},
|
|
39
|
+
origin: {
|
|
40
|
+
type: Object
|
|
41
|
+
},
|
|
42
|
+
causeType: {
|
|
43
|
+
type: String
|
|
44
|
+
},
|
|
45
|
+
cause: {
|
|
46
|
+
type: String
|
|
47
|
+
},
|
|
48
|
+
createdAt: {
|
|
49
|
+
type: Date,
|
|
50
|
+
default: () => new Date()
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
indexes: {
|
|
54
|
+
timestamp: {
|
|
55
|
+
property: 'timestamp',
|
|
56
|
+
function: async function(input, output) {
|
|
57
|
+
const mapper = (obj) => ({ id: (''+obj.timestamp).padStart(16, '0') + '_' + obj.id, to: obj.id })
|
|
58
|
+
await input.table('timer_Timer').onChange(
|
|
59
|
+
(obj, oldObj) => output.change(obj && mapper(obj), oldObj && mapper(oldObj))
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
definition.event({
|
|
68
|
+
queuedBy: 'timer',
|
|
69
|
+
name: "timerCreated",
|
|
70
|
+
async execute({ timer, data, origin }) {
|
|
71
|
+
return Timer.create({
|
|
72
|
+
id: timer,
|
|
73
|
+
...data,
|
|
74
|
+
origin: { ...origin, ...( data.origin || {} ) }
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
definition.event({
|
|
80
|
+
queuedBy: 'timer',
|
|
81
|
+
name: "timerCanceled",
|
|
82
|
+
async execute({ timer }) {
|
|
83
|
+
return Timer.delete(timer)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
definition.event({
|
|
88
|
+
queuedBy: 'timer',
|
|
89
|
+
name: "timerFinished",
|
|
90
|
+
async execute({ timer }) {
|
|
91
|
+
return Timer.delete(timer)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
definition.event({
|
|
96
|
+
queuedBy: 'timer',
|
|
97
|
+
name: "timerFired",
|
|
98
|
+
async execute({ timer, timestamp, loops }) {
|
|
99
|
+
return Timer.update(timer, {
|
|
100
|
+
loops, timestamp
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
definition.event({
|
|
106
|
+
queuedBy: 'timer',
|
|
107
|
+
name: "timerFailed",
|
|
108
|
+
async execute({ timer, timestamp, retries }) {
|
|
109
|
+
return Timer.update(timer, {
|
|
110
|
+
retries, timestamp
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/timer-service",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.44",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@live-change/framework": "^0.9.
|
|
25
|
+
"@live-change/framework": "^0.9.44"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "6e9cf980b2ac6e4e7d78955cbf97ae1c2963bcab"
|
|
28
28
|
}
|