@kensio/yulin 1.21.6 → 1.21.8

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 (74) hide show
  1. package/README.md +22 -2
  2. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-creator.js +1 -0
  3. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.d.ts +4 -0
  4. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.js +26 -1
  5. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-creator.js +3 -1
  6. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.d.ts +0 -7
  7. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.js +0 -20
  8. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.d.ts +13 -0
  9. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.js +59 -0
  10. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.d.ts +1 -1
  11. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.js +1 -1
  12. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-properties.js +1 -1
  13. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.d.ts +3 -2
  14. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.js +25 -3
  15. package/dist/service/ses/command/authorize/sim-ses-authorizer.d.ts +9 -0
  16. package/dist/service/ses/command/authorize/sim-ses-authorizer.js +18 -2
  17. package/dist/service/ses/command/send/sim-ses-send-email.js +2 -1
  18. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-creator.js +1 -0
  19. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.d.ts +9 -4
  20. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.js +18 -6
  21. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.d.ts +10 -0
  22. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.js +16 -1
  23. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-creator.js +1 -1
  24. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.d.ts +11 -6
  25. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.js +18 -9
  26. package/docs/README.md +40 -4
  27. package/docs/ai-skill/README.md +57 -54
  28. package/docs/cli/README.md +84 -94
  29. package/docs/factories/README.md +42 -54
  30. package/docs/lint/README.md +41 -67
  31. package/docs/non-aws-dependencies/README.md +72 -168
  32. package/docs/sdk/README.md +109 -95
  33. package/docs/serve/README.md +192 -898
  34. package/docs/services/acm/README.md +24 -40
  35. package/docs/services/apigateway/README.md +52 -71
  36. package/docs/services/apigatewayv2/README.md +55 -74
  37. package/docs/services/athena/README.md +17 -26
  38. package/docs/services/backup/README.md +29 -39
  39. package/docs/services/bedrock/README.md +38 -52
  40. package/docs/services/cloudformation/README.md +43 -55
  41. package/docs/services/cloudfront/README.md +69 -95
  42. package/docs/services/cloudwatch/README.md +40 -54
  43. package/docs/services/cognito/README.md +30 -45
  44. package/docs/services/dynamodb/README.md +34 -51
  45. package/docs/services/ecr/README.md +36 -77
  46. package/docs/services/ecs/README.md +26 -46
  47. package/docs/services/elbv2/README.md +19 -29
  48. package/docs/services/eventbridge/README.md +24 -20
  49. package/docs/services/firehose/README.md +24 -32
  50. package/docs/services/glue/README.md +41 -78
  51. package/docs/services/iam/README.md +13 -15
  52. package/docs/services/kinesis/README.md +53 -93
  53. package/docs/services/kms/README.md +22 -32
  54. package/docs/services/lambda/README.md +60 -80
  55. package/docs/services/logs/README.md +41 -50
  56. package/docs/services/organizations/README.md +50 -85
  57. package/docs/services/personalize/README.md +28 -44
  58. package/docs/services/rekognition/README.md +26 -38
  59. package/docs/services/route53/README.md +17 -17
  60. package/docs/services/s3/README.md +47 -51
  61. package/docs/services/scheduler/README.md +41 -52
  62. package/docs/services/secretsmanager/README.md +27 -42
  63. package/docs/services/ses/README.md +24 -34
  64. package/docs/services/sns/README.md +31 -33
  65. package/docs/services/sqs/README.md +14 -14
  66. package/docs/services/ssm/README.md +19 -21
  67. package/docs/services/stepfunctions/README.md +18 -20
  68. package/docs/services/sts/README.md +32 -45
  69. package/docs/services/wafv2/README.md +12 -17
  70. package/docs/terraform/README.md +108 -126
  71. package/docs/testing/README.md +228 -0
  72. package/docs/time/README.md +87 -117
  73. package/llms.txt +2 -1
  74. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  # Simulated API Gateway HTTP APIs
2
2
 
3
- Yulin includes a simulated API Gateway v2 service, reachable as `simAws.apiGatewayV2()`. It covers
4
- HTTP APIs with a Lambda proxy integration. A handler that runs behind an HTTP API can be tested
5
- against a real HTTP request, with no hand-built event to keep in step.
3
+ Yulin simulates API Gateway HTTP APIs with Lambda proxy integrations. Use
4
+ `simAws.apiGatewayV2()` directly or intercept an `ApiGatewayV2Client`.
6
5
 
