@adminforth/background-jobs 1.13.1 → 1.14.0

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/build.log CHANGED
@@ -13,5 +13,5 @@ custom/tsconfig.json
13
13
  custom/useBackgroundJobApi.ts
14
14
  custom/utils.ts
15
15
 
16
- sent 22,184 bytes received 172 bytes 44,712.00 bytes/sec
17
- total size is 21,551 speedup is 0.96
16
+ sent 23,563 bytes received 172 bytes 47,470.00 bytes/sec
17
+ total size is 22,930 speedup is 0.97
@@ -27,6 +27,7 @@ import { useBackgroundJobApi } from './useBackgroundJobApi';
27
27
  const jobStore = useBackgroundJobApi();
28
28
 
29
29
  const dialogRef = ref<any>(null);
30
+ let unsubscribeJobUpdates: (() => void) | undefined;
30
31
 
31
32
  const props = defineProps<{
32
33
  meta: {
@@ -69,7 +70,7 @@ onMounted(() => {
69
70
  // @ts-ignore
70
71
  window.OpenJobInfoPopup = openJobInfo;
71
72
 
72
- websocket.subscribe('/background-jobs', (data) => {
73
+ unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
73
74
  if (data.jobId === jobStore.currentJob?.id) {
74
75
  if (data.status) {
75
76
  jobStore.updateCurrentJob({ status: data.status });
@@ -90,6 +91,6 @@ onBeforeUnmount(() => {
90
91
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
91
92
  // @ts-ignore
92
93
  if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
93
- websocket.unsubscribe('/background-jobs');
94
+ unsubscribeJobUpdates?.();
94
95
  });
95
96
  </script>
@@ -69,7 +69,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
69
69
  import { useI18n } from 'vue-i18n';
70
70
  import StateToIcon from './StateToIcon.vue';
71
71
  import { useAdminforth } from '@/adminforth';
72
- import { onBeforeUnmount, ref, watch } from 'vue';
72
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
73
73
  import websocket from '@/websocket';
74
74
  import { useBackgroundJobApi } from './useBackgroundJobApi';
75
75
 
@@ -115,13 +115,11 @@ function createStateFieldSubscription(
115
115
  callback: (data: any) => void,
116
116
  ) {
117
117
  const paths = getUniqueFieldNames(fieldNames).map(pathFactory);
118
- for (const path of paths) {
119
- websocket.subscribe(path, callback);
120
- }
118
+ const pathCleanups = paths.map((path) => websocket.subscribe(path, callback));
121
119
 
122
120
  const unsubscribe = () => {
123
- for (const path of paths) {
124
- websocket.unsubscribe(path);
121
+ for (const cleanup of pathCleanups) {
122
+ cleanup();
125
123
  }
126
124
  subscriptionCleanups.delete(unsubscribe);
127
125
  };
@@ -155,6 +153,50 @@ function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
155
153
  };
156
154
  }
157
155
 
156
+ function handleTaskStatusUpdate(data: { taskIndex: number; status: string }) {
157
+ if (!jobTasks.value[data.taskIndex]) {
158
+ return;
159
+ }
160
+
161
+ jobTasks.value[data.taskIndex].status = data.status;
162
+ }
163
+
164
+ function handleJobUpdate(data: {
165
+ jobId: string;
166
+ status?: IJob['status'];
167
+ progress?: string;
168
+ finishedAt?: Date;
169
+ state?: Record<string, any>;
170
+ }) {
171
+ if (data.jobId !== props.job.id) {
172
+ return;
173
+ }
174
+
175
+ if (data.status) {
176
+ props.job.status = data.status;
177
+ }
178
+ if (data.progress !== undefined) {
179
+ props.job.progress = data.progress;
180
+ }
181
+ if (data.finishedAt) {
182
+ props.job.finishedAt = data.finishedAt;
183
+ }
184
+ if (data.state) {
185
+ props.job.state = {
186
+ ...props.job.state,
187
+ ...data.state,
188
+ };
189
+ }
190
+ if (jobStore.currentJob?.id === props.job.id) {
191
+ jobStore.updateCurrentJob({
192
+ status: props.job.status,
193
+ progress: props.job.progress,
194
+ finishedAt: props.job.finishedAt,
195
+ state: props.job.state,
196
+ });
197
+ }
198
+ }
199
+
158
200
  function subscribeToJobStateFields(fieldNames: string[]) {
159
201
  return createStateFieldSubscription(
160
202
  fieldNames,
@@ -241,6 +283,13 @@ watch(
241
283
  { immediate: true }
242
284
  );
243
285
 
286
+ onMounted(() => {
287
+ const taskStatusPath = `/background-jobs-task-update/${props.job.id}`;
288
+ subscriptionCleanups.add(websocket.subscribe(taskStatusPath, handleTaskStatusUpdate));
289
+
290
+ subscriptionCleanups.add(websocket.subscribe('/background-jobs-job-update', handleJobUpdate));
291
+ });
292
+
244
293
  onBeforeUnmount(() => {
245
294
  for (const unsubscribe of Array.from(subscriptionCleanups)) {
246
295
  unsubscribe();
@@ -3,7 +3,7 @@
3
3
  <div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
4
4
  <div class="relative flex items-center justify-center" v-if="jobs.length > 0">
5
5
  <Tooltip>
6
- <IconBriefcaseSolid class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
6
+ <IconCheckCircleOutline class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
7
7
  <template #tooltip>
8
8
  {{ t('All jobs completed') }}
9
9
  </template>
@@ -48,8 +48,8 @@
48
48
  <script setup lang="ts">
49
49
  import type { AdminUser } from 'adminforth';
50
50
  import { onMounted, onUnmounted, ref, computed } from 'vue';
51
- import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
52
51
  import { Tooltip } from '@/afcl';
52
+ import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
53
53
  import { useI18n } from 'vue-i18n';
54
54
  import JobsList from './JobsList.vue';
55
55
  import type { IJob } from './utils';
@@ -69,6 +69,7 @@
69
69
  const isDropdownOpen = ref(false);
70
70
  const jobs = ref<IJob[]>([]);
71
71
  const dropdownRef = ref<HTMLElement | null>(null);
72
+ let unsubscribeJobUpdates: (() => void) | undefined;
72
73
 
73
74
  onClickOutside(dropdownRef, () => {
74
75
  isDropdownOpen.value = false;
@@ -85,7 +86,7 @@
85
86
 
86
87
 
87
88
  onMounted(async () => {
88
- websocket.subscribe('/background-jobs', (data) => {
89
+ unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
89
90
  const jobIndex = jobs.value.findIndex(job => job.id === data.jobId);
90
91
  if (jobIndex !== -1) {
91
92
  if (data.status) {
@@ -130,7 +131,7 @@
130
131
 
131
132
 
132
133
  onUnmounted(() => {
133
- websocket.unsubscribe('/background-jobs');
134
+ unsubscribeJobUpdates?.();
134
135
  });
135
136
 
136
137
  </script>
@@ -147,4 +148,4 @@
147
148
  opacity: 0;
148
149
  }
149
150
  }
150
- </style>
151
+ </style>
@@ -27,6 +27,7 @@ import { useBackgroundJobApi } from './useBackgroundJobApi';
27
27
  const jobStore = useBackgroundJobApi();
28
28
 
29
29
  const dialogRef = ref<any>(null);
30
+ let unsubscribeJobUpdates: (() => void) | undefined;
30
31
 
31
32
  const props = defineProps<{
32
33
  meta: {
@@ -69,7 +70,7 @@ onMounted(() => {
69
70
  // @ts-ignore
70
71
  window.OpenJobInfoPopup = openJobInfo;
71
72
 
72
- websocket.subscribe('/background-jobs', (data) => {
73
+ unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
73
74
  if (data.jobId === jobStore.currentJob?.id) {
74
75
  if (data.status) {
75
76
  jobStore.updateCurrentJob({ status: data.status });
@@ -90,6 +91,6 @@ onBeforeUnmount(() => {
90
91
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
91
92
  // @ts-ignore
92
93
  if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
93
- websocket.unsubscribe('/background-jobs');
94
+ unsubscribeJobUpdates?.();
94
95
  });
95
96
  </script>
@@ -69,7 +69,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
69
69
  import { useI18n } from 'vue-i18n';
70
70
  import StateToIcon from './StateToIcon.vue';
71
71
  import { useAdminforth } from '@/adminforth';
72
- import { onBeforeUnmount, ref, watch } from 'vue';
72
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
73
73
  import websocket from '@/websocket';
74
74
  import { useBackgroundJobApi } from './useBackgroundJobApi';
75
75
 
@@ -115,13 +115,11 @@ function createStateFieldSubscription(
115
115
  callback: (data: any) => void,
116
116
  ) {
117
117
  const paths = getUniqueFieldNames(fieldNames).map(pathFactory);
118
- for (const path of paths) {
119
- websocket.subscribe(path, callback);
120
- }
118
+ const pathCleanups = paths.map((path) => websocket.subscribe(path, callback));
121
119
 
122
120
  const unsubscribe = () => {
123
- for (const path of paths) {
124
- websocket.unsubscribe(path);
121
+ for (const cleanup of pathCleanups) {
122
+ cleanup();
125
123
  }
126
124
  subscriptionCleanups.delete(unsubscribe);
127
125
  };
@@ -155,6 +153,50 @@ function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
155
153
  };
156
154
  }
157
155
 
156
+ function handleTaskStatusUpdate(data: { taskIndex: number; status: string }) {
157
+ if (!jobTasks.value[data.taskIndex]) {
158
+ return;
159
+ }
160
+
161
+ jobTasks.value[data.taskIndex].status = data.status;
162
+ }
163
+
164
+ function handleJobUpdate(data: {
165
+ jobId: string;
166
+ status?: IJob['status'];
167
+ progress?: string;
168
+ finishedAt?: Date;
169
+ state?: Record<string, any>;
170
+ }) {
171
+ if (data.jobId !== props.job.id) {
172
+ return;
173
+ }
174
+
175
+ if (data.status) {
176
+ props.job.status = data.status;
177
+ }
178
+ if (data.progress !== undefined) {
179
+ props.job.progress = data.progress;
180
+ }
181
+ if (data.finishedAt) {
182
+ props.job.finishedAt = data.finishedAt;
183
+ }
184
+ if (data.state) {
185
+ props.job.state = {
186
+ ...props.job.state,
187
+ ...data.state,
188
+ };
189
+ }
190
+ if (jobStore.currentJob?.id === props.job.id) {
191
+ jobStore.updateCurrentJob({
192
+ status: props.job.status,
193
+ progress: props.job.progress,
194
+ finishedAt: props.job.finishedAt,
195
+ state: props.job.state,
196
+ });
197
+ }
198
+ }
199
+
158
200
  function subscribeToJobStateFields(fieldNames: string[]) {
159
201
  return createStateFieldSubscription(
160
202
  fieldNames,
@@ -241,6 +283,13 @@ watch(
241
283
  { immediate: true }
242
284
  );
243
285
 
286
+ onMounted(() => {
287
+ const taskStatusPath = `/background-jobs-task-update/${props.job.id}`;
288
+ subscriptionCleanups.add(websocket.subscribe(taskStatusPath, handleTaskStatusUpdate));
289
+
290
+ subscriptionCleanups.add(websocket.subscribe('/background-jobs-job-update', handleJobUpdate));
291
+ });
292
+
244
293
  onBeforeUnmount(() => {
245
294
  for (const unsubscribe of Array.from(subscriptionCleanups)) {
246
295
  unsubscribe();
@@ -3,7 +3,7 @@
3
3
  <div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
4
4
  <div class="relative flex items-center justify-center" v-if="jobs.length > 0">
5
5
  <Tooltip>
6
- <IconBriefcaseSolid class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
6
+ <IconCheckCircleOutline class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
7
7
  <template #tooltip>
8
8
  {{ t('All jobs completed') }}
9
9
  </template>
@@ -48,8 +48,8 @@
48
48
  <script setup lang="ts">
49
49
  import type { AdminUser } from 'adminforth';
50
50
  import { onMounted, onUnmounted, ref, computed } from 'vue';
51
- import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
52
51
  import { Tooltip } from '@/afcl';
52
+ import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
53
53
  import { useI18n } from 'vue-i18n';
54
54
  import JobsList from './JobsList.vue';
55
55
  import type { IJob } from './utils';
@@ -69,6 +69,7 @@
69
69
  const isDropdownOpen = ref(false);
70
70
  const jobs = ref<IJob[]>([]);
71
71
  const dropdownRef = ref<HTMLElement | null>(null);
72
+ let unsubscribeJobUpdates: (() => void) | undefined;
72
73
 
73
74
  onClickOutside(dropdownRef, () => {
74
75
  isDropdownOpen.value = false;
@@ -85,7 +86,7 @@
85
86
 
86
87
 
87
88
  onMounted(async () => {
88
- websocket.subscribe('/background-jobs', (data) => {
89
+ unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
89
90
  const jobIndex = jobs.value.findIndex(job => job.id === data.jobId);
90
91
  if (jobIndex !== -1) {
91
92
  if (data.status) {
@@ -130,7 +131,7 @@
130
131
 
131
132
 
132
133
  onUnmounted(() => {
133
- websocket.unsubscribe('/background-jobs');
134
+ unsubscribeJobUpdates?.();
134
135
  });
135
136
 
136
137
  </script>
@@ -147,4 +148,4 @@
147
148
  opacity: 0;
148
149
  }
149
150
  }
150
- </style>
151
+ </style>
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
25
25
  this.jobParallelLimits = {};
26
26
  this.levelDbInstances = {};
27
27
  this.jobStateMutexes = {};
28
+ this.deprecatedWarningsShown = new Set();
28
29
  this.options = options;
29
30
  this.shouldHaveSingleInstancePerWholeApp = () => true;
30
31
  }
@@ -190,6 +191,13 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
190
191
  });
191
192
  }
192
193
  }
194
+ warnDeprecatedOnce(key, message) {
195
+ if (this.deprecatedWarningsShown.has(key)) {
196
+ return;
197
+ }
198
+ this.deprecatedWarningsShown.add(key);
199
+ afLogger.warn(message);
200
+ }
193
201
  triggerOnAllTasksDone(onAllTasksDone, levelDb, jobId) {
194
202
  return __awaiter(this, void 0, void 0, function* () {
195
203
  if (!onAllTasksDone) {
@@ -219,8 +227,8 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
219
227
  registerTaskDetailsComponent({ jobHandlerName, component, }) {
220
228
  this.jobCustomComponents[jobHandlerName] = component;
221
229
  }
222
- startNewJob(jobName, adminUser, tasks, jobHandlerName) {
223
- return __awaiter(this, void 0, void 0, function* () {
230
+ startNewJob(jobName_1, adminUser_1, tasks_1, jobHandlerName_1) {
231
+ return __awaiter(this, arguments, void 0, function* (jobName, adminUser, tasks, jobHandlerName, initialState = {}) {
224
232
  const handleTask = this.taskHandlers[jobHandlerName];
225
233
  const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
226
234
  if (!handleTask) {
@@ -235,7 +243,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
235
243
  [this.options.progressField]: 0,
236
244
  [this.options.statusField]: 'IN_PROGRESS',
237
245
  [this.options.jobHandlerField]: jobHandlerName,
238
- [this.options.stateField]: '{}'
246
+ [this.options.stateField]: initialState
239
247
  };
240
248
  const creationResult = yield this.adminforth.resource(this.getResourceId()).create(objectToSave);
241
249
  let createdRecord = null;
@@ -246,7 +254,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
246
254
  throw new Error(`Failed to create a record for the job. Error: ${creationResult.error}`);
247
255
  }
248
256
  const jobId = createdRecord[this.getResourcePk()];
249
- this.adminforth.websocket.publish('/background-jobs', {
257
+ this.adminforth.websocket.publish('/background-jobs-job-update', {
250
258
  jobId,
251
259
  status: 'IN_PROGRESS',
252
260
  name: jobName,
@@ -288,19 +296,34 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
288
296
  afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
289
297
  return;
290
298
  }
291
- //define the setTaskStateField and getTaskStateField functions to pass to the task
292
- const setTaskStateField = (state) => __awaiter(this, void 0, void 0, function* () {
293
- yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
294
- this.publishTaskStateFields(jobId, taskIndex, state);
295
- });
296
- const getTaskStateField = () => __awaiter(this, void 0, void 0, function* () {
299
+ const getState = () => __awaiter(this, void 0, void 0, function* () {
297
300
  return yield this.getLevelDbTaskStateField(jobLevelDb, taskIndex.toString());
298
301
  });
302
+ const setTaskStateField = (fieldNameOrState, value) => __awaiter(this, void 0, void 0, function* () {
303
+ if (typeof fieldNameOrState === 'string') {
304
+ const state = yield getState();
305
+ const updatedState = Object.assign(Object.assign({}, state), { [fieldNameOrState]: value });
306
+ yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), updatedState);
307
+ this.publishTaskStateFields(jobId, taskIndex, { [fieldNameOrState]: value });
308
+ return;
309
+ }
310
+ this.warnDeprecatedOnce('setTaskStateField-object', 'BackgroundJobsPlugin: setTaskStateField(stateObject) is deprecated and will be removed soon. Use setTaskStateField(fieldName: string, value: any) instead. Use getState() when you need the full task state.');
311
+ yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), fieldNameOrState);
312
+ this.publishTaskStateFields(jobId, taskIndex, fieldNameOrState);
313
+ });
314
+ const getTaskStateField = (fieldName) => __awaiter(this, void 0, void 0, function* () {
315
+ const state = yield getState();
316
+ if (typeof fieldName === 'string') {
317
+ return state[fieldName];
318
+ }
319
+ this.warnDeprecatedOnce('getTaskStateField-no-args', 'BackgroundJobsPlugin: getTaskStateField() without a field name is deprecated and will be removed soon. Use getTaskStateField(fieldName: string) for one field, or getState() for the full task state.');
320
+ return state;
321
+ });
299
322
  yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'IN_PROGRESS');
300
323
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
301
324
  //handling the task
302
325
  try {
303
- yield handleTask({ jobId, setTaskStateField, getTaskStateField });
326
+ yield handleTask({ jobId, setTaskStateField, getTaskStateField, getState });
304
327
  //Set task status to completed in level db
305
328
  yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
306
329
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
@@ -308,7 +331,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
308
331
  catch (error) {
309
332
  const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || 'Unknown error';
310
333
  afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`);
311
- yield this.setJobField(jobId, 'error', errorMessage);
334
+ yield this.setJobStateField(jobId, 'error', errorMessage);
312
335
  yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
313
336
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
314
337
  failedTasks++;
@@ -335,7 +358,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
335
358
  [this.options.statusField]: 'DONE',
336
359
  [this.options.finishedAtField]: (new Date()).toISOString(),
337
360
  });
338
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
361
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
339
362
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
340
363
  yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
341
364
  }
@@ -344,7 +367,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
344
367
  [this.options.statusField]: 'DONE_WITH_ERRORS',
345
368
  [this.options.finishedAtField]: (new Date()).toISOString(),
346
369
  });
347
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
370
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE_WITH_ERRORS' });
348
371
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
349
372
  yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
350
373
  }
@@ -367,7 +390,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
367
390
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
368
391
  [this.options.progressField]: progress,
369
392
  });
370
- this.adminforth.websocket.publish('/background-jobs', { jobId, progress });
393
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, progress });
371
394
  return completedTasks;
372
395
  });
373
396
  }
@@ -411,31 +434,40 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
411
434
  yield this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit, onAllTasksDone);
412
435
  });
413
436
  }
414
- setJobField(jobId, key, value) {
437
+ setJobStateField(jobId, key, value) {
415
438
  return __awaiter(this, void 0, void 0, function* () {
416
439
  const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
417
440
  const state = jobRecord[this.options.stateField];
418
- const parsedState = JSON.parse(state);
419
- parsedState[key] = value;
441
+ state[key] = value;
420
442
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
421
- [this.options.stateField]: JSON.stringify(parsedState),
443
+ [this.options.stateField]: state,
422
444
  });
423
445
  this.publishJobStateField(jobId, key, value);
424
446
  });
425
447
  }
426
- getJobField(jobId, key) {
448
+ getJobStateField(jobId, key) {
427
449
  return __awaiter(this, void 0, void 0, function* () {
428
450
  const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
429
451
  const state = jobRecord[this.options.stateField];
430
- const parsedState = JSON.parse(state);
431
- return parsedState[key];
452
+ return state[key];
432
453
  });
433
454
  }
434
455
  getJobState(jobId) {
435
456
  return __awaiter(this, void 0, void 0, function* () {
436
457
  const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
437
- const state = jobRecord[this.options.stateField];
438
- return JSON.parse(state);
458
+ return jobRecord[this.options.stateField];
459
+ });
460
+ }
461
+ setJobField(jobId, key, value) {
462
+ return __awaiter(this, void 0, void 0, function* () {
463
+ this.warnDeprecatedOnce('setJobField', 'BackgroundJobsPlugin: setJobField(jobId, key, value) is deprecated and will be removed soon. Use setJobStateField(jobId, fieldName: string, value: any) instead.');
464
+ return this.setJobStateField(jobId, key, value);
465
+ });
466
+ }
467
+ getJobField(jobId, key) {
468
+ return __awaiter(this, void 0, void 0, function* () {
469
+ this.warnDeprecatedOnce('getJobField', 'BackgroundJobsPlugin: getJobField(jobId, key) is deprecated and will be removed soon. Use getJobStateField(jobId, fieldName: string) instead.');
470
+ return this.getJobStateField(jobId, key);
439
471
  });
440
472
  }
441
473
  updateJobFieldsAtomically(jobId, updateFunction) {
@@ -504,7 +536,6 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
504
536
  createdAt: job[this.options.createdAtField],
505
537
  finishedAt: job[this.options.finishedAtField] || null,
506
538
  status: job[this.options.statusField],
507
- state: JSON.parse(job[this.options.stateField]),
508
539
  progress: job[this.options.progressField],
509
540
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
510
541
  };
@@ -527,7 +558,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
527
558
  createdAt: job[this.options.createdAtField],
528
559
  finishedAt: job[this.options.finishedAtField] || null,
529
560
  status: job[this.options.statusField],
530
- state: JSON.parse(job[this.options.stateField]),
561
+ state: job[this.options.stateField],
531
562
  progress: job[this.options.progressField],
532
563
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
533
564
  };
@@ -549,7 +580,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
549
580
  [this.options.statusField]: 'CANCELLED',
550
581
  [this.options.finishedAtField]: (new Date()).toISOString(),
551
582
  });
552
- this.adminforth.websocket.publish('/background-jobs', {
583
+ this.adminforth.websocket.publish('/background-jobs-job-update', {
553
584
  jobId,
554
585
  status: 'CANCELLED',
555
586
  });
package/index.ts CHANGED
@@ -8,9 +8,16 @@ import fs from 'fs/promises';
8
8
  import { Mutex } from 'async-mutex';
9
9
 
10
10
  type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
11
- type setStateFieldParams = (state: Record<string, any>) => void;
12
- type getStateFieldParams = () => any;
13
- type taskHandlerType = ( { jobId, setTaskStateField, getTaskStateField }: { jobId: string; setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams } ) => Promise<void>;
11
+ type setStateFieldParams = {
12
+ (fieldName: string, value: any): Promise<void>;
13
+ (state: Record<string, any>): Promise<void>;
14
+ };
15
+ type getStateFieldParams = {
16
+ (fieldName: string): Promise<any>;
17
+ (): Promise<Record<string, any>>;
18
+ };
19
+ type getStateParams = () => Promise<Record<string, any>>;
20
+ type taskHandlerType = ( { jobId, setTaskStateField, getTaskStateField, getState }: { jobId: string; setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams; getState: getStateParams } ) => Promise<void>;
14
21
  type allTasksDoneStatusType = {
15
22
  jobId: string;
16
23
  failedTasks: number;
@@ -34,6 +41,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
34
41
  private jobParallelLimits: Record<string, number> = {};
35
42
  private levelDbInstances: Record<string, Level> = {};
36
43
  private jobStateMutexes: Record<string, Mutex> = {};
44
+ private deprecatedWarningsShown = new Set<string>();
37
45
 
38
46
  constructor(options: PluginOptions) {
39
47
  super(options, import.meta.url);
@@ -207,6 +215,15 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
207
215
  }
208
216
  }
209
217
 
218
+ private warnDeprecatedOnce(key: string, message: string) {
219
+ if (this.deprecatedWarningsShown.has(key)) {
220
+ return;
221
+ }
222
+
223
+ this.deprecatedWarningsShown.add(key);
224
+ afLogger.warn(message);
225
+ }
226
+
210
227
  private async triggerOnAllTasksDone(onAllTasksDone: onAllTasksDoneType | undefined, levelDb: Level, jobId: string) {
211
228
  if (!onAllTasksDone) {
212
229
  return;
@@ -245,6 +262,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
245
262
  adminUser: AdminUser,
246
263
  tasks: taskType[],
247
264
  jobHandlerName: string,
265
+ initialState: Record<string, any> = {},
248
266
  ): Promise<string> {
249
267
 
250
268
  const handleTask: taskHandlerType = this.taskHandlers[jobHandlerName];
@@ -261,7 +279,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
261
279
  [this.options.progressField]: 0,
262
280
  [this.options.statusField]: 'IN_PROGRESS',
263
281
  [this.options.jobHandlerField]: jobHandlerName,
264
- [this.options.stateField]: '{}'
282
+ [this.options.stateField]: initialState
265
283
  }
266
284
 
267
285
  const creationResult = await this.adminforth.resource(this.getResourceId()).create(objectToSave);
@@ -273,7 +291,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
273
291
  }
274
292
  const jobId = createdRecord[this.getResourcePk()];
275
293
 
276
- this.adminforth.websocket.publish('/background-jobs', {
294
+ this.adminforth.websocket.publish('/background-jobs-job-update', {
277
295
  jobId,
278
296
  status: 'IN_PROGRESS',
279
297
  name: jobName,
@@ -327,21 +345,47 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
327
345
  return;
328
346
  }
329
347
 
330
- //define the setTaskStateField and getTaskStateField functions to pass to the task
331
- const setTaskStateField = async (state: Record<string, any>) => {
332
- await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
333
- this.publishTaskStateFields(jobId, taskIndex, state);
334
- }
335
- const getTaskStateField = async () => {
348
+ const getState = async () => {
336
349
  return await this.getLevelDbTaskStateField(jobLevelDb, taskIndex.toString());
337
350
  }
351
+ const setTaskStateField: setStateFieldParams = async (fieldNameOrState: string | Record<string, any>, value?: any) => {
352
+ if (typeof fieldNameOrState === 'string') {
353
+ const state = await getState();
354
+ const updatedState = {
355
+ ...state,
356
+ [fieldNameOrState]: value,
357
+ };
358
+ await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), updatedState);
359
+ this.publishTaskStateFields(jobId, taskIndex, { [fieldNameOrState]: value });
360
+ return;
361
+ }
362
+
363
+ this.warnDeprecatedOnce(
364
+ 'setTaskStateField-object',
365
+ 'BackgroundJobsPlugin: setTaskStateField(stateObject) is deprecated and will be removed soon. Use setTaskStateField(fieldName: string, value: any) instead. Use getState() when you need the full task state.',
366
+ );
367
+ await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), fieldNameOrState);
368
+ this.publishTaskStateFields(jobId, taskIndex, fieldNameOrState);
369
+ }
370
+ const getTaskStateField: getStateFieldParams = async (fieldName?: string) => {
371
+ const state = await getState();
372
+ if (typeof fieldName === 'string') {
373
+ return state[fieldName];
374
+ }
375
+
376
+ this.warnDeprecatedOnce(
377
+ 'getTaskStateField-no-args',
378
+ 'BackgroundJobsPlugin: getTaskStateField() without a field name is deprecated and will be removed soon. Use getTaskStateField(fieldName: string) for one field, or getState() for the full task state.',
379
+ );
380
+ return state;
381
+ }
338
382
 
339
383
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'IN_PROGRESS');
340
384
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
341
385
 
342
386
  //handling the task
343
387
  try {
344
- await handleTask({ jobId, setTaskStateField, getTaskStateField });
388
+ await handleTask({ jobId, setTaskStateField, getTaskStateField, getState });
345
389
 
346
390
  //Set task status to completed in level db
347
391
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
@@ -349,7 +393,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
349
393
  } catch (error) {
350
394
  const errorMessage = error?.message || 'Unknown error';
351
395
  afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`, );
352
- await this.setJobField(jobId, 'error', errorMessage);
396
+ await this.setJobStateField(jobId, 'error', errorMessage);
353
397
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
354
398
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
355
399
  failedTasks++;
@@ -378,7 +422,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
378
422
  [this.options.statusField]: 'DONE',
379
423
  [this.options.finishedAtField]: (new Date()).toISOString(),
380
424
  })
381
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
425
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
382
426
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
383
427
  await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
384
428
  } else if (failedTasks > 0) {
@@ -386,7 +430,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
386
430
  [this.options.statusField]: 'DONE_WITH_ERRORS',
387
431
  [this.options.finishedAtField]: (new Date()).toISOString(),
388
432
  })
389
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
433
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE_WITH_ERRORS' });
390
434
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
391
435
  await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
392
436
  }
@@ -407,7 +451,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
407
451
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
408
452
  [this.options.progressField]: progress,
409
453
  })
410
- this.adminforth.websocket.publish('/background-jobs', { jobId, progress });
454
+ this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, progress });
411
455
  return completedTasks;
412
456
  }
413
457
 
@@ -453,28 +497,41 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
453
497
 
454
498
  }
455
499
 
456
- public async setJobField(jobId: string, key: string, value: any) {
500
+ public async setJobStateField(jobId: string, key: string, value: any) {
457
501
  const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
458
502
  const state = jobRecord[this.options.stateField];
459
- const parsedState = JSON.parse(state);
460
- parsedState[key] = value;
503
+ state[key] = value;
461
504
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
462
- [this.options.stateField]: JSON.stringify(parsedState),
505
+ [this.options.stateField]: state,
463
506
  });
464
507
  this.publishJobStateField(jobId, key, value);
465
508
  }
466
509
 
467
- public async getJobField(jobId: string, key: string) {
510
+ public async getJobStateField(jobId: string, key: string) {
468
511
  const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
469
512
  const state = jobRecord[this.options.stateField];
470
- const parsedState = JSON.parse(state);
471
- return parsedState[key];
513
+ return state[key];
472
514
  }
473
515
 
474
516
  public async getJobState(jobId: string) {
475
517
  const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
476
- const state = jobRecord[this.options.stateField];
477
- return JSON.parse(state);
518
+ return jobRecord[this.options.stateField];
519
+ }
520
+
521
+ public async setJobField(jobId: string, key: string, value: any) {
522
+ this.warnDeprecatedOnce(
523
+ 'setJobField',
524
+ 'BackgroundJobsPlugin: setJobField(jobId, key, value) is deprecated and will be removed soon. Use setJobStateField(jobId, fieldName: string, value: any) instead.',
525
+ );
526
+ return this.setJobStateField(jobId, key, value);
527
+ }
528
+
529
+ public async getJobField(jobId: string, key: string) {
530
+ this.warnDeprecatedOnce(
531
+ 'getJobField',
532
+ 'BackgroundJobsPlugin: getJobField(jobId, key) is deprecated and will be removed soon. Use getJobStateField(jobId, fieldName: string) instead.',
533
+ );
534
+ return this.getJobStateField(jobId, key);
478
535
  }
479
536
 
480
537
  public async updateJobFieldsAtomically(jobId: string, updateFunction: () => Promise<void>) {
@@ -547,7 +604,6 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
547
604
  createdAt: job[this.options.createdAtField],
548
605
  finishedAt: job[this.options.finishedAtField] || null,
549
606
  status: job[this.options.statusField],
550
- state: JSON.parse(job[this.options.stateField]),
551
607
  progress: job[this.options.progressField],
552
608
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
553
609
  }
@@ -572,7 +628,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
572
628
  createdAt: job[this.options.createdAtField],
573
629
  finishedAt: job[this.options.finishedAtField] || null,
574
630
  status: job[this.options.statusField],
575
- state: JSON.parse(job[this.options.stateField]),
631
+ state: job[this.options.stateField],
576
632
  progress: job[this.options.progressField],
577
633
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
578
634
  };
@@ -596,7 +652,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
596
652
  [this.options.statusField]: 'CANCELLED',
597
653
  [this.options.finishedAtField]: (new Date()).toISOString(),
598
654
  });
599
- this.adminforth.websocket.publish('/background-jobs', {
655
+ this.adminforth.websocket.publish('/background-jobs-job-update', {
600
656
  jobId,
601
657
  status: 'CANCELLED',
602
658
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",