@kensio/yulin 1.21.21 → 1.21.22

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.
Files changed (75) hide show
  1. package/dist/service/athena/engine/sim-athena-engine-result.d.ts +9 -1
  2. package/dist/service/athena/engine/sim-athena-engine-result.js +15 -4
  3. package/dist/service/athena/engine/sim-athena-engine-run.d.ts +2 -0
  4. package/dist/service/athena/engine/sim-athena-engine-run.js +6 -1
  5. package/dist/service/athena/engine/sim-athena-query-engine.js +1 -0
  6. package/dist/service/athena/engine/sim-athena-result-columns.d.ts +1 -1
  7. package/dist/service/athena/engine/sim-athena-result-columns.js +11 -4
  8. package/dist/service/athena/engine/sim-athena-round-scales.d.ts +19 -0
  9. package/dist/service/athena/engine/sim-athena-round-scales.js +80 -0
  10. package/dist/service/athena/engine/sim-athena-sql-translation.d.ts +9 -0
  11. package/dist/service/athena/engine/sim-athena-sql-translation.js +17 -2
  12. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-properties.js +2 -0
  13. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-names.js +1 -5
  14. package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-input.d.ts +5 -0
  15. package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-input.js +7 -0
  16. package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-refusals.js +0 -1
  17. package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping.handler.js +1 -0
  18. package/dist/service/lambda/command/event-source-mapping/event-source-mapping.command.d.ts +2 -1
  19. package/dist/service/lambda/command/event-source-mapping/event-source-mapping.command.js +0 -6
  20. package/dist/service/lambda/event-source/filter/sim-lambda-filter-criteria.d.ts +48 -0
  21. package/dist/service/lambda/event-source/filter/sim-lambda-filter-criteria.js +129 -0
  22. package/dist/service/lambda/event-source/filter/sim-lambda-filtered-delivery.d.ts +27 -0
  23. package/dist/service/lambda/event-source/filter/sim-lambda-filtered-delivery.js +15 -0
  24. package/dist/service/lambda/event-source/filter/sim-lambda-filtered-records.d.ts +24 -0
  25. package/dist/service/lambda/event-source/filter/sim-lambda-filtered-records.js +35 -0
  26. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-shard-poller.d.ts +7 -4
  27. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-shard-poller.js +9 -6
  28. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-delivery.d.ts +16 -4
  29. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-delivery.js +36 -5
  30. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event-source-poller.d.ts +4 -3
  31. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event-source-poller.js +6 -7
  32. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event.d.ts +14 -2
  33. package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event.js +21 -2
  34. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-delivery.d.ts +19 -6
  35. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-delivery.js +41 -7
  36. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event-source-poller.d.ts +7 -3
  37. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event-source-poller.js +10 -9
  38. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event.d.ts +6 -2
  39. package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event.js +6 -2
  40. package/dist/service/lambda/event-source/poll/sim-lambda-event-source-delivery.d.ts +21 -1
  41. package/dist/service/lambda/event-source/poll/sim-lambda-event-source-delivery.js +32 -2
  42. package/dist/service/lambda/event-source/poll/sim-lambda-sqs-delivery.js +1 -0
  43. package/dist/service/lambda/event-source/poll/sim-lambda-sqs-event.d.ts +8 -0
  44. package/dist/service/lambda/event-source/poll/sim-lambda-sqs-event.js +11 -0
  45. package/dist/service/lambda/event-source/sim-lambda-event-source-mapping.d.ts +10 -0
  46. package/dist/service/lambda/event-source/sim-lambda-event-source-mapping.js +11 -0
  47. package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade-guard.d.ts +35 -8
  48. package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade-guard.js +35 -12
  49. package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade.error.d.ts +13 -4
  50. package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade.error.js +12 -6
  51. package/dist/service/lambda/event-source/stream/sim-lambda-stream-halt.d.ts +18 -0
  52. package/dist/service/lambda/event-source/stream/sim-lambda-stream-halt.js +22 -0
  53. package/dist/service/scheduler/command/schedule/schedule.command.d.ts +1 -0
  54. package/dist/service/scheduler/command/schedule/sim-scheduler-described-schedule.js +1 -0
  55. package/dist/service/scheduler/command/schedule/sim-scheduler-schedule-writer.js +2 -1
  56. package/dist/service/scheduler/command/schedule/sim-scheduler-unsimulated-input.d.ts +3 -3
  57. package/dist/service/scheduler/command/schedule/sim-scheduler-unsimulated-input.js +19 -11
  58. package/dist/service/scheduler/schedule/sim-scheduler-schedule-expression.d.ts +5 -3
  59. package/dist/service/scheduler/schedule/sim-scheduler-schedule-expression.js +6 -4
  60. package/dist/service/scheduler/schedule/sim-scheduler-schedule.d.ts +9 -0
  61. package/dist/service/scheduler/schedule/sim-scheduler-schedule.js +8 -0
  62. package/dist/util/schedule/at/sim-at-expression.d.ts +2 -1
  63. package/dist/util/schedule/at/sim-at-expression.js +7 -3
  64. package/dist/util/schedule/cron/sim-cron-expression.d.ts +6 -3
  65. package/dist/util/schedule/cron/sim-cron-expression.js +24 -21
  66. package/dist/util/schedule/sim-schedule-zone-clock.d.ts +26 -0
  67. package/dist/util/schedule/sim-schedule-zone-clock.js +56 -0
  68. package/dist/util/schedule/sim-schedule-zone.d.ts +58 -0
  69. package/dist/util/schedule/sim-schedule-zone.js +83 -0
  70. package/dist/util/schedule/sim-schedule.d.ts +1 -1
  71. package/dist/util/schedule/sim-schedule.js +6 -5
  72. package/docs/services/athena/README.md +5 -0
  73. package/docs/services/lambda/README.md +159 -15
  74. package/docs/services/scheduler/README.md +35 -6
  75. package/package.json +1 -1
