@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 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
@@ -0,0 +1,8 @@
1
+ import App from '@live-change/framework'
2
+ const app = App.app()
3
+
4
+ import definition from './definition.js'
5
+
6
+ import './task.js'
7
+
8
+ export default definition
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
+ }