@kensio/yulin 1.21.25 → 1.21.26
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/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/scheduler/README.md +68 -5
- package/docs/time/README.md +18 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export { type SimClock, SimFixedClock, SimRealClock, } from "./util/clock/sim-cl
|
|
|
7
7
|
export { SimControllableClock } from "./util/clock/sim-controllable-clock.js";
|
|
8
8
|
export { SimClockControl } from "./util/clock/sim-clock-control.js";
|
|
9
9
|
export { SimTokenBucket } from "./util/throttle/sim-token-bucket.js";
|
|
10
|
+
export { SimHostRandom, type SimRandom, SimSeededRandom, } from "./util/random/sim-random.js";
|
|
10
11
|
export { SimDuration, type SimDurationInput, SimInvalidDuration, } from "./util/clock/sim-duration.js";
|
|
11
12
|
export { SimAwsTimeNotControllable } from "./service/aws/sim-aws-timekeeping.js";
|
package/dist/index.js
CHANGED
|
@@ -4,5 +4,6 @@ export { SimFixedClock, SimRealClock, } from "./util/clock/sim-clock.js";
|
|
|
4
4
|
export { SimControllableClock } from "./util/clock/sim-controllable-clock.js";
|
|
5
5
|
export { SimClockControl } from "./util/clock/sim-clock-control.js";
|
|
6
6
|
export { SimTokenBucket } from "./util/throttle/sim-token-bucket.js";
|
|
7
|
+
export { SimHostRandom, SimSeededRandom, } from "./util/random/sim-random.js";
|
|
7
8
|
export { SimDuration, SimInvalidDuration, } from "./util/clock/sim-duration.js";
|
|
8
9
|
export { SimAwsTimeNotControllable } from "./service/aws/sim-aws-timekeeping.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SimResponseMetadata } from "../../service/aws/metadata/response-metadata.type.js";
|
|
1
2
|
/**
|
|
2
3
|
* Base class for simulated AWS SDK errors.
|
|
3
4
|
*/
|
|
@@ -44,6 +45,23 @@ export declare class SimSdkAlreadyInterceptedError extends SimSdkError {
|
|
|
44
45
|
export declare class SimSdkCallbackNotSupportedError extends SimSdkError {
|
|
45
46
|
readonly name = "SimSdkCallbackNotSupportedError";
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* An intercepted SDK send was aborted through its `abortSignal` option.
|
|
50
|
+
*
|
|
51
|
+
* This reproduces the error the SDK's own HTTP handler throws, so it extends
|
|
52
|
+
* Error directly and is named `AbortError`, as code catching an aborted send
|
|
53
|
+
* expects. A signal's Error reason becomes the cause, and any other reason
|
|
54
|
+
* becomes the message.
|
|
55
|
+
*/
|
|
56
|
+
export declare class SimSdkAbortError extends Error {
|
|
57
|
+
readonly name = "AbortError";
|
|
58
|
+
readonly $metadata: SimResponseMetadata;
|
|
59
|
+
/**
|
|
60
|
+
* Build the error for a signal that fired with a reason, mirroring
|
|
61
|
+
* `buildAbortError` in `@smithy/node-http-handler`.
|
|
62
|
+
*/
|
|
63
|
+
static fromReason(reason: unknown): SimSdkAbortError;
|
|
64
|
+
}
|
|
47
65
|
/**
|
|
48
66
|
* A serialized AWS API request cannot be routed into the simulation.
|
|
49
67
|
*
|
|
@@ -44,6 +44,47 @@ export class SimSdkAlreadyInterceptedError extends SimSdkError {
|
|
|
44
44
|
export class SimSdkCallbackNotSupportedError extends SimSdkError {
|
|
45
45
|
name = "SimSdkCallbackNotSupportedError";
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* An intercepted SDK send was aborted through its `abortSignal` option.
|
|
49
|
+
*
|
|
50
|
+
* This reproduces the error the SDK's own HTTP handler throws, so it extends
|
|
51
|
+
* Error directly and is named `AbortError`, as code catching an aborted send
|
|
52
|
+
* expects. A signal's Error reason becomes the cause, and any other reason
|
|
53
|
+
* becomes the message.
|
|
54
|
+
*/
|
|
55
|
+
export class SimSdkAbortError extends Error {
|
|
56
|
+
name = "AbortError";
|
|
57
|
+
$metadata = {
|
|
58
|
+
attempts: 1,
|
|
59
|
+
totalRetryDelay: 0,
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Build the error for a signal that fired with a reason, mirroring
|
|
63
|
+
* `buildAbortError` in `@smithy/node-http-handler`.
|
|
64
|
+
*/
|
|
65
|
+
static fromReason(reason) {
|
|
66
|
+
if (reason instanceof Error) {
|
|
67
|
+
return new SimSdkAbortError("Request aborted", { cause: reason });
|
|
68
|
+
}
|
|
69
|
+
if (isFalsy(reason)) {
|
|
70
|
+
return new SimSdkAbortError("Request aborted");
|
|
71
|
+
}
|
|
72
|
+
// oxlint-disable-next-line typescript/no-base-to-string -- the SDK stringifies any other reason as it is, so this matches whatever it prints.
|
|
73
|
+
return new SimSdkAbortError(String(reason));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether an abort reason is falsy, which the SDK treats as no reason.
|
|
78
|
+
*/
|
|
79
|
+
function isFalsy(reason) {
|
|
80
|
+
return (reason === undefined ||
|
|
81
|
+
reason === null ||
|
|
82
|
+
reason === false ||
|
|
83
|
+
reason === "" ||
|
|
84
|
+
reason === 0 ||
|
|
85
|
+
reason === 0n ||
|
|
86
|
+
Number.isNaN(reason));
|
|
87
|
+
}
|
|
47
88
|
/**
|
|
48
89
|
* A serialized AWS API request cannot be routed into the simulation.
|
|
49
90
|
*
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export type { SimSdkCallerOptions } from "./router/sim-sdk-caller-options.js";
|
|
|
7
7
|
export { simSdkEventStream, type SimSdkEventStream, } from "./stream/sim-sdk-event-stream.js";
|
|
8
8
|
export { simSdkStreamBody } from "./stream/sim-sdk-stream-body.js";
|
|
9
9
|
export type { SimSdkStreamBody, SimSdkStreamBodyMethods, } from "./stream/sim-sdk-stream-body.js";
|
|
10
|
-
export { SimSdkError, SimSdkAlreadyInterceptedError, SimSdkCallbackNotSupportedError, SimSdkCommandNotInterceptedError, SimSdkInvalidClientError, SimSdkStreamAlreadyConsumedError, SimSdkUnbridgedWireRequestError, SimSdkUnknownServiceError, SimSdkUnsupportedCommandError, } from "./error/sim-sdk.error.js";
|
|
10
|
+
export { SimSdkError, SimSdkAbortError, SimSdkAlreadyInterceptedError, SimSdkCallbackNotSupportedError, SimSdkCommandNotInterceptedError, SimSdkInvalidClientError, SimSdkStreamAlreadyConsumedError, SimSdkUnbridgedWireRequestError, SimSdkUnknownServiceError, SimSdkUnsupportedCommandError, } from "./error/sim-sdk.error.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { SimSdkInterception } from "./sim-sdk-interception.js";
|
|
|
3
3
|
export { simSdkCallerOptions } from "./router/sim-sdk-caller-options.js";
|
|
4
4
|
export { simSdkEventStream, } from "./stream/sim-sdk-event-stream.js";
|
|
5
5
|
export { simSdkStreamBody } from "./stream/sim-sdk-stream-body.js";
|
|
6
|
-
export { SimSdkError, SimSdkAlreadyInterceptedError, SimSdkCallbackNotSupportedError, SimSdkCommandNotInterceptedError, SimSdkInvalidClientError, SimSdkStreamAlreadyConsumedError, SimSdkUnbridgedWireRequestError, SimSdkUnknownServiceError, SimSdkUnsupportedCommandError, } from "./error/sim-sdk.error.js";
|
|
6
|
+
export { SimSdkError, SimSdkAbortError, SimSdkAlreadyInterceptedError, SimSdkCallbackNotSupportedError, SimSdkCommandNotInterceptedError, SimSdkInvalidClientError, SimSdkStreamAlreadyConsumedError, SimSdkUnbridgedWireRequestError, SimSdkUnknownServiceError, SimSdkUnsupportedCommandError, } from "./error/sim-sdk.error.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The abort signal given to one intercepted SDK send, if any.
|
|
3
|
+
*
|
|
4
|
+
* The real SDK checks the signal in its HTTP handler. A signal aborted before
|
|
5
|
+
* the request goes out rejects the send, and one that fires while the request
|
|
6
|
+
* is pending rejects it then. Either way the send rejects with an AbortError.
|
|
7
|
+
*
|
|
8
|
+
* A real request spends at least one turn of the host's timers on the network,
|
|
9
|
+
* so `AbortSignal.timeout(0)` created for a send aborts it. A simulated
|
|
10
|
+
* operation can settle within microtasks, so a send carrying a signal waits
|
|
11
|
+
* one host timer turn before it is dispatched.
|
|
12
|
+
*
|
|
13
|
+
* The simulated operation is not cancelled. Once dispatched, it carries on, as
|
|
14
|
+
* a request already received by AWS does.
|
|
15
|
+
*/
|
|
16
|
+
export declare class SimSdkSendAbort {
|
|
17
|
+
private readonly signal;
|
|
18
|
+
private constructor();
|
|
19
|
+
/**
|
|
20
|
+
* Read the abort signal from the arguments given to send after the Command.
|
|
21
|
+
*/
|
|
22
|
+
static fromSendArguments(rest: readonly unknown[]): SimSdkSendAbort;
|
|
23
|
+
/**
|
|
24
|
+
* Run a simulated operation unless the signal has already fired, rejecting
|
|
25
|
+
* with an AbortError if it fires before the operation settles.
|
|
26
|
+
*/
|
|
27
|
+
run(operation: () => Promise<unknown>): Promise<unknown>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { setTimeout } from "node:timers";
|
|
2
|
+
import { SimSdkAbortError } from "./error/sim-sdk.error.js";
|
|
3
|
+
/**
|
|
4
|
+
* The abort signal given to one intercepted SDK send, if any.
|
|
5
|
+
*
|
|
6
|
+
* The real SDK checks the signal in its HTTP handler. A signal aborted before
|
|
7
|
+
* the request goes out rejects the send, and one that fires while the request
|
|
8
|
+
* is pending rejects it then. Either way the send rejects with an AbortError.
|
|
9
|
+
*
|
|
10
|
+
* A real request spends at least one turn of the host's timers on the network,
|
|
11
|
+
* so `AbortSignal.timeout(0)` created for a send aborts it. A simulated
|
|
12
|
+
* operation can settle within microtasks, so a send carrying a signal waits
|
|
13
|
+
* one host timer turn before it is dispatched.
|
|
14
|
+
*
|
|
15
|
+
* The simulated operation is not cancelled. Once dispatched, it carries on, as
|
|
16
|
+
* a request already received by AWS does.
|
|
17
|
+
*/
|
|
18
|
+
export class SimSdkSendAbort {
|
|
19
|
+
signal;
|
|
20
|
+
constructor(signal) {
|
|
21
|
+
this.signal = signal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the abort signal from the arguments given to send after the Command.
|
|
25
|
+
*/
|
|
26
|
+
static fromSendArguments(rest) {
|
|
27
|
+
const options = rest[0];
|
|
28
|
+
if (typeof options !== "object" || options === null) {
|
|
29
|
+
return new SimSdkSendAbort(undefined);
|
|
30
|
+
}
|
|
31
|
+
const signal = options.abortSignal;
|
|
32
|
+
if (!isAbortSignal(signal)) {
|
|
33
|
+
return new SimSdkSendAbort(undefined);
|
|
34
|
+
}
|
|
35
|
+
return new SimSdkSendAbort(signal);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run a simulated operation unless the signal has already fired, rejecting
|
|
39
|
+
* with an AbortError if it fires before the operation settles.
|
|
40
|
+
*/
|
|
41
|
+
async run(operation) {
|
|
42
|
+
const signal = this.signal;
|
|
43
|
+
if (signal === undefined) {
|
|
44
|
+
return await operation();
|
|
45
|
+
}
|
|
46
|
+
if (signal.aborted) {
|
|
47
|
+
throw SimSdkAbortError.fromReason(signal.reason);
|
|
48
|
+
}
|
|
49
|
+
const abort = Promise.withResolvers();
|
|
50
|
+
const unsubscribe = subscribe(signal, () => {
|
|
51
|
+
abort.reject(SimSdkAbortError.fromReason(signal.reason));
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
return await Promise.race([
|
|
55
|
+
dispatchAfterRoundTrip(signal, operation),
|
|
56
|
+
abort.promise,
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
unsubscribe();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Dispatch a simulated operation after one turn of the host's timers, unless
|
|
66
|
+
* the signal fires first.
|
|
67
|
+
*
|
|
68
|
+
* Node runs timers of the same delay in the order they were set, so a signal
|
|
69
|
+
* set to time out after 0 or 1 milliseconds before the send fires before this
|
|
70
|
+
* turn ends.
|
|
71
|
+
*/
|
|
72
|
+
async function dispatchAfterRoundTrip(signal, operation) {
|
|
73
|
+
await new Promise((resolve) => {
|
|
74
|
+
setTimeout(resolve, 0);
|
|
75
|
+
});
|
|
76
|
+
if (signal.aborted) {
|
|
77
|
+
throw SimSdkAbortError.fromReason(signal.reason);
|
|
78
|
+
}
|
|
79
|
+
return await operation();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a send option is usable as an abort signal.
|
|
83
|
+
*/
|
|
84
|
+
function isAbortSignal(signal) {
|
|
85
|
+
return (typeof signal === "object" &&
|
|
86
|
+
signal !== null &&
|
|
87
|
+
typeof signal.aborted === "boolean");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Listen for a signal firing, the way the SDK's HTTP handler does: through
|
|
91
|
+
* addEventListener where the signal has it, and through onabort otherwise.
|
|
92
|
+
*
|
|
93
|
+
* Returns the function that stops listening.
|
|
94
|
+
*/
|
|
95
|
+
function subscribe(signal, listener) {
|
|
96
|
+
if (typeof signal.addEventListener === "function" &&
|
|
97
|
+
typeof signal.removeEventListener === "function") {
|
|
98
|
+
signal.addEventListener("abort", listener);
|
|
99
|
+
return () => {
|
|
100
|
+
signal.removeEventListener?.("abort", listener);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// The older Smithy signal offers no addEventListener, so onabort is the only
|
|
104
|
+
// way to listen, and the previous handler is put back afterwards.
|
|
105
|
+
const previous = signal.onabort ?? null;
|
|
106
|
+
// oxlint-disable-next-line unicorn-js/prefer-add-event-listener -- this signal has no addEventListener to prefer.
|
|
107
|
+
signal.onabort = listener;
|
|
108
|
+
return () => {
|
|
109
|
+
// oxlint-disable-next-line unicorn-js/prefer-add-event-listener -- this signal has no addEventListener to prefer.
|
|
110
|
+
signal.onabort = previous;
|
|
111
|
+
};
|
|
112
|
+
}
|
package/dist/sdk/send-patch.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SimSdkAlreadyInterceptedError, SimSdkCallbackNotSupportedError, } from "./error/sim-sdk.error.js";
|
|
2
|
+
import { SimSdkSendAbort } from "./send-abort.js";
|
|
2
3
|
/**
|
|
3
4
|
* All send functions installed by simulated SDK interception, so a second
|
|
4
5
|
* interception of an already-intercepted target can be rejected, including
|
|
@@ -26,8 +27,9 @@ export function installSendPatch(target, handler) {
|
|
|
26
27
|
throw new SimSdkCallbackNotSupportedError("Simulated SDK interception does not support callback-style send; " +
|
|
27
28
|
"use the promise form instead");
|
|
28
29
|
}
|
|
30
|
+
return await SimSdkSendAbort.fromSendArguments(rest).run(
|
|
29
31
|
// oxlint-disable-next-line unicorn-js/no-this-outside-of-class -- this is the client instance send was called on, which the handler needs for per-send config resolution.
|
|
30
|
-
|
|
32
|
+
async () => await handler(command, this));
|
|
31
33
|
};
|
|
32
34
|
installedSends.add(patched);
|
|
33
35
|
Object.defineProperty(target, "send", {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BackgroundCompleter, BackgroundScheduler } from "../../util/background/background.js";
|
|
2
2
|
import type { SimClock } from "../../util/clock/sim-clock.js";
|
|
3
|
+
import type { SimRandom } from "../../util/random/sim-random.js";
|
|
3
4
|
import type { SimIamSigV4ExpectedScope } from "../iam/sigv4/sim-iam-sigv4-expected-scope.js";
|
|
4
5
|
import type { SimAwsAccountId } from "./sim-aws-account.js";
|
|
5
6
|
import type { SimAwsDefaultCaller } from "./caller/sim-aws-caller.js";
|
|
@@ -52,4 +53,11 @@ export interface SimAwsProperties {
|
|
|
52
53
|
* its own clock and time control is refused.
|
|
53
54
|
*/
|
|
54
55
|
readonly clock?: SimClock;
|
|
56
|
+
/**
|
|
57
|
+
* Where this simulation draws the values AWS leaves to chance, such as the
|
|
58
|
+
* moment a Scheduler flexible time window invokes its target. It defaults to
|
|
59
|
+
* the host's `Math.random`. Pass a `SimSeededRandom` to make those values
|
|
60
|
+
* repeatable from run to run.
|
|
61
|
+
*/
|
|
62
|
+
readonly random?: SimRandom;
|
|
55
63
|
}
|
|
@@ -10,6 +10,7 @@ import type { SimClockControl } from "../../util/clock/sim-clock-control.js";
|
|
|
10
10
|
import type { SimAwsProperties, SimAwsRequestCallerOptions } from "./sim-aws-properties.js";
|
|
11
11
|
import type { SimIamCredentialIdentity } from "../iam/credential/sim-aws-credentials.js";
|
|
12
12
|
import type { SimAwsRequestCaller } from "../iam/request/sim-aws-request-caller.js";
|
|
13
|
+
import { type SimRandom } from "../../util/random/sim-random.js";
|
|
13
14
|
import { type SimCloseOnSignalOptions } from "../../util/process/close-on-signal.js";
|
|
14
15
|
/**
|
|
15
16
|
* Top-level container for simulated AWS.
|
|
@@ -32,6 +33,10 @@ export declare class SimAws extends SimAwsServiceAccessors {
|
|
|
32
33
|
* Sim IAM registry for this sim AWS instance.
|
|
33
34
|
*/
|
|
34
35
|
readonly iamRegistry: SimIamRegistry;
|
|
36
|
+
/**
|
|
37
|
+
* Where this simulation draws the values AWS leaves to chance.
|
|
38
|
+
*/
|
|
39
|
+
readonly random: SimRandom;
|
|
35
40
|
private readonly background;
|
|
36
41
|
private readonly scopes;
|
|
37
42
|
private readonly timekeeping;
|
|
@@ -6,6 +6,7 @@ import { SimAwsServiceAccessors } from "./sim-aws-service-accessors.js";
|
|
|
6
6
|
import { simAwsRunAsContext } from "./caller/sim-aws-run-as-context.js";
|
|
7
7
|
import { SimAwsTimekeeping } from "./sim-aws-timekeeping.js";
|
|
8
8
|
import { SimAwsRequestAuthentication } from "./sim-aws-request-authentication.js";
|
|
9
|
+
import { SimHostRandom } from "../../util/random/sim-random.js";
|
|
9
10
|
import { closeOnSignal, } from "../../util/process/close-on-signal.js";
|
|
10
11
|
/**
|
|
11
12
|
* Top-level container for simulated AWS.
|
|
@@ -28,13 +29,17 @@ export class SimAws extends SimAwsServiceAccessors {
|
|
|
28
29
|
* Sim IAM registry for this sim AWS instance.
|
|
29
30
|
*/
|
|
30
31
|
iamRegistry;
|
|
32
|
+
/**
|
|
33
|
+
* Where this simulation draws the values AWS leaves to chance.
|
|
34
|
+
*/
|
|
35
|
+
random;
|
|
31
36
|
background;
|
|
32
37
|
scopes;
|
|
33
38
|
timekeeping;
|
|
34
39
|
requestAuthentication;
|
|
35
40
|
constructor(properties = {}) {
|
|
36
41
|
super();
|
|
37
|
-
const { defaultAccountId = DEFAULT_SIM_AWS_ACCOUNT_ID, defaultRegionName = DEFAULT_SIM_AWS_REGION_NAME, clock, defaultCaller, background: suppliedBackground, } = properties;
|
|
42
|
+
const { defaultAccountId = DEFAULT_SIM_AWS_ACCOUNT_ID, defaultRegionName = DEFAULT_SIM_AWS_REGION_NAME, clock, defaultCaller, background: suppliedBackground, random = new SimHostRandom(), } = properties;
|
|
38
43
|
const timekeeping = new SimAwsTimekeeping({
|
|
39
44
|
clock,
|
|
40
45
|
background: suppliedBackground,
|
|
@@ -44,6 +49,7 @@ export class SimAws extends SimAwsServiceAccessors {
|
|
|
44
49
|
this.defaultRegionName = defaultRegionName;
|
|
45
50
|
this.timekeeping = timekeeping;
|
|
46
51
|
this.background = background;
|
|
52
|
+
this.random = random;
|
|
47
53
|
this.serviceFactory = new SimAwsServiceFactory({
|
|
48
54
|
simAws: this,
|
|
49
55
|
background,
|
|
@@ -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
|
+
}
|