@@ -1738,8 +1738,8 @@ const { TableDescription } = await simAws.dynamoDb().createTable(
1738
1738
  const streamArn = TableDescription?.LatestStreamArn;
1739
1739
 
1740
1740
  // The projection goes into a second table. A function writing back into the
1741
- // table whose stream invoked it would be delivered its own writes, which the
1742
- // simulator refuses rather than looping on.
1741
+ // table whose stream invoked it is delivered its own writes, and the simulator
1742
+ // refuses a chain of those that keeps going.
1743
1743
  await simAws.dynamoDb().createTable(
1744
1744
  new CreateTableCommand({
1745
1745
  TableName: "order-totals",
@@ -2161,12 +2161,16 @@ created without `FunctionResponseTypes` ignores a report entirely.
2161
2161
 
2162
2162
  ### Writing back to the source table
2163
2163
 
2164
- A handler that writes into the table whose stream invoked it is delivered its own write, which
2165
- writes again. Real Lambda runs that loop for as long as the account is willing to pay for it. The
2166
- simulation refuses instead, with an error naming the function, the stream and the table, before the
2167
- test times out.
2164
+ A handler that writes into the table whose stream invoked it is delivered its own write. Where that
2165
+ write brings on another, and that one another, the simulation is going round a loop. Real Lambda
2166
+ runs the loop for as long as the account is willing to pay for it. Yulin counts the deliveries a
2167
+ handler's own writes bring on and refuses after ten of them in a row, with an error naming the
2168
+ function, the stream, the table and the count.
2168
2169
 
2169
- The write itself succeeds. The refusal comes afterwards, from whatever is waiting for the simulation
2170
+ A handler that settles is left to finish. Writing back once and then finding the work already done
2171
+ ends the chain at one link, and the delivery that link caused writes nothing.
2172
+
2173
+ The write itself succeeds. A refusal comes afterwards, from whatever is waiting for the simulation
2170
2174
  to settle:
2171
2175
 
2172
2176
  ```typescript
@@ -2177,7 +2181,7 @@ Only the handler's own writes count. Items written at the same time by the test,
2177
2181
  in the simulation, are an ordinary batch however many of them there are, because the guard tells them
2178
2182
  apart by where the write came from, and never by when it landed.
2179
2183
 
2180
- Writing the projection into a second table is what the guard is asking for, and is what a real
2184
+ A projection that writes back on every delivery belongs in a second table. That is what a real
2181
2185
  aggregation or search index does anyway.
2182
2186
 
2183
2187
  ### Making a stream event without a table
@@ -2361,9 +2365,11 @@ the mapping's execution-role check is looking for.
2361
2365
 
2362
2366
  `bisectBatchOnError` is simulated, and is covered under
2363
2367
  [Splitting a failed batch around the record that broke it](#splitting-a-failed-batch-around-the-record-that-broke-it).
2364
- The other properties a non-default `DynamoEventSource` adds are recorded rather than acted on. Those
2365
- are `FilterCriteria`, `ParallelizationFactor` and `TumblingWindowInSeconds`.
2366
- The mapping deploys, delivers every record whole and unfiltered, and each property it was created
2368
+ So is `filters`, which is covered under
2369
+ [Filtering the records a mapping delivers](#filtering-the-records-a-mapping-delivers). The other
2370
+ properties a non-default `DynamoEventSource` adds are recorded and left alone. Those are
2371
+ `ParallelizationFactor` and `TumblingWindowInSeconds`.
2372
+ The mapping deploys, delivers every record whole, and each property it was created
2367
2373
  without is listed in
2368
2374
  [`stack.ignoredProperties`](https://yulinsim.dev/services/cloudformation/#properties-a-resource-was-created-without "Properties a Resource was created without")
2369
2375
  with what the mapping does in its place. `CreateEventSourceMapping` still refuses the same
@@ -2531,6 +2537,137 @@ work the same way here, and each shard counts its own attempts.
2531
2537
  A mapping naming an enhanced fan-out consumer ARN is refused, since consumers are unsimulated.
2532
2538
  `AWS::Lambda::EventSourceMapping` deploys a Kinesis mapping the same way it deploys a DynamoDB one.
2533
2539
 
2540
+ ## Filtering the records a mapping delivers
2541
+
2542
+ `FilterCriteria` on an event source mapping decides which records reach the function. A record that
2543
+ matches is delivered. One that does not is treated as handled. A stream moves its checkpoint past it
2544
+ and a queue deletes it, the way real Lambda handles a record it filtered out. A batch that comes back
2545
+ empty invokes nothing at all.
2546
+
2547
+ AWS documents Lambda's filter rules as EventBridge event patterns, and simulated EventBridge's
2548
+ matcher is what evaluates them here. Exact values, `prefix`, `suffix`, `anything-but`, `numeric` and
2549
+ `exists` all work. A mapping naming an operator the matcher has no behaviour for is refused when it
2550
+ is created. A mapping carrying several filters delivers a record any one of them matches.
2551
+
2552
+ What a pattern reads depends on the source:
2553
+
2554
+ - **SQS** filters on `body` alone, as real Lambda does. A body holding JSON is read as the object it
2555
+ parses to, and one holding anything else is read as the string it is. A pattern naming any other
2556
+ key is refused when the mapping is created.
2557
+ - **DynamoDB streams** filter on the record the function would have received, so `eventName` and
2558
+ `dynamodb.NewImage.status.S` both work, with the attribute values in their DynamoDB shapes.
2559
+ - **Kinesis streams** filter on the record with the payload decoded, under a top-level `data` key.
2560
+ The payload has to hold JSON for a pattern to read inside it.
2561
+
2562
+ ```typescript sim-lambda-event-source-filter-criteria
2563
+ /**
2564
+ * Delivering only the queue messages a filter matches.
2565
+ */
2566
+
2567
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
2568
+ import {
2569
+ CreateEventSourceMappingCommand,
2570
+ CreateFunctionCommand,
2571
+ } from "@aws-sdk/client-lambda";
2572
+ import { CreateQueueCommand, SendMessageCommand } from "@aws-sdk/client-sqs";
2573
+
2574
+ import { SimAws } from "@kensio/yulin";
2575
+ import {
2576
+ makeLambdaZipFileInput,
2577
+ type SimLambdaSqsEvent,
2578
+ } from "@kensio/yulin/lambda";
2579
+
2580
+ const simAws = new SimAws();
2581
+ const queueArn = `arn:aws:sqs:${simAws.defaultRegionName}:${simAws.defaultAccountId}:orders`;
2582
+
2583
+ const { QueueUrl } = await simAws
2584
+ .sqs()
2585
+ .createQueue(new CreateQueueCommand({ QueueName: "orders" }));
2586
+
2587
+ const role = await simAws.iam().createRole(
2588
+ new CreateRoleCommand({
2589
+ RoleName: "ShippedOrdersRole",
2590
+ AssumeRolePolicyDocument: JSON.stringify({
2591
+ Version: "2012-10-17",
2592
+ Statement: {
2593
+ Effect: "Allow",
2594
+ Principal: { Service: "lambda.amazonaws.com" },
2595
+ Action: "sts:AssumeRole",
2596
+ },
2597
+ }),
2598
+ }),
2599
+ );
2600
+
2601
+ await simAws.iam().putRolePolicy(
2602
+ new PutRolePolicyCommand({
2603
+ RoleName: "ShippedOrdersRole",
2604
+ PolicyName: "ConsumeOrders",
2605
+ PolicyDocument: JSON.stringify({
2606
+ Version: "2012-10-17",
2607
+ Statement: {
2608
+ Effect: "Allow",
2609
+ Action: [
2610
+ "sqs:ReceiveMessage",
2611
+ "sqs:DeleteMessage",
2612
+ "sqs:GetQueueAttributes",
2613
+ ],
2614
+ Resource: queueArn,
2615
+ },
2616
+ }),
2617
+ }),
2618
+ );
2619
+
2620
+ const shipped: string[] = [];
2621
+
2622
+ await simAws.lambda().createFunction(
2623
+ new CreateFunctionCommand({
2624
+ FunctionName: "shipped-orders",
2625
+ Role: role.Role.Arn,
2626
+ Code: {
2627
+ ZipFile: makeLambdaZipFileInput((event: SimLambdaSqsEvent) => {
2628
+ for (const record of event.Records) {
2629
+ shipped.push(
2630
+ (JSON.parse(record.body) as { orderId: string }).orderId,
2631
+ );
2632
+ }
2633
+ }),
2634
+ },
2635
+ }),
2636
+ );
2637
+
2638
+ await simAws.lambda().createEventSourceMapping(
2639
+ new CreateEventSourceMappingCommand({
2640
+ EventSourceArn: queueArn,
2641
+ FunctionName: "shipped-orders",
2642
+ FilterCriteria: {
2643
+ Filters: [{ Pattern: JSON.stringify({ body: { status: ["shipped"] } }) }],
2644
+ },
2645
+ }),
2646
+ );
2647
+
2648
+ for (const order of [
2649
+ { orderId: "order-1", status: "placed" },
2650
+ { orderId: "order-2", status: "shipped" },
2651
+ ]) {
2652
+ await simAws
2653
+ .sqs()
2654
+ .sendMessage(
2655
+ new SendMessageCommand({ QueueUrl, MessageBody: JSON.stringify(order) }),
2656
+ );
2657
+ }
2658
+
2659
+ await simAws.backgroundTasksComplete();
2660
+
2661
+ console.log(shipped); // ["order-2"]
2662
+ ```
2663
+
2664
+ `GetEventSourceMappingCommand` reports the patterns back as they were written. A mapping created
2665
+ without filters reports none.
2666
+
2667
+ An `AWS::Lambda::EventSourceMapping` carrying `FilterCriteria` deploys with it, and so does a SAM
2668
+ function's queue or stream event. A CDK `SqsEventSource` or `DynamoEventSource` given `filters`
2669
+ therefore filters here the way it does deployed.
2670
+
2534
2671
  ## Function URLs
2535
2672
 
2536
2673
  A Function URL is an HTTP endpoint for one function. Creating one with
@@ -4199,6 +4336,8 @@ Sim Lambda currently supports:
4199
4336
  `CreateEventSourceMappingCommand` and read with `GetEventSourceMappingCommand`,
4200
4337
  `ListEventSourceMappingsCommand` and `DeleteEventSourceMappingCommand`, delivering real-shaped SQS,
4201
4338
  DynamoDB stream and Kinesis events and honouring `BatchSize`
4339
+ - `FilterCriteria` on any of those mappings, evaluated with EventBridge event patterns, delivering
4340
+ the records that match and handling the rest without an invocation
4202
4341
  - `StartingPosition: "TRIM_HORIZON"` and `"LATEST"` on a stream mapping, and `"AT_TIMESTAMP"` on a
4203
4342
  Kinesis one, with a failing batch blocking its shard until it is through or discarded
4204
4343
  - `MaximumRetryAttempts` and `MaximumRecordAgeInSeconds` on a stream mapping, ending the retries at
@@ -4361,7 +4500,7 @@ Current documented limitations:
4361
4500
  it stands.
4362
4501
  - SQS queues, DynamoDB streams and Kinesis streams are the only event sources. Kafka, DocumentDB and
4363
4502
  Kinesis enhanced fan-out consumers are refused outright. `CreateEventSourceMapping` also refuses
4364
- `FilterCriteria`, `ScalingConfig`, `ParallelizationFactor`, `TumblingWindowInSeconds` and the
4503
+ `ScalingConfig`, `ParallelizationFactor`, `TumblingWindowInSeconds` and the
4365
4504
  other inputs this simulation has no behaviour for. An
4366
4505
  `AWS::Lambda::EventSourceMapping` naming any of them deploys instead, and records each one against
4367
4506
  the Resource (see
@@ -4377,9 +4516,14 @@ Current documented limitations:
4377
4516
  AWS counts a bisected batch's deliveries against the same quota. Without that, the simulator's own
4378
4517
  cap of five attempts would discard a batch of a hundred long before it was down to one record. The
4379
4518
  splitting still ends on its own, because a batch halves at every step.
4380
- - A handler writing into the table whose stream invoked it is refused with
4381
- `SimLambdaStreamCascadeError` rather than being delivered its own writes forever. Real Lambda runs
4382
- that loop.
4519
+ - Ten deliveries in a row, each one brought on by the handler's own writes to the source that
4520
+ invoked it, are refused with `SimLambdaStreamCascadeError`. Real Lambda runs that loop for as long
4521
+ as it is paid for. A handler that writes back and then settles is delivered its own writes and
4522
+ finishes.
4523
+ - `FilterCriteria` is evaluated by simulated EventBridge's pattern matcher, which leaves out the
4524
+ `equals-ignore-case` and `$or` operators real Lambda takes. A pattern naming one is refused when
4525
+ the mapping is created. The limits AWS puts on the number and length of patterns go unenforced,
4526
+ and `KMSKeyArn` is refused, so criteria are held in plaintext.
4383
4527
  - A shard iterator never expires, where a real one is good for 15 minutes.
4384
4528
  - `MaximumBatchingWindowInSeconds` is only simulated as 0. A partial batch is delivered as soon as
4385
4529
  anything is on the event source, leaving a batching window nothing to wait for. A non-zero value
@@ -61,10 +61,38 @@ Scheduler accepts three expression forms:
61
61
  - `rate(<value> <unit>)` runs from when the schedule was created. The unit is `minute`, `hour` or
62
62
  `day`, and Scheduler lets it disagree with its value. `rate(1 hours)` is an hour here and a
63
63
  refusal on an EventBridge rule.
64
- - `cron(<six fields>)` names absolute instants in UTC. Minutes, hours, day-of-month, month,
65
- day-of-week and year, so every day at two in the morning is `cron(0 2 * * ? *)`. The day-of-month
66
- and day-of-week fields cannot both say something. Whichever is not deciding the day is written
67
- `?`.
64
+ - `cron(<six fields>)` names a wall-clock time. Minutes, hours, day-of-month, month, day-of-week and
65
+ year, so every day at two in the morning is `cron(0 2 * * ? *)`. The day-of-month and day-of-week
66
+ fields cannot both say something. Whichever is not deciding the day is written `?`.
67
+
68
+ ### The zone a schedule runs in
69
+
70
+ `ScheduleExpressionTimezone` says which zone the wall-clock time belongs to, and a schedule created
71
+ without one runs in UTC. Any IANA name the host's own `Intl` timezone data recognises works, and a
72
+ name it does not is refused when the schedule is created.
73
+
74
+ ```typescript
75
+ await simAws.scheduler().createSchedule(
76
+ new CreateScheduleCommand({
77
+ Name: "nightly-rollup",
78
+ ScheduleExpression: "cron(0 2 * * ? *)",
79
+ ScheduleExpressionTimezone: "Europe/London",
80
+ FlexibleTimeWindow: { Mode: "OFF" },
81
+ Target: { Arn: functionArn, RoleArn: roleArn },
82
+ }),
83
+ );
84
+ ```
85
+
86
+ That schedule is due at 01:00 UTC through British Summer Time and at 02:00 UTC through the winter,
87
+ which is what a nightly job written for a zone actually does. The two days a year a zone changes
88
+ offset are read the same way. A schedule keeps its hour on the clock across both.
89
+
90
+ The hour the clocks move takes the rule real Scheduler uses. A wall-clock time inside the hour that
91
+ never happens falls due at the first instant after it (02:30 on the morning the clocks go forward
92
+ fires at 03:30). A wall-clock time the clocks read twice falls due once, at the first of the two
93
+ readings.
94
+
95
+ `GetSchedule` reports the zone back as it was written.
68
96
 
69
97
  ## Firing a schedule
70
98
 
@@ -819,8 +847,9 @@ Resources of the same stack.
819
847
  - `FlexibleTimeWindow` with `Mode: "FLEXIBLE"` is refused. Real Scheduler invokes the target at an
820
848
  unpredictable moment inside the window, and firing at the exact due time instead would let a test
821
849
  rely on timing AWS leaves unpromised.
822
- - `ScheduleExpressionTimezone` other than `UTC` is refused outright, since running a schedule in the
823
- wrong zone fires it at the wrong hour.
850
+ - `ScheduleExpressionTimezone` is read from the host's own timezone data through `Intl`, so a zone
851
+ the host has never heard of is refused even where AWS would take it. A schedule that names none
852
+ runs in UTC.
824
853
  - `StartDate` and `EndDate` are refused outright.
825
854
  - Targets are Lambda, SQS, SNS and ECS. The universal target
826
855
  (`arn:aws:scheduler:::aws-sdk:<service>:<action>`) and every other target service are refused when
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kensio/yulin",
3
- "version": "1.21.21",
3
+ "version": "1.21.22",
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/",