@kensio/yulin 1.21.25 → 1.21.27
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sdk/error/sim-sdk.error.d.ts +18 -0
- package/dist/sdk/error/sim-sdk.error.js +41 -0
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/send-abort.d.ts +28 -0
- package/dist/sdk/send-abort.js +112 -0
- package/dist/sdk/send-patch.js +3 -1
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +1 -0
- package/dist/service/aws/sim-aws-properties.d.ts +8 -0
- package/dist/service/aws/sim-aws.d.ts +5 -0
- package/dist/service/aws/sim-aws.js +7 -1
- package/dist/service/eventbridge/bus/sim-event-bus-name.d.ts +4 -0
- package/dist/service/eventbridge/bus/sim-event-bus-name.js +17 -12
- package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-creator.js +8 -1
- package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.d.ts +5 -0
- package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.js +14 -1
- package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-creator.js +2 -0
- package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-target-delivery.d.ts +15 -0
- package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-target-delivery.js +53 -0
- package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-targets.d.ts +4 -0
- package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-targets.js +7 -3
- package/dist/service/eventbridge/command/authorize/sim-event-bridge-authorizer.d.ts +5 -0
- package/dist/service/eventbridge/command/authorize/sim-event-bridge-authorizer.js +7 -0
- package/dist/service/eventbridge/command/bus/sim-event-bridge-create-event-bus.d.ts +7 -1
- package/dist/service/eventbridge/command/bus/sim-event-bridge-create-event-bus.js +7 -1
- package/dist/service/eventbridge/command/bus/sim-event-bridge-partner-bus.d.ts +24 -0
- package/dist/service/eventbridge/command/bus/sim-event-bridge-partner-bus.js +44 -0
- package/dist/service/eventbridge/command/bus/sim-event-bridge-unsimulated-bus-input.js +0 -5
- package/dist/service/eventbridge/command/put-events/sim-event-bridge-authorized-entries.js +8 -0
- package/dist/service/eventbridge/command/sim-event-bridge-command.types.d.ts +1 -0
- package/dist/service/eventbridge/command/sim-event-bridge-commands.d.ts +6 -0
- package/dist/service/eventbridge/command/sim-event-bridge-commands.js +19 -1
- package/dist/service/eventbridge/command/source/sim-event-bridge-describe-event-source.d.ts +35 -0
- package/dist/service/eventbridge/command/source/sim-event-bridge-describe-event-source.js +46 -0
- package/dist/service/eventbridge/command/source/sim-event-bridge-put-partner-events.d.ts +41 -0
- package/dist/service/eventbridge/command/source/sim-event-bridge-put-partner-events.js +82 -0
- package/dist/service/eventbridge/command/source/source.command.d.ts +59 -0
- package/dist/service/eventbridge/command/source/source.command.js +1 -0
- package/dist/service/eventbridge/command/target/sim-event-bridge-target-commands.js +2 -0
- package/dist/service/eventbridge/command/target/sim-event-bridge-unsimulated-target-input.js +2 -12
- package/dist/service/eventbridge/command/target/target.command.d.ts +8 -5
- package/dist/service/eventbridge/delivery/sim-aws-event-bridge-delivery-targets.d.ts +6 -1
- package/dist/service/eventbridge/delivery/sim-aws-event-bridge-delivery-targets.js +16 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-dead-letter-attributes.d.ts +9 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-dead-letter-attributes.js +47 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-dead-letter-queue.d.ts +24 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-dead-letter-queue.js +28 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-attempt.d.ts +41 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-attempt.js +97 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-queue.js +7 -37
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-retries.test-support.d.ts +25 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-retries.test-support.js +60 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery.d.ts +22 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-failing-targets.test-support.d.ts +23 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-failing-targets.test-support.js +28 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-no-delivery-targets.d.ts +5 -1
- package/dist/service/eventbridge/delivery/sim-event-bridge-no-delivery-targets.js +9 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-queue-message.d.ts +28 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-queue-message.js +45 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-target-delivery.d.ts +4 -1
- package/dist/service/eventbridge/delivery/sim-event-bridge-target-delivery.js +19 -14
- package/dist/service/eventbridge/index.d.ts +2 -0
- package/dist/service/eventbridge/index.js +2 -0
- package/dist/service/eventbridge/routing/sim-event-bridge-router.js +1 -0
- package/dist/service/eventbridge/sdk/sim-event-bridge-sdk-command-router.js +4 -0
- package/dist/service/eventbridge/sim-event-bridge-inspection.d.ts +10 -0
- package/dist/service/eventbridge/sim-event-bridge-inspection.js +9 -0
- package/dist/service/eventbridge/sim-event-bridge-partners.d.ts +38 -0
- package/dist/service/eventbridge/sim-event-bridge-partners.js +43 -0
- package/dist/service/eventbridge/sim-event-bridge.d.ts +12 -12
- package/dist/service/eventbridge/sim-event-bridge.js +14 -11
- package/dist/service/eventbridge/source/sim-partner-event-source-name.d.ts +28 -0
- package/dist/service/eventbridge/source/sim-partner-event-source-name.js +62 -0
- package/dist/service/eventbridge/source/sim-partner-event-source-store.d.ts +18 -0
- package/dist/service/eventbridge/source/sim-partner-event-source-store.js +29 -0
- package/dist/service/eventbridge/source/sim-partner-event-source.d.ts +46 -0
- package/dist/service/eventbridge/source/sim-partner-event-source.js +43 -0
- package/dist/service/eventbridge/target/sim-event-target-dead-letter-config.d.ts +28 -0
- package/dist/service/eventbridge/target/sim-event-target-dead-letter-config.js +42 -0
- package/dist/service/eventbridge/target/sim-event-target-retry-policy.d.ts +28 -0
- package/dist/service/eventbridge/target/sim-event-target-retry-policy.js +53 -0
- package/dist/service/eventbridge/target/sim-event-target.d.ts +17 -0
- package/dist/service/eventbridge/target/sim-event-target.js +19 -0
- package/dist/service/scheduler/cfn/sim-cfn-schedule-properties.d.ts +0 -4
- package/dist/service/scheduler/cfn/sim-cfn-schedule-properties.js +2 -19
- package/dist/service/scheduler/cfn/sim-cfn-schedule-time-window.d.ts +10 -0
- package/dist/service/scheduler/cfn/sim-cfn-schedule-time-window.js +37 -0
- package/dist/service/scheduler/command/schedule/sim-scheduler-described-schedule.d.ts +1 -2
- package/dist/service/scheduler/command/schedule/sim-scheduler-described-schedule.js +2 -3
- package/dist/service/scheduler/command/schedule/sim-scheduler-schedule-writer.js +3 -0
- package/dist/service/scheduler/command/schedule/sim-scheduler-unsimulated-input.js +0 -25
- package/dist/service/scheduler/command/sim-scheduler-commands.d.ts +2 -0
- package/dist/service/scheduler/command/sim-scheduler-commands.js +1 -0
- package/dist/service/scheduler/schedule/sim-scheduler-pending-occurrences.d.ts +23 -0
- package/dist/service/scheduler/schedule/sim-scheduler-pending-occurrences.js +36 -0
- package/dist/service/scheduler/schedule/sim-scheduler-schedule.d.ts +7 -0
- package/dist/service/scheduler/schedule/sim-scheduler-schedule.js +5 -0
- package/dist/service/scheduler/schedule/sim-scheduler-schedules.d.ts +27 -1
- package/dist/service/scheduler/schedule/sim-scheduler-schedules.js +52 -8
- package/dist/service/scheduler/schedule/sim-scheduler-time-window.d.ts +33 -0
- package/dist/service/scheduler/schedule/sim-scheduler-time-window.js +93 -0
- package/dist/service/scheduler/sim-scheduler.d.ts +6 -0
- package/dist/service/scheduler/sim-scheduler.js +3 -1
- package/dist/util/random/sim-random.d.ts +35 -0
- package/dist/util/random/sim-random.js +33 -0
- package/docs/sdk/README.md +23 -0
- package/docs/services/eventbridge/README.md +223 -6
- package/docs/services/scheduler/README.md +68 -5
- package/docs/time/README.md +18 -0
- package/package.json +2 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A target's `RetryPolicy`, as a request or a template carries it.
|
|
3
|
+
*/
|
|
4
|
+
export interface SimEventTargetRetryPolicyInput {
|
|
5
|
+
readonly MaximumEventAgeInSeconds?: number | undefined;
|
|
6
|
+
readonly MaximumRetryAttempts?: number | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The retry limits one target declares, and the values they come to.
|
|
10
|
+
*
|
|
11
|
+
* A limit the policy leaves out takes EventBridge's own default, which is 185
|
|
12
|
+
* retries over at most 24 hours.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SimEventTargetRetryPolicy {
|
|
15
|
+
readonly declared: SimEventTargetRetryPolicyInput;
|
|
16
|
+
readonly maximumEventAgeInSeconds: number;
|
|
17
|
+
readonly maximumRetryAttempts: number;
|
|
18
|
+
private constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Read the retry policy a target may leave out.
|
|
21
|
+
*/
|
|
22
|
+
static optional(policy: SimEventTargetRetryPolicyInput | undefined): SimEventTargetRetryPolicy | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Read a retry policy, refusing values outside EventBridge's ranges.
|
|
25
|
+
*/
|
|
26
|
+
static of(policy: SimEventTargetRetryPolicyInput): SimEventTargetRetryPolicy;
|
|
27
|
+
private static requireWholeNumber;
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SimEventBridgeValidationException } from "../error/sim-event-bridge.error.js";
|
|
2
|
+
/**
|
|
3
|
+
* The longest EventBridge keeps retrying an event, and the most retries it
|
|
4
|
+
* makes, when a target's retry policy leaves either out.
|
|
5
|
+
*/
|
|
6
|
+
const defaultMaximumEventAgeInSeconds = 86_400;
|
|
7
|
+
const defaultMaximumRetryAttempts = 185;
|
|
8
|
+
const minimumEventAgeInSeconds = 60;
|
|
9
|
+
/**
|
|
10
|
+
* The retry limits one target declares, and the values they come to.
|
|
11
|
+
*
|
|
12
|
+
* A limit the policy leaves out takes EventBridge's own default, which is 185
|
|
13
|
+
* retries over at most 24 hours.
|
|
14
|
+
*/
|
|
15
|
+
export class SimEventTargetRetryPolicy {
|
|
16
|
+
declared;
|
|
17
|
+
maximumEventAgeInSeconds;
|
|
18
|
+
maximumRetryAttempts;
|
|
19
|
+
constructor(policy) {
|
|
20
|
+
this.declared = { ...policy };
|
|
21
|
+
this.maximumEventAgeInSeconds =
|
|
22
|
+
policy.MaximumEventAgeInSeconds ?? defaultMaximumEventAgeInSeconds;
|
|
23
|
+
this.maximumRetryAttempts =
|
|
24
|
+
policy.MaximumRetryAttempts ?? defaultMaximumRetryAttempts;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the retry policy a target may leave out.
|
|
28
|
+
*/
|
|
29
|
+
static optional(policy) {
|
|
30
|
+
if (policy === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return this.of(policy);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read a retry policy, refusing values outside EventBridge's ranges.
|
|
37
|
+
*/
|
|
38
|
+
static of(policy) {
|
|
39
|
+
this.requireWholeNumber("MaximumEventAgeInSeconds", policy.MaximumEventAgeInSeconds, minimumEventAgeInSeconds, defaultMaximumEventAgeInSeconds);
|
|
40
|
+
this.requireWholeNumber("MaximumRetryAttempts", policy.MaximumRetryAttempts, 0, defaultMaximumRetryAttempts);
|
|
41
|
+
return new this(policy);
|
|
42
|
+
}
|
|
43
|
+
static requireWholeNumber(name, value, minimum, maximum) {
|
|
44
|
+
if (value === undefined) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
48
|
+
throw new SimEventBridgeValidationException(`Invalid parameter: Target RetryPolicy ${name} Reason: it is a whole ` +
|
|
49
|
+
`number between ${String(minimum)} and ${String(maximum)}, and ` +
|
|
50
|
+
`${String(value)} is not.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { SimEventTargetArn } from "./sim-event-target-arn.js";
|
|
2
|
+
import { SimEventTargetDeadLetterConfig, type SimEventTargetDeadLetterConfigInput } from "./sim-event-target-dead-letter-config.js";
|
|
3
|
+
import { SimEventTargetRetryPolicy, type SimEventTargetRetryPolicyInput } from "./sim-event-target-retry-policy.js";
|
|
2
4
|
import { SimEventTargetEcs, type SimEventTargetTaskProperties } from "./sim-event-target-ecs.js";
|
|
3
5
|
/**
|
|
4
6
|
* One place a rule sends the events it matches.
|
|
@@ -29,6 +31,19 @@ export declare class SimEventTarget {
|
|
|
29
31
|
* is what the task overrides instead.
|
|
30
32
|
*/
|
|
31
33
|
readonly ecs: SimEventTargetEcs | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* How long and how often a failed delivery is retried, where the target
|
|
36
|
+
* says.
|
|
37
|
+
*
|
|
38
|
+
* A target without one is delivered once. Real EventBridge retries such a
|
|
39
|
+
* target for up to 24 hours by default, and that divergence is documented.
|
|
40
|
+
*/
|
|
41
|
+
readonly retryPolicy: SimEventTargetRetryPolicy | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The queue an event goes to once delivery is given up, where the target
|
|
44
|
+
* names one.
|
|
45
|
+
*/
|
|
46
|
+
readonly deadLetterConfig: SimEventTargetDeadLetterConfig | undefined;
|
|
32
47
|
private constructor();
|
|
33
48
|
/**
|
|
34
49
|
* Read a target from request input.
|
|
@@ -36,6 +51,8 @@ export declare class SimEventTarget {
|
|
|
36
51
|
static of(properties: SimEventTargetTaskProperties & {
|
|
37
52
|
readonly Id?: string | undefined;
|
|
38
53
|
readonly Arn?: string | undefined;
|
|
54
|
+
readonly RetryPolicy?: SimEventTargetRetryPolicyInput | undefined;
|
|
55
|
+
readonly DeadLetterConfig?: SimEventTargetDeadLetterConfigInput | undefined;
|
|
39
56
|
}): SimEventTarget;
|
|
40
57
|
/**
|
|
41
58
|
* Read a target's fixed input, which has to be JSON.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SimEventBridgeValidationException } from "../error/sim-event-bridge.error.js";
|
|
2
2
|
import { SimEventTargetArn } from "./sim-event-target-arn.js";
|
|
3
|
+
import { SimEventTargetDeadLetterConfig, } from "./sim-event-target-dead-letter-config.js";
|
|
4
|
+
import { SimEventTargetRetryPolicy, } from "./sim-event-target-retry-policy.js";
|
|
3
5
|
import { SimEventTargetEcs, } from "./sim-event-target-ecs.js";
|
|
4
6
|
/**
|
|
5
7
|
* Real EventBridge allows alphanumerics, full stops, hyphens and underscores,
|
|
@@ -35,11 +37,26 @@ export class SimEventTarget {
|
|
|
35
37
|
* is what the task overrides instead.
|
|
36
38
|
*/
|
|
37
39
|
ecs;
|
|
40
|
+
/**
|
|
41
|
+
* How long and how often a failed delivery is retried, where the target
|
|
42
|
+
* says.
|
|
43
|
+
*
|
|
44
|
+
* A target without one is delivered once. Real EventBridge retries such a
|
|
45
|
+
* target for up to 24 hours by default, and that divergence is documented.
|
|
46
|
+
*/
|
|
47
|
+
retryPolicy;
|
|
48
|
+
/**
|
|
49
|
+
* The queue an event goes to once delivery is given up, where the target
|
|
50
|
+
* names one.
|
|
51
|
+
*/
|
|
52
|
+
deadLetterConfig;
|
|
38
53
|
constructor(properties) {
|
|
39
54
|
this.id = properties.id;
|
|
40
55
|
this.arn = properties.arn;
|
|
41
56
|
this.input = properties.input;
|
|
42
57
|
this.ecs = properties.ecs;
|
|
58
|
+
this.retryPolicy = properties.retryPolicy;
|
|
59
|
+
this.deadLetterConfig = properties.deadLetterConfig;
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
62
|
* Read a target from request input.
|
|
@@ -51,6 +68,8 @@ export class SimEventTarget {
|
|
|
51
68
|
arn,
|
|
52
69
|
input: this.readInput(properties.Input),
|
|
53
70
|
ecs: SimEventTargetEcs.of(arn, properties),
|
|
71
|
+
retryPolicy: SimEventTargetRetryPolicy.optional(properties.RetryPolicy),
|
|
72
|
+
deadLetterConfig: SimEventTargetDeadLetterConfig.optional(properties.DeadLetterConfig),
|
|
54
73
|
});
|
|
55
74
|
}
|
|
56
75
|
/**
|
|
@@ -33,10 +33,6 @@ export declare class SimCfnScheduleProperties {
|
|
|
33
33
|
* than refusing it for being the wrong type.
|
|
34
34
|
*/
|
|
35
35
|
scheduleInput(): SimSchedulerScheduleInput;
|
|
36
|
-
/**
|
|
37
|
-
* The time window, which AWS requires on this Resource type.
|
|
38
|
-
*/
|
|
39
|
-
private flexibleTimeWindow;
|
|
40
36
|
/**
|
|
41
37
|
* The schedule's target, which AWS requires on this Resource type.
|
|
42
38
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRecord } from "../../../util/type-guard/record.js";
|
|
2
2
|
import { SimCfnGeneratedResourceName } from "../../cloudformation/resource/name/sim-cfn-generated-resource-name.js";
|
|
3
3
|
import { schedulerScheduleResourceType, simCfnSchedulerResourceError, } from "./sim-cfn-scheduler-resource-error.js";
|
|
4
|
+
import { cfnFlexibleTimeWindow } from "./sim-cfn-schedule-time-window.js";
|
|
4
5
|
const maximumNameLength = 64;
|
|
5
6
|
/**
|
|
6
7
|
* Reads AWS::Scheduler::Schedule properties into the shape CreateSchedule
|
|
@@ -52,7 +53,7 @@ export class SimCfnScheduleProperties {
|
|
|
52
53
|
GroupName: this.stringProperty("GroupName"),
|
|
53
54
|
ScheduleExpression: this.stringProperty("ScheduleExpression"),
|
|
54
55
|
ScheduleExpressionTimezone: this.stringProperty("ScheduleExpressionTimezone"),
|
|
55
|
-
FlexibleTimeWindow: this.
|
|
56
|
+
FlexibleTimeWindow: cfnFlexibleTimeWindow(this.properties.get("FlexibleTimeWindow"), (reason) => this.propertyError(reason)),
|
|
56
57
|
Target: this.target(),
|
|
57
58
|
State: this.stringProperty("State"),
|
|
58
59
|
Description: this.stringProperty("Description"),
|
|
@@ -62,24 +63,6 @@ export class SimCfnScheduleProperties {
|
|
|
62
63
|
KmsKeyArn: this.stringProperty("KmsKeyArn"),
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
|
-
/**
|
|
66
|
-
* The time window, which AWS requires on this Resource type.
|
|
67
|
-
*/
|
|
68
|
-
flexibleTimeWindow() {
|
|
69
|
-
const window = this.properties.get("FlexibleTimeWindow");
|
|
70
|
-
if (window === undefined) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
if (!isRecord(window)) {
|
|
74
|
-
throw this.propertyError("FlexibleTimeWindow is an object");
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
Mode: typeof window["Mode"] === "string" ? window["Mode"] : undefined,
|
|
78
|
-
MaximumWindowInMinutes: typeof window["MaximumWindowInMinutes"] === "number"
|
|
79
|
-
? window["MaximumWindowInMinutes"]
|
|
80
|
-
: undefined,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
66
|
/**
|
|
84
67
|
* The schedule's target, which AWS requires on this Resource type.
|
|
85
68
|
*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SimCfnTemplateValue } from "../../cloudformation/template/value/sim-cfn-template-value.js";
|
|
2
|
+
import type { SimSchedulerFlexibleTimeWindow } from "../command/schedule/schedule.command.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read an `AWS::Scheduler::Schedule` time window into the shape
|
|
5
|
+
* CreateSchedule takes.
|
|
6
|
+
*
|
|
7
|
+
* Whether the window is valid is left to simulated Scheduler, so a template
|
|
8
|
+
* and an SDK request are refused for the same reasons.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cfnFlexibleTimeWindow(window: SimCfnTemplateValue | undefined, propertyError: (reason: string) => Error): SimSchedulerFlexibleTimeWindow | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isRecord } from "../../../util/type-guard/record.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read a window length, which a template may give as a number or, through a
|
|
4
|
+
* Number Parameter, as a numeric string such as "15" or "15.0". Whether the
|
|
5
|
+
* number is a valid length is left to simulated Scheduler.
|
|
6
|
+
*/
|
|
7
|
+
function windowMinutes(value, propertyError) {
|
|
8
|
+
if (value === undefined || typeof value === "number") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
12
|
+
const minutes = Number(value);
|
|
13
|
+
if (Number.isFinite(minutes)) {
|
|
14
|
+
return minutes;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
throw propertyError("FlexibleTimeWindow MaximumWindowInMinutes is a number");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read an `AWS::Scheduler::Schedule` time window into the shape
|
|
21
|
+
* CreateSchedule takes.
|
|
22
|
+
*
|
|
23
|
+
* Whether the window is valid is left to simulated Scheduler, so a template
|
|
24
|
+
* and an SDK request are refused for the same reasons.
|
|
25
|
+
*/
|
|
26
|
+
export function cfnFlexibleTimeWindow(window, propertyError) {
|
|
27
|
+
if (window === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (!isRecord(window)) {
|
|
31
|
+
throw propertyError("FlexibleTimeWindow is an object");
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
Mode: typeof window["Mode"] === "string" ? window["Mode"] : undefined,
|
|
35
|
+
MaximumWindowInMinutes: windowMinutes(window["MaximumWindowInMinutes"], propertyError),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -5,8 +5,7 @@ import type { SimGetScheduleCommandOutput, SimSchedulerListedSchedule } from "./
|
|
|
5
5
|
*
|
|
6
6
|
* The expression comes back as the string it was created with rather than a
|
|
7
7
|
* re-serialised version of it, so a caller comparing what they read against
|
|
8
|
-
* what they sent sees what they sent.
|
|
9
|
-
* because that is the only mode this simulation takes.
|
|
8
|
+
* what they sent sees what they sent. The same goes for `FlexibleTimeWindow`.
|
|
10
9
|
*/
|
|
11
10
|
export declare function describedSchedule(schedule: SimSchedulerSchedule): Omit<SimGetScheduleCommandOutput, "$metadata">;
|
|
12
11
|
/**
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The expression comes back as the string it was created with rather than a
|
|
5
5
|
* re-serialised version of it, so a caller comparing what they read against
|
|
6
|
-
* what they sent sees what they sent.
|
|
7
|
-
* because that is the only mode this simulation takes.
|
|
6
|
+
* what they sent sees what they sent. The same goes for `FlexibleTimeWindow`.
|
|
8
7
|
*/
|
|
9
8
|
export function describedSchedule(schedule) {
|
|
10
9
|
return {
|
|
@@ -16,7 +15,7 @@ export function describedSchedule(schedule) {
|
|
|
16
15
|
State: schedule.state.value,
|
|
17
16
|
Description: schedule.description,
|
|
18
17
|
ActionAfterCompletion: schedule.actionAfterCompletion,
|
|
19
|
-
FlexibleTimeWindow:
|
|
18
|
+
FlexibleTimeWindow: schedule.timeWindow.declared,
|
|
20
19
|
Target: {
|
|
21
20
|
Arn: schedule.target.arn.value,
|
|
22
21
|
RoleArn: schedule.target.roleArn,
|
|
@@ -2,6 +2,7 @@ import { schedulerSchedule } from "../../schedule/sim-scheduler-schedule-express
|
|
|
2
2
|
import { actionAfterCompletionIn } from "../../schedule/sim-scheduler-action-after-completion.js";
|
|
3
3
|
import { SimSchedulerSchedule } from "../../schedule/sim-scheduler-schedule.js";
|
|
4
4
|
import { SimSchedulerScheduleState } from "../../schedule/sim-scheduler-schedule-state.js";
|
|
5
|
+
import { SimSchedulerTimeWindow } from "../../schedule/sim-scheduler-time-window.js";
|
|
5
6
|
import { SimSchedulerTarget } from "../../target/sim-scheduler-target.js";
|
|
6
7
|
import { refuseUnsimulatedScheduleInput } from "./sim-scheduler-unsimulated-input.js";
|
|
7
8
|
/**
|
|
@@ -27,6 +28,7 @@ export class SimSchedulerScheduleWriter {
|
|
|
27
28
|
* replacing a schedule does not create a new one as far as AWS is concerned.
|
|
28
29
|
*/
|
|
29
30
|
write(input, requested, createdAt) {
|
|
31
|
+
const timeWindow = SimSchedulerTimeWindow.of(input.FlexibleTimeWindow);
|
|
30
32
|
refuseUnsimulatedScheduleInput(input);
|
|
31
33
|
const now = this.clock.now();
|
|
32
34
|
const schedule = new SimSchedulerSchedule({
|
|
@@ -35,6 +37,7 @@ export class SimSchedulerScheduleWriter {
|
|
|
35
37
|
accountRegionScope: this.accountRegionScope,
|
|
36
38
|
schedule: schedulerSchedule(input.ScheduleExpression, input.ScheduleExpressionTimezone),
|
|
37
39
|
timeZone: input.ScheduleExpressionTimezone,
|
|
40
|
+
timeWindow,
|
|
38
41
|
target: SimSchedulerTarget.of(input.Target),
|
|
39
42
|
state: SimSchedulerScheduleState.of(input.State),
|
|
40
43
|
actionAfterCompletion: actionAfterCompletionIn(input.ActionAfterCompletion),
|
|
@@ -18,30 +18,6 @@ function refuseUnknownTimezone(timezone) {
|
|
|
18
18
|
`as 'Europe/London', or 'UTC'`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Read the flexible time window, which AWS requires on every request.
|
|
23
|
-
*
|
|
24
|
-
* `FLEXIBLE` is refused rather than treated as `OFF`. The whole point of it is
|
|
25
|
-
* that AWS invokes the target at some unpredictable moment inside the window,
|
|
26
|
-
* and a simulation firing at the exact due time instead would let a test assert
|
|
27
|
-
* on a precision real Scheduler does not offer.
|
|
28
|
-
*/
|
|
29
|
-
function refuseFlexibleWindow(input) {
|
|
30
|
-
const mode = input.FlexibleTimeWindow?.Mode;
|
|
31
|
-
if (mode === undefined) {
|
|
32
|
-
throw new SimSchedulerValidationException("FlexibleTimeWindow is required, and its Mode is OFF or FLEXIBLE");
|
|
33
|
-
}
|
|
34
|
-
if (mode === "FLEXIBLE") {
|
|
35
|
-
throw new SimSchedulerUnsimulatedInputException("FlexibleTimeWindow Mode FLEXIBLE is not simulated. Real Scheduler " +
|
|
36
|
-
"invokes the target at an unpredictable moment inside the window, " +
|
|
37
|
-
"and firing at the exact due time instead would let a test rely on " +
|
|
38
|
-
"timing AWS does not promise. Use Mode OFF.");
|
|
39
|
-
}
|
|
40
|
-
if (mode !== "OFF") {
|
|
41
|
-
throw new SimSchedulerValidationException(`Invalid parameter: FlexibleTimeWindow Mode Reason: '${mode}' is not a ` +
|
|
42
|
-
`mode. A window is OFF or FLEXIBLE.`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
21
|
/**
|
|
46
22
|
* Refuse the schedule request inputs this simulation does not model.
|
|
47
23
|
*
|
|
@@ -50,7 +26,6 @@ function refuseFlexibleWindow(input) {
|
|
|
50
26
|
* `ScheduleExpression` and send the caller to the wrong parameter.
|
|
51
27
|
*/
|
|
52
28
|
export function refuseUnsimulatedScheduleInput(input) {
|
|
53
|
-
refuseFlexibleWindow(input);
|
|
54
29
|
refuseUnsimulatedTarget(input.Target);
|
|
55
30
|
refuseUnknownTimezone(input.ScheduleExpressionTimezone);
|
|
56
31
|
if (input.StartDate !== undefined || input.EndDate !== undefined) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BackgroundScheduler } from "../../../util/background/background.js";
|
|
2
|
+
import type { SimRandom } from "../../../util/random/sim-random.js";
|
|
2
3
|
import type { SimAwsAccountRegionScope } from "../../aws/sim-aws-account-region-scope.js";
|
|
3
4
|
import type { SimIamInterServiceAuthZ } from "../../iam/authorize/sim-iam-inter-service-auth-z.js";
|
|
4
5
|
import type { SimSchedulerDeliveryTargets } from "../delivery/sim-scheduler-delivery.js";
|
|
@@ -17,6 +18,7 @@ interface SimSchedulerCommandsProperties {
|
|
|
17
18
|
readonly background: BackgroundScheduler;
|
|
18
19
|
readonly deliveryTargets?: SimSchedulerDeliveryTargets | undefined;
|
|
19
20
|
readonly accountRegionScope: SimAwsAccountRegionScope;
|
|
21
|
+
readonly random: SimRandom;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Every command handler one simulated Scheduler scope delegates to.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SimSchedulerSchedule } from "./sim-scheduler-schedule.js";
|
|
2
|
+
/**
|
|
3
|
+
* The occurrences of each schedule that have fallen due and are waiting
|
|
4
|
+
* inside a flexible window to be invoked.
|
|
5
|
+
*
|
|
6
|
+
* A window longer than the gap between due instants lets occurrences overlap,
|
|
7
|
+
* and a finite schedule's last occurrence can then be invoked before an
|
|
8
|
+
* earlier one. The schedule has completed once its last occurrence has fallen
|
|
9
|
+
* due and nothing is left waiting.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SimSchedulerPendingOccurrences {
|
|
12
|
+
private readonly waiting;
|
|
13
|
+
private readonly lastFallenDue;
|
|
14
|
+
/**
|
|
15
|
+
* Note an occurrence that has fallen due and will be invoked later.
|
|
16
|
+
*/
|
|
17
|
+
begin(schedule: SimSchedulerSchedule): void;
|
|
18
|
+
/**
|
|
19
|
+
* Note an occurrence that has been invoked, or given up on, and answer
|
|
20
|
+
* whether the schedule has now completed.
|
|
21
|
+
*/
|
|
22
|
+
end(schedule: SimSchedulerSchedule, last: boolean): boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The occurrences of each schedule that have fallen due and are waiting
|
|
3
|
+
* inside a flexible window to be invoked.
|
|
4
|
+
*
|
|
5
|
+
* A window longer than the gap between due instants lets occurrences overlap,
|
|
6
|
+
* and a finite schedule's last occurrence can then be invoked before an
|
|
7
|
+
* earlier one. The schedule has completed once its last occurrence has fallen
|
|
8
|
+
* due and nothing is left waiting.
|
|
9
|
+
*/
|
|
10
|
+
export class SimSchedulerPendingOccurrences {
|
|
11
|
+
waiting = new Map();
|
|
12
|
+
lastFallenDue = new Set();
|
|
13
|
+
/**
|
|
14
|
+
* Note an occurrence that has fallen due and will be invoked later.
|
|
15
|
+
*/
|
|
16
|
+
begin(schedule) {
|
|
17
|
+
this.waiting.set(schedule, (this.waiting.get(schedule) ?? 0) + 1);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Note an occurrence that has been invoked, or given up on, and answer
|
|
21
|
+
* whether the schedule has now completed.
|
|
22
|
+
*/
|
|
23
|
+
end(schedule, last) {
|
|
24
|
+
const remaining = (this.waiting.get(schedule) ?? 1) - 1;
|
|
25
|
+
this.waiting.set(schedule, remaining);
|
|
26
|
+
if (last) {
|
|
27
|
+
this.lastFallenDue.add(schedule);
|
|
28
|
+
}
|
|
29
|
+
if (remaining > 0 || !this.lastFallenDue.has(schedule)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
this.waiting.delete(schedule);
|
|
33
|
+
this.lastFallenDue.delete(schedule);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { SimAwsAccountRegionScope } from "../../aws/sim-aws-account-region-
|
|
|
3
3
|
import type { SimSchedulerTarget } from "../target/sim-scheduler-target.js";
|
|
4
4
|
import type { SimSchedulerScheduleName } from "./sim-scheduler-schedule-name.js";
|
|
5
5
|
import type { SimSchedulerScheduleState } from "./sim-scheduler-schedule-state.js";
|
|
6
|
+
import type { SimSchedulerTimeWindow } from "./sim-scheduler-time-window.js";
|
|
6
7
|
/**
|
|
7
8
|
* What Scheduler does with a schedule once it has finished invoking its target.
|
|
8
9
|
*
|
|
@@ -23,6 +24,8 @@ interface SimSchedulerScheduleProperties {
|
|
|
23
24
|
readonly description?: string | undefined;
|
|
24
25
|
/** The timezone the request named, which a describe reports back. */
|
|
25
26
|
readonly timeZone?: string | undefined;
|
|
27
|
+
/** When, relative to each due time, the target is invoked. */
|
|
28
|
+
readonly timeWindow: SimSchedulerTimeWindow;
|
|
26
29
|
readonly createdAt: Date;
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
@@ -60,6 +63,10 @@ export declare class SimSchedulerSchedule {
|
|
|
60
63
|
* than reporting the one it fell back to.
|
|
61
64
|
*/
|
|
62
65
|
readonly timeZone: string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* The flexible time window each occurrence is invoked inside.
|
|
68
|
+
*/
|
|
69
|
+
readonly timeWindow: SimSchedulerTimeWindow;
|
|
63
70
|
readonly creationDate: Date;
|
|
64
71
|
/**
|
|
65
72
|
* When this schedule falls due, which is read as simulated time advances.
|
|
@@ -35,6 +35,10 @@ export class SimSchedulerSchedule {
|
|
|
35
35
|
* than reporting the one it fell back to.
|
|
36
36
|
*/
|
|
37
37
|
timeZone;
|
|
38
|
+
/**
|
|
39
|
+
* The flexible time window each occurrence is invoked inside.
|
|
40
|
+
*/
|
|
41
|
+
timeWindow;
|
|
38
42
|
creationDate;
|
|
39
43
|
/**
|
|
40
44
|
* When this schedule falls due, which is read as simulated time advances.
|
|
@@ -53,6 +57,7 @@ export class SimSchedulerSchedule {
|
|
|
53
57
|
this.actionAfterCompletion = properties.actionAfterCompletion;
|
|
54
58
|
this.description = properties.description;
|
|
55
59
|
this.timeZone = properties.timeZone;
|
|
60
|
+
this.timeWindow = properties.timeWindow;
|
|
56
61
|
this.creationDate = properties.createdAt;
|
|
57
62
|
this.modified = properties.createdAt;
|
|
58
63
|
this.held = properties.state;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BackgroundScheduler } from "../../../util/background/background.js";
|
|
2
|
+
import type { SimRandom } from "../../../util/random/sim-random.js";
|
|
2
3
|
import type { SimSchedulerTargetDelivery } from "../delivery/sim-scheduler-target-delivery.js";
|
|
3
4
|
import type { SimSchedulerSchedule } from "./sim-scheduler-schedule.js";
|
|
4
5
|
import type { SimSchedulerScheduleStore } from "./sim-scheduler-schedule-store.js";
|
|
@@ -6,6 +7,8 @@ interface SimSchedulerSchedulesProperties {
|
|
|
6
7
|
readonly schedules: SimSchedulerScheduleStore;
|
|
7
8
|
readonly delivery: SimSchedulerTargetDelivery;
|
|
8
9
|
readonly background: BackgroundScheduler;
|
|
10
|
+
/** Where each occurrence's moment inside a flexible window is drawn. */
|
|
11
|
+
readonly random: SimRandom;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* What makes a schedule fire, which is simulated time reaching it.
|
|
@@ -22,11 +25,19 @@ interface SimSchedulerSchedulesProperties {
|
|
|
22
25
|
* no timer to cancel, only a firing that finds itself out of date. That is also
|
|
23
26
|
* what makes an update reschedule from the new expression rather than keeping
|
|
24
27
|
* the old due times, since an update stores a new schedule and arms it.
|
|
28
|
+
*
|
|
29
|
+
* A schedule with a flexible time window is armed for each due instant all
|
|
30
|
+
* the same, and then invokes its target at a moment drawn inside the window.
|
|
31
|
+
* The next occurrence is armed from the due instant rather than from the
|
|
32
|
+
* invocation, so the window delays each invocation without moving the
|
|
33
|
+
* schedule.
|
|
25
34
|
*/
|
|
26
35
|
export declare class SimSchedulerSchedules {
|
|
27
36
|
private readonly schedules;
|
|
28
37
|
private readonly delivery;
|
|
29
38
|
private readonly background;
|
|
39
|
+
private readonly random;
|
|
40
|
+
private readonly pending;
|
|
30
41
|
constructor(properties: SimSchedulerSchedulesProperties);
|
|
31
42
|
/**
|
|
32
43
|
* Start a schedule, from now.
|
|
@@ -40,9 +51,24 @@ export declare class SimSchedulerSchedules {
|
|
|
40
51
|
*/
|
|
41
52
|
private armAfter;
|
|
42
53
|
/**
|
|
43
|
-
*
|
|
54
|
+
* Handle a schedule that has fallen due: arm the next occurrence, and
|
|
55
|
+
* invoke the target at the moment its time window gives this one.
|
|
56
|
+
*/
|
|
57
|
+
private occur;
|
|
58
|
+
/**
|
|
59
|
+
* Invoke a schedule's target for one occurrence.
|
|
60
|
+
*
|
|
61
|
+
* A schedule deleted or replaced while its occurrence waited inside the
|
|
62
|
+
* window is not invoked. The delivery carries the due instant rather than
|
|
63
|
+
* the moment it was made, since that is the occurrence it belongs to. A
|
|
64
|
+
* schedule completes with whichever occurrence is invoked last, which need
|
|
65
|
+
* not be its last due instant when windows overlap.
|
|
44
66
|
*/
|
|
45
67
|
private fire;
|
|
68
|
+
/**
|
|
69
|
+
* Whether a schedule has been deleted or replaced since it was armed.
|
|
70
|
+
*/
|
|
71
|
+
private isStale;
|
|
46
72
|
/**
|
|
47
73
|
* Deal with a schedule that has no next occurrence.
|
|
48
74
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SimSchedulerPendingOccurrences } from "./sim-scheduler-pending-occurrences.js";
|
|
1
2
|
/**
|
|
2
3
|
* What makes a schedule fire, which is simulated time reaching it.
|
|
3
4
|
*
|
|
@@ -13,15 +14,24 @@
|
|
|
13
14
|
* no timer to cancel, only a firing that finds itself out of date. That is also
|
|
14
15
|
* what makes an update reschedule from the new expression rather than keeping
|
|
15
16
|
* the old due times, since an update stores a new schedule and arms it.
|
|
17
|
+
*
|
|
18
|
+
* A schedule with a flexible time window is armed for each due instant all
|
|
19
|
+
* the same, and then invokes its target at a moment drawn inside the window.
|
|
20
|
+
* The next occurrence is armed from the due instant rather than from the
|
|
21
|
+
* invocation, so the window delays each invocation without moving the
|
|
22
|
+
* schedule.
|
|
16
23
|
*/
|
|
17
24
|
export class SimSchedulerSchedules {
|
|
18
25
|
schedules;
|
|
19
26
|
delivery;
|
|
20
27
|
background;
|
|
28
|
+
random;
|
|
29
|
+
pending = new SimSchedulerPendingOccurrences();
|
|
21
30
|
constructor(properties) {
|
|
22
31
|
this.schedules = properties.schedules;
|
|
23
32
|
this.delivery = properties.delivery;
|
|
24
33
|
this.background = properties.background;
|
|
34
|
+
this.random = properties.random;
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
37
|
* Start a schedule, from now.
|
|
@@ -41,15 +51,45 @@ export class SimSchedulerSchedules {
|
|
|
41
51
|
return;
|
|
42
52
|
}
|
|
43
53
|
this.background.scheduleAt(due, () => {
|
|
44
|
-
this.
|
|
54
|
+
this.occur(schedule, due);
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Handle a schedule that has fallen due: arm the next occurrence, and
|
|
60
|
+
* invoke the target at the moment its time window gives this one.
|
|
61
|
+
*/
|
|
62
|
+
occur(schedule, due) {
|
|
63
|
+
if (this.isStale(schedule)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const last = schedule.schedule.nextAfter(due) === undefined;
|
|
67
|
+
if (!last) {
|
|
68
|
+
this.armAfter(schedule, due);
|
|
69
|
+
}
|
|
70
|
+
const at = schedule.timeWindow.invocationAt(due, this.random);
|
|
71
|
+
this.pending.begin(schedule);
|
|
72
|
+
if (at.getTime() === due.getTime()) {
|
|
73
|
+
this.fire(schedule, due, last);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.background.scheduleAt(at, () => {
|
|
77
|
+
this.fire(schedule, due, last);
|
|
45
78
|
return Promise.resolve();
|
|
46
79
|
});
|
|
47
80
|
}
|
|
48
81
|
/**
|
|
49
|
-
*
|
|
82
|
+
* Invoke a schedule's target for one occurrence.
|
|
83
|
+
*
|
|
84
|
+
* A schedule deleted or replaced while its occurrence waited inside the
|
|
85
|
+
* window is not invoked. The delivery carries the due instant rather than
|
|
86
|
+
* the moment it was made, since that is the occurrence it belongs to. A
|
|
87
|
+
* schedule completes with whichever occurrence is invoked last, which need
|
|
88
|
+
* not be its last due instant when windows overlap.
|
|
50
89
|
*/
|
|
51
|
-
fire(schedule, due) {
|
|
52
|
-
|
|
90
|
+
fire(schedule, due, last) {
|
|
91
|
+
const completed = this.pending.end(schedule, last);
|
|
92
|
+
if (this.isStale(schedule)) {
|
|
53
93
|
return;
|
|
54
94
|
}
|
|
55
95
|
const invoked = schedule.state.isEnabled;
|
|
@@ -58,11 +98,15 @@ export class SimSchedulerSchedules {
|
|
|
58
98
|
await this.delivery.deliver({ schedule, at: due });
|
|
59
99
|
});
|
|
60
100
|
}
|
|
61
|
-
if (
|
|
62
|
-
this.
|
|
63
|
-
return;
|
|
101
|
+
if (completed) {
|
|
102
|
+
this.completed(schedule, invoked);
|
|
64
103
|
}
|
|
65
|
-
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a schedule has been deleted or replaced since it was armed.
|
|
107
|
+
*/
|
|
108
|
+
isStale(schedule) {
|
|
109
|
+
return (this.schedules.find(schedule.groupName, schedule.name.value) !== schedule);
|
|
66
110
|
}
|
|
67
111
|
/**
|
|
68
112
|
* Deal with a schedule that has no next occurrence.
|