@kensio/yulin 1.21.15 → 1.21.17

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 (58) hide show
  1. package/dist/sdk/router/sim-sdk-command-router.type.d.ts +9 -0
  2. package/dist/sdk/sim-sdk-command-dispatcher.js +1 -1
  3. package/dist/service/cloudformation/changeset/sim-cfn-change-set-changes.js +1 -1
  4. package/dist/service/cloudformation/resource/factory/sim-cfn-resource-factory.type.d.ts +25 -1
  5. package/dist/service/cloudformation/resource/resolve/service/sim-cfn-resource-service-factory.d.ts +18 -0
  6. package/dist/service/cloudformation/resource/resolve/service/sim-cfn-resource-service-factory.js +30 -0
  7. package/dist/service/cloudformation/resource/sim-cfn-resource.type.d.ts +18 -0
  8. package/dist/service/cloudformation/resource/update/sim-cfn-resource-in-place-updater.d.ts +35 -0
  9. package/dist/service/cloudformation/resource/update/sim-cfn-resource-in-place-updater.js +61 -0
  10. package/dist/service/cloudformation/resource/update/sim-cfn-resource-update-error.d.ts +9 -0
  11. package/dist/service/cloudformation/resource/update/sim-cfn-resource-update-error.js +14 -0
  12. package/dist/service/cloudformation/resource/update/sim-cfn-resource-update-validator.js +5 -18
  13. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +7 -2
  14. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +16 -12
  15. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-change-pairs.d.ts +12 -0
  16. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-change-pairs.js +18 -0
  17. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-changes.d.ts +52 -0
  18. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-changes.js +86 -0
  19. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-updates.d.ts +42 -0
  20. package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-updates.js +70 -0
  21. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.d.ts +25 -18
  22. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.js +43 -32
  23. package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.d.ts +7 -5
  24. package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.js +12 -7
  25. package/dist/service/dynamodb/document/sim-dynamodb-document-marshall-options.d.ts +42 -0
  26. package/dist/service/dynamodb/document/sim-dynamodb-document-marshall-options.js +36 -0
  27. package/dist/service/dynamodb/document/sim-dynamodb-document-marshall.d.ts +6 -5
  28. package/dist/service/dynamodb/document/sim-dynamodb-document-marshall.js +37 -43
  29. package/dist/service/dynamodb/document/sim-dynamodb-document-number.d.ts +4 -2
  30. package/dist/service/dynamodb/document/sim-dynamodb-document-number.js +5 -3
  31. package/dist/service/dynamodb/document/sim-dynamodb-document-path.d.ts +0 -4
  32. package/dist/service/dynamodb/document/sim-dynamodb-document-path.js +17 -10
  33. package/dist/service/dynamodb/document/sim-dynamodb-document-route.js +5 -1
  34. package/dist/service/dynamodb/document/sim-dynamodb-document-scalar.d.ts +13 -0
  35. package/dist/service/dynamodb/document/sim-dynamodb-document-scalar.js +46 -0
  36. package/dist/service/dynamodb/document/sim-dynamodb-document-set.d.ts +8 -3
  37. package/dist/service/dynamodb/document/sim-dynamodb-document-set.js +21 -14
  38. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-change.d.ts +19 -0
  39. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-change.js +39 -0
  40. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-deleter.d.ts +23 -0
  41. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-deleter.js +23 -0
  42. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-template.test-support.d.ts +21 -0
  43. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-template.test-support.js +64 -0
  44. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-update.test-support.d.ts +18 -0
  45. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-update.test-support.js +39 -0
  46. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-updater.d.ts +37 -0
  47. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-updater.js +66 -0
  48. package/dist/service/secretsmanager/cfn/sim-cfn-secrets-manager-resource-factory.d.ts +20 -10
  49. package/dist/service/secretsmanager/cfn/sim-cfn-secrets-manager-resource-factory.js +41 -27
  50. package/dist/service/secretsmanager/cfn/sim-cfn-secrets-manager-resource-type.d.ts +12 -0
  51. package/dist/service/secretsmanager/cfn/sim-cfn-secrets-manager-resource-type.js +22 -0
  52. package/docs/sdk/README.md +5 -4
  53. package/docs/services/cloudformation/README.md +21 -3
  54. package/docs/services/dynamodb/README.md +81 -2
  55. package/docs/services/secretsmanager/README.md +35 -1
  56. package/package.json +1 -1
  57. package/dist/service/cloudformation/stack/update/sim-cfn-stack-replaced-resources.d.ts +0 -21
  58. package/dist/service/cloudformation/stack/update/sim-cfn-stack-replaced-resources.js +0 -51
