@kensio/yulin 1.20.15 → 1.20.16

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 (95) hide show
  1. package/README.md +13 -0
  2. package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
  3. package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
  4. package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
  5. package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
  6. package/dist/sdk/send-patch.d.ts +8 -0
  7. package/dist/sdk/send-patch.js +11 -0
  8. package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
  9. package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
  10. package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
  11. package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
  12. package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
  13. package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
  14. package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
  15. package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
  16. package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
  17. package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
  18. package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
  19. package/dist/service/aws/sim-aws-properties.d.ts +15 -0
  20. package/dist/service/aws/sim-aws.js +2 -1
  21. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
  22. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
  23. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
  24. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
  25. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
  26. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
  27. package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
  28. package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
  29. package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
  30. package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
  31. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
  32. package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
  33. package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
  34. package/dist/service/iam/sim-iam-account-parts.js +1 -0
  35. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
  36. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
  37. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
  38. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
  39. package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
  40. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
  41. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
  42. package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
  43. package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
  44. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
  45. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
  46. package/dist/service/sts/service-role/sim-service-role.js +5 -1
  47. package/dist/service/sts/sim-sts.d.ts +9 -1
  48. package/dist/service/sts/sim-sts.js +4 -0
  49. package/docs/README.md +54 -0
  50. package/docs/ai-skill/README.md +69 -0
  51. package/docs/cli/README.md +284 -0
  52. package/docs/factories/README.md +140 -0
  53. package/docs/lint/README.md +176 -0
  54. package/docs/non-aws-dependencies/README.md +261 -0
  55. package/docs/sdk/README.md +272 -0
  56. package/docs/serve/README.md +1126 -0
  57. package/docs/services/acm/README.md +823 -0
  58. package/docs/services/apigateway/README.md +1919 -0
  59. package/docs/services/apigatewayv2/README.md +2678 -0
  60. package/docs/services/athena/README.md +1044 -0
  61. package/docs/services/bedrock/README.md +350 -0
  62. package/docs/services/cloudformation/README.md +3440 -0
  63. package/docs/services/cloudfront/README.md +2874 -0
  64. package/docs/services/cloudwatch/README.md +420 -0
  65. package/docs/services/cognito/README.md +4923 -0
  66. package/docs/services/dynamodb/README.md +3784 -0
  67. package/docs/services/ecr/README.md +302 -0
  68. package/docs/services/ecs/README.md +2010 -0
  69. package/docs/services/elbv2/README.md +1897 -0
  70. package/docs/services/eventbridge/README.md +1110 -0
  71. package/docs/services/firehose/README.md +805 -0
  72. package/docs/services/glue/README.md +501 -0
  73. package/docs/services/iam/README.md +1283 -0
  74. package/docs/services/kinesis/README.md +454 -0
  75. package/docs/services/kms/README.md +659 -0
  76. package/docs/services/lambda/README.md +3645 -0
  77. package/docs/services/logs/README.md +868 -0
  78. package/docs/services/organizations/README.md +574 -0
  79. package/docs/services/personalize/README.md +835 -0
  80. package/docs/services/rekognition/README.md +1070 -0
  81. package/docs/services/route53/README.md +1783 -0
  82. package/docs/services/s3/README.md +3024 -0
  83. package/docs/services/scheduler/README.md +806 -0
  84. package/docs/services/secretsmanager/README.md +667 -0
  85. package/docs/services/ses/README.md +1011 -0
  86. package/docs/services/sns/README.md +1904 -0
  87. package/docs/services/sqs/README.md +1185 -0
  88. package/docs/services/ssm/README.md +1072 -0
  89. package/docs/services/stepfunctions/README.md +1332 -0
  90. package/docs/services/sts/README.md +245 -0
  91. package/docs/services/wafv2/README.md +1378 -0
  92. package/docs/terraform/README.md +248 -0
  93. package/docs/time/README.md +284 -0
  94. package/llms.txt +59 -0
  95. package/package.json +7 -4
