@live-change/task-service 0.9.106 → 0.9.108

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.
Files changed (3) hide show
  1. package/model.js +32 -1
  2. package/package.json +4 -4
  3. package/task.ts +110 -70
package/model.js CHANGED
@@ -19,6 +19,9 @@ const taskProperties = {
19
19
  properties: {
20
20
  type: Object
21
21
  },
22
+ client: {
23
+ type: Object,
24
+ },
22
25
  result: {
23
26
  type: Object
24
27
  },
@@ -96,6 +99,9 @@ const Task = definition.model({
96
99
  byCauseAndState: {
97
100
  property: ['causeType', 'cause', 'state']
98
101
  },
102
+ byCauseAndStart: {
103
+ property: ['causeType', 'cause', 'startedAt']
104
+ },
99
105
  byState: {
100
106
  property: ['state']
101
107
  },
@@ -182,7 +188,7 @@ const Task = definition.model({
182
188
  function: async function(input, output, { tableName }) {
183
189
  function mapFunction(obj) {
184
190
  if(!obj) return null
185
- if(['done', 'failed', 'canceled'].includes(obj.state)) return null
191
+ if(['done', 'failed', 'canceled', 'fallbackDone'].includes(obj.state)) return null
186
192
  if(obj.causeType === tableName) return null
187
193
  return { id: `"${obj.name}"_${obj.id}`, to: obj.id }
188
194
  }
@@ -259,6 +265,31 @@ definition.view({
259
265
  }
260
266
  })
261
267
 
268
+ definition.view({
269
+ name: 'tasksByCauseAndStart',
270
+ internal: true,
271
+ properties: {
272
+ causeType: {
273
+ type: String
274
+ },
275
+ cause: {
276
+ type: String
277
+ },
278
+ ...App.rangeProperties
279
+ },
280
+ returns: {
281
+ type: Array,
282
+ of: {
283
+ type: Task
284
+ }
285
+ },
286
+ async daoPath(props) {
287
+ const { causeType, cause } = props
288
+ const range = App.extractRange(props)
289
+ return Task.sortedIndexRangePath('byCauseAndStart', [causeType, cause], range)
290
+ }
291
+ })
292
+
262
293
  definition.view({
263
294
  name: 'tasksByRoot',
264
295
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/task-service",
3
- "version": "0.9.106",
3
+ "version": "0.9.108",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "type": "module",
24
24
  "dependencies": {
25
- "@live-change/framework": "^0.9.106",
26
- "@live-change/relations-plugin": "^0.9.106"
25
+ "@live-change/framework": "^0.9.108",
26
+ "@live-change/relations-plugin": "^0.9.108"
27
27
  },
28
- "gitHead": "1afc853b9044a57046dae972df68d89c8c1cd1a6"
28
+ "gitHead": "c500fad0c238b17fa816248f16e9e9692abea569"
29
29
  }
package/task.ts CHANGED
@@ -36,11 +36,17 @@ async function triggerOnTaskStateChange(taskObject, causeType, cause) {
36
36
  })
37
37
  }
38
38
 
39
- async function createOrReuseTask(taskDefinition, props, causeType, cause, expire) {
39
+ interface ClientInfo {
40
+ user?: string
41
+ session?: string
42
+ }
43
+
44
+ async function createOrReuseTask(taskDefinition, props, causeType, cause, expire, client:ClientInfo) {
40
45
  const propertiesJson = JSON.stringify(props)
46
+ const userInfo = client?.user ? `user:${client.user}` : `session:${client?.session}`
41
47
  const hash = crypto
42
48
  .createHash('sha256')
43
- .update(taskDefinition.name + ':' + propertiesJson)
49
+ .update(taskDefinition.name + ':' + propertiesJson + userInfo)
44
50
  .digest('hex')
45
51
 
46
52
  const expireDate = (expire ?? taskDefinition.expire) ? new Date(Date.now() - taskDefinition.expire) : null
@@ -66,7 +72,8 @@ async function createOrReuseTask(taskDefinition, props, causeType, cause, expire
66
72
  state: 'created',
67
73
  service: taskDefinition.service,
68
74
  retries: [],
69
- maxRetries: taskDefinition.maxRetries ?? 5
75
+ maxRetries: taskDefinition.maxRetries ?? 5,
76
+ client
70
77
  }
71
78
 
72
79
  if(!oldTask) {
@@ -82,12 +89,13 @@ async function createOrReuseTask(taskDefinition, props, causeType, cause, expire
82
89
  return taskObject
83
90
  }
84
91
 
85
- async function startTask(taskFunction, props, causeType, cause, expire){
86
- const taskObject = await createOrReuseTask(taskFunction.definition, props, causeType, cause, expire)
92
+ async function startTask(taskFunction, props, causeType, cause, expire, client:ClientInfo){
93
+ const taskObject = await createOrReuseTask(taskFunction.definition, props, causeType, cause, expire, client)
87
94
  const context = {
88
95
  causeType,
89
96
  cause,
90
97
  taskObject,
98
+ client
91
99
  }
92
100
  console.log("START TASK!", taskFunction.name)
93
101
  const promise = taskFunction(props, context)
@@ -181,6 +189,34 @@ interface TaskDefinition {
181
189
 
182
190
  }
183
191
 
192
+ function progressCounter(reportProgress) {
193
+ let currentAcc = 0
194
+ let totalAcc = 0
195
+ const progressFunction = (current, total, action, opts) => {
196
+ currentAcc = current
197
+ totalAcc = total
198
+ reportProgress(currentAcc, totalAcc, action, opts)
199
+ }
200
+ progressFunction.increment = (action, by = 1, opts) => {
201
+ currentAcc += by
202
+ progressFunction(currentAcc, totalAcc, action, opts)
203
+ }
204
+ progressFunction.incrementTotal = (action, by = 1, opts) => {
205
+ totalAcc += by
206
+ progressFunction(currentAcc, totalAcc, action, opts)
207
+ }
208
+ progressFunction.slice = (sliceSize, factor = 1.0) => {
209
+ const sliceStart = currentAcc
210
+ const sliceEnd = sliceStart + sliceSize
211
+ currentAcc = sliceEnd
212
+ return progressCounter((current, total, action, opts) => {
213
+ progressFunction(sliceStart + Math.min(current, sliceSize) * factor,
214
+ sliceEnd + Math.min(total, sliceSize) * factor, action, opts)
215
+ })
216
+ }
217
+ return progressFunction
218
+ }
219
+
184
220
  type TaskFunction = (props, context: TaskExecuteContext, emit, reportProgress) => Promise<any>
185
221
 
186
222
  export default function task(definition:TaskDefinition, serviceDefinition) {
@@ -196,7 +232,7 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
196
232
  }
197
233
 
198
234
  let taskObject = context.taskObject ??
199
- await createOrReuseTask(definition, props, context.causeType, context.cause, context.expire)
235
+ await createOrReuseTask(definition, props, context.causeType, context.cause, context.expire, context.client)
200
236
 
201
237
  if(!taskObject?.state) throw new Error('Task object state is not defined in ' + taskObject)
202
238
  if(!taskObject?.id) throw new Error('Task object id is not defined in '+taskObject)
@@ -258,60 +294,65 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
258
294
  startedAt: new Date()
259
295
  })
260
296
  await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
297
+ const commonFunctions = {
298
+ async run(taskFunction: TaskFunction, props, progressFactor = 1, expire = undefined) {
299
+ if(typeof taskFunction !== 'function') {
300
+ console.log("TASK FUNCTION", taskFunction)
301
+ throw new Error('Task function is not a function')
302
+ }
303
+ //console.log("SUBTASK RUN", taskFunction.definition.name, props)
304
+ const subtaskProgress = { current: 0, total: 1, factor: progressFactor }
305
+ subtasksProgress.push(subtaskProgress)
306
+ try {
307
+ const result = await taskFunction(
308
+ props,
309
+ {
310
+ ...context,
311
+ taskObject: undefined,
312
+ causeType: 'task_Task',
313
+ cause: taskObject.id,
314
+ expire
315
+ },
316
+ (events) => app.emitEvents(serviceDefinition.name,
317
+ Array.isArray(events) ? events : [events], {}),
318
+ (current, total, action) => {
319
+ subtaskProgress.current = current
320
+ subtaskProgress.total = total
321
+ updateProgress()
322
+ }
323
+ )
324
+ //console.log("SUBTASK DONE", taskFunction.definition.name, props, '=>', result)
325
+ subtaskProgress.current = subtaskProgress.total
326
+ updateProgress()
327
+ return result
328
+ } catch(error) {
329
+ subtaskProgress.current = subtaskProgress.total // failed = finished
330
+ const outputError: any = new Error("Subtask error: " + error.toString())
331
+ outputError.stack = error.stack
332
+ outputError.taskNoRetry = true
333
+ throw outputError
334
+ }
335
+ },
336
+ progress: progressCounter((current, total, action, opts) => { // throttle this
337
+ selfProgress = {
338
+ ...opts,
339
+ current, total, action
340
+ }
341
+ updateProgress()
342
+ })
343
+ }
261
344
  const runContext = {
262
345
  ...context,
263
346
  task: {
264
347
  id: taskObject.id,
265
- async run(taskFunction: TaskFunction, props, progressFactor = 1, expire = undefined) {
266
- if(typeof taskFunction !== 'function') {
267
- console.log("TASK FUNCTION", taskFunction)
268
- throw new Error('Task function is not a function')
269
- }
270
- //console.log("SUBTASK RUN", taskFunction.definition.name, props)
271
- const subtaskProgress = { current: 0, total: 1, factor: progressFactor }
272
- subtasksProgress.push(subtaskProgress)
273
- try {
274
- const result = await taskFunction(
275
- props,
276
- {
277
- ...context,
278
- taskObject: undefined,
279
- causeType: 'task_Task',
280
- cause: taskObject.id,
281
- expire
282
- },
283
- (events) => app.emitEvents(serviceDefinition.name,
284
- Array.isArray(events) ? events : [events], {}),
285
- (current, total, action) => {
286
- subtaskProgress.current = current
287
- subtaskProgress.total = total
288
- updateProgress()
289
- }
290
- )
291
- //console.log("SUBTASK DONE", taskFunction.definition.name, props, '=>', result)
292
- subtaskProgress.current = subtaskProgress.total
293
- updateProgress()
294
- return result
295
- } catch(error) {
296
- subtaskProgress.current = subtaskProgress.total // failed = finished
297
- const outputError: any = new Error("Subtask error: " + error.toString())
298
- outputError.stack = error.stack
299
- outputError.taskNoRetry = true
300
- throw outputError
301
- }
302
- },
303
- async progress(current, total, action, opts) { // throttle this
304
- selfProgress = {
305
- ...opts,
306
- current, total, action
307
- }
308
- updateProgress()
309
- }
310
- },
348
+ ...commonFunctions
349
+ },
350
+ ...commonFunctions,
311
351
  async trigger(trigger, props) {
312
352
  return await app.trigger({
313
353
  causeType: 'task_Task',
314
354
  cause: taskObject.id,
355
+ client: context.client,
315
356
  ...trigger,
316
357
  }, props)
317
358
  },
@@ -319,6 +360,7 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
319
360
  return await app.triggerService({
320
361
  causeType: 'task_Task',
321
362
  cause: taskObject.id,
363
+ client: context.client,
322
364
  ...trigger
323
365
  }, props, returnArray)
324
366
  },
@@ -326,6 +368,7 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
326
368
  const tasks = await app.trigger({
327
369
  causeType: 'task_Task',
328
370
  cause: taskObject.id,
371
+ client: context.client,
329
372
  ...trigger
330
373
  }, data)
331
374
  const fullProgressSum = tasks.length * progressFactor
@@ -374,7 +417,7 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
374
417
  //console.log("TASK WATCHERS PROMISES FULLFILLED", taskWatchers)
375
418
  const results = taskWatchers.map(watcher => {
376
419
  //console.log("WATCHER OBSERVABLE", watcher.observable)
377
- watcher.observable.getValue().result
420
+ return watcher.observable.getValue().result
378
421
  })
379
422
  return results
380
423
  } catch(subtask) {
@@ -447,17 +490,17 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
447
490
  }
448
491
  }
449
492
 
450
- while(taskObject.state !== 'done' && taskObject.state !== 'failed') {
493
+ if(taskObject.state === 'failed') {
494
+ throw new Error(taskObject.retries[taskObject.retries.length - 1].error)
495
+ }
496
+
497
+ while(taskObject.state !== 'done' && taskObject.state !== 'fallbackDone' && taskObject.state !== 'failed') {
451
498
  console.log("RUNNING TASK", definition.name, "STATE", taskObject.state, "OBJECT", taskObject)
452
499
  await runTask()
453
500
  console.log("TASK", definition.name, "AFTER RUNTASK", taskObject)
454
501
  // console.log("TASK", definition.name, "AFTER RUNTASK", taskObject)
455
502
  }
456
503
 
457
- if(taskObject.state === 'failed') {
458
- throw new Error(taskObject.retries[taskObject.retries.length - 1].error)
459
- }
460
-
461
504
  return taskObject.result
462
505
  }
463
506
 
@@ -481,6 +524,7 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
481
524
  causeType: task.causeType,
482
525
  cause: task.cause,
483
526
  taskObject,
527
+ client: task.client
484
528
  }
485
529
  /// mark started subtasks as interrupted
486
530
  const subtasks = await app.serviceViewGet('task', 'tasksByRoot', {
@@ -526,10 +570,10 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
526
570
  },
527
571
  async execute(props, context, emit) {
528
572
  const startResult =
529
- await startTask(taskFunction, props,
573
+ await startTask(taskFunction, props,
530
574
  context.reaction.causeType ?? 'trigger',
531
575
  context.reaction.cause ?? context.reaction.id,
532
- props.taskExpire) // Must be done that way, so subtasks can be connected to the parent task
576
+ props.taskExpire, context.client) // Must be done that way, so subtasks can be connected to the parent task
533
577
  return startResult.task
534
578
  }
535
579
  })
@@ -543,10 +587,11 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
543
587
  },
544
588
  async execute(props, context, emit) {
545
589
  const startResult =
546
- await startTask(taskFunction, props,
590
+ await startTask(taskFunction,
591
+ props,
547
592
  context.reaction.causeType ?? 'trigger',
548
593
  context.reaction.cause ?? context.reaction.id,
549
- props.taskExpire) // Must be done that way, so subtasks can be connected to the parent task
594
+ props.taskExpire, context.client) // Must be done that way, so subtasks can be connected to the parent task
550
595
  return startResult.task
551
596
  }
552
597
  })
@@ -562,23 +607,18 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
562
607
  type: Task
563
608
  },
564
609
  async execute(props, context, emit) {
565
- const client = {
566
- ...context.client,
567
- sessionKey: undefined
568
- }
569
610
  const startResult =
570
- await startTask(taskFunction, { ...props, client }, 'command', context.command.id, undefined)
611
+ await startTask(taskFunction, props, 'command', context.command.id, undefined, context.client)
571
612
  return startResult.task
572
613
  },
573
614
  ...(typeof definition.action === 'object' && definition.action)
574
615
  })
575
616
 
576
-
577
617
  }
578
618
 
579
619
  taskFunction.definition = definition
580
- taskFunction.start = async (props, causeType, cause, expire = undefined) => {
581
- return await startTask(taskFunction, props, causeType, cause, expire)
620
+ taskFunction.start = async (props, causeType, cause, expire = undefined, client:ClientInfo) => {
621
+ return await startTask(taskFunction, props, causeType, cause, expire, client)
582
622
  }
583
623
  return taskFunction
584
624