@live-change/task-service 0.9.173 → 0.9.175

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/LICENSE.md +11 -0
  2. package/package.json +4 -4
  3. package/task.ts +215 -186
package/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2019-2024 Michał Łaszczewski
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/task-service",
3
- "version": "0.9.173",
3
+ "version": "0.9.175",
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.173",
26
- "@live-change/relations-plugin": "^0.9.173"
25
+ "@live-change/framework": "^0.9.175",
26
+ "@live-change/relations-plugin": "^0.9.175"
27
27
  },
28
- "gitHead": "1d70f7baf204d8f9848f53f5023db91016c6093f"
28
+ "gitHead": "526a46cfabf1044ceb3f6ad39dbb3749acca68fb"
29
29
  }
package/task.ts CHANGED
@@ -89,6 +89,7 @@ async function createOrReuseTask(taskDefinition, props, causeType, cause, expire
89
89
  return taskObject
90
90
  }
91
91
 
92
+ import { logs, SeverityNumber } from '@opentelemetry/api-logs'
92
93
 
93
94
  async function startTask(taskFunction, props, causeType, cause, expire, client:ClientInfo){
94
95
  const taskObject = await createOrReuseTask(taskFunction.definition, props, causeType, cause, expire, client)
@@ -98,7 +99,15 @@ async function startTask(taskFunction, props, causeType, cause, expire, client:C
98
99
  taskObject,
99
100
  client
100
101
  }
101
- console.log("START TASK!", taskFunction.name)
102
+ const logger = logs.getLogger('task', '0.0.1')
103
+ logger.emit({
104
+ severityNumber: SeverityNumber.INFO,
105
+ severityText: 'INFO',
106
+ body: 'START TASK!',
107
+ attributes: {
108
+ task: taskFunction.name
109
+ }
110
+ })
102
111
  const promise = taskFunction(props, context)
103
112
  return { task: taskObject.id, taskObject, promise, causeType, cause }
104
113
  }
