@kensio/yulin 1.21.31 → 1.21.33
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/serve/message/sim-message-log-console.js +3 -3
- package/dist/service/apigateway/api/deployment/sim-rest-api-deployment.js +3 -3
- package/dist/service/apigateway/api/method/sim-rest-api-method.js +12 -12
- package/dist/service/apigateway/api/resource/sim-rest-api-resource.js +11 -9
- package/dist/service/apigateway/api/sim-rest-api-view.js +3 -3
- package/dist/service/apigateway/api/stage/sim-rest-api-stage.js +8 -10
- package/dist/service/apigatewayv2/api/integration/sim-http-api-integration.js +3 -3
- package/dist/service/apigatewayv2/api/route/sim-http-api-route-view.js +6 -6
- package/dist/service/apigatewayv2/api/sim-http-api-view.js +3 -3
- package/dist/service/apigatewayv2/api/stage/settings/sim-http-api-stage-route-settings.js +8 -7
- package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.js +9 -9
- package/dist/service/athena/engine/sim-athena-partitioned-objects.js +7 -7
- package/dist/service/athena/sim-athena-engine.fixture.js +3 -0
- package/dist/service/athena/table/sim-athena-partition-filters.js +6 -5
- package/dist/service/backup/command/sim-backup-authorizer.js +5 -4
- package/dist/service/bedrock/response/sim-bedrock-declared-content.js +5 -10
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-changes.js +19 -10
- package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-security-values.js +2 -6
- package/dist/service/cognito/user-pool/trigger/sim-cognito-pre-sign-up-response.js +4 -7
- package/dist/service/dynamodb/command/transact/sim-dynamodb-transact-write.js +34 -25
- package/dist/service/dynamodb/secondary-index/sim-dynamodb-index-limits.js +5 -4
- package/dist/service/dynamodb/stream/sim-dynamodb-stream-shard-iterator.js +1 -3
- package/dist/service/dynamodb/time-to-live/sim-dynamodb-time-to-live.js +4 -3
- package/dist/service/glue/expression/sim-glue-partition-comparisons.js +1 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-allow-requirement.js +6 -6
- package/dist/service/iam/authorize/match/condition/null/sim-iam-null.js +4 -1
- package/dist/service/kinesis/stream/sim-kinesis-shard-iterator.js +1 -3
- package/dist/service/lambda/command/create-function/create-function.handler.js +1 -0
- package/dist/service/lambda/destination/sim-lambda-destination-record.js +7 -7
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflict.d.ts +22 -0
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflict.js +33 -0
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflicts.d.ts +53 -11
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflicts.js +78 -24
- package/dist/service/lambda/function/policy/sim-lambda-permission.js +15 -14
- package/dist/service/lambda/function/sim-lambda-function.js +1 -0
- package/dist/service/lambda/function/sim-lambda-function.type.d.ts +8 -0
- package/dist/service/lambda/metric/sim-lambda-function-metrics.js +11 -9
- package/dist/service/lambda/sim-lambda-commands.js +3 -1
- package/dist/service/logs/metric/sim-logs-metric-filter.js +1 -8
- package/dist/service/route53/dns/answer/sim-route53-dns-zone-finder.js +5 -4
- package/dist/service/route53/resolve/sim-r53-zone-record-finder.js +8 -6
- package/dist/service/s3/bucket/notification/sim-s3-notification-filter.js +4 -7
- package/dist/service/s3/bucket/sim-s3-bucket.d.ts +8 -0
- package/dist/service/s3/bucket/sim-s3-bucket.js +11 -0
- package/dist/service/s3/bucket/sim-s3-key-writes.d.ts +16 -0
- package/dist/service/s3/bucket/sim-s3-key-writes.js +43 -0
- package/dist/service/s3/bucket/versioning/sim-s3-bucket-versioning.js +1 -3
- package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.command.d.ts +2 -1
- package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.handler.js +5 -1
- package/dist/service/s3/command/copy-object/copy-object-precondition.d.ts +23 -0
- package/dist/service/s3/command/copy-object/copy-object-precondition.js +34 -0
- package/dist/service/s3/command/copy-object/copy-object.command.d.ts +3 -1
- package/dist/service/s3/command/copy-object/copy-object.handler.d.ts +4 -0
- package/dist/service/s3/command/copy-object/copy-object.handler.js +11 -1
- package/dist/service/s3/command/object/sim-s3-write-precondition.d.ts +34 -0
- package/dist/service/s3/command/object/sim-s3-write-precondition.js +47 -0
- package/dist/service/s3/command/put-object/put-object.command.d.ts +2 -1
- package/dist/service/s3/command/put-object/put-object.handler.js +5 -1
- package/dist/service/s3/error/sim-s3.error.d.ts +10 -0
- package/dist/service/s3/error/sim-s3.error.js +12 -0
- package/dist/service/s3/object/s3-object-listing.js +4 -3
- package/dist/service/s3/serve/api/sim-s3-api-input.d.ts +4 -0
- package/dist/service/s3/serve/api/sim-s3-api-input.js +10 -0
- package/dist/service/s3/serve/api/sim-s3-api-multipart-input.js +2 -0
- package/dist/service/s3/serve/api/sim-s3-api-object-input.d.ts +6 -0
- package/dist/service/s3/serve/api/sim-s3-api-object-input.js +34 -2
- package/dist/service/s3/storage/filesystem/s3-filesystem-object-keys.js +9 -8
- package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-template.test-support.js +15 -23
- package/dist/service/sqs/message/sim-sqs-message.js +3 -3
- package/dist/service/stepfunctions/machine/sim-state-machine.js +4 -3
- package/dist/util/process/sim-process-environment-reads.d.ts +52 -0
- package/dist/util/process/sim-process-environment-reads.js +94 -0
- package/dist/util/process/sim-process-environment.d.ts +32 -9
- package/dist/util/process/sim-process-environment.js +43 -17
- package/dist/util/schedule/cron/sim-cron-field-bounds.js +1 -1
- package/docs/services/lambda/README.md +6 -0
- package/docs/services/s3/README.md +84 -3
- package/package.json +3 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records the names code reads from the host process.env.
|
|
3
|
+
*
|
|
4
|
+
* SimLambdaEnvironmentConflicts warns that a module-scope read sees the host
|
|
5
|
+
* value of a variable instead of the function's own. Whether that matters
|
|
6
|
+
* depends on a read having happened at all, which configuration cannot tell.
|
|
7
|
+
* This keeps the evidence: the host environment is handed out behind a Proxy
|
|
8
|
+
* that notes each name read through it.
|
|
9
|
+
*
|
|
10
|
+
* Enumerating the whole environment is not counted as reading every name in
|
|
11
|
+
* it. Node.js copies process.env key by key when it spawns a child process,
|
|
12
|
+
* and so does any tool that snapshots it, and none of that is handler code
|
|
13
|
+
* capturing a value it later depends on. An enumeration is recognised by its
|
|
14
|
+
* shape: the property reads that follow a listing of the keys, in the order
|
|
15
|
+
* the listing gave them. Its reads are recorded as they happen and taken off
|
|
16
|
+
* the record again once every key has been read, so a listing that is not
|
|
17
|
+
* followed by a complete copy hides nothing. A module copying the environment
|
|
18
|
+
* with a spread at module scope is missed as a result.
|
|
19
|
+
*/
|
|
20
|
+
export class SimProcessEnvironmentReads {
|
|
21
|
+
names = new Set();
|
|
22
|
+
/**
|
|
23
|
+
* The environment objects each watching Proxy stands in front of, so that
|
|
24
|
+
* assigning one back to process.env does not stack a Proxy on a Proxy.
|
|
25
|
+
*/
|
|
26
|
+
targets = new WeakMap();
|
|
27
|
+
/**
|
|
28
|
+
* The keys of an enumeration in progress, and how far its reads have got.
|
|
29
|
+
*/
|
|
30
|
+
enumeration;
|
|
31
|
+
/**
|
|
32
|
+
* Whether code has read the name through a watched environment.
|
|
33
|
+
*/
|
|
34
|
+
wasRead(name) {
|
|
35
|
+
return this.names.has(name);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A view of the environment that records the names read through it.
|
|
39
|
+
*
|
|
40
|
+
* Writes and deletes go straight to the environment, so a value assigned
|
|
41
|
+
* through the view is coerced to a string as process.env would coerce it.
|
|
42
|
+
*/
|
|
43
|
+
watch(environment) {
|
|
44
|
+
const target = this.unwrap(environment);
|
|
45
|
+
const view = new Proxy(target, {
|
|
46
|
+
get: (watched, key) => {
|
|
47
|
+
if (typeof key === "string") {
|
|
48
|
+
this.recordRead(key);
|
|
49
|
+
}
|
|
50
|
+
return Reflect.get(watched, key);
|
|
51
|
+
},
|
|
52
|
+
set: (watched, key, value) => Reflect.set(watched, key, value),
|
|
53
|
+
ownKeys: (watched) => {
|
|
54
|
+
const keys = Reflect.ownKeys(watched);
|
|
55
|
+
this.enumeration = { keys, next: 0, added: [] };
|
|
56
|
+
return keys;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
this.targets.set(view, target);
|
|
60
|
+
return view;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The environment behind a watching view, or the environment itself when it
|
|
64
|
+
* is not one.
|
|
65
|
+
*/
|
|
66
|
+
unwrap(environment) {
|
|
67
|
+
return this.targets.get(environment) ?? environment;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Note a read, and take an enumeration's reads off the record once it has
|
|
71
|
+
* read every key.
|
|
72
|
+
*/
|
|
73
|
+
recordRead(name) {
|
|
74
|
+
const enumeration = this.enumeration;
|
|
75
|
+
if (enumeration === undefined ||
|
|
76
|
+
enumeration.keys[enumeration.next] !== name) {
|
|
77
|
+
this.enumeration = undefined;
|
|
78
|
+
this.names.add(name);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
enumeration.next += 1;
|
|
82
|
+
if (!this.names.has(name)) {
|
|
83
|
+
this.names.add(name);
|
|
84
|
+
enumeration.added.push(name);
|
|
85
|
+
}
|
|
86
|
+
if (enumeration.next < enumeration.keys.length) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const added of enumeration.added) {
|
|
90
|
+
this.names.delete(added);
|
|
91
|
+
}
|
|
92
|
+
this.enumeration = undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -20,19 +20,33 @@
|
|
|
20
20
|
*
|
|
21
21
|
* The one thing this cannot reach is a read that already happened. A handler
|
|
22
22
|
* module doing `const TABLE = process.env.TABLE_NAME` at module scope is
|
|
23
|
-
* evaluated when the test file imports it, long before any run,
|
|
23
|
+
* evaluated when the test file imports it, long before any run, and captures
|
|
24
24
|
* the host value. Reads inside the handler body see the run's own variables.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
*
|
|
26
|
+
* Outside a run, process.env is the host environment behind a
|
|
27
|
+
* SimProcessEnvironmentReads view, which records the names read. The patch is
|
|
28
|
+
* installed when this module loads, which is when Yulin is imported, so a
|
|
29
|
+
* module-scope read in a handler imported after Yulin is on record.
|
|
30
|
+
* SimLambdaEnvironmentConflicts warns from that record, and only for a name
|
|
31
|
+
* whose early read changed what the code saw. A read made before Yulin was
|
|
32
|
+
* imported is invisible to it.
|
|
27
33
|
*/
|
|
28
34
|
declare class SimProcessEnvironment {
|
|
29
35
|
private readonly storage;
|
|
36
|
+
private readonly reads;
|
|
30
37
|
private hostEnvironment;
|
|
31
|
-
private
|
|
38
|
+
private hostView;
|
|
39
|
+
constructor();
|
|
32
40
|
/**
|
|
33
41
|
* Run something with the given variables as its process.env.
|
|
34
42
|
*/
|
|
35
43
|
run<T>(variables: Record<string, string>, run: () => Promise<T>): Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether code read the name from process.env while no run was active.
|
|
46
|
+
*
|
|
47
|
+
* Reads made before this module loaded are not on record.
|
|
48
|
+
*/
|
|
49
|
+
wasReadOutsideRun(name: string): boolean;
|
|
36
50
|
/**
|
|
37
51
|
* The host process environment, ignoring any run in progress.
|
|
38
52
|
*/
|
|
@@ -51,14 +65,23 @@ declare class SimProcessEnvironment {
|
|
|
51
65
|
*
|
|
52
66
|
* process.env is a plain configurable data property on process, so it can
|
|
53
67
|
* be replaced with a getter. Outside a simulated run the getter hands back
|
|
54
|
-
* the
|
|
55
|
-
*
|
|
68
|
+
* the host environment behind a view that records the names read, and
|
|
69
|
+
* passes every read, write and delete through to it. Anything that reads,
|
|
70
|
+
* writes, or stubs process.env normally is unaffected.
|
|
56
71
|
*
|
|
57
|
-
* Installed once and never removed. Removing it would be
|
|
58
|
-
* another run is in flight, and there is nothing to gain
|
|
59
|
-
*
|
|
72
|
+
* Installed once, at construction, and never removed. Removing it would be
|
|
73
|
+
* unsafe while another run is in flight, and there is nothing to gain. With
|
|
74
|
+
* no run in progress, the patch passes everything through to the host
|
|
75
|
+
* environment.
|
|
60
76
|
*/
|
|
61
77
|
private install;
|
|
78
|
+
/**
|
|
79
|
+
* Make the environment the host environment, and watch reads of it.
|
|
80
|
+
*
|
|
81
|
+
* Code saving process.env and assigning it back hands over the watching
|
|
82
|
+
* view, which is unwrapped so that views do not nest.
|
|
83
|
+
*/
|
|
84
|
+
private useHostEnvironment;
|
|
62
85
|
/**
|
|
63
86
|
* Apply an assignment to process.env as a whole.
|
|
64
87
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { SimProcessEnvironmentReads } from "./sim-process-environment-reads.js";
|
|
2
3
|
/**
|
|
3
4
|
* The process.env in-process handler code sees while a simulated run lasts.
|
|
4
5
|
*
|
|
@@ -21,22 +22,39 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
21
22
|
*
|
|
22
23
|
* The one thing this cannot reach is a read that already happened. A handler
|
|
23
24
|
* module doing `const TABLE = process.env.TABLE_NAME` at module scope is
|
|
24
|
-
* evaluated when the test file imports it, long before any run,
|
|
25
|
+
* evaluated when the test file imports it, long before any run, and captures
|
|
25
26
|
* the host value. Reads inside the handler body see the run's own variables.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* Outside a run, process.env is the host environment behind a
|
|
29
|
+
* SimProcessEnvironmentReads view, which records the names read. The patch is
|
|
30
|
+
* installed when this module loads, which is when Yulin is imported, so a
|
|
31
|
+
* module-scope read in a handler imported after Yulin is on record.
|
|
32
|
+
* SimLambdaEnvironmentConflicts warns from that record, and only for a name
|
|
33
|
+
* whose early read changed what the code saw. A read made before Yulin was
|
|
34
|
+
* imported is invisible to it.
|
|
28
35
|
*/
|
|
29
36
|
class SimProcessEnvironment {
|
|
30
37
|
storage = new AsyncLocalStorage();
|
|
38
|
+
reads = new SimProcessEnvironmentReads();
|
|
31
39
|
hostEnvironment = process.env;
|
|
32
|
-
|
|
40
|
+
hostView = process.env;
|
|
41
|
+
constructor() {
|
|
42
|
+
this.install();
|
|
43
|
+
}
|
|
33
44
|
/**
|
|
34
45
|
* Run something with the given variables as its process.env.
|
|
35
46
|
*/
|
|
36
47
|
async run(variables, run) {
|
|
37
|
-
this.install();
|
|
38
48
|
return await this.storage.run(variables, run);
|
|
39
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether code read the name from process.env while no run was active.
|
|
52
|
+
*
|
|
53
|
+
* Reads made before this module loaded are not on record.
|
|
54
|
+
*/
|
|
55
|
+
wasReadOutsideRun(name) {
|
|
56
|
+
return this.reads.wasRead(name);
|
|
57
|
+
}
|
|
40
58
|
/**
|
|
41
59
|
* The host process environment, ignoring any run in progress.
|
|
42
60
|
*/
|
|
@@ -65,27 +83,35 @@ class SimProcessEnvironment {
|
|
|
65
83
|
*
|
|
66
84
|
* process.env is a plain configurable data property on process, so it can
|
|
67
85
|
* be replaced with a getter. Outside a simulated run the getter hands back
|
|
68
|
-
* the
|
|
69
|
-
*
|
|
86
|
+
* the host environment behind a view that records the names read, and
|
|
87
|
+
* passes every read, write and delete through to it. Anything that reads,
|
|
88
|
+
* writes, or stubs process.env normally is unaffected.
|
|
70
89
|
*
|
|
71
|
-
* Installed once and never removed. Removing it would be
|
|
72
|
-
* another run is in flight, and there is nothing to gain
|
|
73
|
-
*
|
|
90
|
+
* Installed once, at construction, and never removed. Removing it would be
|
|
91
|
+
* unsafe while another run is in flight, and there is nothing to gain. With
|
|
92
|
+
* no run in progress, the patch passes everything through to the host
|
|
93
|
+
* environment.
|
|
74
94
|
*/
|
|
75
95
|
install() {
|
|
76
|
-
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
this.hostEnvironment = process.env;
|
|
96
|
+
this.useHostEnvironment(process.env);
|
|
80
97
|
Object.defineProperty(process, "env", {
|
|
81
98
|
configurable: true,
|
|
82
99
|
enumerable: true,
|
|
83
|
-
get: () => this.storage.getStore() ?? this.
|
|
100
|
+
get: () => this.storage.getStore() ?? this.hostView,
|
|
84
101
|
set: (replacement) => {
|
|
85
102
|
this.replaceVariables(replacement);
|
|
86
103
|
},
|
|
87
104
|
});
|
|
88
|
-
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Make the environment the host environment, and watch reads of it.
|
|
108
|
+
*
|
|
109
|
+
* Code saving process.env and assigning it back hands over the watching
|
|
110
|
+
* view, which is unwrapped so that views do not nest.
|
|
111
|
+
*/
|
|
112
|
+
useHostEnvironment(environment) {
|
|
113
|
+
this.hostEnvironment = this.reads.unwrap(environment);
|
|
114
|
+
this.hostView = this.reads.watch(this.hostEnvironment);
|
|
89
115
|
}
|
|
90
116
|
/**
|
|
91
117
|
* Apply an assignment to process.env as a whole.
|
|
@@ -104,7 +130,7 @@ class SimProcessEnvironment {
|
|
|
104
130
|
replaceVariables(replacement) {
|
|
105
131
|
const store = this.storage.getStore();
|
|
106
132
|
if (store === undefined) {
|
|
107
|
-
this.
|
|
133
|
+
this.useHostEnvironment(replacement);
|
|
108
134
|
return;
|
|
109
135
|
}
|
|
110
136
|
const replacing = Object.entries(replacement);
|
|
@@ -3355,6 +3355,12 @@ console in the two situations where the difference changes what your code sees:
|
|
|
3355
3355
|
- a declared variable whose name the host process also sets, with a different value
|
|
3356
3356
|
- two simulated functions declaring the same variable name with different values
|
|
3357
3357
|
|
|
3358
|
+
Either one is reported only once code has read that name from `process.env` with no invocation
|
|
3359
|
+
running. Yulin records those reads from the moment it is imported. A read made earlier is invisible
|
|
3360
|
+
to it (a handler module imported ahead of Yulin, or a setup file that loads first), and so is a copy
|
|
3361
|
+
of the whole environment such as `{ ...process.env }`. Neither produces a warning. A handler that
|
|
3362
|
+
reads its variables inside the handler function produces none either.
|
|
3363
|
+
|
|
3358
3364
|
## The time inside a handler
|
|
3359
3365
|
|
|
3360
3366
|
A function runs on its simulation's clock, and so does the function code. `Date.now()` and
|
|
@@ -531,6 +531,74 @@ reports it there.
|
|
|
531
531
|
An Object uploaded in parts gets a different form. See
|
|
532
532
|
[Uploading an Object in parts](#uploading-an-object-in-parts).
|
|
533
533
|
|
|
534
|
+
## Conditional writes
|
|
535
|
+
|
|
536
|
+
`PutObject`, `CopyObject` and `CompleteMultipartUpload` take `IfNoneMatch` and `IfMatch`, which
|
|
537
|
+
state what the key has to hold before the write goes ahead. `IfNoneMatch: "*"` writes only to a key
|
|
538
|
+
that holds nothing. `IfMatch` writes only over an Object with that ETag. A write whose condition
|
|
539
|
+
fails is refused with `PreconditionFailed` (status `412`), and the Object already under the key is
|
|
540
|
+
left as it was.
|
|
541
|
+
|
|
542
|
+
```typescript sim-s3-conditional-write
|
|
543
|
+
/**
|
|
544
|
+
* Writing a simulated S3 Object only where the key is free.
|
|
545
|
+
*/
|
|
546
|
+
|
|
547
|
+
import {
|
|
548
|
+
CopyObjectCommand,
|
|
549
|
+
CreateBucketCommand,
|
|
550
|
+
PutObjectCommand,
|
|
551
|
+
} from "@aws-sdk/client-s3";
|
|
552
|
+
import { SimAws } from "@kensio/yulin";
|
|
553
|
+
|
|
554
|
+
const simAws = new SimAws();
|
|
555
|
+
const simS3 = simAws.s3();
|
|
556
|
+
|
|
557
|
+
await simS3.createBucket(new CreateBucketCommand({ Bucket: "audio-bucket" }));
|
|
558
|
+
|
|
559
|
+
await simS3.putObject(
|
|
560
|
+
new PutObjectCommand({
|
|
561
|
+
Bucket: "audio-bucket",
|
|
562
|
+
Key: "uploads/take-1.mp3",
|
|
563
|
+
Body: "first take",
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
568
|
+
try {
|
|
569
|
+
await simS3.copyObject(
|
|
570
|
+
new CopyObjectCommand({
|
|
571
|
+
Bucket: "audio-bucket",
|
|
572
|
+
Key: "shared/take.mp3",
|
|
573
|
+
CopySource: "audio-bucket/uploads/take-1.mp3",
|
|
574
|
+
IfNoneMatch: "*",
|
|
575
|
+
}),
|
|
576
|
+
);
|
|
577
|
+
console.log("copied");
|
|
578
|
+
} catch (error) {
|
|
579
|
+
// The second attempt finds the shared key taken.
|
|
580
|
+
if (error instanceof Error && error.name === "PreconditionFailed") {
|
|
581
|
+
console.log("already there");
|
|
582
|
+
} else {
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Writes to one key run one at a time. Of several conditional writes racing for an empty key, the first
|
|
590
|
+
to finish succeeds and the rest are refused. `IfMatch` on a key holding nothing answers `NoSuchKey`. On a versioned Bucket both conditions look at
|
|
591
|
+
the current version, and a delete marker counts as nothing. `IfNoneMatch` on a write accepts only
|
|
592
|
+
`*`, and real S3 refuses anything else with `NotImplemented`.
|
|
593
|
+
|
|
594
|
+
A copy also takes conditions on its source. `CopySourceIfMatch`, `CopySourceIfNoneMatch`,
|
|
595
|
+
`CopySourceIfModifiedSince` and `CopySourceIfUnmodifiedSince` refuse the copy with
|
|
596
|
+
`PreconditionFailed` when the source does not meet them. An ETag condition outranks the date
|
|
597
|
+
condition paired with it, as RFC 7232 has it. The served endpoint reads all of these from the same
|
|
598
|
+
headers real S3 does.
|
|
599
|
+
|
|
600
|
+
A failed completion leaves the upload in place, and it can be completed again.
|
|
601
|
+
|
|
534
602
|
## Uploading an Object in parts
|
|
535
603
|
|
|
536
604
|
Yulin supports multipart upload through the SDK and served S3 endpoint. This covers the operations
|
|
@@ -834,9 +902,6 @@ See [Serve simulated S3 on localhost](#serve-simulated-s3-on-localhost) for sett
|
|
|
834
902
|
- Both Buckets have to belong to the same simulated S3. A copy across Accounts or Regions is left
|
|
835
903
|
out.
|
|
836
904
|
- A presigned copy is left out, and so is a copy reaching a Bucket through simulated CloudFront.
|
|
837
|
-
- `CopySourceIfMatch`, `CopySourceIfNoneMatch`, `CopySourceIfModifiedSince` and
|
|
838
|
-
`CopySourceIfUnmodifiedSince` are ignored. A conditional copy happens whatever the condition
|
|
839
|
-
says.
|
|
840
905
|
- `ACL` is ignored. Sim S3 models no Object ACL.
|
|
841
906
|
- A `versionId` in `CopySource` is refused with `NotImplemented`.
|
|
842
907
|
- A copy of an Object that was uploaded in parts gets a plain ETag rather than the multipart form.
|
|
@@ -3252,6 +3317,22 @@ The `getBucketWebsiteUrl(...)` method returns the simulated S3 website URL for t
|
|
|
3252
3317
|
`localUrl(...)` method on the localhost server adapts that URL so the request is sent to the local
|
|
3253
3318
|
server while preserving the simulated S3 website hostname.
|
|
3254
3319
|
|
|
3320
|
+
### AWS CLI uploads
|
|
3321
|
+
|
|
3322
|
+
Set `preferred_transfer_client` to `classic` in the AWS config file used for the simulated
|
|
3323
|
+
endpoint:
|
|
3324
|
+
|
|
3325
|
+
```ini
|
|
3326
|
+
[default]
|
|
3327
|
+
s3 =
|
|
3328
|
+
preferred_transfer_client = classic
|
|
3329
|
+
```
|
|
3330
|
+
|
|
3331
|
+
The CLI's [automatic transfer-client selection](https://docs.aws.amazon.com/cli/latest/topic/s3-config.html#preferred-transfer-client)
|
|
3332
|
+
can choose CRT. Its uploads use streaming payload trailers, which Yulin's signature verifier
|
|
3333
|
+
does not support. Those requests fail with `403`. The classic client supports the single-file
|
|
3334
|
+
and multipart uploads exercised by Yulin's CLI tests.
|
|
3335
|
+
|
|
3255
3336
|
## Presigned URLs
|
|
3256
3337
|
|
|
3257
3338
|
Sim S3 serves a REST API endpoint alongside the website endpoint, and it accepts presigned URLs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kensio/yulin",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.33",
|
|
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": "^76.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.73.0",
|
|
303
303
|
"oxlint": "^1.77.0",
|
|
304
304
|
"oxlint-tsgolint": "^7.0.2001",
|
|
305
305
|
"semantic-release": "25.0.9",
|