@@ -0,0 +1,70 @@
1
+ import { SimCfnResourceInPlaceUpdater } from "../../resource/update/sim-cfn-resource-in-place-updater.js";
2
+ import { simCfnResourceServiceFactory } from "../../resource/resolve/service/sim-cfn-resource-service-factory.js";
3
+ import { SimCfnResourceUpdateValidator } from "../../resource/update/sim-cfn-resource-update-validator.js";
4
+ /**
5
+ * The Resource changes a Stack update applies to the deployed Resources,
6
+ * rather than by replacing them.
7
+ *
8
+ * Which changes those are is the owning service's answer, given before the
9
+ * update touches anything. A service with no way to change a deployed Resource
10
+ * answers nothing, and every change to its Resources is a replacement.
11
+ */
12
+ export class SimCfnStackResourceUpdates {
13
+ simAws;
14
+ caller;
15
+ constructor(properties) {
16
+ this.simAws = properties.simAws;
17
+ this.caller = properties.caller;
18
+ }
19
+ /**
20
+ * Whether the service owning a Resource can apply this change to the
21
+ * deployed Resource.
22
+ *
23
+ * A Resource type no service simulates cannot be updated where it is. Its
24
+ * creation was skipped, so there is nothing deployed to change.
25
+ */
26
+ claimed(current, updated) {
27
+ if (updated.type === undefined || current.type !== updated.type) {
28
+ return false;
29
+ }
30
+ const service = simCfnResourceServiceFactory(this.simAws, updated);
31
+ // Both halves or neither. A service that can recognise a change it can
32
+ // apply but cannot apply it would leave the update with nothing to run.
33
+ if (service?.factory.updatesInPlace === undefined ||
34
+ service.factory.updateInPlace === undefined) {
35
+ return false;
36
+ }
37
+ return service.factory.updatesInPlace(service.resourceTypeName, current, updated);
38
+ }
39
+ /**
40
+ * Validate every Resource replacement before the update changes the Stack.
41
+ */
42
+ async assertAllowed(currentResources, updatedResources, replacements) {
43
+ await Promise.all(replacements.map(async ({ current, updated }) => {
44
+ await new SimCfnResourceUpdateValidator({
45
+ current,
46
+ updated,
47
+ }).assertAllowed({
48
+ simAws: this.simAws,
49
+ currentResources,
50
+ updatedResources,
51
+ caller: this.caller,
52
+ });
53
+ }));
54
+ }
55
+ /**
56
+ * Apply the given changes to the deployed Resources.
57
+ *
58
+ * Each one is independent of the others, since none of them creates or
59
+ * deletes anything another could be waiting on.
60
+ */
61
+ async apply(resources, updates) {
62
+ await Promise.all(updates.map(async ({ current, updated }) => {
63
+ await new SimCfnResourceInPlaceUpdater({ current, updated }).apply({
64
+ simAws: this.simAws,
65
+ resources,
66
+ caller: this.caller,
67
+ });
68
+ }));
69
+ }
70
+ }
@@ -1,28 +1,33 @@
1
1
  import type { SimCfnResource } from "../../resource/sim-cfn-resource.js";
2
+ import { SimCfnResourceRetention } from "../../resource/delete/sim-cfn-resource-retention.js";
3
+ import { type SimCfnStackInPlaceUpdates } from "./sim-cfn-stack-resource-changes.js";
4
+ import { type SimCfnStackResourceChange } from "./sim-cfn-stack-resource-change-pairs.js";
2
5
  interface SimCfnStackUpdatePlanProperties {
3
6
  readonly current: ReadonlyMap<string, SimCfnResource>;
4
7
  readonly updated: ReadonlyMap<string, SimCfnResource>;
5
- }
6
- export interface SimCfnStackResourceReplacement {
7
- readonly current: SimCfnResource;
8
- readonly updated: SimCfnResource;
8
+ readonly inPlaceUpdates?: SimCfnStackInPlaceUpdates | undefined;
9
9
  }
10
10
  /**
11
11
  * What an update has to do to a Stack's Resources, worked out before any of it
12
12
  * happens.
13
13
  *
14
14
  * The plan compares the Resources the Stack has with the ones its new template
15
- * describes, and says which to delete, which to create, and what the Stack
16
- * holds afterwards. A Resource the template still describes unchanged is left
17
- * alone, keeping whatever it holds in simulated AWS.
15
+ * describes, and says which to update where they are, which to delete, which to
16
+ * create, and what the Stack holds afterwards. A Resource the template still
17
+ * describes unchanged is left alone, keeping whatever it holds in simulated
18
+ * AWS.
18
19
  *
19
- * It does not delete or create anything, order the work, or decide what counts
20
- * as a changed Resource. SimCfnStackUpdater runs the plan, and
21
- * simCfnStackReplacedLogicalIds decides what changed.
20
+ * It does not delete, create or update anything, order the work, or decide what
21
+ * counts as a changed Resource. SimCfnStackUpdater runs the plan, and
22
+ * simCfnStackResourceChanges decides what changed and how.
22
23
  */
