@kensio/yulin 1.21.24 → 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/dynamodb/command/authorize/sim-dynamodb-authorizer.d.ts +19 -1
- package/dist/service/dynamodb/command/authorize/sim-dynamodb-authorizer.js +21 -3
- package/dist/service/dynamodb/command/authorize/sim-dynamodb-reached.d.ts +6 -3
- package/dist/service/dynamodb/command/query/sim-dynamodb-query.js +3 -1
- package/dist/service/dynamodb/command/scan/sim-dynamodb-scan.js +3 -1
- package/dist/service/dynamodb/command/table/sim-dynamodb-table-reach.d.ts +4 -0
- package/dist/service/dynamodb/command/table/sim-dynamodb-table-reach.js +1 -0
- package/dist/service/dynamodb/table/sim-dynamodb-table-arn.d.ts +13 -0
- package/dist/service/dynamodb/table/sim-dynamodb-table-arn.js +20 -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/dynamodb/README.md +26 -0
- package/docs/services/scheduler/README.md +68 -5
- package/docs/time/README.md +18 -0
- package/package.json +3 -3
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,
|
|
@@ -12,7 +12,8 @@ interface SimDynamoDbAuthorizerProperties {
|
|
|
12
12
|
*
|
|
13
13
|
* AWS maps each DynamoDB API operation to the `dynamodb:` action of the same
|
|
14
14
|
* name, and the resource is the ARN of the table the operation names. ListTables
|
|
15
|
-
* is the exception: it names no table, so it authorizes against `*`.
|
|
15
|
+
* is the exception: it names no table, so it authorizes against `*`. A Query or
|
|
16
|
+
* Scan naming an `IndexName` authorizes against the index's own ARN.
|
|
16
17
|
*/
|
|
17
18
|
export declare class SimDynamoDbAuthorizer {
|
|
18
19
|
private readonly iam;
|
|
@@ -29,6 +30,23 @@ export declare class SimDynamoDbAuthorizer {
|
|
|
29
30
|
* The caller is passed through unchanged so sim IAM can distinguish an
|
|
30
31
|
* omitted caller, which defaults to Account root, from an explicit anonymous
|
|
31
32
|
* caller.
|
|
33
|
+
*
|
|
34
|
+
* A request reaching one of the table's indexes is authorized against the
|
|
35
|
+
* index's ARN. The service authorization reference for DynamoDB gives Query and Scan two
|
|
36
|
+
* resource types, `table` and `index`. The `index` ARN is the table's with
|
|
37
|
+
* `/index/<name>` after it. The developer guide's fine-grained access control
|
|
38
|
+
* page grants index queries on that ARN (its examples 5 and 6). A read naming
|
|
39
|
+
* an index is authorized against the index ARN alone. A statement naming only
|
|
40
|
+
* the table refuses it, and one naming only an index does not reach the
|
|
41
|
+
* table. CDK's `grantReadData` adds `<table ARN>/index/*` only for a table it
|
|
42
|
+
* knows has indexes, and a grant on a table imported by name covers none.
|
|
43
|
+
*
|
|
44
|
+
* The reference also lists `index` for PartiQLSelect, SearchVectors and the
|
|
45
|
+
* contributor insights actions, none of which are simulated. DescribeTable
|
|
46
|
+
* and the item actions take `table` only.
|
|
47
|
+
*
|
|
48
|
+
* The condition keys are the ones the action supplies on the table, with
|
|
49
|
+
* the partition key values read through the index's own key schema.
|
|
32
50
|
*/
|
|
33
51
|
authorizeTable(action: string, tableName: string, caller?: SimAwsCaller, reached?: SimDynamoDbReached): SimAwsResolvedCaller;
|
|
34
52
|
/**
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { SimIamAccessDenied } from "../../../iam/error/sim-iam.error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { simDynamoDbReadArn } from "../../table/sim-dynamodb-table-arn.js";
|
|
3
3
|
import { simDynamoDbConditionContextOf } from "./sim-dynamodb-reached.js";
|
|
4
4
|
/**
|
|
5
5
|
* Applies simulated IAM authorization to DynamoDB requests.
|
|
6
6
|
*
|
|
7
7
|
* AWS maps each DynamoDB API operation to the `dynamodb:` action of the same
|
|
8
8
|
* name, and the resource is the ARN of the table the operation names. ListTables
|
|
9
|
-
* is the exception: it names no table, so it authorizes against `*`.
|
|
9
|
+
* is the exception: it names no table, so it authorizes against `*`. A Query or
|
|
10
|
+
* Scan naming an `IndexName` authorizes against the index's own ARN.
|
|
10
11
|
*/
|
|
11
12
|
export class SimDynamoDbAuthorizer {
|
|
12
13
|
iam;
|
|
@@ -26,9 +27,26 @@ export class SimDynamoDbAuthorizer {
|
|
|
26
27
|
* The caller is passed through unchanged so sim IAM can distinguish an
|
|
27
28
|
* omitted caller, which defaults to Account root, from an explicit anonymous
|
|
28
29
|
* caller.
|
|
30
|
+
*
|
|
31
|
+
* A request reaching one of the table's indexes is authorized against the
|
|
32
|
+
* index's ARN. The service authorization reference for DynamoDB gives Query and Scan two
|
|
33
|
+
* resource types, `table` and `index`. The `index` ARN is the table's with
|
|
34
|
+
* `/index/<name>` after it. The developer guide's fine-grained access control
|
|
35
|
+
* page grants index queries on that ARN (its examples 5 and 6). A read naming
|
|
36
|
+
* an index is authorized against the index ARN alone. A statement naming only
|
|
37
|
+
* the table refuses it, and one naming only an index does not reach the
|
|
38
|
+
* table. CDK's `grantReadData` adds `<table ARN>/index/*` only for a table it
|
|
39
|
+
* knows has indexes, and a grant on a table imported by name covers none.
|
|
40
|
+
*
|
|
41
|
+
* The reference also lists `index` for PartiQLSelect, SearchVectors and the
|
|
42
|
+
* contributor insights actions, none of which are simulated. DescribeTable
|
|
43
|
+
* and the item actions take `table` only.
|
|
44
|
+
*
|
|
45
|
+
* The condition keys are the ones the action supplies on the table, with
|
|
46
|
+
* the partition key values read through the index's own key schema.
|
|
29
47
|
*/
|
|
30
48
|
authorizeTable(action, tableName, caller, reached = {}) {
|
|
31
|
-
return this.authorizeResource(action,
|
|
49
|
+
return this.authorizeResource(action, simDynamoDbReadArn(this.accountRegionScope, tableName, reached.indexName), caller, reached);
|
|
32
50
|
}
|
|
33
51
|
/**
|
|
34
52
|
* Ensure the caller may perform an action on a table's stream.
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What one request reaches in the table it names.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* under one of them leaves it out,
|
|
6
|
-
* that reaches nothing it names.
|
|
4
|
+
* The partition key values and attribute names each become a DynamoDB
|
|
5
|
+
* condition key. A request reaching nothing under one of them leaves it out,
|
|
6
|
+
* as AWS leaves a key out for an operation that reaches nothing it names. The
|
|
7
|
+
* index name decides the resource authorized.
|
|
7
8
|
*/
|
|
8
9
|
export interface SimDynamoDbReached {
|
|
10
|
+
/** The index the request reads, when it names one. */
|
|
11
|
+
readonly indexName?: string | undefined;
|
|
9
12
|
/** The partition key values the request reaches. */
|
|
10
13
|
readonly leadingKeys?: readonly string[] | undefined;
|
|
11
14
|
/** The top-level attribute names the request names. */
|
|
@@ -33,11 +33,13 @@ export class SimDynamoDbQuery {
|
|
|
33
33
|
const expressions = readSimDynamoDbQueryExpressions(input);
|
|
34
34
|
// The one partition key the query reads is what authorization needs, and
|
|
35
35
|
// reading it needs the view rather than the table, so it is read again
|
|
36
|
-
// here against whichever of the two the request names.
|
|
36
|
+
// here against whichever of the two the request names. A query naming an
|
|
37
|
+
// index is authorized against the index's own ARN.
|
|
37
38
|
const table = this.access.required("dynamodb:Query", input.TableName, options?.caller, {
|
|
38
39
|
leadingKeys: (reached) => simDynamoDbLeadingKeyOf(expressions.terms.forTable(reached.view(input.IndexName))
|
|
39
40
|
.partitionKeyValue),
|
|
40
41
|
attributes: expressions.attributes,
|
|
42
|
+
indexName: input.IndexName,
|
|
41
43
|
});
|
|
42
44
|
// What is being read is settled here: the table, or one of its indexes. An
|
|
43
45
|
// IndexName the table does not have is refused rather than read as the
|
|
@@ -37,9 +37,11 @@ export class SimDynamoDbScan {
|
|
|
37
37
|
// input DynamoDB would refuse is refused whether or not the table is
|
|
38
38
|
// there. A scan narrows nothing, so unlike a query its filter may name any
|
|
39
39
|
// attribute, including a key attribute, and needs no key schema to check.
|
|
40
|
+
// A scan naming an index of either kind is authorized against the index's
|
|
41
|
+
// own ARN.
|
|
40
42
|
const segment = readSimDynamoDbScanSegment(input);
|
|
41
43
|
const expressions = readSimDynamoDbScanExpressions(input);
|
|
42
|
-
const table = this.access.required("dynamodb:Scan", input.TableName, options?.caller, { attributes: expressions.attributes });
|
|
44
|
+
const table = this.access.required("dynamodb:Scan", input.TableName, options?.caller, { attributes: expressions.attributes, indexName: input.IndexName });
|
|
43
45
|
// What is being read is settled here: the table, or one of its indexes.
|
|
44
46
|
const view = table.view(input.IndexName);
|
|
45
47
|
const scan = view.scan();
|
|
@@ -8,10 +8,14 @@ import type { SimDynamoDbTable } from "../../table/sim-dynamodb-table.js";
|
|
|
8
8
|
* The partition key values need the table to read them, so they arrive as a
|
|
9
9
|
* function of it. The attribute names are read from the request's own
|
|
10
10
|
* parameters before the table is reached, so they arrive as they are.
|
|
11
|
+
*
|
|
12
|
+
* A read naming one of the table's indexes carries the index name. The index
|
|
13
|
+
* is then the resource authorized.
|
|
11
14
|
*/
|
|
12
15
|
export interface SimDynamoDbTableReach {
|
|
13
16
|
readonly leadingKeys?: SimDynamoDbLeadingKeys | undefined;
|
|
14
17
|
readonly attributes?: readonly string[] | undefined;
|
|
18
|
+
readonly indexName?: string | undefined;
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* Resolve what a request reaches against the table it named.
|
|
@@ -7,3 +7,16 @@ import type { SimAwsAccountRegionScope } from "../../aws/sim-aws-account-region-
|
|
|
7
7
|
* about to have, as real IAM does before the service handles the request.
|
|
8
8
|
*/
|
|
9
9
|
export declare function simDynamoDbTableArn(accountRegionScope: SimAwsAccountRegionScope, tableName: string): SimArn;
|
|
10
|
+
/**
|
|
11
|
+
* The ARN an index of a table has in an Account and Region.
|
|
12
|
+
*
|
|
13
|
+
* It is the table's ARN with `/index/` and the index name after it, the form
|
|
14
|
+
* the service authorization reference gives the `index` resource type. Like
|
|
15
|
+
* the table, the index need not exist.
|
|
16
|
+
*/
|
|
17
|
+
export declare function simDynamoDbIndexArn(accountRegionScope: SimAwsAccountRegionScope, tableName: string, indexName: string): SimArn;
|
|
18
|
+
/**
|
|
19
|
+
* The ARN a read is authorized against: the table's, or the index's when the
|
|
20
|
+
* read names one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function simDynamoDbReadArn(accountRegionScope: SimAwsAccountRegionScope, tableName: string, indexName: string | undefined): SimArn;
|
|
@@ -7,3 +7,23 @@
|
|
|
7
7
|
export function simDynamoDbTableArn(accountRegionScope, tableName) {
|
|
8
8
|
return `arn:aws:dynamodb:${accountRegionScope.regionName}:${accountRegionScope.accountId}:table/${tableName}`;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The ARN an index of a table has in an Account and Region.
|
|
12
|
+
*
|
|
13
|
+
* It is the table's ARN with `/index/` and the index name after it, the form
|
|
14
|
+
* the service authorization reference gives the `index` resource type. Like
|
|
15
|
+
* the table, the index need not exist.
|
|
16
|
+
*/
|
|
17
|
+
export function simDynamoDbIndexArn(accountRegionScope, tableName, indexName) {
|
|
18
|
+
return `${simDynamoDbTableArn(accountRegionScope, tableName)}/index/${indexName}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The ARN a read is authorized against: the table's, or the index's when the
|
|
22
|
+
* read names one.
|
|
23
|
+
*/
|
|
24
|
+
export function simDynamoDbReadArn(accountRegionScope, tableName, indexName) {
|
|
25
|
+
if (indexName === undefined) {
|
|
26
|
+
return simDynamoDbTableArn(accountRegionScope, tableName);
|
|
27
|
+
}
|
|
28
|
+
return simDynamoDbIndexArn(accountRegionScope, tableName, indexName);
|
|
29
|
+
}
|
|
@@ -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;
|