7
- This is HTTP APIs only. WebSocket APIs and REST APIs (the older API Gateway v1 service, on a
8
- separate SDK client) are outside it.
6
+ REST APIs use the separate `simAws.apiGateway()` service. WebSocket APIs are unsupported.
9
7
 
10
8
  ## Creating an API
11
9
 
@@ -46,21 +44,19 @@ The endpoint names the API id and the region, as a real one does:
46
44
  https://a1b2c3d4e5.execute-api.eu-west-1.amazonaws.com
47
45
  ```
48
46
 
49
- A name is a label here, as it is on real AWS. Two APIs in one Account and Region may share a name,
50
- and only the id tells them apart.
47
+ An API name is a label. Two APIs in the same account and region may share a name. Use the API ID to
48
+ address it.
51
49
 
52
50
  ## Routing requests to a Lambda function
53
51
 
54
- An API needs three more resources before it serves anything. Those are an integration naming the
55
- function, a route pointing at that integration, and a stage to serve it from. The function also has to allow API
56
- Gateway to invoke it. That grant is a permission on the function, not on the API. See
52
+ An API needs an integration, a route and a stage before it can serve requests. The function must
53
+ also grant API Gateway permission to invoke it. See
57
54
  [Granting the API permission to invoke the function](#granting-the-api-permission-to-invoke-the-function).
58
- Once all of that exists, `serveSimAws` answers requests to the generated endpoint by invoking the
59
- function.
60
55
 
61
- Pass the API endpoint through `srv.localUrl(...)`. It keeps the endpoint's hostname and sends the
62
- request to the local server, the way it adapts simulated S3 website and Lambda Function URL
63
- endpoints.
56
+ `serveSimAws` then routes requests from the generated endpoint to the function.
57
+
58
+ Pass the API endpoint to `srv.localUrl(...)` before making a local HTTP request. The returned URL
59
+ keeps the simulated hostname and points at the local server.
64
60
 
65
61
  ```typescript sim-apigatewayv2-lambda-proxy