23
24
  export declare class SimCfnStackUpdatePlan {
24
25
  /** The current and updated halves of each Resource replacement. */
25
- readonly replacements: readonly SimCfnStackResourceReplacement[];
26
+ readonly replacements: readonly SimCfnStackResourceChange[];
27
+ /**
28
+ * The Resources to change where they are, rather than by replacing them.
29
+ */
30
+ readonly updates: readonly SimCfnStackResourceChange[];
26
31
  /**
27
32
  * The deployed Resources to delete: the ones the new template drops, and the
28
33
  * deployed halves of the ones it replaces.
@@ -36,14 +41,14 @@ export declare class SimCfnStackUpdatePlan {
36
41
  readonly updated: ReadonlyMap<string, SimCfnResource>;
37
42
  constructor(properties: SimCfnStackUpdatePlanProperties);
38
43
  /**
39
- * For each logical ID the update is replacing, whether the definition taking
40
- * its place says to keep the deployed Resource.
44
+ * What the update's deletions are to leave in simulated AWS.
41
45
  *
42
- * The new half answers it because CloudFormation reads UpdateReplacePolicy
43
- * off the template it is applying. An update that adds the attribute keeps
44
- * the Resource it replaces, and one that drops it deletes that Resource.
46
+ * A replacement's new half answers for it, because CloudFormation reads
47
+ * UpdateReplacePolicy off the template it is applying. An update that adds
48
+ * the attribute keeps the Resource it replaces, and one that drops it
49
+ * deletes that Resource.
45
50
  */
46
- replacementRetentions(): ReadonlyMap<string, boolean>;
51
+ retention(): SimCfnResourceRetention;
47
52
  /**
48
53
  * Whether the update changes any Resource at all.
49
54
  *
@@ -57,7 +62,9 @@ export declare class SimCfnStackUpdatePlan {
57
62
  * The map is changed in place rather than replaced, so anything already
58
63
  * holding the Stack's Resources sees the update. A Resource the template
59
64
  * leaves alone keeps the object it already had, which is what keeps its
60
- * simulated AWS Resource and its creation status.
65
+ * simulated AWS Resource and its creation status. A Resource updated where it
66
+ * is takes the new record, which the update has already moved on to the
67
+ * deployed simulated AWS Resource.
61
68
  */
62
69
  applyTo(resources: Map<string, SimCfnResource>): void;
63
70
  }
@@ -1,20 +1,27 @@
1
- import { simCfnStackReplacedLogicalIds } from "./sim-cfn-stack-replaced-resources.js";
1
+ import { SimCfnResourceRetention } from "../../resource/delete/sim-cfn-resource-retention.js";
2
+ import { simCfnStackResourceChanges, } from "./sim-cfn-stack-resource-changes.js";
3
+ import { simCfnStackResourceChangePairs, } from "./sim-cfn-stack-resource-change-pairs.js";
2
4
  /**
3
5
  * What an update has to do to a Stack's Resources, worked out before any of it
4
6
  * happens.
5
7
  *
6
8
  * The plan compares the Resources the Stack has with the ones its new template
7
- * describes, and says which to delete, which to create, and what the Stack
8
- * holds afterwards. A Resource the template still describes unchanged is left
9
- * alone, keeping whatever it holds in simulated AWS.
9
+ * describes, and says which to update where they are, which to delete, which to
10
+ * create, and what the Stack holds afterwards. A Resource the template still
11
+ * describes unchanged is left alone, keeping whatever it holds in simulated
12
+ * AWS.
10
13
  *
11
- * It does not delete or create anything, order the work, or decide what counts
12
- * as a changed Resource. SimCfnStackUpdater runs the plan, and
13
- * simCfnStackReplacedLogicalIds decides what changed.
14
+ * It does not delete, create or update anything, order the work, or decide what
15
+ * counts as a changed Resource. SimCfnStackUpdater runs the plan, and
16
+ * simCfnStackResourceChanges decides what changed and how.
14
17
  */