@@ -231,6 +240,12 @@ function progressCounter(reportProgress) {
231
240
 
232
241
  type TaskFunction = (props, context: TaskExecuteContext, emit, reportProgress) => Promise<any>
233
242
 
243
+ import { context, propagation, trace } from '@opentelemetry/api'
244
+ import { SpanKind } from '@opentelemetry/api'
245
+ const tracer = trace.getTracer('live-change:triggerHandler')
246
+
247
+ const { expandObjectAttributes } = App.utils
248
+
234
249
  export default function task(definition:TaskDefinition, serviceDefinition) {
235
250
  if(!definition) throw new Error('Task definition is not defined')
236
251
  if(!serviceDefinition) throw new Error('Service definition is not defined')
@@ -303,206 +318,220 @@ export default function task(definition:TaskDefinition, serviceDefinition) {
303
318
  }
304
319
 
305
320
  const runTask = async () => {
306
- await updateTask({
307
- state: 'running',
308
- startedAt: new Date()
309
- })
310
- await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
311
- const commonFunctions = {
312
- async run(taskFunction: TaskFunction, props, progressFactor = 1, expire = undefined) {
313
- if(typeof taskFunction !== 'function') {
314
- console.log("TASK FUNCTION", taskFunction)
315
- throw new Error('Task function is not a function')
316
- }
317
- //console.log("SUBTASK RUN", taskFunction.definition.name, props)
318
- const subtaskProgress = { current: 0, total: 1, factor: progressFactor }
319
- subtasksProgress.push(subtaskProgress)
320
- try {
321
- const result = await taskFunction(
322
- props,
323
- {
324
- ...context,
325
- taskObject: undefined,
326
- causeType: 'task_Task',
327
- cause: taskObject.id,
328
- expire
329
- },
330
- (events) => app.emitEvents(serviceDefinition.name,
331
- Array.isArray(events) ? events : [events], {}),
332
- (current, total, action) => {
333
- subtaskProgress.current = current
334
- subtaskProgress.total = total
335
- updateProgress()
336
- }
337
- )
338
- //console.log("SUBTASK DONE", taskFunction.definition.name, props, '=>', result)
339
- subtaskProgress.current = subtaskProgress.total
340
- updateProgress()
341
- return result
342
- } catch(error) {
343
- subtaskProgress.current = subtaskProgress.total // failed = finished
344
- const outputError: any = new Error("Subtask error: " + error.toString())
345
- outputError.stack = error.stack
346
- outputError.taskNoRetry = true
347
- throw outputError
348
- }
349
- },
350
- progress: progressCounter((current, total, action, opts) => { // throttle this
351
- selfProgress = {
352
- ...opts,
353
- current, total, action
354
- }
355
- updateProgress()
321
+ return tracer.startActiveSpan('runTask:'+serviceDefinition.name+'.'+definition.name, {
322
+ kind: SpanKind.INTERNAL,
323
+ attributes: {
324
+ ...expandObjectAttributes(taskObject, 'task'),
325
+ service: serviceDefinition.name,
326
+ taskName: definition.name
327
+ }
328
+ }, async (taskSpan) => {
329
+ await updateTask({
330
+ state: 'running',
331
+ startedAt: new Date()
356
332
  })
357
- }
358
- const runContext = {
359
- ...context,
360
- task: {
361
- id: taskObject.id,
362
- ...commonFunctions
363
- },
364
- ...commonFunctions,
365
- async trigger(trigger, props) {
366
- return await app.trigger({
367
- causeType: 'task_Task',
368
- cause: taskObject.id,
369
- client: context.client,
370
- ...trigger,
371
- }, props)
372
- },
373
- async triggerService(trigger, props, returnArray = false) {
374
- return await app.triggerService({
375
- causeType: 'task_Task',
376
- cause: taskObject.id,
377
- client: context.client,
378
- ...trigger
379
- }, props, returnArray)
380
- },
381
- async triggerTask(trigger, data, progressFactor = 1) {
382
- const tasks = await app.trigger({
383
- causeType: 'task_Task',
384
- cause: taskObject.id,
385
- client: context.client,
386
- ...trigger
387
- }, data)
388
- const fullProgressSum = tasks.length * progressFactor
389
- const task = this
390
- const taskWatchers = tasks.map(task => {
391
- const observable = Task.observable(task)
392
- if(!observable) {
393
- console.error("SUBTASK OBSERVABLE NOT FOUND", task)
394
- throw new Error("SUBTASK OBSERVABLE NOT FOUND")
333
+ await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
334
+ const commonFunctions = {
335
+ async run(taskFunction: TaskFunction, props, progressFactor = 1, expire = undefined) {
336
+ if(typeof taskFunction !== 'function') {
337
+ console.log("TASK FUNCTION", taskFunction)
338
+ throw new Error('Task function is not a function')
395
339
  }
396
- const watcher: any = {
397
- observable,
398
- observer(signal, value) {
399
- if(signal !== 'set') return
400
- if(value) {
401
- if(value.progress) {
402
- watcher.progress = value.progress
403
- updateProgress()
404
- }
405
- if(value.state === 'done') {
406
- watcher.resolve(value.result)
407
- } else if(value.state === 'failed') {
408
- watcher.reject(value)
409
- }
340
+ //console.log("SUBTASK RUN", taskFunction.definition.name, props)
341
+ const subtaskProgress = { current: 0, total: 1, factor: progressFactor }
342
+ subtasksProgress.push(subtaskProgress)
343
+ try {
344
+ const result = await taskFunction(
345
+ props,
346
+ {
347
+ ...context,
348
+ taskObject: undefined,
349
+ causeType: 'task_Task',
350
+ cause: taskObject.id,
351
+ expire
352
+ },
353
+ (events) => app.emitEvents(serviceDefinition.name,
354
+ Array.isArray(events) ? events : [events], {}),
355
+ (current, total, action) => {
356
+ subtaskProgress.current = current
357
+ subtaskProgress.total = total
358
+ updateProgress()
410
359
  }
411
- },
412
- progress: {
413
- current: 0,
414
- total: 1,
415
- factor: progressFactor/tasks.length
416
- },
417
- run(resolve, reject) {
418
- watcher.resolve = resolve
419
- watcher.reject = reject
420
- //console.log("SUBTASK WATCHER", watcher, "TASK OBSERVABLE", watcher.observable)
421
- subtasksProgress.push(watcher.progress)
422
- watcher.observable.observe(watcher.observer)
423
- }
360
+ )
361
+ //console.log("SUBTASK DONE", taskFunction.definition.name, props, '=>', result)
362
+ subtaskProgress.current = subtaskProgress.total
363
+ updateProgress()
364
+ return result
365
+ } catch(error) {
366
+ subtaskProgress.current = subtaskProgress.total // failed = finished
367
+ const outputError: any = new Error("Subtask error: " + error.toString())
368
+ outputError.stack = error.stack
369
+ outputError.taskNoRetry = true
370
+ throw outputError
424
371
  }
425
- return watcher
426
- })
427
-
428
- const promises = taskWatchers.map(watcher => new Promise((resolve, reject) => watcher.run(resolve, reject)))
429
- try {
430
- await Promise.all(promises)
431
- //console.log("TASK WATCHERS PROMISES FULLFILLED", taskWatchers)
432
- const results = taskWatchers.map(watcher => {
433
- //console.log("WATCHER OBSERVABLE", watcher.observable)
434
- return watcher.observable.getValue().result
435
- })
436
- return results
437
- } catch(subtask) {
438
- const retry = subtask.retries?.at(-1)
439
- const outputError: any = new Error("Subtask error: " + retry?.error)
440
- outputError.stack = retry?.stack
441
- outputError.taskNoRetry = true
442
- throw outputError
443
- }
372
+ },
373
+ progress: progressCounter((current, total, action, opts) => { // throttle this
374
+ selfProgress = {
375
+ ...opts,
376
+ current, total, action
377
+ }
378
+ updateProgress()
379
+ })
444
380
  }
445
- }
446
- try {
447
- const result = await definition.execute(props, runContext, emit)
448
- await updateTask({
449
- state: 'done',
450
- doneAt: new Date(),
451
- result
381
+ const loggingHelpers = App.utils.loggingHelpers(serviceDefinition.name, app.config.clientConfig.version, {
382
+ taskName: definition.name,
383
+ taskId: taskObject.id
452
384
  })
453
- await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
454
- } catch(error) {
455
- if((taskObject.retries?.length || 0) >= taskObject.maxRetries - 1 || error.taskNoRetry) {
385
+ const runContext = {
386
+ ...context,
387
+ task: {
388
+ id: taskObject.id,
389
+ ...commonFunctions
390
+ },
391
+ ...commonFunctions,
392
+ async trigger(trigger, props) {
393
+ return await app.trigger({
394
+ causeType: 'task_Task',
395
+ cause: taskObject.id,
396
+ client: context.client,
397
+ ...trigger,
398
+ }, props)
399
+ },
400
+ async triggerService(trigger, props, returnArray = false) {
401
+ return await app.triggerService({
402
+ causeType: 'task_Task',
403
+ cause: taskObject.id,
404
+ client: context.client,
405
+ ...trigger
406
+ }, props, returnArray)
407
+ },
408
+ async triggerTask(trigger, data, progressFactor = 1) {
409
+ const tasks = await app.trigger({
410
+ causeType: 'task_Task',
411
+ cause: taskObject.id,
412
+ client: context.client,
413
+ ...trigger
414
+ }, data)
415
+ const fullProgressSum = tasks.length * progressFactor
416
+ const task = this
417
+ const taskWatchers = tasks.map(task => {
418
+ const observable = Task.observable(task)
419
+ if(!observable) {
420
+ console.error("SUBTASK OBSERVABLE NOT FOUND", task)
421
+ throw new Error("SUBTASK OBSERVABLE NOT FOUND")
422
+ }
423
+ const watcher: any = {
424
+ observable,
425
+ observer(signal, value) {
426
+ if(signal !== 'set') return
427
+ if(value) {
428
+ if(value.progress) {
429
+ watcher.progress = value.progress
430
+ updateProgress()
431
+ }
432
+ if(value.state === 'done') {
433
+ watcher.resolve(value.result)
434
+ } else if(value.state === 'failed') {
435
+ watcher.reject(value)
436
+ }
437
+ }
438
+ },
439
+ progress: {
440
+ current: 0,
441
+ total: 1,
442
+ factor: progressFactor/tasks.length
443
+ },
444
+ run(resolve, reject) {
445
+ watcher.resolve = resolve
446
+ watcher.reject = reject
447
+ //console.log("SUBTASK WATCHER", watcher, "TASK OBSERVABLE", watcher.observable)
448
+ subtasksProgress.push(watcher.progress)
449
+ watcher.observable.observe(watcher.observer)
450
+ }
451
+ }
452
+ return watcher
453
+ })
454
+
455
+ const promises = taskWatchers.map(watcher => new Promise((resolve, reject) => watcher.run(resolve, reject)))
456
+ try {
457
+ await Promise.all(promises)
458
+ //console.log("TASK WATCHERS PROMISES FULLFILLED", taskWatchers)
459
+ const results = taskWatchers.map(watcher => {
460
+ //console.log("WATCHER OBSERVABLE", watcher.observable)
461
+ return watcher.observable.getValue().result
462
+ })
463
+ return results
464
+ } catch(subtask) {
465
+ const retry = subtask.retries?.at(-1)
466
+ const outputError: any = new Error("Subtask error: " + retry?.error)
467
+ outputError.stack = retry?.stack
468
+ outputError.taskNoRetry = true
469
+ throw outputError
470
+ }
471
+ },
472
+ ...loggingHelpers
473
+ }
474
+ try {
475
+ const result = await definition.execute(props, runContext, emit)
456
476
  await updateTask({
457
- state: (definition.fallback && !error.taskNoFallback) ? 'fallback' : 'failed',
477
+ state: 'done',
458
478
  doneAt: new Date(),
459
- error: /*error.stack ??*/ error.message ?? error,
460
- retries: [...(taskObject.retries || []), {
461
- startedAt: taskObject.startedAt,
462
- failedAt: new Date(),
463
- error: /*error.stack ??*/ error.message ?? error,
464
- stack: error.stack
465
- }]
479
+ result
466
480
  })
467
- console.error("TASK", taskObject.id, "OF TYPE", definition.name,
468
- "WITH PARAMETERS", props, "FAILED WITH ERROR", error.stack ?? error.message ?? error)
469
- if(definition.fallback && !error.taskNoFallback) {
470
- await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
471
- let result
472
- if(typeof definition.fallback !== 'function') {
473
- result = definition.fallback
481
+ await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
482
+ } catch(error) {
483
+ if((taskObject.retries?.length || 0) >= taskObject.maxRetries - 1 || error.taskNoRetry) {
484
+ await updateTask({
485
+ state: (definition.fallback && !error.taskNoFallback) ? 'fallback' : 'failed',
486
+ doneAt: new Date(),
487
+ error: /*error.stack ??*/ error.message ?? error,
488
+ retries: [...(taskObject.retries || []), {
489
+ startedAt: taskObject.startedAt,
490
+ failedAt: new Date(),
491
+ error: /*error.stack ??*/ error.message ?? error,
492
+ stack: error.stack
493
+ }]
494
+ })
495
+ console.error("TASK", taskObject.id, "OF TYPE", definition.name,
496
+ "WITH PARAMETERS", props, "FAILED WITH ERROR", error.stack ?? error.message ?? error)
497
+ if(definition.fallback && !error.taskNoFallback) {
498
+ await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
499
+ let result
500
+ if(typeof definition.fallback !== 'function') {
501
+ result = definition.fallback
502
+ } else {
503
+ result = definition.fallback(props, runContext, error.message ?? error)
504
+ }
505
+ await updateTask({
506
+ state: 'fallbackDone',
507
+ result
508
+ })
474
509
  } else {
475
- result = definition.fallback(props, runContext, error.message ?? error)
510
+ await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
511
+ throw error
476
512
  }
513
+ } else {
514
+ const retriesCount = (taskObject.retries || []).length
477
515
  await updateTask({
478
- state: 'fallbackDone',
479
- result
516
+ state: 'retrying',
517
+ retries: [...(taskObject.retries || []), {
518
+ startedAt: taskObject.startedAt,
519
+ failedAt: new Date(),
520
+ error: error.message ?? error,
521
+ stack: error.stack
522
+ }]
480
523
  })
481
- } else {
482
- await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
483
- throw error
524
+ const retryDelay = definition.retryDelay ? definition.retryDelay(retriesCount) : 1000 * Math.pow(2, retriesCount)
525
+ console.log("RETRYING TASK", taskObject.id, "IN", retryDelay, "ms")
526
+ await new Promise(resolve => setTimeout(resolve, retryDelay))
527
+ }
528
+ await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
529
+ } finally {
530
+ if(definition.cleanup) {
531
+ await definition.cleanup(props, runContext)
484
532
  }
485
- } else {
486
- const retriesCount = (taskObject.retries || []).length
487
- await updateTask({
488
- state: 'retrying',
489
- retries: [...(taskObject.retries || []), {
490
- startedAt: taskObject.startedAt,
491
- failedAt: new Date(),
492
- error: error.message ?? error,
493
- stack: error.stack
494
- }]
495
- })
496
- const retryDelay = definition.retryDelay ? definition.retryDelay(retriesCount) : 1000 * Math.pow(2, retriesCount)
497
- console.log("RETRYING TASK", taskObject.id, "IN", retryDelay, "ms")
498
- await new Promise(resolve => setTimeout(resolve, retryDelay))
499
- }
500
- await triggerOnTaskStateChange(taskObject, context.causeType, context.cause)
501
- } finally {
502
- if(definition.cleanup) {
503
- await definition.cleanup(props, runContext)
504
533
  }
505
- }
534
+ })
506
535
  }
507
536
 
508
537
  if(taskObject.state === 'failed') {