@memberjunction/scheduling-engine 6.1.0-edge.0 → 6.1.0-edge.2
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/README.md +26 -0
- package/dist/MissedRunPolicy.d.ts +51 -0
- package/dist/MissedRunPolicy.d.ts.map +1 -0
- package/dist/MissedRunPolicy.js +39 -0
- package/dist/MissedRunPolicy.js.map +1 -0
- package/dist/NotificationManager.d.ts +68 -7
- package/dist/NotificationManager.d.ts.map +1 -1
- package/dist/NotificationManager.js +126 -26
- package/dist/NotificationManager.js.map +1 -1
- package/dist/ScheduledJobEngine.d.ts +52 -1
- package/dist/ScheduledJobEngine.d.ts.map +1 -1
- package/dist/ScheduledJobEngine.js +153 -4
- package/dist/ScheduledJobEngine.js.map +1 -1
- package/dist/drivers/ActionLogRetentionScheduledJobDriver.d.ts +106 -0
- package/dist/drivers/ActionLogRetentionScheduledJobDriver.d.ts.map +1 -0
- package/dist/drivers/ActionLogRetentionScheduledJobDriver.js +224 -0
- package/dist/drivers/ActionLogRetentionScheduledJobDriver.js.map +1 -0
- package/dist/drivers/AgentScheduledJobDriver.d.ts.map +1 -1
- package/dist/drivers/AgentScheduledJobDriver.js +16 -0
- package/dist/drivers/AgentScheduledJobDriver.js.map +1 -1
- package/dist/drivers/UserRoutineDispatcherDriver.d.ts +1 -1
- package/dist/drivers/UserRoutineDispatcherDriver.d.ts.map +1 -1
- package/dist/drivers/UserRoutineDispatcherDriver.js +1 -1
- package/dist/drivers/UserRoutineDispatcherDriver.js.map +1 -1
- package/dist/drivers/index.d.ts +1 -0
- package/dist/drivers/index.d.ts.map +1 -1
- package/dist/drivers/index.js +1 -0
- package/dist/drivers/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +17 -16
package/README.md
CHANGED
|
@@ -133,6 +133,32 @@ Abstract base class for all job drivers. Provides:
|
|
|
133
133
|
|--------|----------|-------------|
|
|
134
134
|
| `AgentScheduledJobDriver` | Agent | Executes AI agents with configurable conversations and payloads |
|
|
135
135
|
| `ActionScheduledJobDriver` | Action | Executes MJ Actions with static or SQL-based parameter values |
|
|
136
|
+
| `RecordProcessScheduledJobDriver` | Run Record Process | Runs a `MJ: Record Processes` definition over its scoped record set |
|
|
137
|
+
| `IntegrationSyncScheduledJobDriver` | Integration Sync | Runs an integration's sync on a schedule |
|
|
138
|
+
| `IntegrationDiscoveryScheduledJobDriver` | Integration Discovery | Discovers new integration entities on a schedule |
|
|
139
|
+
| `UserRoutineDispatcherDriver` | User Routine Dispatcher | Dispatches due user routines |
|
|
140
|
+
| `AgentRunSweepScheduledJobDriver` | Agent Run Sweep | Force-fails agent runs orphaned by a dead process |
|
|
141
|
+
| `ActionLogRetentionScheduledJobDriver` | Action Log Retention | Purges expired `MJ: Action Execution Logs` rows, bounded per run |
|
|
142
|
+
|
|
143
|
+
#### Action Log Retention
|
|
144
|
+
|
|
145
|
+
`Action.RetentionPeriod` (days; `NULL` = indefinite) is stamped onto each execution-log row **when the
|
|
146
|
+
run starts**, so retention is decided at write time — editing an action's retention changes what is
|
|
147
|
+
kept going forward rather than retroactively deleting history written under the previous policy.
|
|
148
|
+
|
|
149
|
+
This driver enforces it. It is opt-in like the sweep: shipping the job type activates nothing until
|
|
150
|
+
someone creates a `MJ: Scheduled Job` of it with a cron expression.
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{ "DefaultRetentionDays": 90, "MaxDeletesPerRun": 5000 }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Both fields are optional. Omitting `DefaultRetentionDays` means rows with **no** retention are kept
|
|
157
|
+
indefinitely — `NULL` is what the schema calls indefinite, and a purge job that invented a lifetime
|
|
158
|
+
for it would destroy history with nothing left to notice by. `MaxDeletesPerRun` defaults to 5000 so a
|
|
159
|
+
first run against a long-neglected table cannot become an unbounded transaction; the run drains
|
|
160
|
+
oldest-first over successive runs and reports `ReachedCap: true` when it stopped at the ceiling rather
|
|
161
|
+
than because it was finished.
|
|
136
162
|
|
|
137
163
|
### CronExpressionHelper
|
|
138
164
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview What a scheduled job does about fire times that passed while nothing was running.
|
|
3
|
+
*
|
|
4
|
+
* A scheduler is down sometimes — a deploy, a crash, a machine that slept. When it comes back, a
|
|
5
|
+
* job whose `NextRunAt` is in the past has to answer a question the schema never asked: *should the
|
|
6
|
+
* missed occurrences happen now?* The right answer depends entirely on what the job does, and only
|
|
7
|
+
* its author knows:
|
|
8
|
+
*
|
|
9
|
+
* - A **nightly digest** wants one catch-up run. The latest state is the whole point; running it
|
|
10
|
+
* five times because the server was down five days sends five identical emails.
|
|
11
|
+
* - A **per-period billing snapshot** wants every occurrence. Each period is its own unit of work,
|
|
12
|
+
* and skipping one loses data nothing else will recreate.
|
|
13
|
+
* - A **cache warm** wants none of them. Whatever it was warming is stale anyway; the next scheduled
|
|
14
|
+
* run will do the job.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/scheduling-engine
|
|
17
|
+
*/
|
|
18
|
+
import { MJScheduledJobEntity } from '@memberjunction/core-entities';
|
|
19
|
+
/**
|
|
20
|
+
* Derived from the entity rather than restated, so a CHECK-constraint change flows through instead
|
|
21
|
+
* of silently drifting from a hand-copied union.
|
|
22
|
+
*/
|
|
23
|
+
export type MissedRunPolicy = MJScheduledJobEntity['MissedRunPolicy'];
|
|
24
|
+
/** What the engine should do with a job this tick. */
|
|
25
|
+
export type MissedRunDecision =
|
|
26
|
+
/** Run it now. Either it is normally due, or its policy says to catch up. */
|
|
27
|
+
{
|
|
28
|
+
Action: 'Run'; /** Base date for computing the following `NextRunAt`. */
|
|
29
|
+
AdvanceFrom: 'Now' | 'MissedOccurrence';
|
|
30
|
+
}
|
|
31
|
+
/** Do not run; just move `NextRunAt` forward past the occurrences that were missed. */
|
|
32
|
+
| {
|
|
33
|
+
Action: 'SkipAndAdvance';
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Decide what to do with a job whose `NextRunAt` has passed.
|
|
37
|
+
*
|
|
38
|
+
* **"Missed" is defined cron-relatively, not by a grace constant.** A run is missed only when
|
|
39
|
+
* *another* occurrence has already come due since the one we are looking at — i.e.
|
|
40
|
+
* `nextOccurrenceAfter(NextRunAt) <= now`. A job that simply came due a few seconds ago has not
|
|
41
|
+
* missed anything; it is just due, and every policy runs it. Defining it with a fixed grace window
|
|
42
|
+
* would misclassify both ends of the range: an every-minute job would look "missed" after a 90-second
|
|
43
|
+
* pause, and a monthly job would look "on time" a week late.
|
|
44
|
+
*
|
|
45
|
+
* @param policy the job's declared policy
|
|
46
|
+
* @param scheduledFor the occurrence being evaluated (`Job.NextRunAt`)
|
|
47
|
+
* @param nextOccurrenceAfterScheduled the following cron occurrence after `scheduledFor`
|
|
48
|
+
* @param now evaluation time
|
|
49
|
+
*/
|
|
50
|
+
export declare function DecideMissedRun(policy: MissedRunPolicy, scheduledFor: Date, nextOccurrenceAfterScheduled: Date, now: Date): MissedRunDecision;
|
|
51
|
+
//# sourceMappingURL=MissedRunPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MissedRunPolicy.d.ts","sourceRoot":"","sources":["../src/MissedRunPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;AAEtE,sDAAsD;AACtD,MAAM,MAAM,iBAAiB;AACzB,6EAA6E;AAC3E;IAAE,MAAM,EAAE,KAAK,CAAC,CAAC,yDAAyD;IAAC,WAAW,EAAE,KAAK,GAAG,kBAAkB,CAAA;CAAE;AACtH,uFAAuF;GACrF;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE,eAAe,EACvB,YAAY,EAAE,IAAI,EAClB,4BAA4B,EAAE,IAAI,EAClC,GAAG,EAAE,IAAI,GACV,iBAAiB,CA0BnB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide what to do with a job whose `NextRunAt` has passed.
|
|
3
|
+
*
|
|
4
|
+
* **"Missed" is defined cron-relatively, not by a grace constant.** A run is missed only when
|
|
5
|
+
* *another* occurrence has already come due since the one we are looking at — i.e.
|
|
6
|
+
* `nextOccurrenceAfter(NextRunAt) <= now`. A job that simply came due a few seconds ago has not
|
|
7
|
+
* missed anything; it is just due, and every policy runs it. Defining it with a fixed grace window
|
|
8
|
+
* would misclassify both ends of the range: an every-minute job would look "missed" after a 90-second
|
|
9
|
+
* pause, and a monthly job would look "on time" a week late.
|
|
10
|
+
*
|
|
11
|
+
* @param policy the job's declared policy
|
|
12
|
+
* @param scheduledFor the occurrence being evaluated (`Job.NextRunAt`)
|
|
13
|
+
* @param nextOccurrenceAfterScheduled the following cron occurrence after `scheduledFor`
|
|
14
|
+
* @param now evaluation time
|
|
15
|
+
*/
|
|
16
|
+
export function DecideMissedRun(policy, scheduledFor, nextOccurrenceAfterScheduled, now) {
|
|
17
|
+
const missedAnOccurrence = nextOccurrenceAfterScheduled.getTime() <= now.getTime();
|
|
18
|
+
if (!missedAnOccurrence) {
|
|
19
|
+
// Normally due. No policy has anything to say about this case — advancing from `now` is what
|
|
20
|
+
// the engine has always done.
|
|
21
|
+
return { Action: 'Run', AdvanceFrom: 'Now' };
|
|
22
|
+
}
|
|
23
|
+
switch (policy) {
|
|
24
|
+
case 'Skip':
|
|
25
|
+
return { Action: 'SkipAndAdvance' };
|
|
26
|
+
case 'RunAll':
|
|
27
|
+
// Advance from the occurrence just consumed, not from now, so the backlog is walked one
|
|
28
|
+
// occurrence per tick rather than collapsed. That is also what keeps a long outage from
|
|
29
|
+
// firing a week of runs simultaneously — the engine drains at its poll cadence.
|
|
30
|
+
return { Action: 'Run', AdvanceFrom: 'MissedOccurrence' };
|
|
31
|
+
case 'RunOnce':
|
|
32
|
+
default:
|
|
33
|
+
// The default, and what the engine did before this policy existed: run once, then jump
|
|
34
|
+
// to the next future occurrence. Kept as the fallback so an unrecognized value degrades
|
|
35
|
+
// to today's behavior rather than to silence.
|
|
36
|
+
return { Action: 'Run', AdvanceFrom: 'Now' };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=MissedRunPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MissedRunPolicy.js","sourceRoot":"","sources":["../src/MissedRunPolicy.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAC3B,MAAuB,EACvB,YAAkB,EAClB,4BAAkC,EAClC,GAAS;IAET,MAAM,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;IAEnF,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACtB,6FAA6F;QAC7F,8BAA8B;QAC9B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACjD,CAAC;IAED,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,MAAM;YACP,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QAExC,KAAK,QAAQ;YACT,wFAAwF;YACxF,wFAAwF;YACxF,gFAAgF;YAChF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAE9D,KAAK,SAAS,CAAC;QACf;YACI,uFAAuF;YACvF,wFAAwF;YACxF,8CAA8C;YAC9C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACrD,CAAC;AACL,CAAC"}
|
|
@@ -1,19 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Notification delivery
|
|
2
|
+
* @fileoverview Notification delivery for scheduled jobs.
|
|
3
|
+
*
|
|
4
|
+
* **Two people have a say in how a job's notification is delivered**, and this class exists to
|
|
5
|
+
* compose them. The person who configured the job chose which channels it may use
|
|
6
|
+
* (`NotifyViaEmail` / `NotifyViaInApp` on the job row); the recipient chose which channels they
|
|
7
|
+
* want (their notification preferences). The delivered set is the intersection — a job cannot add
|
|
8
|
+
* a channel the recipient declined, and a type default cannot add a channel the job never asked
|
|
9
|
+
* for. `NotificationEngine` resolves the recipient's half; the job's half is handed down as
|
|
10
|
+
* `allowedDeliveryChannels`, a ceiling that can only subtract.
|
|
11
|
+
*
|
|
12
|
+
* **Nothing here may throw.** It runs at the tail of job completion, after the run record has been
|
|
13
|
+
* written and the lock released. A notification that fails is an annoyance; a notification that
|
|
14
|
+
* faults the completion path would corrupt run bookkeeping for a job that actually succeeded.
|
|
15
|
+
*
|
|
3
16
|
* @module @memberjunction/scheduling-engine
|
|
4
17
|
*/
|
|
18
|
+
import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
5
19
|
import { NotificationContent, NotificationChannel } from '@memberjunction/scheduling-base-types';
|
|
6
20
|
/**
|
|
7
|
-
*
|
|
21
|
+
* The seeded `MJ: User Notification Types` row scheduled-job notifications are delivered under.
|
|
22
|
+
* Pinned by name rather than ID so an instance that re-seeded metadata still resolves; a rename
|
|
23
|
+
* would silently drop every job notification to the raw fallback, so the integration tier pins it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SCHEDULED_JOB_NOTIFICATION_TYPE = "Scheduled Job";
|
|
26
|
+
/** Everything needed to deliver one job's completion notice. */
|
|
27
|
+
export type ScheduledJobNotificationParams = {
|
|
28
|
+
/** Who hears about it — the job's NotifyUser, or its owner. */
|
|
29
|
+
RecipientUserID: string;
|
|
30
|
+
/** Subject/body/priority, formatted by the job's own plugin via `FormatNotification`. */
|
|
31
|
+
Content: NotificationContent;
|
|
32
|
+
/** The channels the JOB asked for. Acts as a ceiling, never an escalation. */
|
|
33
|
+
Channels: NotificationChannel[];
|
|
34
|
+
ContextUser: UserInfo;
|
|
35
|
+
/** Deep-link context, so clicking the notification lands on the job that produced it. */
|
|
36
|
+
ScheduledJobID?: string;
|
|
37
|
+
ScheduledJobRunID?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The provider the job ran under. Threaded rather than resolved globally so a server hosting
|
|
40
|
+
* more than one connection notifies through the same one the job read its data from.
|
|
41
|
+
*/
|
|
42
|
+
Provider?: IMetadataProvider;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Delivers notifications about scheduled job execution.
|
|
8
46
|
*/
|
|
9
47
|
export declare class NotificationManager {
|
|
10
48
|
/**
|
|
11
|
-
* Send a notification about a scheduled job completion
|
|
49
|
+
* Send a notification about a scheduled job completion.
|
|
50
|
+
*
|
|
51
|
+
* Delivery goes through `NotificationEngine`, which honors the recipient's per-type
|
|
52
|
+
* preferences and handles email/SMS templating. When the engine cannot deliver at all — the
|
|
53
|
+
* type is not seeded in this instance, the engine is unavailable — an in-app notification is
|
|
54
|
+
* written directly, but only if the job asked for in-app; falling back onto a channel the job
|
|
55
|
+
* did not request would be worse than not delivering.
|
|
56
|
+
*
|
|
57
|
+
* A recipient who has *opted out* is not a failure and does not trigger the fallback.
|
|
12
58
|
*
|
|
13
|
-
* @
|
|
14
|
-
|
|
15
|
-
|
|
59
|
+
* @returns true when at least one channel actually delivered.
|
|
60
|
+
*/
|
|
61
|
+
static SendScheduledJobNotification(params: ScheduledJobNotificationParams): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Translate the job's channel toggles into a ceiling.
|
|
64
|
+
*
|
|
65
|
+
* A channel the job did not list is explicitly `false` — the ceiling has to be closed, not
|
|
66
|
+
* merely silent, or a type default would deliver on a channel nobody asked for. SMS is always
|
|
67
|
+
* closed: a scheduled job has no way to request it today, so leaving it open would let a type
|
|
68
|
+
* default text someone about a nightly sync.
|
|
69
|
+
*/
|
|
70
|
+
private static channelCeiling;
|
|
71
|
+
/** Deep-link context stored on the notification, so it can navigate back to the run. */
|
|
72
|
+
private static buildResourceConfiguration;
|
|
73
|
+
/**
|
|
74
|
+
* Write an in-app notification row directly — the minimal, always-available path when the
|
|
75
|
+
* engine is not usable. Deliberately does not set `NotificationTypeID`: the reason we are here
|
|
76
|
+
* is that the type could not be resolved.
|
|
16
77
|
*/
|
|
17
|
-
static
|
|
78
|
+
private static deliverInAppDirect;
|
|
18
79
|
}
|
|
19
80
|
//# sourceMappingURL=NotificationManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationManager.d.ts","sourceRoot":"","sources":["../src/NotificationManager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"NotificationManager.d.ts","sourceRoot":"","sources":["../src/NotificationManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAiC,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAG5G,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAEjG;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,kBAAkB,CAAC;AAE/D,gEAAgE;AAChE,MAAM,MAAM,8BAA8B,GAAG;IACzC,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,yFAAyF;IACzF,OAAO,EAAE,mBAAmB,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,WAAW,EAAE,QAAQ,CAAC;IACtB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAEF;;GAEG;AACH,qBAAa,mBAAmB;IAC5B;;;;;;;;;;;;OAYG;WACiB,4BAA4B,CAAC,MAAM,EAAE,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;IAmD1G;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAQ7B,wFAAwF;IACxF,OAAO,CAAC,MAAM,CAAC,0BAA0B;IAWzC;;;;OAIG;mBACkB,kBAAkB;CA8B1C"}
|
|
@@ -1,39 +1,139 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Notification delivery
|
|
2
|
+
* @fileoverview Notification delivery for scheduled jobs.
|
|
3
|
+
*
|
|
4
|
+
* **Two people have a say in how a job's notification is delivered**, and this class exists to
|
|
5
|
+
* compose them. The person who configured the job chose which channels it may use
|
|
6
|
+
* (`NotifyViaEmail` / `NotifyViaInApp` on the job row); the recipient chose which channels they
|
|
7
|
+
* want (their notification preferences). The delivered set is the intersection — a job cannot add
|
|
8
|
+
* a channel the recipient declined, and a type default cannot add a channel the job never asked
|
|
9
|
+
* for. `NotificationEngine` resolves the recipient's half; the job's half is handed down as
|
|
10
|
+
* `allowedDeliveryChannels`, a ceiling that can only subtract.
|
|
11
|
+
*
|
|
12
|
+
* **Nothing here may throw.** It runs at the tail of job completion, after the run record has been
|
|
13
|
+
* written and the lock released. A notification that fails is an annoyance; a notification that
|
|
14
|
+
* faults the completion path would corrupt run bookkeeping for a job that actually succeeded.
|
|
15
|
+
*
|
|
3
16
|
* @module @memberjunction/scheduling-engine
|
|
4
17
|
*/
|
|
5
|
-
import { LogStatus } from '@memberjunction/core';
|
|
18
|
+
import { LogError, LogStatus, Metadata } from '@memberjunction/core';
|
|
19
|
+
import { NotificationEngine } from '@memberjunction/notifications';
|
|
6
20
|
/**
|
|
7
|
-
*
|
|
21
|
+
* The seeded `MJ: User Notification Types` row scheduled-job notifications are delivered under.
|
|
22
|
+
* Pinned by name rather than ID so an instance that re-seeded metadata still resolves; a rename
|
|
23
|
+
* would silently drop every job notification to the raw fallback, so the integration tier pins it.
|
|
24
|
+
*/
|
|
25
|
+
export const SCHEDULED_JOB_NOTIFICATION_TYPE = 'Scheduled Job';
|
|
26
|
+
/**
|
|
27
|
+
* Delivers notifications about scheduled job execution.
|
|
8
28
|
*/
|
|
9
29
|
export class NotificationManager {
|
|
10
30
|
/**
|
|
11
|
-
* Send a notification about a scheduled job completion
|
|
31
|
+
* Send a notification about a scheduled job completion.
|
|
32
|
+
*
|
|
33
|
+
* Delivery goes through `NotificationEngine`, which honors the recipient's per-type
|
|
34
|
+
* preferences and handles email/SMS templating. When the engine cannot deliver at all — the
|
|
35
|
+
* type is not seeded in this instance, the engine is unavailable — an in-app notification is
|
|
36
|
+
* written directly, but only if the job asked for in-app; falling back onto a channel the job
|
|
37
|
+
* did not request would be worse than not delivering.
|
|
12
38
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @
|
|
39
|
+
* A recipient who has *opted out* is not a failure and does not trigger the fallback.
|
|
40
|
+
*
|
|
41
|
+
* @returns true when at least one channel actually delivered.
|
|
16
42
|
*/
|
|
17
|
-
static async SendScheduledJobNotification(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
static async SendScheduledJobNotification(params) {
|
|
44
|
+
const { RecipientUserID, Content, Channels, ContextUser } = params;
|
|
45
|
+
if (!RecipientUserID || !Channels?.length) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const ceiling = this.channelCeiling(Channels);
|
|
49
|
+
const resourceConfiguration = this.buildResourceConfiguration(params);
|
|
50
|
+
try {
|
|
51
|
+
await NotificationEngine.Instance.Config(false, ContextUser, params.Provider);
|
|
52
|
+
const result = await NotificationEngine.Instance.SendNotification({
|
|
53
|
+
userId: RecipientUserID,
|
|
54
|
+
typeNameOrId: SCHEDULED_JOB_NOTIFICATION_TYPE,
|
|
55
|
+
title: Content.Subject,
|
|
56
|
+
message: Content.Body,
|
|
57
|
+
resourceConfiguration,
|
|
58
|
+
templateData: {
|
|
59
|
+
subject: Content.Subject,
|
|
60
|
+
body: Content.Body,
|
|
61
|
+
priority: Content.Priority,
|
|
62
|
+
scheduledJobId: params.ScheduledJobID ?? '',
|
|
63
|
+
scheduledJobRunId: params.ScheduledJobRunID ?? '',
|
|
64
|
+
...(Content.Metadata ?? {}),
|
|
65
|
+
},
|
|
66
|
+
allowedDeliveryChannels: ceiling,
|
|
67
|
+
}, ContextUser);
|
|
68
|
+
if (result.success) {
|
|
69
|
+
const { inApp, email, sms } = result.deliveryChannels;
|
|
70
|
+
// All-false here means the recipient opted out (or the type disables every
|
|
71
|
+
// channel). That is a decision, not a failure — no fallback.
|
|
72
|
+
return inApp || email || sms;
|
|
36
73
|
}
|
|
74
|
+
LogStatus(`[NotificationManager] NotificationEngine could not deliver "${Content.Subject}" ` +
|
|
75
|
+
`(${(result.errors ?? []).join('; ') || 'no detail'})${ceiling.inApp ? ' — writing an in-app notification directly' : ''}`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
LogStatus(`[NotificationManager] NotificationEngine unavailable (${message})` +
|
|
80
|
+
`${ceiling.inApp ? ' — writing an in-app notification directly' : ''}`);
|
|
81
|
+
}
|
|
82
|
+
// Only in-app has a fallback: it is a single row this class can write itself. Email and SMS
|
|
83
|
+
// need a configured communication provider, which is exactly what was unavailable above.
|
|
84
|
+
return ceiling.inApp ? this.deliverInAppDirect(params, resourceConfiguration) : false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Translate the job's channel toggles into a ceiling.
|
|
88
|
+
*
|
|
89
|
+
* A channel the job did not list is explicitly `false` — the ceiling has to be closed, not
|
|
90
|
+
* merely silent, or a type default would deliver on a channel nobody asked for. SMS is always
|
|
91
|
+
* closed: a scheduled job has no way to request it today, so leaving it open would let a type
|
|
92
|
+
* default text someone about a nightly sync.
|
|
93
|
+
*/
|
|
94
|
+
static channelCeiling(channels) {
|
|
95
|
+
return {
|
|
96
|
+
inApp: channels.includes('InApp'),
|
|
97
|
+
email: channels.includes('Email'),
|
|
98
|
+
sms: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Deep-link context stored on the notification, so it can navigate back to the run. */
|
|
102
|
+
static buildResourceConfiguration(params) {
|
|
103
|
+
const config = { type: 'ScheduledJob' };
|
|
104
|
+
if (params.ScheduledJobID) {
|
|
105
|
+
config.scheduledJobId = params.ScheduledJobID;
|
|
106
|
+
}
|
|
107
|
+
if (params.ScheduledJobRunID) {
|
|
108
|
+
config.scheduledJobRunId = params.ScheduledJobRunID;
|
|
109
|
+
}
|
|
110
|
+
return config;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Write an in-app notification row directly — the minimal, always-available path when the
|
|
114
|
+
* engine is not usable. Deliberately does not set `NotificationTypeID`: the reason we are here
|
|
115
|
+
* is that the type could not be resolved.
|
|
116
|
+
*/
|
|
117
|
+
static async deliverInAppDirect(params, resourceConfiguration) {
|
|
118
|
+
try {
|
|
119
|
+
const provider = params.Provider ?? Metadata.Provider;
|
|
120
|
+
const notification = await provider.GetEntityObject('MJ: User Notifications', params.ContextUser);
|
|
121
|
+
notification.NewRecord();
|
|
122
|
+
notification.UserID = params.RecipientUserID;
|
|
123
|
+
notification.Title = params.Content.Subject;
|
|
124
|
+
notification.Message = params.Content.Body;
|
|
125
|
+
notification.Unread = true;
|
|
126
|
+
notification.ResourceConfiguration = JSON.stringify(resourceConfiguration);
|
|
127
|
+
if (await notification.Save()) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
LogError(`[NotificationManager] Direct in-app notification failed for user ${params.RecipientUserID}: ` +
|
|
131
|
+
`${notification.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
LogError(`[NotificationManager] Direct in-app notification threw for user ${params.RecipientUserID}`, undefined, error);
|
|
136
|
+
return false;
|
|
37
137
|
}
|
|
38
138
|
}
|
|
39
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationManager.js","sourceRoot":"","sources":["../src/NotificationManager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"NotificationManager.js","sourceRoot":"","sources":["../src/NotificationManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -352,7 +352,58 @@ export declare class SchedulingEngine extends BaseSingleton<SchedulingEngine> {
|
|
|
352
352
|
*/
|
|
353
353
|
private updateJobStatistics;
|
|
354
354
|
/**
|
|
355
|
-
*
|
|
355
|
+
* Apply the job's missed-run policy, advancing past the missed occurrences when it says Skip.
|
|
356
|
+
*
|
|
357
|
+
* Only reached for a job that is already due, so the "merely due" case costs one cron
|
|
358
|
+
* computation and always runs. Whether anything was actually *missed* is decided
|
|
359
|
+
* cron-relatively — a later occurrence has also come due — rather than against a grace window,
|
|
360
|
+
* which would misjudge a per-minute job after a short pause and a monthly job that is a week
|
|
361
|
+
* late in opposite directions.
|
|
362
|
+
*
|
|
363
|
+
* **Synchronous on purpose.** It runs inside the dispatch loop, immediately before lock
|
|
364
|
+
* acquisition; making it async would insert a microtask exactly where ordering against the
|
|
365
|
+
* sweep's fire-and-forget cleanup matters. The decision needs no I/O — only the skip branch
|
|
366
|
+
* writes, and that is awaited separately once the answer is known.
|
|
367
|
+
*
|
|
368
|
+
* @returns true when the job should NOT run this tick.
|
|
369
|
+
*/
|
|
370
|
+
private shouldSkipMissedRun;
|
|
371
|
+
/**
|
|
372
|
+
* Move a skipped job past the occurrences it missed, so it rejoins its schedule instead of
|
|
373
|
+
* being re-evaluated as missed on every subsequent poll.
|
|
374
|
+
*
|
|
375
|
+
* Best-effort: a failed advance is logged and retried next tick, never propagated.
|
|
376
|
+
*/
|
|
377
|
+
private advancePastMissedRuns;
|
|
378
|
+
/**
|
|
379
|
+
* Move jobs past their `EndAt` to `Status='Expired'`.
|
|
380
|
+
*
|
|
381
|
+
* **`Expired` has always been a declared status that nothing ever set.** `isJobDue` already
|
|
382
|
+
* refuses a job whose `EndAt` has passed, so such a job stops running on its own — but it stays
|
|
383
|
+
* `Active` forever, permanently inert, and keeps counting toward `UpdatePollingInterval`. A
|
|
384
|
+
* one-shot expressed as "cron at T plus `EndAt` just after T" therefore left the whole scheduler
|
|
385
|
+
* polling at that job's cadence for a job that will never run again.
|
|
386
|
+
*
|
|
387
|
+
* That makes this the one-shot story rather than a new schedule shape: "run once at T" is
|
|
388
|
+
* already expressible with the columns that exist, and what was missing was the retirement.
|
|
389
|
+
*
|
|
390
|
+
* Deliberately narrow. It only transitions `Active`/`Pending` — a `Paused` or `Disabled` job was
|
|
391
|
+
* put there by a person, and quietly rewriting their decision to `Expired` would lose it. And it
|
|
392
|
+
* only fires on `EndAt`, never on "the cron has no future occurrence": a cron always has one, so
|
|
393
|
+
* inferring exhaustion would be guessing.
|
|
394
|
+
*
|
|
395
|
+
* Best-effort: a failed transition is logged and retried on the next poll, never propagated —
|
|
396
|
+
* retiring a finished job must not be able to stop live ones from dispatching.
|
|
397
|
+
*/
|
|
398
|
+
private expireFinishedJobs;
|
|
399
|
+
/**
|
|
400
|
+
* Send notifications if configured.
|
|
401
|
+
*
|
|
402
|
+
* Runs at the tail of job completion, after the run record is written — so a delivery problem
|
|
403
|
+
* is contained here rather than faulting the bookkeeping of a job that actually succeeded.
|
|
404
|
+
* `NotificationManager` already swallows its own errors; the guard covers the formatting call
|
|
405
|
+
* into plugin code, which does not carry that contract.
|
|
406
|
+
*
|
|
356
407
|
* @private
|
|
357
408
|
*/
|
|
358
409
|
private sendNotificationsIfNeeded;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScheduledJobEngine.d.ts","sourceRoot":"","sources":["../src/ScheduledJobEngine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EACH,QAAQ,EAER,iBAAiB,EAOpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxH,OAAO,EAAE,aAAa,EAAwB,MAAM,wBAAwB,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"ScheduledJobEngine.d.ts","sourceRoot":"","sources":["../src/ScheduledJobEngine.ts"],"names":[],"mappings":"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"}
|