@live-change/task-service 0.8.24
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/definition.js +12 -0
- package/index.js +8 -0
- package/package.json +29 -0
- package/task.js +182 -0
package/definition.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import relationsPlugin from '@live-change/relations-plugin'
|
|
5
|
+
const accessControlService = require('@live-change/access-control-service')
|
|
6
|
+
|
|
7
|
+
const definition = app.createServiceDefinition({
|
|
8
|
+
name: "task",
|
|
9
|
+
use: [ relationsPlugin, accessControlService ]
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export default definition
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/task-service",
|
|
3
|
+
"version": "0.8.24",
|
|
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.8.24",
|
|
26
|
+
"@live-change/relations-plugin": "^0.8.24"
|
|
27
|
+
},
|
|
28
|
+
"gitHead": "63e942caccbcb1c9bfbd1a3ef1d097124514c5a7"
|
|
29
|
+
}
|
package/task.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import App from '@live-change/framework'
|
|
2
|
+
const app = App.app()
|
|
3
|
+
|
|
4
|
+
import definition from './definition.js'
|
|
5
|
+
import crypto from 'crypto'
|
|
6
|
+
|
|
7
|
+
const taskProperties = {
|
|
8
|
+
name: {
|
|
9
|
+
type: String,
|
|
10
|
+
validation: ['nonEmpty']
|
|
11
|
+
},
|
|
12
|
+
definition: {
|
|
13
|
+
type: Object
|
|
14
|
+
},
|
|
15
|
+
properties: {
|
|
16
|
+
type: Object
|
|
17
|
+
},
|
|
18
|
+
result: {
|
|
19
|
+
type: Object
|
|
20
|
+
},
|
|
21
|
+
hash: {
|
|
22
|
+
type: String,
|
|
23
|
+
},
|
|
24
|
+
state: {
|
|
25
|
+
type: String
|
|
26
|
+
},
|
|
27
|
+
startedAt: {
|
|
28
|
+
type: Date
|
|
29
|
+
},
|
|
30
|
+
createdAt: {
|
|
31
|
+
type: Date,
|
|
32
|
+
default: () => new Date()
|
|
33
|
+
},
|
|
34
|
+
doneAt: {
|
|
35
|
+
type: Date
|
|
36
|
+
},
|
|
37
|
+
retries: {
|
|
38
|
+
type: Array,
|
|
39
|
+
of: {
|
|
40
|
+
type: Object,
|
|
41
|
+
properties: {
|
|
42
|
+
startedAt: {
|
|
43
|
+
type: Date
|
|
44
|
+
},
|
|
45
|
+
failedAt: {
|
|
46
|
+
type: Date
|
|
47
|
+
},
|
|
48
|
+
error: {
|
|
49
|
+
type: String
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
default: []
|
|
54
|
+
},
|
|
55
|
+
progress: {
|
|
56
|
+
type: Object,
|
|
57
|
+
properties: {
|
|
58
|
+
current: {
|
|
59
|
+
type: Number
|
|
60
|
+
},
|
|
61
|
+
total: {
|
|
62
|
+
type: Number
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const Task = definition.model({
|
|
69
|
+
name: 'Task',
|
|
70
|
+
itemOfAny: {
|
|
71
|
+
to: 'cause',
|
|
72
|
+
readAccess: () => true,
|
|
73
|
+
},
|
|
74
|
+
properties: {
|
|
75
|
+
...taskProperties
|
|
76
|
+
},
|
|
77
|
+
indexes: {
|
|
78
|
+
byCauseAndHash: {
|
|
79
|
+
property: ['causeType', 'cause', 'hash']
|
|
80
|
+
},
|
|
81
|
+
byCauseAndState: {
|
|
82
|
+
property: ['causeType', 'cause', 'state']
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const task = (taskDefinition) => { /// TODO: modify to use triggers
|
|
88
|
+
return async (props, context, emit) => {
|
|
89
|
+
if(!emit) emit = (events) => app.emitEvents(definition.name, Array.isArray(events) ? events : [events], {})
|
|
90
|
+
const propertiesJson = JSON.stringify(props)
|
|
91
|
+
const hash = crypto
|
|
92
|
+
.createHash('sha256')
|
|
93
|
+
.update(taskDefinition.name + ':' + propertiesJson)
|
|
94
|
+
.digest('hex')
|
|
95
|
+
|
|
96
|
+
const similarTasks = Task.indexRangeGet('byCauseAndHash',
|
|
97
|
+
[context.causeType, context.cause, hash],
|
|
98
|
+
{ limit: 23 } // sha256 collisions are very unlikely
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const oldTask = similarTasks.find(similarTask => similarTask.name === taskDefinition.name
|
|
102
|
+
&& JSON.stringify(similarTask.properties) === propertiesJson)
|
|
103
|
+
|
|
104
|
+
let taskObject = oldTask ? await Task.get(oldTask.id) : {
|
|
105
|
+
id: app.generateUid(),
|
|
106
|
+
name: taskDefinition.name,
|
|
107
|
+
properties: props,
|
|
108
|
+
hash,
|
|
109
|
+
state: 'created'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(!oldTask) {
|
|
113
|
+
/// app.emitEvents
|
|
114
|
+
await Task.create({
|
|
115
|
+
...taskObject,
|
|
116
|
+
causeType: context.causeType,
|
|
117
|
+
cause: context.cause
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const maxRetries = taskDefinition.maxRetries ?? 5
|
|
122
|
+
|
|
123
|
+
async function updateTask(data) {
|
|
124
|
+
await Task.update(taskObject.id, data)
|
|
125
|
+
taskObject = await Task.get(taskObject.id)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const runTask = async () => {
|
|
129
|
+
await updateTask({
|
|
130
|
+
state: 'running',
|
|
131
|
+
startedAt: new Date()
|
|
132
|
+
})
|
|
133
|
+
try {
|
|
134
|
+
const result = await taskDefinition.execute(props, {
|
|
135
|
+
...context,
|
|
136
|
+
task: {
|
|
137
|
+
async run(taskFunction, props) {
|
|
138
|
+
return await taskFunction(props, {
|
|
139
|
+
...context,
|
|
140
|
+
causeType: definition.name + '_Task',
|
|
141
|
+
cause: taskObject.id
|
|
142
|
+
}, (events) => app.emitEvents(definition.name, Array.isArray(events) ? events : [events], {}))
|
|
143
|
+
},
|
|
144
|
+
async progress(current, total) {
|
|
145
|
+
await updateTask({
|
|
146
|
+
progress: { current, total }
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
await updateTask({
|
|
152
|
+
state: 'done',
|
|
153
|
+
doneAt: new Date(),
|
|
154
|
+
result
|
|
155
|
+
})
|
|
156
|
+
} catch(error) {
|
|
157
|
+
if(taskObject.retries.length >= maxRetries) {
|
|
158
|
+
await updateTask({
|
|
159
|
+
state: 'failed',
|
|
160
|
+
doneAt: new Date(),
|
|
161
|
+
error: error.message
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
await updateTask(taskObject.id, {
|
|
165
|
+
state: 'retrying',
|
|
166
|
+
retries: [...taskObject.retries, {
|
|
167
|
+
startedAt: taskObject.startedAt,
|
|
168
|
+
failedAt: new Date(),
|
|
169
|
+
error: error.message
|
|
170
|
+
}]
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// TODO: implement task queues
|
|
176
|
+
while(taskObject.state !== 'done' && taskObject.state !== 'failed') {
|
|
177
|
+
await runTask()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return taskObject.result
|
|
181
|
+
}
|
|
182
|
+
}
|