@@ -0,0 +1,176 @@
1
+ # Linting CFF JS2
2
+
3
+ CloudFront Functions run JS2, ECMAScript 5.1 with a named subset of ES 6 to 12 on top, rather than
4
+ a current JavaScript engine. A class or a `for...of` is a syntax error, and CloudFront refuses the
5
+ code when you upload it. A call to `fetch` parses, and then fails at the edge, where that global is
6
+ absent and there is no network to reach. Both are a long way from where the code was written, and
7
+ Yulin publishes lint configs that refuse the same things in the editor.
8
+
9
+ The configs apply to `**/*.cff.js` files. That is the naming
10
+ [sim CloudFront](https://yulinsim.dev/services/cloudfront/ "Simulated CloudFront usage docs") already uses for
11
+ CloudFront Function source.
12
+
13
+ Every restriction comes from the runtime's
14
+ [own feature list](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html),
15
+ an allow-list, and anything outside it is unsupported. None of it is house style. A rule
16
+ banning syntax JS2 accepts would send you away from code that works.
17
+
18
+ ## Setting it up with ESLint
19
+
20
+ `@kensio/yulin/eslint` exports a flat config to spread into your own. It restricts itself to
21
+ `**/*.cff.js`. The rest of your config still applies everywhere else.
22
+
23
+ ```typescript cff-js2-eslint-config
24
+ /**
25
+ * An ESLint config that lints CloudFront Function files as JS2.
26
+ */
27
+
28
+ import eslint from "@eslint/js";
29
+ import { defineConfig } from "eslint/config";
30
+
31
+ import { cloudFrontFunctionsJs2 } from "@kensio/yulin/eslint";
32
+
33
+ export default defineConfig(
34
+ eslint.configs.recommended,
35
+
36
+ // Applies only to **/*.cff.js, so it goes after the configs it relaxes.
37
+ ...cloudFrontFunctionsJs2,
38
+ );
39
+ ```
40
+
41
+ It goes after any config whose rules it needs to turn off. That is a short list. `const`, `let`,
42
+ template literals, arrow functions, rest parameters and `async`/`await` all work in JS2, and the
43
+ rules asking for them stay on. Only `object-shorthand` is switched off, because shorthand property
44
+ names are ES 6 literal syntax the runtime lacks.
45
+
46
+ `eslint` and `typescript-eslint` are optional peer dependencies, needed only if you use this export.
47
+
48
+ ## Setting it up with Oxlint
49
+
50
+ `@kensio/yulin/oxlint` ships the same rules as an Oxlint config fragment to extend. The rules
51
+ themselves are one plugin loaded by both linters. The two configs report the same things in the same
52
+ places.
53
+
54
+ ```json
55
+ {
56
+ "extends": ["./node_modules/@kensio/yulin/cffjs2.oxlintrc.json"],
57
+ "rules": {
58
+ "no-console": "error"
59
+ }
60
+ }
61
+ ```
62
+
63
+ Oxlint's `extends` takes a file path rather than a package name. The path into `node_modules` is
64
+ written out in full. The file sits in the package root. A build that moves what it emits under
65
+ `dist/` leaves the path alone. The fragment brings its own `overrides` entry scoped to `**/*.cff.js`
66
+ and the JS plugin the rules live in, and leaves every other file to your own rules.
67
+
68
+ The plugin is loaded through Oxlint's JS plugin support, which needs Oxlint 1.77 or later.
69
+
70
+ ## What is reported
71
+
72
+ Each restriction is its own rule, under the `cff-js2` name in both linters.
73
+
74
+ | Rule | What it refuses |
75
+ | ------------------------------- | -------------------------------------------------------- |
76
+ | `cff-js2/no-import` | `import` declarations, apart from `cloudfront` |
77
+ | `cff-js2/only-handler-export` | Any export other than `export function handler(...)` |
78
+ | `cff-js2/only-built-in-require` | Requiring anything but `querystring`, `crypto`, `buffer` |
79
+ | `cff-js2/no-class` | Class declarations and expressions |
80
+ | `cff-js2/no-generator` | Generators and `yield` |
81
+ | `cff-js2/no-destructuring` | Object and array destructuring |
82
+ | `cff-js2/no-spread` | Spread syntax, but not rest parameters |
83
+ | `cff-js2/no-for-of` | `for...of`, in favour of an index-based loop |
84
+ | `cff-js2/no-unavailable-global` | Globals the runtime does not have, such as `fetch` |
85
+
86
+ `cff-js2/no-unavailable-global` covers three groups. `fetch`, `XMLHttpRequest` and `WebSocket` need
87
+ a network the runtime lacks. `process` needs Node.js. `setTimeout`, `setInterval`, `setImmediate`
88
+ and `clearTimeout` need an event loop a Function never gets. The rule resolves names through scope
89
+ rather than matching them as text, and a local variable named `fetch` or a property named
90
+ `event.fetch` is left alone. Each report says why the global is missing, because `fetch` being
91
+ absent for want of a network and `setTimeout` for want of an event loop call for different rewrites.
92
+
93
+ Alongside these, both configs turn on `no-eval`, `no-new-func` and `no-implied-eval`, which the
94
+ runtime refuses outright. They also set `no-unused-vars` to leave `handler` alone, since it is the
95
+ entry point CloudFront calls.
96
+
97
+ ## What is not reported
98
+
99
+ These all work in JS2 and no rule here objects to them:
100
+
101
+ - Template literals, including interpolation and nesting
102
+ - Arrow functions and rest parameters
103
+ - `const` and `let`
104
+ - `async` and `await`
105
+ - `Promise`, including `all`, `allSettled`, `any` and `race`
106
+ - `Buffer`, and `require` of `querystring`, `crypto` or `buffer`
107
+ - `import cf from "cloudfront"`, which a Function needs for `cf.kvs()` and
108
+ `cf.updateRequestOrigin()`
109
+ - `String.prototype.replaceAll`, `atob`, `btoa` and numeric separators
110
+
111
+ ## Turning one restriction off
112
+
113
+ Rules are individual. A restriction you disagree with can be switched off on its own. In ESLint:
114
+
115
+ ```typescript cff-js2-eslint-relax
116
+ /**
117
+ * Allowing class syntax in CloudFront Function files.
118
+ */
119
+
120
+ import { defineConfig } from "eslint/config";
121
+
122
+ import { cloudFrontFunctionsJs2 } from "@kensio/yulin/eslint";
123
+
124
+ export default defineConfig(...cloudFrontFunctionsJs2, {
125
+ files: ["**/*.cff.js"],
126
+ rules: {
127
+ "cff-js2/no-class": "off",
128
+ },
129
+ });
130
+ ```
131
+
132
+ In Oxlint, the same thing goes in an `overrides` entry after the `extends`:
133
+
134
+ ```json
135
+ {
136
+ "extends": ["./node_modules/@kensio/yulin/cffjs2.oxlintrc.json"],
137
+ "overrides": [
138
+ {
139
+ "files": ["**/*.cff.js"],
140
+ "rules": {
141
+ "cff-js2/no-class": "off"
142
+ }
143
+ }
144
+ ]
145
+ }
146
+ ```
147
+
148
+ ## Available functionality
149
+
150
+ - A flat ESLint config at `@kensio/yulin/eslint`, exported as `cloudFrontFunctionsJs2`
151
+ - An Oxlint config fragment at `cffjs2.oxlintrc.json` in the package root, with the object it is
152
+ generated from exported as `cloudFrontFunctionsJs2Oxlint` from `@kensio/yulin/oxlint`
153
+ - Nine `cff-js2` rules, one per restriction, shared by both linters
154
+ - Scoping to `**/*.cff.js`, leaving a repository's own rules untouched elsewhere
155
+
156
+ ## Limitations
157
+
158
+ Where the configs knowingly stop short:
159
+
160
+ - **`async` arguments and closures are not checked.** JS2 supports `async` and `await`, but not
161
+ `async` arguments or closures, and `await` only inside an `async` function. Where the runtime's
162
+ wording stops short of saying exactly which forms those are, no rule guesses at it. A Function can
163
+ pass the lint and still be refused for one.
164
+ - **The list of unavailable globals is the useful part of one, not all of it.** It names what test
165
+ code and Node habits reach for. A global outside that list goes unreported, even if JS2 lacks it.
166
+ - **The rules are syntactic.** Nothing here knows CloudFront's size limit on Function code or its CPU
167
+ budget. A Function that passes the lint can still be refused at publication for being too large or
168
+ too slow. No rule checks which methods of a supported built-in you call either, and the runtime
169
+ supports only some of them.
170
+ - **Only the `cff-js2` rules are shared between the linters.** ESLint and Oxlint each bring their own
171
+ built-in rules, and their own defaults for which are on. A `.cff.js` file linted by both may pick
172
+ up findings from one that the other lacks.
173
+ - **Oxlint's JS plugin support is in alpha.** It is what loads these rules into Oxlint, and its API
174
+ is still moving upstream.
175
+ - **The Oxlint fragment is extended by path.** Oxlint has no package-name resolution in `extends`.
176
+ The written-out path into `node_modules` depends on your installer's layout.
@@ -0,0 +1,261 @@
1
+ # Non-AWS dependencies
2
+
3
+ Most applications talk to something other than AWS, such as a Redis or a Postgres. This page covers
4
+ what happens to those when the application runs under Yulin.
5
+
6
+ ## Two kinds of dependency
7
+
8
+ A simulated Lambda function and a simulated ECS container both run your own code in the same Node.js
9
+ process as the test. Everything that code talks to falls into one of two categories.
10
+
11
+ Simulated AWS services are handled by Yulin. A DynamoDB call reaches an in-memory table and an S3
12
+ call reaches an in-memory bucket, with no network involved. [AWS SDK interception](https://yulinsim.dev/sdk/)
13
+ is how an ordinary SDK client in the code under test gets there.
14
+
15
+ Everything else is yours to provide, and connects the way it normally would. Yulin leaves it alone
16
+ entirely, with no simulation and no interception. Code that opens a Redis connection opens a real
17
+ one, to whatever address it was given.
18
+
19
+ ## Pointing the code at your own dependency
20
+
21
+ A deployed Lambda function or ECS container reads its connection details from environment variables.
22
+ The simulated ones do the same. A function's `Environment.Variables` and a container definition's
23
+ `environment` are visible through `process.env` while the code runs, so pointing the application
24
+ somewhere else means setting the value it already reads. No Yulin feature is involved, and the code
25
+ under test stays as it is.
26
+
27
+ ```typescript non-aws-dependency-lambda
28
+ /**
29
+ * Pointing a simulated Lambda function at a dependency Yulin does not
30
+ * simulate, alongside one it does.
31
+ */
32
+
33
+ import { CreateFunctionCommand, InvokeCommand } from "@aws-sdk/client-lambda";
34
+
35
+ import { SimAws } from "@kensio/yulin";
36
+ import { makeLambdaZipFileInput } from "@kensio/yulin/lambda";
37
+
38
+ const simAws = new SimAws();
39
+ const lambda = simAws.lambda();
40
+
41
+ await lambda.createFunction(
42
+ new CreateFunctionCommand({
43
+ FunctionName: "rates",
44
+ Role: "arn:aws:iam::111111111111:role/RatesRole",
45
+ Environment: {
46
+ Variables: {
47
+ // Simulated by Yulin, reached with no network involved.
48
+ TABLE_NAME: "rates",
49
+ // Yours. A deployment points this at ElastiCache. A test points it
50
+ // at whatever it wants the code to talk to instead.
51
+ CACHE_URL: "redis://127.0.0.1:6379",
52
+ },
53
+ },
54
+ Code: {
55
+ ZipFile: makeLambdaZipFileInput(() => ({
56
+ // Building a Redis client from the second value involves nothing of
57
+ // Yulin's. It is an ordinary environment variable read.
58
+ tableName: process.env["TABLE_NAME"],
59
+ cacheUrl: process.env["CACHE_URL"],
60
+ })),
61
+ },
62
+ }),
63
+ );
64
+
65
+ const output = await lambda.invoke(
66
+ new InvokeCommand({ FunctionName: "rates" }),
67
+ );
68
+
69
+ if (output.Payload === undefined) throw new Error("No invoke Payload");
70
+ // {"tableName":"rates","cacheUrl":"redis://127.0.0.1:6379"}
71
+ console.log(Buffer.from(output.Payload).toString());
72
+ ```
73
+
74
+ What that address points at is up to the test. A Redis running on localhost, one the test suite
75
+ starts in a container, or a stand-in the test defines itself all work, because the application is
76
+ doing what it always does with the value it is given.
77
+
78
+ ## A container reading from both
79
+
80
+ The worked example below runs one simulated ECS container that writes to a simulated DynamoDB table
81
+ and reads from a cache of its own. The two categories sit next to each other in the same handler and
82
+ are configured the same way, through the container definition's `environment`.
83
+
84
+ The cache here is a stand-in defined by the example, and it works with no server running. A real
85
+ client built from `CACHE_URL` would go in the same place.
86
+
87
+ ```typescript non-aws-dependency-ecs
88
+ /**
89
+ * A simulated ECS container writing to a simulated DynamoDB table and reading
90
+ * from a cache of its own.
91
+ */
92
+
93
+ import {
94
+ CreateTableCommand,
95
+ DynamoDBClient,
96
+ GetItemCommand,
97
+ PutItemCommand,
98
+ } from "@aws-sdk/client-dynamodb";
99
+ import {
100
+ CreateClusterCommand,
101
+ RegisterTaskDefinitionCommand,
102
+ RunTaskCommand,
103
+ } from "@aws-sdk/client-ecs";
104
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
105
+
106
+ import { SimSdk } from "@kensio/yulin/sdk";
107
+
108
+ /**
109
+ * Where the worker reads exchange rates from.
110
+ *
111
+ * A deployment builds a Redis client from the URL. This example stands one in,
112
+ * so nothing has to be running for it to work.
113
+ */
114
+ class RateCache {
115
+ constructor(private readonly url: string) {}
116
+
117
+ rate(currency: string): Promise<string> {
118
+ console.log(`reading ${currency} from ${this.url}`);
119
+ // reading GBP from redis://127.0.0.1:6379
120
+ return Promise.resolve("1.27");
121
+ }
122
+ }
123
+
124
+ using simSdk = new SimSdk();
125
+ const { simAws } = simSdk;
126
+ const ecs = simAws.ecs();
127
+
128
+ simSdk.intercept(DynamoDBClient);
129
+
130
+ await simAws.dynamoDb().createTable(
131
+ new CreateTableCommand({
132
+ TableName: "rates",
133
+ KeySchema: [{ AttributeName: "currency", KeyType: "HASH" }],
134
+ AttributeDefinitions: [{ AttributeName: "currency", AttributeType: "S" }],
135
+ BillingMode: "PAY_PER_REQUEST",
136
+ }),
137
+ );
138
+
139
+ const taskRole = await simAws.iam().createRole(
140
+ new CreateRoleCommand({
141
+ RoleName: "RatesTaskRole",
142
+ AssumeRolePolicyDocument: JSON.stringify({
143
+ Version: "2012-10-17",
144
+ Statement: {
145
+ Effect: "Allow",
146
+ Principal: { Service: "ecs-tasks.amazonaws.com" },
147
+ Action: "sts:AssumeRole",
148
+ },
149
+ }),
150
+ }),
151
+ );
152
+
153
+ await simAws.iam().putRolePolicy(
154
+ new PutRolePolicyCommand({
155
+ RoleName: "RatesTaskRole",
156
+ PolicyName: "WriteRates",
157
+ PolicyDocument: JSON.stringify({
158
+ Version: "2012-10-17",
159
+ Statement: {
160
+ Effect: "Allow",
161
+ Action: "dynamodb:PutItem",
162
+ Resource:
163
+ `arn:aws:dynamodb:${simAws.defaultRegionName}:` +
164
+ `${simAws.defaultAccountId}:table/rates`,
165
+ },
166
+ }),
167
+ }),
168
+ );
169
+
170
+ await ecs.createCluster(new CreateClusterCommand({}));
171
+
172
+ ecs.bindContainer({
173
+ family: "rates-worker",
174
+ containerName: "app",
175
+ run: async () => {
176
+ // Both reads happen inside the handler, so they see the container's own
177
+ // variables rather than the test process's.
178
+ const cache = new RateCache(process.env["CACHE_URL"] ?? "");
179
+ const rate = await cache.rate("GBP");
180
+
181
+ await new DynamoDBClient({}).send(
182
+ new PutItemCommand({
183
+ TableName: process.env["TABLE_NAME"],
184
+ Item: { currency: { S: "GBP" }, rate: { S: rate } },
185
+ }),
186
+ );
187
+ },
188
+ });
189
+
190
+ await ecs.registerTaskDefinition(
191
+ new RegisterTaskDefinitionCommand({
192
+ family: "rates-worker",
193
+ taskRoleArn: taskRole.Role.Arn,
194
+ containerDefinitions: [
195
+ {
196
+ name: "app",
197
+ image: "rates-worker:1",
198
+ environment: [
199
+ // Simulated by Yulin, reached with no network involved.
200
+ { name: "TABLE_NAME", value: "rates" },
201
+ // Yours, connected to the way it normally would be.
202
+ { name: "CACHE_URL", value: "redis://127.0.0.1:6379" },
203
+ ],
204
+ },
205
+ ],
206
+ }),
207
+ );
208
+
209
+ await ecs.runTask(new RunTaskCommand({ taskDefinition: "rates-worker" }));
210
+ await simAws.backgroundTasksComplete();
211
+
212
+ const stored = await simAws.dynamoDb().getItem(
213
+ new GetItemCommand({
214
+ TableName: "rates",
215
+ Key: { currency: { S: "GBP" } },
216
+ }),
217
+ );
218
+
219
+ console.log(stored.Item?.["rate"]?.S); // "1.27"
220
+ ```
221
+
222
+ The DynamoDB write is authorized as the task role, in the same way it would be in a deployment. The
223
+ cache read passes through no authorization at all, because IAM has no part in it.
224
+
225
+ ## Sidecar containers are not started
226
+
227
+ A task definition sometimes declares the dependency itself as a second container, such as a Redis
228
+ running next to the application in the same task. Yulin never looks inside a container image, and
229
+ the only thing it can run is JavaScript or TypeScript in its own process. An image holding a Redis
230
+ server is beyond it.
231
+
232
+ The container is stored and reported back as declared, and it is recorded as not simulated when the
233
+ task runs, with a reason saying so. An application expecting it has to be given something else to
234
+ talk to. Set the variable holding the address to something that answers, whether that is a Redis you
235
+ run yourself or a stand-in, in the same way as for any other dependency of your own.
236
+
237
+ ## Reads happen inside the handler
238
+
239
+ Handler code gets the function's or the container's variables while it runs, and reads the host
240
+ process environment otherwise. A read at module scope, as in `const url = process.env.CACHE_URL` at
241
+ the top of a file, happens when the test imports that file rather than when the code runs. It sees
242
+ the host value.
243
+
244
+ That matters here because a connection is often built at module scope. Read inside the handler, or
245
+ build the client there, to get the configured value. Sim Lambda warns on the console when the
246
+ difference changes what the code sees, which is covered under
247
+ [environment variables](https://yulinsim.dev/services/lambda/#environment-variables) on the simulated Lambda
248
+ page. Zip code running in the vm runtime is unaffected, because it is imported during an invocation.
249
+
250
+ ## Limitations
251
+
252
+ Current documented limitations:
253
+
254
+ - Everything outside the simulated AWS services is yours to run and to tear down. Yulin starts none
255
+ of it.
256
+ - A dependency declared as a sidecar container in an ECS task definition is not started, because
257
+ Yulin never runs a container image.
258
+ - A connection built at module scope reads the host environment rather than the function's or the
259
+ container's, since the value is read before anything runs.
260
+ - There is no interception point for a non-AWS dependency. The way to send the code somewhere else
261
+ is the environment variable it already reads, or an injection point in the code itself.