66
62
  /**
@@ -152,12 +148,11 @@ Region. It is looked up where its ARN says it is.
152
148
 
153
149
  ## Granting the API permission to invoke the function
154
150
 
155
- A Lambda proxy integration works once the function's resource policy allows
156
- `apigateway.amazonaws.com` to invoke it. The console adds that permission for you. An integration
157
- created through CloudFormation, the CLI or an SDK leaves it to you. That is what
158
- `AddPermissionCommand` in the example above does. Without it the request is answered with a 500 and
159
- `{"message":"Internal Server Error"}`, and the handler never runs. CDK's `HttpLambdaIntegration`
160
- emits the same grant as an `AWS::Lambda::Permission`.
151
+ A Lambda proxy integration requires a resource-based permission for
152
+ `apigateway.amazonaws.com`. Add it with `AddPermissionCommand` or deploy an
153
+ `AWS::Lambda::Permission`. Without the permission, the API returns 500 with
154
+ `{"message":"Internal Server Error"}` and does not invoke the handler. CDK's
155
+ `HttpLambdaIntegration` creates this permission.
161
156
 
162
157
  Each request is authorized as `lambda:InvokeFunction` on the function ARN, with the caller being the
163
158
  service principal `apigateway.amazonaws.com`. The function's own resource policy is the whole
@@ -204,9 +199,8 @@ arn:aws:apigateway:eu-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-
204
199
 
205
200
  A Lambda `REQUEST` authorizer's `AuthorizerUri` takes the same qualifier.
206
201
 
207
- The qualifier is read when the request arrives. A route built on the alias `live` runs whichever
208
- version `live` points at now, and `UpdateAliasCommand` carries the route to another version with the
209
- API untouched. (That is how a deployment behind an HTTP API usually moves on AWS.)
202
+ The qualifier is resolved for each request. A route that uses the `live` alias follows changes made
203
+ by `UpdateAliasCommand` without changing the API.
210
204
 
211
205
  The invoke permission is granted on the qualifier as well. An alias holds a resource policy of its
212
206
  own, and a grant made on the function admits an unqualified call and says nothing about `live`. Pass
@@ -336,8 +330,7 @@ was never created does. The endpoint answers 500 and the handler never runs.
336
330
 
337
331
  ## Route keys
338
332
 
339
- A route key is either the literal `$default` or an upper-case HTTP method and a path separated by one
340
- space:
333
+ A route key is `$default` or an uppercase HTTP method followed by a path:
341
334
 
342
335
  ```text
343
336
  GET /pets
@@ -370,7 +363,7 @@ Gateway refuses it too is unestablished. This is stricter than AWS is known to b
370
363
 
371
364
  ## Which route serves a request
372
365
 
373
- More than one route may match a request, and the most specific one takes it. In order:
366
+ When several routes match, Yulin selects one in this order:
374
367
 
375
368
  1. A route matching the whole path beats a route ending in a greedy parameter, which beats
376
369
  `$default`.
@@ -402,9 +395,8 @@ A request whose path matches a route with a different method matches no route at
402
395
 
403
396
  ## Path parameters and named stages
404
397
 
405
- What a route captured reaches the handler as `event.pathParameters`. A named stage is served under
406
- its own path segment. Stage selection runs before route selection, and the routes never see the stage
407
- name.
398
+ Captured route parameters are available as `event.pathParameters`. A named stage uses the first path
399
+ segment. Yulin removes that segment before selecting a route.
408
400
 
409
401
  ```typescript sim-apigatewayv2-routes
410
402
  /**
@@ -529,16 +521,14 @@ left out the same way when the stage has none.
529
521
 
530
522
  ## Throttling a stage and a route
531
523
 
532
- A stage holds a token bucket for every route it serves. `DefaultRouteSettings` sets the rate and the
533
- burst those buckets start with, and a `RouteSettings` entry keyed by route key overrides them for the
534
- route it names. `ThrottlingRateLimit` is requests per second, and `ThrottlingBurstLimit` is how many
535
- requests a route will take at once.
524
+ Each stage keeps a token bucket per route. `DefaultRouteSettings` sets the default rate and burst.
525
+ `RouteSettings` can override them by route key. `ThrottlingRateLimit` is requests per second and
526
+ `ThrottlingBurstLimit` is the number accepted at once.
536
527
 
537
528
  A request that finds an empty bucket is answered 429 with `{"message":"Too Many Requests"}`. The
538
529
  route's authorizer and its integration are both skipped.
539
530
 
540
- The buckets refill against the simulated clock. Freeze it, spend a route's burst, assert on the 429,
541
- then move a second on and watch the route serve again.
531
+ Buckets refill against the simulated clock. Advance the clock to test recovery after a 429 response.
542
532
 
543
533
  ```typescript sim-apigatewayv2-throttling
544
534
  /**
@@ -681,8 +671,8 @@ are refused by `CreateStage`. A template carrying one deploys, and the member it
681
671
 
682
672
  ## Protecting a route with a Cognito user pool
683
673
 
684
- A JWT authorizer verifies a signed token before the integration is invoked. `CreateAuthorizerCommand`
685
- creates one, and a route asks for it with `AuthorizationType: "JWT"` and the authorizer's id.
674
+ A JWT authorizer verifies a signed token before invoking the integration. Create it with
675
+ `CreateAuthorizerCommand`, then set `AuthorizationType: "JWT"` and `AuthorizerId` on the route.
686
676
 
687
677
  The issuer is a URL. Point it at a [simulated Cognito user pool](https://yulinsim.dev/services/cognito/ "Simulated Cognito docs")
688
678
  and the pool's own signing key verifies the token. A token from `InitiateAuthCommand` or
@@ -852,9 +842,8 @@ console.log(expired.status); // 401
852
842
  await srv.close();
853
843
  ```
854
844
 
855
- The verification is real. The token is parsed, its `alg` has to be `RS256`, its `kid` has to name a
856
- key the issuer publishes, and the signature is checked against that key with `node:crypto`. The whole
857
- check runs in process, with no network fetch and no verification library.
845
+ Yulin parses the token, requires `alg` to be `RS256`, resolves `kid` against the issuer's keys and
846
+ checks the signature with `node:crypto`. Verification runs in process.
858
847
 
859
848
  ### What a refused request gets back
860
849
 
@@ -933,9 +922,8 @@ left out of its events entirely.
933
922
 
934
923
  ## Protecting a route with IAM
935
924
 
936
- A route declared `AuthorizationType: "AWS_IAM"` reaches its integration only when the caller is
937
- allowed `execute-api:Invoke` on the ARN of the route being called. IAM itself decides. The route takes
938
- no authorizer, and naming one is refused.
925
+ A route with `AuthorizationType: "AWS_IAM"` requires `execute-api:Invoke` on the request ARN. The
926
+ route has no separate authorizer.
939
927
 
940
928
  The caller comes from the request, through either a SigV4 signature or an `x-sim-aws-caller` header
941
929
  naming a principal directly. A request offering neither is anonymous, owns no policies, and is
@@ -948,14 +936,13 @@ The ARN a request is authorized against is:
948
936
  arn:aws:execute-api:<region>:<account>:<apiId>/<stage>/<METHOD>/<path>
949
937
  ```
950
938
 
951
- - The Account and Region are the API's own, not the caller's.
939
+ - The account and region identify the API.
952
940
  - The stage is the one that served the request, so `$default` for the default stage.
953
941
  - The method is the one the client sent, upper case. A `GET` reaching a route keyed `ANY /orders`
954
942
  gives `GET`.
955
- - The path is the request path with the stage segment and the leading slash taken off, so
956
- `/dev/orders/42` served from stage `dev` gives `orders/42`. This is the path asked for, not the route
957
- key, because a policy is written against it by hand. A request to the API root gives an ARN ending
958
- `/GET/`.
943
+ - The path is the request path without the stage segment or leading slash. `/dev/orders/42` on stage
944
+ `dev` becomes `orders/42`. It uses the requested path, including resolved parameter values. A
945
+ request to the API root produces an ARN ending in `/GET/`.
959
946
 
960
947
  An identity policy may wildcard any part of that. `<apiId>/*`, `<apiId>/$default/*` and
961
948
  `<apiId>/*/GET/orders/*` all allow a `GET` of `/orders/42` on the default stage.
@@ -1132,13 +1119,13 @@ Account.
1132
1119
 
1133
1120
  ## Protecting a route with a Lambda authorizer
1134
1121
 
1135
- A Lambda `REQUEST` authorizer runs a function of its own before the integration is invoked, and that
1136
- function decides. `CreateAuthorizerCommand` with `AuthorizerType: "REQUEST"` creates one, and a route
1137
- asks for it with `AuthorizationType: "CUSTOM"` and the authorizer's id.
1122
+ A Lambda `REQUEST` authorizer invokes a function before the integration. Create it with
1123
+ `AuthorizerType: "REQUEST"`, then configure the route with `AuthorizationType: "CUSTOM"` and the
1124
+ authorizer ID.
1138
1125
 
1139
- `IdentitySource` is what the request has to carry before the function is invoked at all. Each entry
1140
- is `$request.header.<name>` or `$request.querystring.<name>`, and a request missing any one of them is
1141
- refused without the function running.
1126
+ `IdentitySource` lists values required before invoking the authorizer. Each entry is
1127
+ `$request.header.<name>` or `$request.querystring.<name>`. A missing value returns 401 without running
1128
+ the function.
1142
1129
 
1143
1130
  `EnableSimpleResponses: true` asks the function for `{ isAuthorized, context }`. With it off, the
1144
1131
  function answers a `principalId` and an IAM `policyDocument` instead. Either way, the `context` it
@@ -1357,9 +1344,8 @@ is a different grant from the integration's, and a function used for both needs
1357
1344
 
1358
1345
  ### Caching the authorizer's decision
1359
1346
 
1360
- `AuthorizerResultTtlInSeconds` holds a decision for that many seconds, and a request presenting the
1361
- same identity source values within it is served from that decision without the function running
1362
- again. AWS accepts up to 3600. The default, 0, holds no decision at all.
1347
+ `AuthorizerResultTtlInSeconds` caches a decision by identity source values. AWS accepts values up to
1348
+ 3,600 seconds. The default of zero disables caching.
1363
1349
 
1364
1350
  The key is the identity source values and nothing else, so one decision covers every route of the API
1365
1351
  that uses the authorizer. Adding `$context.routeKey` as an identity source puts the route in the key.
@@ -1517,8 +1503,7 @@ ran.
1517
1503
 
1518
1504
  ## The event the handler receives
1519
1505
 
1520
- The handler is invoked with the API Gateway HTTP API payload format 2.0 event, exported as
1521
- `SimPayload2Event`:
1506
+ The handler receives a payload format 2.0 event, exported as `SimPayload2Event`:
1522
1507
 
1523
1508
  ```json
1524
1509
  {
@@ -1630,9 +1615,8 @@ endpoint, and has
1630
1615
 
1631
1616
  ## The response the handler returns
1632
1617
 
1633
- A handler returning an object with a `statusCode` produces that HTTP response. Its `headers` are
1634
- sent as headers, its `cookies` become `set-cookie` headers, and an empty body is sent as no body,
1635
- leaving a 204 a valid response.
1618
+ A result with `statusCode` becomes the HTTP response. `headers` are sent as response headers,
1619
+ `cookies` become `set-cookie` headers and an omitted body stays empty.
1636
1620
 
1637
1621
  A handler returning anything else produces a 200 whose body is that value as JSON. That includes an
1638
1622
  object with no `statusCode` in it. A handler returning `{ body: "hi" }` produces a 200 whose body is
@@ -1640,8 +1624,8 @@ the JSON `{"body":"hi"}` with `content-type: application/json`. Real API Gateway
1640
1624
 
1641
1625
  ## Reading an API back
1642
1626
 
1643
- `GetApisCommand`, `GetIntegrationsCommand`, `GetRoutesCommand` and `GetStagesCommand` list what an
1644
- API has. Each answers in full, as paging is outside the simulation.
1627
+ `GetApisCommand`, `GetIntegrationsCommand`, `GetRoutesCommand` and `GetStagesCommand` list the API's
1628
+ resources. Each command returns all results because pagination is unsupported.
1645
1629
 
1646
1630
  ```typescript sim-apigatewayv2-list-resources
1647
1631
  /**
@@ -1684,10 +1668,9 @@ console.log(stages.Items[0]?.StageVariables);
1684
1668
 
1685
1669
  ## Deleting what an API has
1686
1670
 
1687
- `DeleteRouteCommand`, `DeleteIntegrationCommand` and `DeleteStageCommand` each take one resource off
1688
- an API and leave the rest of it in place. A deleted route stops matching, and a request that used to
1689
- reach it is answered the way any unmatched request is. A deleted stage stops resolving. A request
1690
- addressed to it finds no stage, while the routes it served are still served by the API's other stages.
1671
+ `DeleteRouteCommand`, `DeleteIntegrationCommand` and `DeleteStageCommand` remove one resource. A
1672
+ deleted route stops matching. Deleting a stage removes its URL while leaving the API's routes
1673
+ available through other stages.
1691
1674
 
1692
1675
  An integration a route still points at cannot be deleted. That is a `BadRequestException` naming the
1693
1676
  routes in the way, as it is on real AWS, and an API comes apart routes first and then the integrations
@@ -1761,8 +1744,7 @@ const integrations = await apiGateway.getIntegrations(
1761
1744
  console.log(routes.Items.length, integrations.Items.length);
1762
1745
  ```
1763
1746
 
1764
- `DeleteApiCommand` deletes the API and everything under it, so taking a whole API away needs none of
1765
- these.
1747
+ `DeleteApiCommand` deletes the API and all of its resources.
1766
1748
 
1767
1749
  ## Turning the generated endpoint off
1768
1750
 
@@ -1774,9 +1756,8 @@ status nor the body for that case, so both are what a disabled endpoint was obse
1774
1756
 
1775
1757
  ## Serving an API on a custom domain name
1776
1758
 
1777
- `CreateDomainNameCommand` creates a domain that answers on a hostname the project owns.
1778
- `CreateApiMappingCommand` points a base path of that domain at one API and one of its stages. A
1779
- domain with no mapping answers 404 to everything.
1759
+ `CreateDomainNameCommand` creates a custom domain. `CreateApiMappingCommand` maps a base path to an
1760
+ API stage. A domain with no mapping returns 404.
1780
1761
 
1781
1762
  An empty `ApiMappingKey` maps the root of the domain. Every request reaching the domain goes to that
1782
1763
  API, with the path as the client sent it. A non-empty key is one or more path segments (`orders`,
@@ -1,22 +1,15 @@
1
1
  # Simulated Athena
2
2
 
3
- Yulin includes a simulated Amazon Athena for tests and local development. It holds workgroups and
4
- named queries, and hands both back through the SDK.
3
+ Yulin simulates Amazon Athena workgroups, named queries and query execution for tests and local
4
+ development. Tests can supply a result for a particular SQL statement, or enable the optional
5
+ [query engine](#running-a-query-for-real) to run SQL against objects in simulated S3.
5
6
 
6
- A query is answered one of two ways. A test declares what it answers with, and the simulation
7
- matches that declaration on the query text. Or the
8
- [query engine](#running-a-query-for-real) runs the SQL for real over the objects a test seeded into
9
- simulated S3. The engine is off until a test turns it on, and it needs one package added to the
10
- project.
7
+ Both paths use the same query lifecycle. Athena checks tables in the simulated
8
+ [Glue Data Catalog](https://yulinsim.dev/services/glue/ "Simulated Glue usage docs"), applies the
9
+ workgroup's bytes-scanned limit and writes results to the configured S3 location. This lets client
10
+ code start a query, poll its status and read its results through the AWS SDK.
11
11
 
12
- Either way the lifecycle around the query is real. A test can prove its bytes-scanned cutoff
13
- refuses a query, that results land where the workgroup says, and that a client polls the lifecycle
14
- correctly. The tables a query names are looked for in the simulated
15
- [Glue Data Catalog](https://yulinsim.dev/services/glue/ "Simulated Glue usage docs"), and a query
16
- naming one that is absent fails the way real Athena fails it. The
17
- [Limitations](#limitations) at the end say what this leaves out.
18
-
19
- Athena-specific types are imported from the `@kensio/yulin/athena` subpath.
12
+ Import Athena-specific types from `@kensio/yulin/athena`.
20
13
 
21
14
  ## Workgroups from a template
22
15
 
@@ -177,23 +170,21 @@ const results = await simAws.athena().getQueryResults({
177
170
  console.log(results.ResultSet?.Rows?.[1]?.Data?.[1]?.VarCharValue);
178
171
  ```
179
172
 
180
- A rule for an exact query wins, then a rule for a workgroup, then the default. `onWorkGroup` covers
181
- every query a stack's rollups run, and `byDefault` covers everything else. Matching is exact, on the
182
- query text as it was sent, so two queries differing only in whitespace are two different keys.
173
+ A result declared for an exact query takes precedence over a workgroup result, which takes
174
+ precedence over the default result. Use `onWorkGroup` to cover every query in one workgroup and
175
+ `byDefault` as the fallback. Exact matching includes whitespace.
183
176
 
184
177
  The query engine sits between the two tiers. A rule for an exact query is ahead of it and the
185
178
  workgroup rule and the default are behind it.
186
179
 
187
- `failsWith` fails a query instead of answering it. Nothing here reads SQL, so a query that should
188
- fail cannot be discovered on its own. Saying so is what makes a client's failure handling
189
- reachable.
180
+ Use `failsWith` to declare a failed query and test the client's error handling. Declared results do
181
+ not parse the SQL, so failures must be configured explicitly.
190
182
 
191
183
  ## Running a query for real
192
184
 
193
- The query engine answers a `SELECT` from the objects a test seeded into simulated S3. It reads the
194
- table's schema out of the Glue Data Catalog, decodes each object with the SerDe the table declares,
195
- loads the rows into an in-memory SQLite database, and answers the statement from them. Roughly
196
- nineteen queries in twenty of the shapes a test writes run this way.
185
+ The query engine runs a `SELECT` against objects seeded into simulated S3. It reads the table schema
186
+ from the Glue Data Catalog, decodes each object with the table's SerDe, loads the rows into an
187
+ in-memory SQLite database and executes the statement.
197
188
 
198
189
  The engine is off until a test turns it on, and it needs `node-sql-parser` in the project. The
199
190
  parser is an optional peer dependency, so a project that never runs a query never installs it. A
@@ -965,7 +956,7 @@ Every command is authorized against the workgroup ARN,
965
956
  own and authorizes work on one against the workgroup it belongs to. This asks the same question.
966
957
  `ListWorkGroups` names no workgroup, so IAM evaluates it against `*`.
967
958
 
968
- ## Available functionality
959
+ ## Supported operations
969
960
 
970
961
  - Query executions, moving through `QUEUED` and `RUNNING` to `SUCCEEDED`, `FAILED` or `CANCELLED`
971
962
  - `StartQueryExecution`, `GetQueryExecution`, `GetQueryResults` and `StopQueryExecution`
@@ -1,12 +1,12 @@
1
1
  # Simulated AWS Backup
2
2
 
3
- Yulin includes simulated AWS Backup vaults, plans, selections, backup jobs and recovery points for
4
- tests and local development. AWS Backup types are imported from the `@kensio/yulin/backup` subpath.
3
+ Yulin simulates AWS Backup vaults, plans, selections, jobs and recovery points. Import Backup types
4
+ from `@kensio/yulin/backup`.
5
5
 
6
6
  ## Creating a vault, plan and selection
7
7
 
8
- `simAws.backup()` gives the AWS Backup service for the default account and Region. A plan rule names
9
- an existing vault. A selection belongs to one plan and records the resource ARNs assigned to it.
8
+ Use `simAws.backup()` for the default account and Region. Each plan rule names an existing vault. A
9
+ selection assigns resource ARNs to a plan.
10
10
 
11
11
  ```typescript sim-backup-create-plan-selection
12
12
  /**
@@ -78,19 +78,17 @@ console.log(selection.BackupSelection?.Resources);
78
78
  // ["arn:aws:dynamodb:us-east-1:888888888888:table/orders"]
79
79
  ```
80
80
 
81
- A plan needs at least one rule. Every rule needs a name and a target vault. An omitted schedule uses
82
- `cron(0 5 ? * * *)`, the AWS Backup default. Six-field AWS cron expressions and rate expressions
83
- are validated when the plan is created. A one-time `at(...)` expression is refused.
81
+ A plan needs at least one named rule and target vault. The default schedule is
82
+ `cron(0 5 ? * * *)`. Yulin accepts six-field AWS cron expressions and rate expressions. It rejects
83
+ `at(...)` expressions.
84
84
 
85
- `MoveToColdStorageAfterDays` can be combined with `DeleteAfterDays`. The deletion must be at least
86
- 90 days after the move to cold storage. A shorter lifecycle raises
87
- `InvalidParameterValueException`. Set both values to `-1` to retain recovery points indefinitely.
85
+ When both lifecycle values are set, `DeleteAfterDays` must be at least 90 days after
86
+ `MoveToColdStorageAfterDays`. Use `-1` for both values to keep recovery points indefinitely.
88
87
 
89
88
  ## Running scheduled backups
90
89
 
91
- Each plan rule runs on the simulated clock. A due rule creates one recovery point for each distinct
92
- resource ARN in the plan's selections. The recovery point records the rule, resource ARN, lifecycle
93
- and creation time. The simulation completes backup jobs at the scheduled instant.
90
+ Plan rules run on the simulated clock. A due rule creates one completed job and recovery point for
91
+ each distinct resource ARN in its selections.
94
92
 
95
93
  ```typescript sim-backup-run-schedule
96
94
  /**
@@ -161,19 +159,16 @@ console.log(points.RecoveryPoints?.[0]?.ResourceArn);
161
159
  console.log(jobs.BackupJobs?.[0]?.State); // "COMPLETED"
162
160
  ```
163
161
 
164
- `DeleteAfterDays` removes a recovery point when the clock reaches its deletion time. Vault reads,
165
- `ListRecoveryPointsByBackupVault` and `DescribeRecoveryPoint` apply the expiry before returning.
166
- Repeated schedules keep every unexpired recovery point.
162
+ `DeleteAfterDays` removes a recovery point when simulated time reaches its expiry. Recovery point
163
+ reads apply expiry before returning.
167
164
 
168
- Vault Lock bounds apply when a backup starts. A lifecycle shorter than `MinRetentionDays`, longer
169
- than `MaxRetentionDays` or indefinite under a finite maximum produces a `FAILED` backup job. The
170
- vault receives no recovery point for that job. Use `ListBackupJobs` or `DescribeBackupJob` to read
171
- the failure and its `StatusMessage`.
165
+ Vault Lock bounds are checked when a backup starts. An invalid lifecycle produces a `FAILED` job and
166
+ no recovery point. Read the reason from the job's `StatusMessage`.
172
167
 
173
168
  ## Starting an on-demand backup
174
169
 
175
- `StartBackupJob` completes an on-demand job at the current simulated time. It applies the same
176
- lifecycle validation and Vault Lock bounds as a scheduled rule.
170
+ `StartBackupJob` completes at the current simulated time. It applies the same lifecycle and Vault
171
+ Lock checks as a scheduled job.
177
172
 
178
173
  ```typescript sim-backup-start-job
179
174
  /**
@@ -219,9 +214,8 @@ console.log(point.CreationDate?.toISOString());
219
214
 
220
215
  ## Vault Lock
221
216
 
222
- `PutBackupVaultLockConfiguration` records minimum and maximum retention periods on a vault. Adding
223
- `ChangeableForDays` creates a compliance lock. The configuration stays changeable until the grace
224
- period ends, then becomes immutable.
217
+ `PutBackupVaultLockConfiguration` sets minimum and maximum retention. `ChangeableForDays` adds a
218
+ grace period. The configuration becomes immutable when that period ends.
225
219
 
226
220
  ```typescript sim-backup-vault-lock
227
221
  /**
@@ -284,8 +278,7 @@ exceed the maximum, and the maximum cannot exceed 36,500 days. A lock without
284
278
  ## Deploying from CloudFormation
285
279
 
286
280
  Simulated CloudFormation deploys `AWS::Backup::BackupVault`, `AWS::Backup::BackupPlan` and
287
- `AWS::Backup::BackupSelection`. References between the resources resolve before AWS Backup creates
288
- them.
281
+ `AWS::Backup::BackupSelection`.
289
282
 
290
283
  ```typescript sim-backup-cloudformation
291
284
  /**
@@ -376,10 +369,9 @@ Stack teardown removes all three resource types from the simulation.
376
369
 
377
370
  ## Permissions
378
371
 
379
- Every supported operation is authorized by simulated IAM. Vault operations use the vault ARN. Plan
380
- and selection operations use the plan ARN. `ListBackupVaults` has no resource in its request and is
381
- authorized against `*`. `StartBackupJob` and `ListRecoveryPointsByBackupVault` use the vault ARN.
382
- `DescribeRecoveryPoint` uses the recovery point ARN. Backup job reads use `*`.
372
+ Every supported operation uses simulated IAM. Vault operations authorize against the vault ARN.
373
+ Plan and selection operations use the plan ARN. `DescribeRecoveryPoint` uses the recovery point ARN.
374
+ List and backup job read operations that name no resource use `*`.
383
375
 
384
376
  ```typescript sim-backup-iam-policy
385
377
  /**
@@ -432,13 +424,12 @@ const created = await simAws.backup().createBackupVault(
432
424
  console.log(created.BackupVaultName); // "application-backups"
433
425
  ```
434
426
 
435
- Authorization runs before resource lookup. An unauthorized request for a missing vault or plan
436
- raises `AccessDeniedException`, without revealing whether the resource exists.
427
+ Authorization runs before resource lookup. An unauthorized request for a missing resource raises
428
+ `AccessDeniedException`.
437
429
 
438
430
  ## SDK interception
439
431
 
440
- `SimSdk` routes commands from an AWS `BackupClient` to the simulation. Intercept the client instance
441
- when a test owns it, or intercept the class when application code creates the client.
432
+ Intercept a `BackupClient` instance or the client class to route SDK commands to Yulin.
442
433
 
443
434
  ```typescript sim-backup-sdk-interception
444
435
  /**
@@ -466,13 +457,12 @@ console.log(listed.BackupVaultList?.[0]?.BackupVaultName);
466
457
  // "application-backups"
467
458
  ```
468
459
 
469
- The client's configured Region selects the simulated Region. Credentials select the account and
470
- caller when the intercepted client has them. See the [SDK interception docs](https://yulinsim.dev/sdk/)
471
- for class interception and credential handling.
460
+ Client Region and credentials select the simulated scope and caller. See
461
+ [SDK interception](https://yulinsim.dev/sdk/) for details.
472
462
 
473
463
  ## Account and Region scoping
474
464
 
475
- AWS Backup state belongs to one account and Region. The same vault name can exist in another scope.
465
+ Backup state is scoped by account and Region. The same vault name can exist in another scope.
476
466
 
477
467
  ```typescript sim-backup-account-region-scoping
478
468
  /**