@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,33 @@
|
|
|
1
|
+
import type { SimRandom } from "../../../util/random/sim-random.js";
|
|
2
|
+
import type { SimSchedulerFlexibleTimeWindow } from "../command/schedule/schedule.command.js";
|
|
3
|
+
/**
|
|
4
|
+
* When, relative to its due time, a schedule invokes its target.
|
|
5
|
+
*
|
|
6
|
+
* With `OFF` that is the due time itself. With `FLEXIBLE` it is some moment
|
|
7
|
+
* after the due time and inside the window, which AWS chooses and does not
|
|
8
|
+
* reveal. Firing on the dot instead would let a test pass by relying on
|
|
9
|
+
* timing AWS does not promise, so each occurrence draws its own moment.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SimSchedulerTimeWindow {
|
|
12
|
+
/**
|
|
13
|
+
* The window length in minutes, or nothing when the mode is `OFF`.
|
|
14
|
+
*/
|
|
15
|
+
readonly minutes: number | undefined;
|
|
16
|
+
private constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Read the window a request carries, which AWS requires on every one.
|
|
19
|
+
*/
|
|
20
|
+
static of(window: SimSchedulerFlexibleTimeWindow | undefined): SimSchedulerTimeWindow;
|
|
21
|
+
/**
|
|
22
|
+
* The window as GetSchedule reports it, which is what the request sent.
|
|
23
|
+
*/
|
|
24
|
+
get declared(): SimSchedulerFlexibleTimeWindow;
|
|
25
|
+
/**
|
|
26
|
+
* The instant one occurrence invokes its target, drawn from `random`.
|
|
27
|
+
*
|
|
28
|
+
* It is never before the due time and always before the window closes, so
|
|
29
|
+
* a test that moves the clock past the end of the window sees the
|
|
30
|
+
* invocation whatever was drawn.
|
|
31
|
+
*/
|
|
32
|
+
invocationAt(due: Date, random: SimRandom): Date;
|
|
33
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SimSchedulerValidationException } from "../error/sim-scheduler.error.js";
|
|
2
|
+
const minimumWindowInMinutes = 1;
|
|
3
|
+
const maximumWindowInMinutes = 1440;
|
|
4
|
+
const millisecondsPerMinute = 60_000;
|
|
5
|
+
/**
|
|
6
|
+
* Refuse a value the API model's own constraints reject, in the words AWS
|
|
7
|
+
* uses for them.
|
|
8
|
+
*/
|
|
9
|
+
function constraintFailure(value, member, constraint) {
|
|
10
|
+
return new SimSchedulerValidationException(`1 validation error detected: Value '${value}' at ` +
|
|
11
|
+
`'flexibleTimeWindow.${member}' failed to satisfy constraint: ` +
|
|
12
|
+
`Member must ${constraint}`);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Read the length of a flexible window, refusing one outside 1 to 1440.
|
|
16
|
+
*/
|
|
17
|
+
function windowLength(minutes) {
|
|
18
|
+
if (minutes === undefined) {
|
|
19
|
+
throw new SimSchedulerValidationException("Invalid parameter: FlexibleTimeWindow Reason: a FLEXIBLE window " +
|
|
20
|
+
"needs MaximumWindowInMinutes, from 1 to 1440");
|
|
21
|
+
}
|
|
22
|
+
if (!Number.isSafeInteger(minutes)) {
|
|
23
|
+
throw new SimSchedulerValidationException(`Invalid parameter: FlexibleTimeWindow Reason: MaximumWindowInMinutes ` +
|
|
24
|
+
`is a whole number of minutes, and ${String(minutes)} is not`);
|
|
25
|
+
}
|
|
26
|
+
if (minutes < minimumWindowInMinutes) {
|
|
27
|
+
throw constraintFailure(String(minutes), "maximumWindowInMinutes", `have value greater than or equal to ${String(minimumWindowInMinutes)}`);
|
|
28
|
+
}
|
|
29
|
+
if (minutes > maximumWindowInMinutes) {
|
|
30
|
+
throw constraintFailure(String(minutes), "maximumWindowInMinutes", `have value less than or equal to ${String(maximumWindowInMinutes)}`);
|
|
31
|
+
}
|
|
32
|
+
return minutes;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* When, relative to its due time, a schedule invokes its target.
|
|
36
|
+
*
|
|
37
|
+
* With `OFF` that is the due time itself. With `FLEXIBLE` it is some moment
|
|
38
|
+
* after the due time and inside the window, which AWS chooses and does not
|
|
39
|
+
* reveal. Firing on the dot instead would let a test pass by relying on
|
|
40
|
+
* timing AWS does not promise, so each occurrence draws its own moment.
|
|
41
|
+
*/
|
|
42
|
+
export class SimSchedulerTimeWindow {
|
|
43
|
+
/**
|
|
44
|
+
* The window length in minutes, or nothing when the mode is `OFF`.
|
|
45
|
+
*/
|
|
46
|
+
minutes;
|
|
47
|
+
constructor(minutes) {
|
|
48
|
+
this.minutes = minutes;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the window a request carries, which AWS requires on every one.
|
|
52
|
+
*/
|
|
53
|
+
static of(window) {
|
|
54
|
+
const mode = window?.Mode;
|
|
55
|
+
if (mode === undefined) {
|
|
56
|
+
throw new SimSchedulerValidationException("FlexibleTimeWindow is required, and its Mode is OFF or FLEXIBLE");
|
|
57
|
+
}
|
|
58
|
+
if (mode === "FLEXIBLE") {
|
|
59
|
+
return new SimSchedulerTimeWindow(windowLength(window?.MaximumWindowInMinutes));
|
|
60
|
+
}
|
|
61
|
+
if (mode !== "OFF") {
|
|
62
|
+
throw constraintFailure(mode, "mode", "satisfy enum value set: [OFF, FLEXIBLE]");
|
|
63
|
+
}
|
|
64
|
+
if (window?.MaximumWindowInMinutes !== undefined) {
|
|
65
|
+
throw new SimSchedulerValidationException("Invalid parameter: FlexibleTimeWindow Reason: " +
|
|
66
|
+
"MaximumWindowInMinutes is only allowed when Mode is FLEXIBLE");
|
|
67
|
+
}
|
|
68
|
+
return new SimSchedulerTimeWindow(undefined);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The window as GetSchedule reports it, which is what the request sent.
|
|
72
|
+
*/
|
|
73
|
+
get declared() {
|
|
74
|
+
if (this.minutes === undefined) {
|
|
75
|
+
return { Mode: "OFF" };
|
|
76
|
+
}
|
|
77
|
+
return { Mode: "FLEXIBLE", MaximumWindowInMinutes: this.minutes };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The instant one occurrence invokes its target, drawn from `random`.
|
|
81
|
+
*
|
|
82
|
+
* It is never before the due time and always before the window closes, so
|
|
83
|
+
* a test that moves the clock past the end of the window sees the
|
|
84
|
+
* invocation whatever was drawn.
|
|
85
|
+
*/
|
|
86
|
+
invocationAt(due, random) {
|
|
87
|
+
if (this.minutes === undefined) {
|
|
88
|
+
return due;
|
|
89
|
+
}
|
|
90
|
+
const offset = Math.floor(random.next() * this.minutes * millisecondsPerMinute);
|
|
91
|
+
return new Date(due.getTime() + offset);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -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 { SimSdkCommandRouter } from "../../sdk/router/sim-sdk-command-router.type.js";
|
|
3
4
|
import type { SimAwsAccountRegionScope } from "../aws/sim-aws-account-region-scope.js";
|
|
4
5
|
import type { SimIamInterServiceAuthZ } from "../iam/authorize/sim-iam-inter-service-auth-z.js";
|
|
@@ -21,6 +22,11 @@ interface SimSchedulerProperties {
|
|
|
21
22
|
* in another simulated service is only reachable through SimAws.
|
|
22
23
|
*/
|
|
23
24
|
readonly deliveryTargets?: SimSchedulerDeliveryTargets;
|
|
25
|
+
/**
|
|
26
|
+
* Where a flexible time window draws the moment each occurrence is invoked,
|
|
27
|
+
* which is the host's `Math.random` unless a test supplies its own.
|
|
28
|
+
*/
|
|
29
|
+
readonly random?: SimRandom;
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* Simulated EventBridge Scheduler. Handles SDK commands. Emulates AWS
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BackgroundTasks, } from "../../util/background/background.js";
|
|
2
|
+
import { SimHostRandom } from "../../util/random/sim-random.js";
|
|
2
3
|
import { simAwsAccountRegionScopeFactory } from "../aws/sim-aws-account-region-scope.factory.js";
|
|
3
4
|
import { simIamInRegion } from "../iam/authorize/sim-iam-region-auth-z.js";
|
|
4
5
|
import { SimSchedulerCommands } from "./command/sim-scheduler-commands.js";
|
|
@@ -30,7 +31,7 @@ export class SimScheduler extends SimSchedulerInspection {
|
|
|
30
31
|
});
|
|
31
32
|
constructor(properties = {}) {
|
|
32
33
|
super();
|
|
33
|
-
const { accountRegionScope = simAwsAccountRegionScopeFactory.make(), background = new BackgroundTasks(), } = properties;
|
|
34
|
+
const { accountRegionScope = simAwsAccountRegionScopeFactory.make(), background = new BackgroundTasks(), random = new SimHostRandom(), } = properties;
|
|
34
35
|
const iam = simIamInRegion(properties.iam, accountRegionScope.regionName);
|
|
35
36
|
this.background = background;
|
|
36
37
|
this.groupStore = new SimSchedulerScheduleGroupStore({
|
|
@@ -44,6 +45,7 @@ export class SimScheduler extends SimSchedulerInspection {
|
|
|
44
45
|
background,
|
|
45
46
|
deliveryTargets: properties.deliveryTargets,
|
|
46
47
|
accountRegionScope,
|
|
48
|
+
random,
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a simulation draws the values AWS leaves to chance.
|
|
3
|
+
*
|
|
4
|
+
* Some AWS behaviour is unpredictable by design, such as the moment inside a
|
|
5
|
+
* Scheduler flexible time window at which the target is invoked. A simulation
|
|
6
|
+
* draws those values from here, so a test can make them repeatable by passing
|
|
7
|
+
* a seeded source and leave them unpredictable otherwise.
|
|
8
|
+
*/
|
|
9
|
+
export interface SimRandom {
|
|
10
|
+
/**
|
|
11
|
+
* The next value, at least 0 and less than 1.
|
|
12
|
+
*/
|
|
13
|
+
next(): number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Draws from the host's `Math.random`, so values differ from run to run.
|
|
17
|
+
*
|
|
18
|
+
* This is the default, because a test that passes only for one particular
|
|
19
|
+
* draw is relying on something AWS does not promise.
|
|
20
|
+
*/
|
|
21
|
+
export declare class SimHostRandom implements SimRandom {
|
|
22
|
+
next(): number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Draws a repeatable sequence from a seed.
|
|
26
|
+
*
|
|
27
|
+
* Two sources built from the same seed give the same values in the same order,
|
|
28
|
+
* so a failure seen once can be reproduced. The generator is mulberry32, which
|
|
29
|
+
* is small and fast and has nothing to do with security.
|
|
30
|
+
*/
|
|
31
|
+
export declare class SimSeededRandom implements SimRandom {
|
|
32
|
+
private state;
|
|
33
|
+
constructor(seed: number);
|
|
34
|
+
next(): number;
|
|
35
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draws from the host's `Math.random`, so values differ from run to run.
|
|
3
|
+
*
|
|
4
|
+
* This is the default, because a test that passes only for one particular
|
|
5
|
+
* draw is relying on something AWS does not promise.
|
|
6
|
+
*/
|
|
7
|
+
export class SimHostRandom {
|
|
8
|
+
next() {
|
|
9
|
+
return Math.random();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Draws a repeatable sequence from a seed.
|
|
14
|
+
*
|
|
15
|
+
* Two sources built from the same seed give the same values in the same order,
|
|
16
|
+
* so a failure seen once can be reproduced. The generator is mulberry32, which
|
|
17
|
+
* is small and fast and has nothing to do with security.
|
|
18
|
+
*/
|
|
19
|
+
export class SimSeededRandom {
|
|
20
|
+
state;
|
|
21
|
+
constructor(seed) {
|
|
22
|
+
// Only the low 32 bits of the seed take part, as the generator works on
|
|
23
|
+
// 32-bit integers.
|
|
24
|
+
this.state = Math.trunc(seed) >>> 0;
|
|
25
|
+
}
|
|
26
|
+
next() {
|
|
27
|
+
this.state = (this.state + 0x6d_2b_79_f5) >>> 0;
|
|
28
|
+
let mixed = this.state;
|
|
29
|
+
mixed = Math.imul(mixed ^ (mixed >>> 15), mixed | 1);
|
|
30
|
+
mixed ^= mixed + Math.imul(mixed ^ (mixed >>> 7), mixed | 61);
|
|
31
|
+
return ((mixed ^ (mixed >>> 14)) >>> 0) / 4_294_967_296;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/docs/sdk/README.md
CHANGED
|
@@ -172,6 +172,24 @@ specific commands:
|
|
|
172
172
|
The list accepts command classes or command names. Sending another command throws
|
|
173
173
|
`SimSdkCommandNotInterceptedError`.
|
|
174
174
|
|
|
175
|
+
## Abort a send
|
|
176
|
+
|
|
177
|
+
An intercepted client honours the `abortSignal` option of `send`, as the SDK's own HTTP handler does:
|
|
178
|
+
|
|
179
|
+
`await client.send(command, { abortSignal: AbortSignal.timeout(5000) })`
|
|
180
|
+
|
|
181
|
+
A signal that has already fired rejects the send before it reaches the simulation. A signal that fires
|
|
182
|
+
while the simulated operation is pending rejects the send then. A Lambda handler sleeping on the
|
|
183
|
+
simulation's clock is one operation that stays pending.
|
|
184
|
+
|
|
185
|
+
Either way the send rejects with an `AbortError` shaped like the one the SDK throws. Its message is
|
|
186
|
+
`Request aborted` and its `cause` is the signal's reason (a `TimeoutError` for
|
|
187
|
+
`AbortSignal.timeout`). Match it by `error.name`.
|
|
188
|
+
|
|
189
|
+
A send carrying a signal waits one turn of the host's timers before it reaches the simulation. That
|
|
190
|
+
turn stands in for the request's time on the network. A timeout of 0 or 1 milliseconds set before the
|
|
191
|
+
send aborts it, as it would a request to AWS.
|
|
192
|
+
|
|
175
193
|
## Intercept the DynamoDB document client
|
|
176
194
|
|
|
177
195
|
The DynamoDB document client accepts plain JavaScript values. Intercept the document client object,
|
|
@@ -282,6 +300,11 @@ service throws `SimSdkUnknownServiceError`.
|
|
|
282
300
|
- Simulated errors have SDK-shaped `name` and `$metadata` fields. They are separate classes from the
|
|
283
301
|
SDK exceptions, so match them by `error.name` instead of `instanceof`.
|
|
284
302
|
- The callback form of `send(command, callback)` is not supported. Use the promise form.
|
|
303
|
+
- An aborted send stops waiting for its answer. The simulated operation it started still runs to the
|
|
304
|
+
end, as a request AWS has already received does.
|
|
305
|
+
- Most simulated operations answer within one turn of the host's timers. A timeout longer than a few
|
|
306
|
+
milliseconds only fires during an operation that waits, such as a sleeping Lambda handler. A
|
|
307
|
+
simulated Bedrock answer cannot be delayed.
|
|
285
308
|
- Yulin reads the `marshallOptions` in
|
|
286
309
|
`DynamoDBDocumentClient.from(client, { marshallOptions, unmarshallOptions })` and ignores the
|
|
287
310
|
`unmarshallOptions`. A stored value comes back the way a document client built with no options of
|
|
@@ -921,6 +921,91 @@ and a rule routes the same events whether it carries them or not. They are recor
|
|
|
921
921
|
property and the deploy stands. Nothing reads them back. A rule or a bus deployed with tags behaves
|
|
922
922
|
as though the template had never named them.
|
|
923
923
|
|
|
924
|
+
## Receiving events from a SaaS partner
|
|
925
|
+
|
|
926
|
+
A SaaS partner such as Stripe sends events to an account through a partner event source, named like
|
|
927
|
+
`aws.partner/stripe.com/ed_...`. The partner creates the source from its own account, so a test
|
|
928
|
+
shares one with `addPartnerEventSource(...)`. The account then creates a partner event bus with the
|
|
929
|
+
same `Name` and `EventSourceName`, and `putPartnerEvents(...)` sends events onto it as the partner
|
|
930
|
+
would.
|
|
931
|
+
|
|
932
|
+
```typescript sim-event-bridge-partner-events
|
|
933
|
+
/**
|
|
934
|
+
* Receiving Stripe's events on a partner event bus.
|
|
935
|
+
*/
|
|
936
|
+
|
|
937
|
+
import {
|
|
938
|
+
CreateEventBusCommand,
|
|
939
|
+
PutPartnerEventsCommand,
|
|
940
|
+
PutRuleCommand,
|
|
941
|
+
} from "@aws-sdk/client-eventbridge";
|
|
942
|
+
|
|
943
|
+
import { SimAws } from "@kensio/yulin";
|
|
944
|
+
|
|
945
|
+
const simAws = new SimAws();
|
|
946
|
+
const events = simAws.eventBridge();
|
|
947
|
+
const source = "aws.partner/stripe.com/ed_test_billing";
|
|
948
|
+
|
|
949
|
+
// What Stripe does when an event destination is set up for the account.
|
|
950
|
+
events.addPartnerEventSource(source);
|
|
951
|
+
|
|
952
|
+
// What the account does to receive from it.
|
|
953
|
+
await events.createEventBus(
|
|
954
|
+
new CreateEventBusCommand({ Name: source, EventSourceName: source }),
|
|
955
|
+
);
|
|
956
|
+
await events.putRule(
|
|
957
|
+
new PutRuleCommand({
|
|
958
|
+
Name: "subscriptions",
|
|
959
|
+
EventBusName: source,
|
|
960
|
+
EventPattern: JSON.stringify({
|
|
961
|
+
source: [{ prefix: "aws.partner/stripe.com" }],
|
|
962
|
+
"detail-type": ["customer.subscription.created"],
|
|
963
|
+
}),
|
|
964
|
+
}),
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
// What Stripe sends when a subscription starts.
|
|
968
|
+
await events.putPartnerEvents(
|
|
969
|
+
new PutPartnerEventsCommand({
|
|
970
|
+
Entries: [
|
|
971
|
+
{
|
|
972
|
+
Source: source,
|
|
973
|
+
DetailType: "customer.subscription.created",
|
|
974
|
+
Detail: JSON.stringify({
|
|
975
|
+
id: "evt_test_subscription",
|
|
976
|
+
type: "customer.subscription.created",
|
|
977
|
+
data: { object: { id: "sub_test" } },
|
|
978
|
+
}),
|
|
979
|
+
},
|
|
980
|
+
],
|
|
981
|
+
}),
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
console.log(events.receiptsOn(source)[0]?.matchedRuleNames); // ["subscriptions"]
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
`putPartnerEvents(...)` takes the entries of the partner's `PutPartnerEvents` request. Each entry's
|
|
988
|
+
`Source` names a shared source, and the event goes to the bus of the same name. Rules match it on
|
|
989
|
+
`source`, `detail-type` and `detail` like any other event. A target receives the usual envelope, with
|
|
990
|
+
`account` set to the receiving account. Stripe puts its event type in `DetailType` and the whole
|
|
991
|
+
Stripe event object, as JSON, in `Detail`. An entry whose `Source` names no shared source fails the
|
|
992
|
+
whole request with `ResourceNotFoundException`.
|
|
993
|
+
|
|
994
|
+
An `AWS::Events::EventBus` with `Name` and `EventSourceName` both set to the source name creates the
|
|
995
|
+
same bus from a template, and a rule names it with a `Ref` to the bus.
|
|
996
|
+
|
|
997
|
+
The source has to be shared before the bus is created. `CreateEventBus` fails with
|
|
998
|
+
`ResourceNotFoundException` when it is missing, and a template creating the bus fails its stack, as
|
|
999
|
+
both do on AWS. A `Name` that differs from the `EventSourceName` is a `ValidationException`, as is a
|
|
1000
|
+
`/` in the name of a bus created without one.
|
|
1001
|
+
|
|
1002
|
+
`DescribeEventSource` reports the source as `PENDING` until its bus exists, and `ACTIVE` after.
|
|
1003
|
+
Deleting the bus puts it back to `PENDING`. An event sent while the source is pending is dropped,
|
|
1004
|
+
as AWS documents.
|
|
1005
|
+
|
|
1006
|
+
Only the partner sends events to a partner event bus. `PutEvents` naming one is refused with a
|
|
1007
|
+
`ValidationException` (its `EventBusName` takes no `/`).
|
|
1008
|
+
|
|
924
1009
|
## Reading back a failed delivery
|
|
925
1010
|
|
|
926
1011
|
Real EventBridge tells the caller nothing about a failed delivery. A `PutEvents` that matched a rule
|
|
@@ -981,6 +1066,125 @@ no is a modelled outcome a test may be asking for on purpose.
|
|
|
981
1066
|
`JSON.stringify` on a failure carries the message alongside the rule, the target and the event it
|
|
982
1067
|
names.
|
|
983
1068
|
|
|
1069
|
+
## Retrying and dead-lettering a delivery
|
|
1070
|
+
|
|
1071
|
+
A target can carry a `RetryPolicy` and a `DeadLetterConfig`, from `PutTargets` or from a target in an
|
|
1072
|
+
`AWS::Events::Rule`. The dead-letter queue receives an event once EventBridge gives up delivering it.
|
|
1073
|
+
|
|
1074
|
+
```typescript sim-event-bridge-dead-letter
|
|
1075
|
+
/**
|
|
1076
|
+
* Sending an event a target refused to its dead-letter queue.
|
|
1077
|
+
*/
|
|
1078
|
+
|
|
1079
|
+
import {
|
|
1080
|
+
PutEventsCommand,
|
|
1081
|
+
PutRuleCommand,
|
|
1082
|
+
PutTargetsCommand,
|
|
1083
|
+
} from "@aws-sdk/client-eventbridge";
|
|
1084
|
+
import {
|
|
1085
|
+
CreateQueueCommand,
|
|
1086
|
+
ReceiveMessageCommand,
|
|
1087
|
+
SetQueueAttributesCommand,
|
|
1088
|
+
} from "@aws-sdk/client-sqs";
|
|
1089
|
+
|
|
1090
|
+
import { SimAws } from "@kensio/yulin";
|
|
1091
|
+
|
|
1092
|
+
const simAws = new SimAws();
|
|
1093
|
+
const dlqArn = "arn:aws:sqs:us-east-1:888888888888:undelivered-orders";
|
|
1094
|
+
|
|
1095
|
+
// A dead-letter queue admitting EventBridge for the rule.
|
|
1096
|
+
const { QueueUrl } = await simAws
|
|
1097
|
+
.sqs()
|
|
1098
|
+
.createQueue(new CreateQueueCommand({ QueueName: "undelivered-orders" }));
|
|
1099
|
+
await simAws.sqs().setQueueAttributes(
|
|
1100
|
+
new SetQueueAttributesCommand({
|
|
1101
|
+
QueueUrl,
|
|
1102
|
+
Attributes: {
|
|
1103
|
+
Policy: JSON.stringify({
|
|
1104
|
+
Version: "2012-10-17",
|
|
1105
|
+
Statement: [
|
|
1106
|
+
{
|
|
1107
|
+
Effect: "Allow",
|
|
1108
|
+
Principal: { Service: "events.amazonaws.com" },
|
|
1109
|
+
Action: "sqs:SendMessage",
|
|
1110
|
+
Resource: dlqArn,
|
|
1111
|
+
Condition: {
|
|
1112
|
+
ArnEquals: {
|
|
1113
|
+
"aws:SourceArn":
|
|
1114
|
+
"arn:aws:events:us-east-1:888888888888:rule/orders",
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
}),
|
|
1120
|
+
},
|
|
1121
|
+
}),
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
// A target queue that was never created, so every delivery fails.
|
|
1125
|
+
await simAws.eventBridge().putRule(
|
|
1126
|
+
new PutRuleCommand({
|
|
1127
|
+
Name: "orders",
|
|
1128
|
+
EventPattern: JSON.stringify({ source: ["orders.service"] }),
|
|
1129
|
+
}),
|
|
1130
|
+
);
|
|
1131
|
+
await simAws.eventBridge().putTargets(
|
|
1132
|
+
new PutTargetsCommand({
|
|
1133
|
+
Rule: "orders",
|
|
1134
|
+
Targets: [
|
|
1135
|
+
{
|
|
1136
|
+
Id: "fulfilment",
|
|
1137
|
+
Arn: "arn:aws:sqs:us-east-1:888888888888:fulfilment",
|
|
1138
|
+
RetryPolicy: { MaximumRetryAttempts: 2 },
|
|
1139
|
+
DeadLetterConfig: { Arn: dlqArn },
|
|
1140
|
+
},
|
|
1141
|
+
],
|
|
1142
|
+
}),
|
|
1143
|
+
);
|
|
1144
|
+
|
|
1145
|
+
await simAws.eventBridge().putEvents(
|
|
1146
|
+
new PutEventsCommand({
|
|
1147
|
+
Entries: [
|
|
1148
|
+
{ Source: "orders.service", DetailType: "OrderPlaced", Detail: "{}" },
|
|
1149
|
+
],
|
|
1150
|
+
}),
|
|
1151
|
+
);
|
|
1152
|
+
await simAws.backgroundTasksComplete();
|
|
1153
|
+
|
|
1154
|
+
const { Messages } = await simAws
|
|
1155
|
+
.sqs()
|
|
1156
|
+
.receiveMessage(
|
|
1157
|
+
new ReceiveMessageCommand({ QueueUrl, MessageAttributeNames: ["All"] }),
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
console.log(Messages?.[0]?.MessageAttributes?.["ERROR_CODE"]?.StringValue);
|
|
1161
|
+
// "NO_RESOURCE"
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
The message body is the event, as JSON. Its attributes are the ones EventBridge documents.
|
|
1165
|
+
`RULE_ARN` and `TARGET_ARN` name where the event was going. `ERROR_CODE` and `ERROR_MESSAGE` say why
|
|
1166
|
+
it failed, and `RETRY_ATTEMPTS` counts the retries made. `EXHAUSTED_RETRY_CONDITION` is
|
|
1167
|
+
`MaximumRetryAttempts` or `MaximumEventAgeInSeconds` when a retry limit ran out.
|
|
1168
|
+
|
|
1169
|
+
`ERROR_CODE` is `NO_PERMISSIONS` for a target that refused EventBridge and `NO_RESOURCE` for one
|
|
1170
|
+
that is not there. Every other failure is `ERROR_FROM_TARGET`. EventBridge lists these codes
|
|
1171
|
+
without saying which failure gets which.
|
|
1172
|
+
|
|
1173
|
+
A missing target and a refusal are never retried, because the same request gets the same answer.
|
|
1174
|
+
EventBridge sends both straight to the dead-letter queue, and so does Yulin. Any other failure is
|
|
1175
|
+
retried on the simulation's clock. The first retry is due one second after the failed attempt, and
|
|
1176
|
+
each following wait doubles to 2, 4, 8 seconds and so on. Retries stop at `MaximumRetryAttempts`, or
|
|
1177
|
+
once the event is `MaximumEventAgeInSeconds` old. A retry only runs as `advanceBy(...)` moves the
|
|
1178
|
+
clock past it.
|
|
1179
|
+
|
|
1180
|
+
EventBridge sends to the dead-letter queue as `events.amazonaws.com`, with the rule ARN as
|
|
1181
|
+
`aws:SourceArn`, so the queue policy has to admit it. A dead-letter queue that is missing or refuses
|
|
1182
|
+
EventBridge leaves the failure in `deliveryFailures`. An event that reached its dead-letter queue
|
|
1183
|
+
leaves nothing there.
|
|
1184
|
+
|
|
1185
|
+
A Lambda target is invoked asynchronously. Once Lambda accepts the invocation the delivery is done,
|
|
1186
|
+
and a handler error is left to the function's own retries and destinations.
|
|
1187
|
+
|
|
984
1188
|
## Permissions
|
|
985
1189
|
|
|
986
1190
|
Most operations are authorized by simulated IAM against the bus ARN. The two that name no bus
|
|
@@ -1048,6 +1252,8 @@ no permission for.
|
|
|
1048
1252
|
## Supported operations
|
|
1049
1253
|
|
|
1050
1254
|
- `CreateEventBus`, `DeleteEventBus`, `DescribeEventBus`, `ListEventBuses` and `PutEvents`.
|
|
1255
|
+
- Partner event buses created with `EventSourceName`, and `DescribeEventSource`. A test acts as the
|
|
1256
|
+
partner through `addPartnerEventSource(...)` and `putPartnerEvents(...)`.
|
|
1051
1257
|
- `PutRule`, `DeleteRule`, `DescribeRule`, `ListRules`, `EnableRule`, `DisableRule` and
|
|
1052
1258
|
`TestEventPattern`.
|
|
1053
1259
|
- Scheduled rules, with a `rate(...)` or six-field `cron(...)` `ScheduleExpression`, fired by
|
|
@@ -1061,6 +1267,8 @@ no permission for.
|
|
|
1061
1267
|
- Targets in another account or region of the same simulation, admitted by the target's own resource
|
|
1062
1268
|
policy on `aws:SourceArn` or `aws:SourceAccount`, as real EventBridge does.
|
|
1063
1269
|
- A target's fixed `Input`, and `deliveryFailures` for a delivery that failed.
|
|
1270
|
+
- A target's `RetryPolicy`, retried on the simulation's clock, and its `DeadLetterConfig`, with the
|
|
1271
|
+
dead-letter message attributes EventBridge documents.
|
|
1064
1272
|
- The `default` bus in every account and region, without one being created.
|
|
1065
1273
|
- Bus descriptions, creation timestamps from the simulation's clock, and prefix-narrowed paged
|
|
1066
1274
|
listings.
|
|
@@ -1073,9 +1281,13 @@ no permission for.
|
|
|
1073
1281
|
|
|
1074
1282
|
- Targets deliver to Lambda, SQS and SNS, and run a task in ECS. A target ARN naming any other
|
|
1075
1283
|
service is refused as the target is added, ahead of any matching event.
|
|
1076
|
-
- Target `InputPath` and `InputTransformer` are refused
|
|
1077
|
-
|
|
1078
|
-
|
|
1284
|
+
- Target `InputPath` and `InputTransformer` are refused. A target `RoleArn` is refused except on an
|
|
1285
|
+
ECS target. That is the one target type that runs as a role, where the others run as the service
|
|
1286
|
+
principal.
|
|
1287
|
+
- A target with no `RetryPolicy` is delivered once. Real EventBridge retries such a target for up to
|
|
1288
|
+
24 hours and 185 times. Retry waits are deterministic powers of two seconds, where real
|
|
1289
|
+
EventBridge uses exponential backoff with jitter. The fixed sequence lets a test move the clock to
|
|
1290
|
+
a known retry.
|
|
1079
1291
|
- An ECS target's `EcsParameters` takes `TaskDefinitionArn` and `TaskCount`, and takes and ignores
|
|
1080
1292
|
`LaunchType`, `PlatformVersion`, `NetworkConfiguration` and `CapacityProviderStrategy`, since
|
|
1081
1293
|
there is no placement and no network here for them to apply to. Anything else it can carry, such
|
|
@@ -1115,8 +1327,13 @@ no permission for.
|
|
|
1115
1327
|
attribute are all missing, and a caller from another account cannot be admitted to a bus. That is
|
|
1116
1328
|
stricter than real AWS.
|
|
1117
1329
|
- Putting an event onto another account's or region's bus is refused.
|
|
1118
|
-
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1330
|
+
- Event bus tags, encryption with a customer managed key, dead letter queues, logging configuration
|
|
1331
|
+
and global endpoints are refused. An `AWS::Events::EventBus` carrying `Tags` deploys with the tags
|
|
1332
|
+
dropped and the property recorded.
|
|
1333
|
+
- A pending partner event source never expires, and `DescribeEventSource` leaves out
|
|
1334
|
+
`ExpirationTime`. Its `CreatedBy` is the segment after `aws.partner/`, such as `stripe.com`.
|
|
1335
|
+
`ListEventSources`, `ActivateEventSource` and `DeactivateEventSource` are absent, as are the
|
|
1336
|
+
partner's own commands. A partner is a test calling `addPartnerEventSource(...)` and
|
|
1337
|
+
`putPartnerEvents(...)`.
|
|
1121
1338
|
- Archives, replay, schema registry and discovery, API destinations and connections are not
|
|
1122
1339
|
simulated.
|
|
@@ -303,6 +303,64 @@ regardless of `ActionAfterCompletion`.
|
|
|
303
303
|
enabling it picks up from the next due instant. What it missed is never replayed. An update that
|
|
304
304
|
changes the expression reschedules from the new one.
|
|
305
305
|
|
|
306
|
+
### Flexible time windows
|
|
307
|
+
|
|
308
|
+
`FlexibleTimeWindow: { Mode: "FLEXIBLE", MaximumWindowInMinutes: n }` delays each invocation by up
|
|
309
|
+
to `n` minutes. The schedule still falls due at the times its expression gives. Each occurrence then
|
|
310
|
+
invokes its target at a moment drawn inside `[due, due + n minutes)`. The moment is never before the
|
|
311
|
+
due time and always before the window closes.
|
|
312
|
+
|
|
313
|
+
The draw is made separately for every occurrence. A test that advances the clock past the end of an
|
|
314
|
+
occurrence's window sees exactly one invocation for it, whatever was drawn. A test that stops inside
|
|
315
|
+
the window may or may not see it. Assert after the window has closed.
|
|
316
|
+
|
|
317
|
+
The draws come from the `random` source the `SimAws` was built with. It defaults to the host's
|
|
318
|
+
`Math.random`, and the moments differ from run to run. Pass a `SimSeededRandom` to draw the same
|
|
319
|
+
moments every time (to reproduce a failure, for example).
|
|
320
|
+
|
|
321
|
+
```typescript sim-scheduler-flexible-window
|
|
322
|
+
/**
|
|
323
|
+
* A schedule invoking its target up to fifteen minutes after each hour.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
import {
|
|
327
|
+
CreateScheduleCommand,
|
|
328
|
+
GetScheduleCommand,
|
|
329
|
+
} from "@aws-sdk/client-scheduler";
|
|
330
|
+
|
|
331
|
+
import { SimAws, SimSeededRandom } from "@kensio/yulin";
|
|
332
|
+
|
|
333
|
+
// The same seed draws the same invocation moments on every run.
|
|
334
|
+
const simAws = new SimAws({ random: new SimSeededRandom(2026) });
|
|
335
|
+
|
|
336
|
+
await simAws.scheduler().createSchedule(
|
|
337
|
+
new CreateScheduleCommand({
|
|
338
|
+
Name: "hourly-digest",
|
|
339
|
+
ScheduleExpression: "cron(0 * * * ? *)",
|
|
340
|
+
FlexibleTimeWindow: { Mode: "FLEXIBLE", MaximumWindowInMinutes: 15 },
|
|
341
|
+
Target: {
|
|
342
|
+
Arn: "arn:aws:lambda:us-east-1:888888888888:function:digest",
|
|
343
|
+
RoleArn: "arn:aws:iam::888888888888:role/SchedulerRole",
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const described = await simAws
|
|
349
|
+
.scheduler()
|
|
350
|
+
.getSchedule(new GetScheduleCommand({ Name: "hourly-digest" }));
|
|
351
|
+
|
|
352
|
+
console.log(described.FlexibleTimeWindow);
|
|
353
|
+
// { Mode: "FLEXIBLE", MaximumWindowInMinutes: 15 }
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
A `FLEXIBLE` window needs `MaximumWindowInMinutes`, a whole number from 1 to 1440. An `OFF` window
|
|
357
|
+
must leave it out. Either mistake is a `ValidationException`. `GetSchedule` reports the window the
|
|
358
|
+
schedule was created with.
|
|
359
|
+
|
|
360
|
+
A schedule deleted or replaced while an occurrence is waiting inside its window does not invoke for
|
|
361
|
+
that occurrence. A one-time schedule with `ActionAfterCompletion: "DELETE"` is removed once its
|
|
362
|
+
delayed invocation has been made.
|
|
363
|
+
|
|
306
364
|
## Running an ECS task on a schedule
|
|
307
365
|
|
|
308
366
|
A target whose ARN names an ECS cluster runs a [simulated ECS](https://yulinsim.dev/services/ecs/)
|
|
@@ -812,6 +870,8 @@ Resources of the same stack.
|
|
|
812
870
|
authorized against that role's own policies. Lambda targets use asynchronous Event invocation.
|
|
813
871
|
- ECS targets, running a simulated task as the execution role, with the task definition and
|
|
814
872
|
`TaskCount` from `EcsParameters` and container overrides from the target's `Input`.
|
|
873
|
+
- `FlexibleTimeWindow` in both modes, with each `FLEXIBLE` invocation drawn inside its window from
|
|
874
|
+
the simulation's `random` source.
|
|
815
875
|
- `ActionAfterCompletion`, and `deliveryFailures` for invocations that did not happen.
|
|
816
876
|
- Target retry policies driven by simulated time, and standard SQS dead-letter queues carrying the
|
|
817
877
|
original input and Scheduler diagnostic attributes.
|
|
@@ -829,8 +889,8 @@ Resources of the same stack.
|
|
|
829
889
|
|
|
830
890
|
- A schedule only fires while a test advances the simulation's clock. The host's clock drives none
|
|
831
891
|
of it, and a simulation left alone in real time never fires however long it is left.
|
|
832
|
-
- Firing is exact and exactly once
|
|
833
|
-
promise is at-least-once.
|
|
892
|
+
- Firing is exact and exactly once with the window `OFF`. Real Scheduler invokes within a minute of
|
|
893
|
+
the due time, and its promise is at-least-once.
|
|
834
894
|
- Retry delays are deterministic powers of two seconds. Real Scheduler uses exponential backoff
|
|
835
895
|
without publishing an exact sequence. The fixed sequence lets a test advance to a known retry
|
|
836
896
|
instant.
|
|
@@ -844,9 +904,12 @@ Resources of the same stack.
|
|
|
844
904
|
real Scheduler holds the group in `DELETING` until they have gone.
|
|
845
905
|
- The `default` schedule group cannot be deleted. AWS leaves the answer to that request
|
|
846
906
|
undocumented.
|
|
847
|
-
-
|
|
848
|
-
|
|
849
|
-
|
|
907
|
+
- A flexible window draws each invocation moment uniformly, to the millisecond. AWS documents only
|
|
908
|
+
that the target is invoked inside the window after the scheduled time, and publishes no
|
|
909
|
+
distribution.
|
|
910
|
+
- The wording of the refusals for a `FLEXIBLE` window with no `MaximumWindowInMinutes`, and an `OFF`
|
|
911
|
+
window with one, is Yulin's own. AWS refuses both and does not document the message. The range
|
|
912
|
+
refusals use the API's constraint wording.
|
|
850
913
|
- `ScheduleExpressionTimezone` is read from the host's own timezone data through `Intl`, so a zone
|
|
851
914
|
the host has never heard of is refused even where AWS would take it. A schedule that names none
|
|
852
915
|
runs in UTC.
|
package/docs/time/README.md
CHANGED
|
@@ -247,6 +247,24 @@ Yulin uses the clock for:
|
|
|
247
247
|
- `Date`, global timers, and invocation deadlines inside a simulated Lambda invocation
|
|
248
248
|
- HTTP response `Date` headers
|
|
249
249
|
|
|
250
|
+
## Values AWS leaves to chance
|
|
251
|
+
|
|
252
|
+
Some AWS behaviour is unpredictable by design. An EventBridge Scheduler
|
|
253
|
+
[flexible time window](https://yulinsim.dev/services/scheduler/#flexible-time-windows) invokes its
|
|
254
|
+
target at a moment AWS chooses inside the window. Yulin draws those values from the `random` option
|
|
255
|
+
of `SimAws`, which defaults to the host's `Math.random`.
|
|
256
|
+
|
|
257
|
+
Pass a `SimSeededRandom` to draw the same values on every run:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { SimAws, SimSeededRandom } from "@kensio/yulin";
|
|
261
|
+
|
|
262
|
+
const simAws = new SimAws({ random: new SimSeededRandom(2026) });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
A test that advances the clock past the end of a window passes whatever was drawn. A seed is for
|
|
266
|
+
reproducing a run (one that failed, for example).
|
|
267
|
+
|
|
250
268
|
## Limitations
|
|
251
269
|
|
|
252
270
|
- Scheduled [EventBridge rules](https://yulinsim.dev/services/eventbridge/#rules-that-fire-on-a-schedule)
|