@live-change/cron-service 0.9.162
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 +15 -0
- package/definition.js +13 -0
- package/index.js +11 -0
- package/interval.js +163 -0
- package/package.json +28 -0
- package/run.js +204 -0
- package/schedule.js +176 -0
package/config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import definition from './definition.js'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
adminRoles = ['admin', 'owner'],
|
|
5
|
+
ownerTypes = ['user_User'],
|
|
6
|
+
topicTypes = ['topic_Topic']
|
|
7
|
+
} = definition.config
|
|
8
|
+
|
|
9
|
+
const config = {
|
|
10
|
+
adminRoles,
|
|
11
|
+
ownerTypes,
|
|
12
|
+
topicTypes
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default config
|
package/definition.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import relationsPlugin from '@live-change/relations-plugin'
|
|
5
|
+
import accessControlService from '@live-change/access-control-service'
|
|
6
|
+
import taskService from '@live-change/task-service'
|
|
7
|
+
|
|
8
|
+
const definition = app.createServiceDefinition({
|
|
9
|
+
name: "cron",
|
|
10
|
+
use: [ relationsPlugin, accessControlService, taskService ]
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export default definition
|
package/index.js
ADDED
package/interval.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
import config from './config.js'
|
|
6
|
+
import { triggerType } from './run.js'
|
|
7
|
+
|
|
8
|
+
export const Interval = definition.model({
|
|
9
|
+
name: "Interval",
|
|
10
|
+
propertyOfAny: {
|
|
11
|
+
to: ['owner', 'topic'],
|
|
12
|
+
ownerTypes: config.ownerTypes,
|
|
13
|
+
topicTypes: config.topicTypes,
|
|
14
|
+
writeAccessControl: {
|
|
15
|
+
roles: config.adminRoles
|
|
16
|
+
},
|
|
17
|
+
readAccessControl: {
|
|
18
|
+
roles: config.adminRoles
|
|
19
|
+
},
|
|
20
|
+
readAllAccess: ['admin'],
|
|
21
|
+
},
|
|
22
|
+
properties: {
|
|
23
|
+
description: {
|
|
24
|
+
type: String,
|
|
25
|
+
description: "Description of the interval"
|
|
26
|
+
},
|
|
27
|
+
interval: {
|
|
28
|
+
type: Number, // milliseconds
|
|
29
|
+
description: "Interval between triggers in milliseconds"
|
|
30
|
+
},
|
|
31
|
+
wait: {
|
|
32
|
+
type: Number, // milliseconds
|
|
33
|
+
description: "Wait for previous trigger to finish before planning next trigger"
|
|
34
|
+
},
|
|
35
|
+
trigger: triggerType
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
async function processInterval({ id, interval, wait, trigger }) {
|
|
40
|
+
if(wait) await waitForTasks('cron_Interval', id)
|
|
41
|
+
const nextTimestamp = Date.now() + interval
|
|
42
|
+
const nextTime = new Date(nextTimestamp)
|
|
43
|
+
await triggerService({
|
|
44
|
+
service: 'timer',
|
|
45
|
+
type: 'createTimer',
|
|
46
|
+
}, {
|
|
47
|
+
timer: {
|
|
48
|
+
id: 'cron_Interval_' + id,
|
|
49
|
+
timestamp: nextTimestamp,
|
|
50
|
+
time: nextTime,
|
|
51
|
+
service: definition.name,
|
|
52
|
+
causeType: 'cron_Interval',
|
|
53
|
+
cause: id,
|
|
54
|
+
trigger: {
|
|
55
|
+
type: 'runInterval',
|
|
56
|
+
data: {
|
|
57
|
+
interval: id
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
definition.trigger({
|
|
66
|
+
name: 'runInterval',
|
|
67
|
+
properties: {
|
|
68
|
+
interval: {
|
|
69
|
+
type: Interval,
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
execute: async ({ interval }, { service, trigger, triggerService }, emit) => {
|
|
73
|
+
const intervalData = await Interval.get(interval)
|
|
74
|
+
if(!intervalData) return /// interval was deleted
|
|
75
|
+
if(intervalData.wait) {
|
|
76
|
+
await runTrigger(intervalData.trigger, {
|
|
77
|
+
trigger,
|
|
78
|
+
triggerService,
|
|
79
|
+
jobType: 'cron_Interval',
|
|
80
|
+
job: interval,
|
|
81
|
+
timeout: Number.isInteger(intervalData.wait) ? intervalData.wait : undefined
|
|
82
|
+
})
|
|
83
|
+
await processInterval(intervalData)
|
|
84
|
+
} else {
|
|
85
|
+
await processInterval(intervalData) // no wait, process immediately
|
|
86
|
+
try {
|
|
87
|
+
await doRunTrigger(intervalData.trigger, {
|
|
88
|
+
trigger,
|
|
89
|
+
triggerService,
|
|
90
|
+
jobType: 'cron_Interval',
|
|
91
|
+
job: interval
|
|
92
|
+
})
|
|
93
|
+
} catch(error) {
|
|
94
|
+
console.error("ERROR RUNNING INTERVAL TRIGGER", error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
definition.trigger({
|
|
102
|
+
name: 'changeCron_Interval',
|
|
103
|
+
properties: {
|
|
104
|
+
object: {
|
|
105
|
+
type: Interval,
|
|
106
|
+
validation: ['nonEmpty'],
|
|
107
|
+
},
|
|
108
|
+
data: {
|
|
109
|
+
type: Object,
|
|
110
|
+
},
|
|
111
|
+
oldData: {
|
|
112
|
+
type: Object,
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
execute: async ({ object, data, oldData }, { service, trigger, triggerService }, emit) => {
|
|
116
|
+
if(oldData) { // clear old version
|
|
117
|
+
await triggerService({
|
|
118
|
+
service: 'timer',
|
|
119
|
+
type: 'cancelTimerIfExists',
|
|
120
|
+
data: {
|
|
121
|
+
timer: 'cron_Interval_' + object.id
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
if(!data) { // full cleanup
|
|
125
|
+
await triggerService({
|
|
126
|
+
service: 'timer',
|
|
127
|
+
type: 'cancelTimerIfExists',
|
|
128
|
+
data: {
|
|
129
|
+
timer: 'cron_run_timeout_' + object.id
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
RunState.delete(App.encodeIdentifier(['cron_Interval', object.id]))
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if(data) { // setup new version
|
|
136
|
+
await processInterval({
|
|
137
|
+
id: object,
|
|
138
|
+
...data``
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const Timer = definition.foreignModel('timer', 'Timer')
|
|
145
|
+
|
|
146
|
+
definition.afterStart(async (service) => {
|
|
147
|
+
const position = ''
|
|
148
|
+
const bucketSize = 128
|
|
149
|
+
let bucket = []
|
|
150
|
+
do {
|
|
151
|
+
bucket = await Interval.rangeGet({
|
|
152
|
+
gt: position,
|
|
153
|
+
limit: bucketSize
|
|
154
|
+
})
|
|
155
|
+
for(const interval of bucket) {
|
|
156
|
+
const existingTimer = await Timer.get('cron_Interval_' + interval.id)
|
|
157
|
+
if(!existingTimer) {
|
|
158
|
+
console.error("INTERVAL", interval, "HAS NO TIMER, REPROCESSING")
|
|
159
|
+
await processInterval(interval)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} while(bucket.length === bucketSize)
|
|
163
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/cron-service",
|
|
3
|
+
"version": "0.9.162",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "NODE_ENV=test tape tests/*"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/live-change/live-change-stack.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/live-change/live-change-stack/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/live-change/live-change-stack",
|
|
18
|
+
"author": {
|
|
19
|
+
"email": "michal@laszczewski.pl",
|
|
20
|
+
"name": "Michał Łaszczewski",
|
|
21
|
+
"url": "https://www.viamage.com/"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@live-change/framework": "^0.9.162"
|
|
26
|
+
},
|
|
27
|
+
"gitHead": "59cd1485ca6d76bd87a6634a92d6e661e5803d5e"
|
|
28
|
+
}
|
package/run.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
|
|
6
|
+
const Task = definition.foreignModel('task', 'Task')
|
|
7
|
+
|
|
8
|
+
export const RunState = definition.model({
|
|
9
|
+
name: "RunState",
|
|
10
|
+
propertyOfAny: {
|
|
11
|
+
to: ['job'],
|
|
12
|
+
jobTypes: ['cron_Interval', 'cron_Schedule']
|
|
13
|
+
},
|
|
14
|
+
properties: {
|
|
15
|
+
state: {
|
|
16
|
+
type: String,
|
|
17
|
+
enum: ['running', 'waiting']
|
|
18
|
+
},
|
|
19
|
+
tasks: {
|
|
20
|
+
type: Array,
|
|
21
|
+
of: {
|
|
22
|
+
type: Task
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
startedAt: {
|
|
26
|
+
type: Date
|
|
27
|
+
},
|
|
28
|
+
returnTask: {
|
|
29
|
+
type: Boolean
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
indexes: {
|
|
33
|
+
byTasks: {
|
|
34
|
+
multi: true,
|
|
35
|
+
property: ['tasks']
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export const triggerType = {
|
|
42
|
+
description: "Trigger to schedule",
|
|
43
|
+
input: 'triggerEditor',
|
|
44
|
+
type: Object,
|
|
45
|
+
properties: {
|
|
46
|
+
name: {
|
|
47
|
+
description: "Trigger name",
|
|
48
|
+
type: String,
|
|
49
|
+
},
|
|
50
|
+
service: {
|
|
51
|
+
description: "Service holding the trigger, or null for triggering any service",
|
|
52
|
+
type: String,
|
|
53
|
+
},
|
|
54
|
+
properties: {
|
|
55
|
+
description: "Properties to pass to the trigger",
|
|
56
|
+
type: Object,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function doRunTrigger(triggerInfo, { trigger, triggerService, jobType, job }) {
|
|
62
|
+
if(triggerInfo.service) {
|
|
63
|
+
return await triggerService({
|
|
64
|
+
type: triggerInfo.name,
|
|
65
|
+
service: triggerInfo.service,
|
|
66
|
+
causeType: jobType, cause: job
|
|
67
|
+
}, triggerInfo.properties, true)
|
|
68
|
+
} else {
|
|
69
|
+
return await trigger({
|
|
70
|
+
type: triggerInfo.name,
|
|
71
|
+
causeType: jobType, cause: job
|
|
72
|
+
}, triggerInfo.properties)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runTrigger(triggerInfo, { trigger, triggerService, jobType, job, timeout }) {
|
|
77
|
+
const id = App.encodeIdentifier([jobType, job])
|
|
78
|
+
await RunState.create({
|
|
79
|
+
id,
|
|
80
|
+
jobType, job,
|
|
81
|
+
startedAt: new Date(),
|
|
82
|
+
state: 'running'
|
|
83
|
+
})
|
|
84
|
+
if(timeout) {
|
|
85
|
+
const timestamp = Date.now() + timeout
|
|
86
|
+
const time = new Date(timestamp)
|
|
87
|
+
await triggerService({
|
|
88
|
+
service: 'timer',
|
|
89
|
+
type: 'createTimer'
|
|
90
|
+
}, {
|
|
91
|
+
timer: {
|
|
92
|
+
id: 'cron_run_timeout_' + id,
|
|
93
|
+
timestamp, time,
|
|
94
|
+
causeType: jobType, cause: job,
|
|
95
|
+
service: definition.name,
|
|
96
|
+
trigger: {
|
|
97
|
+
type: 'runTimeout',
|
|
98
|
+
data: {
|
|
99
|
+
jobType, job
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
let triggerResults = []
|
|
106
|
+
try {
|
|
107
|
+
triggerResults = await doRunTrigger(triggerInfo, { trigger, triggerService, jobType, job })
|
|
108
|
+
} catch(error) {
|
|
109
|
+
console.error("ERROR RUNNING TRIGGER", error)
|
|
110
|
+
/// Ignore error, it will be handled by the task service
|
|
111
|
+
}
|
|
112
|
+
if(triggerInfo.returnTask) {
|
|
113
|
+
const tasks = await Promise.all(triggerResults.map((result) => Task.get(result)))
|
|
114
|
+
.filter(task => task !== undefined)
|
|
115
|
+
.filter(task => task.state !== 'done' && task.state !== 'failed')
|
|
116
|
+
if(tasks.length > 0) {
|
|
117
|
+
await RunState.update(id, {
|
|
118
|
+
state: 'waiting',
|
|
119
|
+
tasks: tasks.map(task => task.id)
|
|
120
|
+
})
|
|
121
|
+
/// Double check to avoid race condition
|
|
122
|
+
/* const runningTasks = await Promise.all(tasks.map(task => Task.get(task.id))).filter(task => task.state !== 'done' && task.state !== 'failed')
|
|
123
|
+
if(runningTasks.length === 0) {
|
|
124
|
+
await RunState.delete(id)
|
|
125
|
+
return 'done'
|
|
126
|
+
} */
|
|
127
|
+
/// There are still running tasks, wait for them
|
|
128
|
+
return 'waiting'
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await RunState.delete(id)
|
|
132
|
+
if(timeout) {
|
|
133
|
+
await triggerService({
|
|
134
|
+
service: 'timer',
|
|
135
|
+
type: 'cancelTimer'
|
|
136
|
+
}, {
|
|
137
|
+
timer: 'cron_run_timeout_' + id,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
return 'done'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
definition.trigger({
|
|
144
|
+
name: 'runTimeout',
|
|
145
|
+
properties: {
|
|
146
|
+
jobType: {
|
|
147
|
+
type: String
|
|
148
|
+
},
|
|
149
|
+
job: {
|
|
150
|
+
type: String
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
execute: async ({ jobType, job }, { trigger, triggerService }) => {
|
|
154
|
+
const id = App.encodeIdentifier([jobType, job])
|
|
155
|
+
await RunState.delete(id)
|
|
156
|
+
return 'done'
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
export async function waitForTasks(jobType, job) {
|
|
161
|
+
const runState = App.encodeIdentifier([jobType, job])
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
let done = false
|
|
164
|
+
const taskObservations = new Map()
|
|
165
|
+
function addTaskObservation(taskId) {
|
|
166
|
+
const observable = Task.observable(taskId)
|
|
167
|
+
if(!observable) return
|
|
168
|
+
const observer = {
|
|
169
|
+
set: (value) => {
|
|
170
|
+
if(!value) return updateTasks()
|
|
171
|
+
if(value.state === 'done' || value.state === 'failed') updateTasks()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
taskObservations.set(taskId, { observable, observer })
|
|
175
|
+
observable.observe(observer)
|
|
176
|
+
}
|
|
177
|
+
async function updateTasks() {
|
|
178
|
+
if(done) return
|
|
179
|
+
const runningTasks = taskObservations.values()
|
|
180
|
+
.filter(observation => observation.observable.getValue().state !== 'done' && observation.observable.getValue().state !== 'failed')
|
|
181
|
+
if(runningTasks.length === 0) {
|
|
182
|
+
await RunState.delete(runState)
|
|
183
|
+
finish()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const runStateObservable = RunState.observable(runState)
|
|
187
|
+
const runStateObserver = {
|
|
188
|
+
set: (value) => {
|
|
189
|
+
if(!value) finish()
|
|
190
|
+
if(value.tasks) {
|
|
191
|
+
for(const taskId of value.tasks) {
|
|
192
|
+
addTaskObservation(taskId)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function finish() {
|
|
198
|
+
if(done) return
|
|
199
|
+
done = true
|
|
200
|
+
runStateObservable.unobserve(runStateObserver)
|
|
201
|
+
resolve()
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
package/schedule.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
import config from './config.js'
|
|
6
|
+
import { triggerType } from './run.js'
|
|
7
|
+
|
|
8
|
+
export const Schedule = definition.model({
|
|
9
|
+
name: "Schedule",
|
|
10
|
+
propertyOfAny: {
|
|
11
|
+
to: ['owner', 'topic'],
|
|
12
|
+
ownerTypes: config.ownerTypes,
|
|
13
|
+
topicTypes: config.topicTypes,
|
|
14
|
+
writeAccessControl: {
|
|
15
|
+
roles: config.adminRoles
|
|
16
|
+
},
|
|
17
|
+
readAccessControl: {
|
|
18
|
+
roles: config.adminRoles
|
|
19
|
+
},
|
|
20
|
+
readAllAccess: ['admin'],
|
|
21
|
+
},
|
|
22
|
+
properties: {
|
|
23
|
+
description: {
|
|
24
|
+
type: String
|
|
25
|
+
},
|
|
26
|
+
minute: {
|
|
27
|
+
type: Number // NaN for every minute
|
|
28
|
+
},
|
|
29
|
+
hour: {
|
|
30
|
+
type: Number, // NaN for every hour
|
|
31
|
+
},
|
|
32
|
+
day: {
|
|
33
|
+
type: Number, // NaN for every day
|
|
34
|
+
},
|
|
35
|
+
dayOfWeek: {
|
|
36
|
+
type: Number, // NaN for every day of week
|
|
37
|
+
},
|
|
38
|
+
month: {
|
|
39
|
+
type: Number, // NaN for every month
|
|
40
|
+
},
|
|
41
|
+
trigger: triggerType
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
function getNextTime(schedule) {
|
|
46
|
+
const now = new Date()
|
|
47
|
+
let time = now
|
|
48
|
+
if(Number.isInteger(schedule.minute)) {
|
|
49
|
+
if(time.getMinutes() >= schedule.minute) { // next hour
|
|
50
|
+
time = new Date(time.getFullYear(), time.getMonth(), time.getDate(), time.getHours() + 1, schedule.minute, 0)
|
|
51
|
+
} else {
|
|
52
|
+
time = new Date(time.getFullYear(), time.getMonth(), time.getDate(), time.getHours(), schedule.minute, 0)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if(Number.isInteger(schedule.hour)) {
|
|
56
|
+
if(time.getHours() > schedule.hour) { // next day
|
|
57
|
+
time = new Date(time.getFullYear(), time.getMonth(), time.getDate() + 1, schedule.hour, schedule.minute || 0, 0)
|
|
58
|
+
} else {
|
|
59
|
+
time = new Date(time.getFullYear(), time.getMonth(), time.getDate(), schedule.hour, schedule.minute || 0, 0)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isDayOk() {
|
|
64
|
+
if(Number.isInteger(schedule.month) && time.getMonth()+1 !== schedule.month) return false
|
|
65
|
+
if(Number.isInteger(schedule.day) && time.getDate() !== schedule.day) return false
|
|
66
|
+
if(Number.isInteger(schedule.dayOfWeek) && time.getDay()+1 !== schedule.dayOfWeek) return false
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
while(!isDayOk()) {
|
|
70
|
+
time = new Date(time.getTime() + 24 * 60 * 60 * 1000)
|
|
71
|
+
}
|
|
72
|
+
return time
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function processSchedule({ id, minute, hour, day, dayOfWeek, month, trigger }) {
|
|
76
|
+
const nextTime = getNextTime(schedule)
|
|
77
|
+
const nextTimestamp = nextTime.getTime()
|
|
78
|
+
await triggerService({
|
|
79
|
+
service: 'timer',
|
|
80
|
+
type: 'createTimer',
|
|
81
|
+
}, {
|
|
82
|
+
timer: {
|
|
83
|
+
id: 'cron_Schedule_' + id,
|
|
84
|
+
timestamp: nextTimestamp,
|
|
85
|
+
time: nextTime,
|
|
86
|
+
service: definition.name,
|
|
87
|
+
causeType: 'cron_Schedule',
|
|
88
|
+
cause: id,
|
|
89
|
+
trigger: {
|
|
90
|
+
type: 'runSchedule',
|
|
91
|
+
data: {
|
|
92
|
+
schedule: id
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
definition.trigger({
|
|
100
|
+
name: 'runSchedule',
|
|
101
|
+
properties: {
|
|
102
|
+
schedule: {
|
|
103
|
+
type: Schedule,
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
execute: async ({ schedule }, { service, trigger, triggerService }, emit) => {
|
|
107
|
+
const scheduleData = await Schedule.get(schedule)
|
|
108
|
+
if(!scheduleData) return /// schedule was deleted
|
|
109
|
+
await processSchedule(scheduleData) // no wait, process immediately
|
|
110
|
+
try {
|
|
111
|
+
await doRunTrigger(scheduleData.trigger, {
|
|
112
|
+
trigger,
|
|
113
|
+
triggerService,
|
|
114
|
+
jobType: 'cron_Schedule',
|
|
115
|
+
job: schedule
|
|
116
|
+
})
|
|
117
|
+
} catch(error) {
|
|
118
|
+
console.error("ERROR RUNNING SCHEDULE TRIGGER", error)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
definition.trigger({
|
|
125
|
+
name: 'changeCron_Schedule',
|
|
126
|
+
properties: {
|
|
127
|
+
object: {
|
|
128
|
+
type: Schedule,
|
|
129
|
+
validation: ['nonEmpty'],
|
|
130
|
+
},
|
|
131
|
+
data: {
|
|
132
|
+
type: Object,
|
|
133
|
+
},
|
|
134
|
+
oldData: {
|
|
135
|
+
type: Object,
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
execute: async ({ object, data, oldData }, { service, trigger, triggerService }, emit) => {
|
|
139
|
+
if(oldData) {
|
|
140
|
+
await triggerService({
|
|
141
|
+
service: 'timer',
|
|
142
|
+
type: 'cancelTimerIfExists',
|
|
143
|
+
data: {
|
|
144
|
+
timer: 'cron_Schedule_' + object.id
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
if(data) {
|
|
149
|
+
await processSchedule({
|
|
150
|
+
id: object,
|
|
151
|
+
...data
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const Timer = definition.foreignModel('timer', 'Timer')
|
|
158
|
+
|
|
159
|
+
definition.afterStart(async (service) => {
|
|
160
|
+
const position = ''
|
|
161
|
+
const bucketSize = 128
|
|
162
|
+
let bucket = []
|
|
163
|
+
do {
|
|
164
|
+
bucket = await Schedule.rangeGet({
|
|
165
|
+
gt: position,
|
|
166
|
+
limit: bucketSize
|
|
167
|
+
})
|
|
168
|
+
} while(bucket.length === bucketSize)
|
|
169
|
+
for(const schedule of bucket) {
|
|
170
|
+
const existingTimer = await Timer.get('cron_Schedule_' + schedule.id)
|
|
171
|
+
if(!existingTimer) {
|
|
172
|
+
console.error("SCHEDULE", schedule, "HAS NO TIMER, REPROCESSING")
|
|
173
|
+
await processSchedule(schedule)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
})
|