@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/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
|
|
@@ -1434,6 +1434,8 @@ from a different partition key is refused, since it names a collection this quer
|
|
|
1434
1434
|
## Reading a global secondary index
|
|
1435
1435
|
|
|
1436
1436
|
Set `IndexName` on `Query` or `Scan` to read an index. Key conditions then use the index key schema.
|
|
1437
|
+
IAM authorizes the read against the index's own ARN, covered under
|
|
1438
|
+
[authorizing a read of an index](#authorizing-a-read-of-an-index).
|
|
1437
1439
|
|
|
1438
1440
|
```typescript sim-dynamodb-query-index
|
|
1439
1441
|
/**
|
|
@@ -3716,6 +3718,26 @@ unauthorized caller cannot find out which names are taken.
|
|
|
3716
3718
|
in the same way, each against the `dynamodb:` action of its own name. `ListTables` names no table.
|
|
3717
3719
|
It authorizes against `*`.
|
|
3718
3720
|
|
|
3721
|
+
### Authorizing a read of an index
|
|
3722
|
+
|
|
3723
|
+
A `Query` or `Scan` of the table authorizes against the table ARN. One naming an `IndexName`
|
|
3724
|
+
authorizes against the index's own ARN, the table ARN followed by `/index/` and the index name:
|
|
3725
|
+
|
|
3726
|
+
```text
|
|
3727
|
+
arn:aws:dynamodb:eu-west-2:111111111111:table/OrdersTable/index/byStatus
|
|
3728
|
+
```
|
|
3729
|
+
|
|
3730
|
+
Global and local secondary indexes work the same way. A statement allowing `dynamodb:Query` on the
|
|
3731
|
+
table ARN alone refuses a query of any index, with an `AccessDenied` naming the index ARN. A
|
|
3732
|
+
statement allowing only an index ARN refuses a query of the table. A caller that reads both needs
|
|
3733
|
+
the table ARN and `<table ARN>/index/*` (or each index ARN) in its policy.
|
|
3734
|
+
|
|
3735
|
+
CDK's `grantReadData` and `grantReadWriteData` add `<table ARN>/index/*` only for a table construct
|
|
3736
|
+
that declares its indexes. A table imported with `Table.fromTableName` declares none, so its grant
|
|
3737
|
+
covers the table ARN alone, and a query of an index fails here as it fails on AWS. Import the table
|
|
3738
|
+
with `Table.fromTableAttributes` and its `globalIndexes` or `localIndexes` to have the grant cover
|
|
3739
|
+
them.
|
|
3740
|
+
|
|
3719
3741
|
### Fine-grained access control with dynamodb:LeadingKeys
|
|
3720
3742
|
|
|
3721
3743
|
`GetItem`, `BatchGetItem`, `Query`, `PutItem`, `UpdateItem`, `DeleteItem` and `BatchWriteItem`
|
|
@@ -3723,6 +3745,8 @@ supply the partition key values they reach as `dynamodb:LeadingKeys`. A policy c
|
|
|
3723
3745
|
key restricts a caller to the items under particular partition keys. AWS scopes one user of a
|
|
3724
3746
|
shared table to their own rows this way.
|
|
3725
3747
|
|
|
3748
|
+
A `Query` of an index supplies the index's partition key value, the value its key condition names.
|
|
3749
|
+
|
|
3726
3750
|
The condition takes the `ForAllValues:` qualifier, as AWS requires for this key. Every partition key
|
|
3727
3751
|
value the request reaches has to match. A batch or a write naming one item outside the allowed set
|
|
3728
3752
|
is refused whole, and nothing it asked for is applied.
|
|
@@ -3847,6 +3871,8 @@ is written.
|
|
|
3847
3871
|
answering with the attributes it projects, and paging with a `LastEvaluatedKey` carrying the index
|
|
3848
3872
|
key and the table key together. A local secondary index also answers a strongly consistent read,
|
|
3849
3873
|
and fetches an unprojected attribute from the base table.
|
|
3874
|
+
- `Query` and `Scan` of an index authorized against the index ARN, for a global and a local
|
|
3875
|
+
secondary index alike.
|
|
3850
3876
|
- `dynamodb:LeadingKeys` on `GetItem`, `BatchGetItem`, `Query`, `PutItem`, `UpdateItem`,
|
|
3851
3877
|
`DeleteItem` and `BatchWriteItem`, carrying the partition key values the request reaches so a
|
|
3852
3878
|
`ForAllValues:` condition can scope a caller to particular items.
|
|
@@ -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)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kensio/yulin",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.26",
|
|
4
4
|
"description": "AWS system behaviour simulation for isolated unit testing",
|
|
5
5
|
"repository": "https://github.com/KensioSoftware/yulin",
|
|
6
6
|
"homepage": "https://yulinsim.dev/",
|
|
@@ -293,13 +293,13 @@
|
|
|
293
293
|
"eslint-plugin-jsdoc": "^64.0.1",
|
|
294
294
|
"eslint-plugin-no-secrets": "^2.3.3",
|
|
295
295
|
"eslint-plugin-security": "^4.0.1",
|
|
296
|
-
"eslint-plugin-unicorn": "^
|
|
296
|
+
"eslint-plugin-unicorn": "^74.0.0",
|
|
297
297
|
"execa": "^10.0.0",
|
|
298
298
|
"fta-cli": "^3.0.0",
|
|
299
299
|
"hyparquet": "^1.29.2",
|
|
300
300
|
"hyparquet-writer": "^0.16.9",
|
|
301
301
|
"node-sql-parser": "^5.4.0",
|
|
302
|
-
"oxfmt": "^0.
|
|
302
|
+
"oxfmt": "^0.71.0",
|
|
303
303
|
"oxlint": "^1.77.0",
|
|
304
304
|
"oxlint-tsgolint": "^7.0.2001",
|
|
305
305
|
"semantic-release": "25.0.9",
|