15
18
  export class SimCfnStackUpdatePlan {
16
19
  /** The current and updated halves of each Resource replacement. */
17
20
  replacements;
21
+ /**
22
+ * The Resources to change where they are, rather than by replacing them.
23
+ */
24
+ updates;
18
25
  /**
19
26
  * The deployed Resources to delete: the ones the new template drops, and the
20
27
  * deployed halves of the ones it replaces.
@@ -27,20 +34,15 @@ export class SimCfnStackUpdatePlan {
27
34
  creations;
28
35
  updated;
29
36
  constructor(properties) {
30
- const { current, updated } = properties;
31
- const replaced = simCfnStackReplacedLogicalIds({ current, updated });
37
+ const { current, updated, inPlaceUpdates } = properties;
38
+ const { replaced, updatedInPlace } = simCfnStackResourceChanges({
39
+ current,
40
+ updated,
41
+ inPlaceUpdates,
42
+ });
32
43
  this.updated = updated;
33
- this.replacements = replaced
34
- .values()
35
- .map((logicalId) => {
36
- const currentResource = current.get(logicalId);
37
- const updatedResource = updated.get(logicalId);
38
- if (currentResource === undefined || updatedResource === undefined) {
39
- throw new Error(`CloudFormation replacement ${logicalId} is missing one of its Resource definitions`);
40
- }
41
- return { current: currentResource, updated: updatedResource };
42
- })
43
- .toArray();
44
+ this.replacements = simCfnStackResourceChangePairs(current, updated, replaced);
45
+ this.updates = simCfnStackResourceChangePairs(current, updated, updatedInPlace);
44
46
  // Both lists are worked out now rather than on demand, because the Stack's
45
47
  // Resource map is the current one and applying the plan changes it.
46
48
  this.deletions = current
@@ -57,18 +59,20 @@ export class SimCfnStackUpdatePlan {
57
59
  .toArray();
58
60
  }
59
61
  /**
60
- * For each logical ID the update is replacing, whether the definition taking
61
- * its place says to keep the deployed Resource.
62
+ * What the update's deletions are to leave in simulated AWS.
62
63
  *
63
- * The new half answers it because CloudFormation reads UpdateReplacePolicy
64
- * off the template it is applying. An update that adds the attribute keeps
65
- * the Resource it replaces, and one that drops it deletes that Resource.
64
+ * A replacement's new half answers for it, because CloudFormation reads
65
+ * UpdateReplacePolicy off the template it is applying. An update that adds
66
+ * the attribute keeps the Resource it replaces, and one that drops it
67
+ * deletes that Resource.
66
68
  */
67
- replacementRetentions() {
68
- return new Map(this.replacements.map(({ current, updated }) => [
69
- current.logicalId,
70
- updated.retainedOnReplace,
71
- ]));
69
+ retention() {
70
+ return new SimCfnResourceRetention({
71
+ replaced: new Map(this.replacements.map(({ current, updated }) => [
72
+ current.logicalId,
73
+ updated.retainedOnReplace,
74
+ ])),
75
+ });
72
76
  }
73
77
  /**
74
78
  * Whether the update changes any Resource at all.
@@ -77,7 +81,9 @@ export class SimCfnStackUpdatePlan {
77
81
  * updates the Stack, so this is not the whole no-op question.
78
82
  */
79
83
  get changesResources() {
80
- return this.deletions.length > 0 || this.creations.length > 0;
84
+ return (this.deletions.length > 0 ||
85
+ this.creations.length > 0 ||
86
+ this.updates.length > 0);
81
87
  }
82
88
  /**
83
89
  * Move the Stack's Resource map on to the new template.
@@ -85,7 +91,9 @@ export class SimCfnStackUpdatePlan {
85
91
  * The map is changed in place rather than replaced, so anything already
86
92
  * holding the Stack's Resources sees the update. A Resource the template
87
93
  * leaves alone keeps the object it already had, which is what keeps its
88
- * simulated AWS Resource and its creation status.
94
+ * simulated AWS Resource and its creation status. A Resource updated where it
95
+ * is takes the new record, which the update has already moved on to the
96
+ * deployed simulated AWS Resource.
89
97
  */
90
98
  applyTo(resources) {
91
99
  const dropped = resources
@@ -98,5 +106,8 @@ export class SimCfnStackUpdatePlan {
98
106
  for (const resource of this.creations) {
99
107
  resources.set(resource.logicalId, resource);
100
108
  }
109
+ for (const { updated } of this.updates) {
110
+ resources.set(updated.logicalId, updated);
111
+ }
101
112
  }
102
113
  }
@@ -22,11 +22,13 @@ interface SimCfnStackUpdaterProperties {
22
22
  /**
23
23
  * Applies a changed template to a Stack that is already deployed.
24
24
  *
25
- * The Resources the new template drops or replaces go first, in the reverse of
26
- * the order they were created in, and the ones it adds or replaces are created
27
- * after, in dependency order. Everything else is left where it is, which is the
28
- * point of updating a Stack rather than replacing it: what a Bucket holds and
29
- * what a Table has in it survive a change elsewhere in the template.
25
+ * The Resources a service can change where they are go first, while everything
26
+ * the Stack had is still deployed. Then the Resources the new template drops or
27
+ * replaces, in the reverse of the order they were created in, and last the ones
28
+ * it adds or replaces, in dependency order. Everything else is left where it
29
+ * is, which is the point of updating a Stack rather than replacing it: what a
30
+ * Bucket holds and what a Table has in it survive a change elsewhere in the
31
+ * template.
30
32
  *
31
33
  * It does not own Stack status or the Stack's visible template.
32
34
  * SimCfnStackUpdateLifecycle owns the first and SimCfnStack the second.
@@ -2,7 +2,6 @@ import { SimCloudFormationValidationError } from "../../error/sim-cloudformation
2
2
  import { makeSimCfnStackResourceMap } from "../resource-map/sim-cfn-stack-resource-map.js";
3
3
  import { SimCfnStackUpdatePlan } from "./sim-cfn-stack-update-plan.js";
4
4
  import { simCfnStackTemplateChanged } from "./sim-cfn-stack-template-changes.js";
5
- import { SimCfnResourceRetention } from "../../resource/delete/sim-cfn-resource-retention.js";
6
5
  /**
7
6
  * What CloudFormation answers an update that would change nothing.
8
7
  *
@@ -14,11 +13,13 @@ export const simCfnNoUpdatesMessage = "No updates are to be performed.";
14
13
  /**
15
14
  * Applies a changed template to a Stack that is already deployed.
16
15
  *
17
- * The Resources the new template drops or replaces go first, in the reverse of
18
- * the order they were created in, and the ones it adds or replaces are created
19
- * after, in dependency order. Everything else is left where it is, which is the
20
- * point of updating a Stack rather than replacing it: what a Bucket holds and
21
- * what a Table has in it survive a change elsewhere in the template.
16
+ * The Resources a service can change where they are go first, while everything
17
+ * the Stack had is still deployed. Then the Resources the new template drops or
18
+ * replaces, in the reverse of the order they were created in, and last the ones
19
+ * it adds or replaces, in dependency order. Everything else is left where it
20
+ * is, which is the point of updating a Stack rather than replacing it: what a
21
+ * Bucket holds and what a Table has in it survive a change elsewhere in the
22
+ * template.
22
23
  *
23
24
  * It does not own Stack status or the Stack's visible template.
24
25
  * SimCfnStackUpdateLifecycle owns the first and SimCfnStack the second.
@@ -36,6 +37,7 @@ export class SimCfnStackUpdater {
36
37
  background,
37
38
  template: updated,
38
39
  }),
40
+ inPlaceUpdates: properties.operations,
39
41
  });
40
42
  }
41
43
  /**
@@ -59,7 +61,10 @@ export class SimCfnStackUpdater {
59
61
  const { resources, operations } = this.properties;
60
62
  const { plan } = this;
61
63
  await operations.assertUpdatesAllowed(resources, plan.updated, plan.replacements);
62
- await operations.delete(resources, plan.deletions, new SimCfnResourceRetention({ replaced: plan.replacementRetentions() }));
64
+ // Before anything comes down. An update that fails here has changed some
65
+ // of the Stack's Resources and deleted none of them.
66
+ await operations.updateInPlace(resources, plan.updates);
67
+ await operations.delete(resources, plan.deletions, plan.retention());
63
68
  plan.applyTo(resources);
64
69
  // Once the Stack has stopped holding them, and before the replacements are
65
70
  // created. An update that keeps a Resource and then fails to create the one
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The marshalling options a document client was built with.
3
+ *
4
+ * `DynamoDBDocumentClient.from(client, { marshallOptions })` changes what the
5
+ * real conversion does with values it would otherwise refuse, so the simulated
6
+ * conversion reads the same options rather than always applying the defaults.
7
+ * Every one of them is off unless the client asked for it, which is how the
8
+ * real document client leaves them.
9
+ */
10
+ export interface SimDynamoDbDocumentMarshallOptions {
11
+ /**
12
+ * Drop an undefined value out of a map, a list or a set instead of refusing
13
+ * it.
14
+ */
15
+ readonly removeUndefinedValues: boolean;
16
+ /**
17
+ * Write an empty string, an empty binary value and an empty set as NULL.
18
+ */
19
+ readonly convertEmptyValues: boolean;
20
+ /**
21
+ * Read a class instance as a map of its own properties.
22
+ */
23
+ readonly convertClassInstanceToMap: boolean;
24
+ /**
25
+ * Write a number outside the safe integer range, digits already lost, rather
26
+ * than refusing it.
27
+ */
28
+ readonly allowImpreciseNumbers: boolean;
29
+ }
30
+ /**
31
+ * What a document client built with no options of its own converts by.
32
+ */
33
+ export declare const simDynamoDbDocumentMarshallDefaults: SimDynamoDbDocumentMarshallOptions;
34
+ /**
35
+ * Read the marshalling options off the client a Command was sent through.
36
+ *
37
+ * `DynamoDBDocumentClient` keeps the translation config it was built with on
38
+ * its resolved config, which is where the real marshalling middleware reads it
39
+ * from. A client that named none, and anything that is not a document client
40
+ * at all, converts by the defaults.
41
+ */
42
+ export declare function simDynamoDbDocumentMarshallOptions(client: unknown): SimDynamoDbDocumentMarshallOptions;
@@ -0,0 +1,36 @@
1
+ import { isRecord } from "../../../util/type-guard/record.js";
2
+ /**
3
+ * What a document client built with no options of its own converts by.
4
+ */
5
+ export const simDynamoDbDocumentMarshallDefaults = {
6
+ removeUndefinedValues: false,
7
+ convertEmptyValues: false,
8
+ convertClassInstanceToMap: false,
9
+ allowImpreciseNumbers: false,
10
+ };
11
+ /**
12
+ * Read the marshalling options off the client a Command was sent through.
13
+ *
14
+ * `DynamoDBDocumentClient` keeps the translation config it was built with on
15
+ * its resolved config, which is where the real marshalling middleware reads it
16
+ * from. A client that named none, and anything that is not a document client
17
+ * at all, converts by the defaults.
18
+ */
19
+ export function simDynamoDbDocumentMarshallOptions(client) {
20
+ const config = isRecord(client) ? client["config"] : undefined;
21
+ const translateConfig = isRecord(config)
22
+ ? config["translateConfig"]
23
+ : undefined;
24
+ const options = isRecord(translateConfig)
25
+ ? translateConfig["marshallOptions"]
26
+ : undefined;
27
+ if (!isRecord(options)) {
28
+ return simDynamoDbDocumentMarshallDefaults;
29
+ }
30
+ return {
31
+ removeUndefinedValues: options["removeUndefinedValues"] === true,
32
+ convertEmptyValues: options["convertEmptyValues"] === true,
33
+ convertClassInstanceToMap: options["convertClassInstanceToMap"] === true,
34
+ allowImpreciseNumbers: options["allowImpreciseNumbers"] === true,
35
+ };
36
+ }
@@ -1,4 +1,5 @@
1
1
  import type { SimDynamoDbAttributeValue } from "../command/item/item.types.js";
2
+ import type { SimDynamoDbDocumentMarshallOptions } from "./sim-dynamodb-document-marshall-options.js";
2
3
  /**
3
4
  * Read a native JavaScript value as the AttributeValue it stands for.
4
5
  *
@@ -7,9 +8,9 @@ import type { SimDynamoDbAttributeValue } from "../command/item/item.types.js";
7
8
  * order, so a value that reaches a simulated table through the document client
8
9
  * is the value that would have reached the real one.
9
10
  *
10
- * `undefined` is refused rather than dropped. The real document client drops it
11
- * when the client was built with `removeUndefinedValues`, which is a translate
12
- * config this simulation does not read yet, so refusing is what keeps a test
13
- * from passing against an item AWS would have written differently.
11
+ * The options are the ones the document client was built with. They decide
12
+ * what happens to a value the defaults refuse: an undefined member, an empty
13
+ * string or binary value, a class instance, and a number past the range a
14
+ * JavaScript number holds exactly.
14
15
  */
15
- export declare function simDynamoDbDocumentAttributeValue(value: unknown, path: string): SimDynamoDbAttributeValue;
16
+ export declare function simDynamoDbDocumentAttributeValue(value: unknown, path: string, options: SimDynamoDbDocumentMarshallOptions): SimDynamoDbAttributeValue;
@@ -1,6 +1,5 @@
1
1
  import { SimDynamoDbDocumentValueError } from "../error/dynamodb.error.js";
2
- import { isSimDynamoDbDocumentBinary } from "./sim-dynamodb-document-binary.js";
3
- import { isSimDynamoDbDocumentNumberValue, simDynamoDbDocumentNumberAttribute, } from "./sim-dynamodb-document-number.js";
2
+ import { simDynamoDbDocumentScalarAttribute } from "./sim-dynamodb-document-scalar.js";
4
3
  import { simDynamoDbDocumentSetAttribute } from "./sim-dynamodb-document-set.js";
5
4
  /**
6
5
  * Read a native JavaScript value as the AttributeValue it stands for.
@@ -10,61 +9,48 @@ import { simDynamoDbDocumentSetAttribute } from "./sim-dynamodb-document-set.js"
10
9
  * order, so a value that reaches a simulated table through the document client
11
10
  * is the value that would have reached the real one.
12
11
  *
13
- * `undefined` is refused rather than dropped. The real document client drops it
14
- * when the client was built with `removeUndefinedValues`, which is a translate
15
- * config this simulation does not read yet, so refusing is what keeps a test
16
- * from passing against an item AWS would have written differently.
12
+ * The options are the ones the document client was built with. They decide
13
+ * what happens to a value the defaults refuse: an undefined member, an empty
14
+ * string or binary value, a class instance, and a number past the range a
15
+ * JavaScript number holds exactly.
17
16
  */
18
- export function simDynamoDbDocumentAttributeValue(value, path) {
17
+ export function simDynamoDbDocumentAttributeValue(value, path, options) {
19
18
  if (value === undefined) {
20
- throw new SimDynamoDbDocumentValueError(`${path} is undefined. The real document client drops it only when it ` +
21
- `was built with removeUndefinedValues, which simulated DynamoDB does ` +
22
- `not read yet, so leave the attribute out instead`);
19
+ throw new SimDynamoDbDocumentValueError(`${path} is undefined. Build the document client with ` +
20
+ `removeUndefinedValues to drop it, which is what the real one asks ` +
21
+ `for, or leave the attribute out instead`);
23
22
  }
24
23
  if (value === null) {
25
24
  return { NULL: true };
26
25
  }
27
26
  if (Array.isArray(value)) {
28
- return { L: listMembers(value, path) };
27
+ return { L: listMembers(value, path, options) };
29
28
  }
30
- return containerOrScalar(value, path);
29
+ return containerOrScalar(value, path, options);
31
30
  }
32
31
  /**
33
32
  * Read a value that is not null, undefined or a list.
34
33
  */
35
- function containerOrScalar(value, path) {
34
+ function containerOrScalar(value, path, options) {
36
35
  if (value instanceof Set) {
37
- return simDynamoDbDocumentSetAttribute(value, path);
36
+ return simDynamoDbDocumentSetAttribute(value, path, options);
38
37
  }
39
38
  if (value instanceof Map) {
40
- return { M: mapEntries([...value], path) };
39
+ return { M: mapEntries([...value], path, options) };
41
40
  }
42
41
  if (isPlainObject(value)) {
43
- return { M: mapEntries(Object.entries(value), path) };
42
+ return { M: mapEntries(Object.entries(value), path, options) };
44
43
  }
45
- return scalar(value, path);
46
- }
47
- /**
48
- * Read a value that stands for one attribute on its own.
49
- */
50
- function scalar(value, path) {
51
- if (isSimDynamoDbDocumentBinary(value)) {
52
- return { B: value };
53
- }
54
- if (typeof value === "boolean") {
55
- return { BOOL: value };
56
- }
57
- if (typeof value === "number") {
58
- return simDynamoDbDocumentNumberAttribute(value, path);
44
+ const scalar = simDynamoDbDocumentScalarAttribute(value, path, options);
45
+ if (scalar !== undefined) {
46
+ return scalar;
59
47
  }
60
- if (isSimDynamoDbDocumentNumberValue(value)) {
61
- return { N: value.toAttributeValue().N };
62
- }
63
- if (typeof value === "bigint") {
64
- return { N: value.toString() };
65
- }
66
- if (typeof value === "string") {
67
- return { S: value };
48
+ // A class instance is read as a map only when the client asked for it, which
49
+ // is the last thing the real conversion tries before giving up. Null reached
50
+ // an answer of its own before any of this.
51
+ if (typeof value === "object" && options.convertClassInstanceToMap) {
52
+ const instance = value;
53
+ return { M: mapEntries(Object.entries(instance), path, options) };
68
54
  }
69
55
  throw new SimDynamoDbDocumentValueError(`${path} is a ${typeof value} the document client has no attribute type ` +
70
56
  `for`);
@@ -72,27 +58,35 @@ function scalar(value, path) {
72
58
  /**
73
59
  * The members of a list, with the functions left out as the real one leaves
74
60
  * them out.
61
+ *
62
+ * A dropped undefined member takes its position with it, so the members after
63
+ * it move up. That is what the real conversion does: it filters before it
64
+ * converts, rather than writing a NULL where the member was.
75
65
  */
76
- function listMembers(values, path) {
66
+ function listMembers(values, path, options) {
77
67
  return values
78
- .filter((member) => typeof member !== "function")
79
- .map((member, index) => simDynamoDbDocumentAttributeValue(member, `${path}[${index.toString()}]`));
68
+ .filter((member) => typeof member !== "function" &&
69
+ !(member === undefined && options.removeUndefinedValues))
70
+ .map((member, index) => simDynamoDbDocumentAttributeValue(member, `${path}[${index.toString()}]`, options));
80
71
  }
81
72
  /**
82
73
  * The entries of a map, with the functions left out.
83
74
  */
84
- function mapEntries(entries, path) {
75
+ function mapEntries(entries, path, options) {
85
76
  const attributes = {};
86
77
  for (const [name, member] of entries) {
87
78
  if (typeof member === "function") {
88
79
  continue;
89
80
  }
81
+ if (member === undefined && options.removeUndefinedValues) {
82
+ continue;
83
+ }
90
84
  const key = String(name);
91
85
  // Defined rather than assigned, so an attribute named `__proto__` becomes
92
86
  // an ordinary attribute instead of reaching the prototype setter. The real
93
87
  // document client assigns, and so loses that attribute.
94
88
  Object.defineProperty(attributes, key, {
95
- value: simDynamoDbDocumentAttributeValue(member, `${path}.${key}`),
89
+ value: simDynamoDbDocumentAttributeValue(member, `${path}.${key}`, options),
96
90
  enumerable: true,
97
91
  writable: true,
98
92
  configurable: true,
@@ -1,4 +1,5 @@
1
1
  import type { SimDynamoDbAttributeValue } from "../command/item/item.types.js";
2
+ import type { SimDynamoDbDocumentMarshallOptions } from "./sim-dynamodb-document-marshall-options.js";
2
3
  /**
3
4
  * A value carrying its own Number attribute, which is what lib-dynamodb's
4
5
  * `NumberValue` is.
@@ -23,9 +24,10 @@ export declare function isSimDynamoDbDocumentNumberValue(value: unknown): value
23
24
  * than storing one that has already lost digits. A simulated table holds the
24
25
  * digits it is given exactly, so this refusal is the only thing standing
25
26
  * between an application and a silently rounded identifier, which is why it is
26
- * kept rather than relaxed. A decimal inside the range is written as it stands.
27
+ * kept until a client asks for `allowImpreciseNumbers` and takes the rounding
28
+ * on. A decimal inside the range is written as it stands.
27
29
  */
28
- export declare function simDynamoDbDocumentNumberAttribute(value: number, path: string): SimDynamoDbAttributeValue;
30
+ export declare function simDynamoDbDocumentNumberAttribute(value: number, path: string, options: SimDynamoDbDocumentMarshallOptions): SimDynamoDbAttributeValue;
29
31
  /**
30
32
  * Read a Number attribute back as the document client answers with it.
31
33
  *
@@ -15,13 +15,15 @@ export function isSimDynamoDbDocumentNumberValue(value) {
15
15
  * than storing one that has already lost digits. A simulated table holds the
16
16
  * digits it is given exactly, so this refusal is the only thing standing
17
17
  * between an application and a silently rounded identifier, which is why it is
18
- * kept rather than relaxed. A decimal inside the range is written as it stands.
18
+ * kept until a client asks for `allowImpreciseNumbers` and takes the rounding
19
+ * on. A decimal inside the range is written as it stands.
19
20
  */
20
- export function simDynamoDbDocumentNumberAttribute(value, path) {
21
+ export function simDynamoDbDocumentNumberAttribute(value, path, options) {
21
22
  if (!Number.isFinite(value)) {
22
23
  throw new SimDynamoDbDocumentValueError(`${path} is ${value.toString()}, and DynamoDB has no such number`);
23
24
  }
24
- if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
25
+ if (!options.allowImpreciseNumbers &&
26
+ (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER)) {
25
27
  throw new SimDynamoDbDocumentValueError(`${path} is ${value.toString()}, which is outside the range a ` +
26
28
  `JavaScript number holds exactly. Write it as a bigint, or as a ` +
27
29
  `NumberValue from @aws-sdk/lib-dynamodb, so its digits survive`);
@@ -16,10 +16,6 @@ export interface SimDynamoDbDocumentPath {
16
16
  */
17
17
  convert(value: unknown, conversion: SimDynamoDbDocumentConversion, path: string): unknown;
18
18
  }
19
- /**
20
- * A path to one attribute value.
21
- */
22
- export declare function simDynamoDbDocumentValue(): SimDynamoDbDocumentPath;
23
19
  /**
24
20
  * A path to a record or list whose every member is one attribute value, which
25
21
  * is what an Item, a Key and a set of expression values are.