local-serverless-stack 0.6.0 → 0.8.0

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 (215) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +99 -11
  3. package/bin/cli.js +31 -3
  4. package/dist/server/engine/backends/localstack-backend.d.ts +25 -0
  5. package/dist/server/engine/backends/localstack-backend.d.ts.map +1 -0
  6. package/dist/server/engine/backends/localstack-backend.js +199 -0
  7. package/dist/server/engine/backends/localstack-backend.js.map +1 -0
  8. package/dist/server/engine/backends/self-backend.d.ts +32 -0
  9. package/dist/server/engine/backends/self-backend.d.ts.map +1 -0
  10. package/dist/server/engine/backends/self-backend.js +205 -0
  11. package/dist/server/engine/backends/self-backend.js.map +1 -0
  12. package/dist/server/engine/bus.d.ts +10 -0
  13. package/dist/server/engine/bus.d.ts.map +1 -0
  14. package/dist/server/engine/bus.js +24 -0
  15. package/dist/server/engine/bus.js.map +1 -0
  16. package/dist/server/engine/dispatch/dispatcher.d.ts +46 -0
  17. package/dist/server/engine/dispatch/dispatcher.d.ts.map +1 -0
  18. package/dist/server/engine/dispatch/dispatcher.js +312 -0
  19. package/dist/server/engine/dispatch/dispatcher.js.map +1 -0
  20. package/dist/server/engine/dispatch/scheduler.d.ts +47 -0
  21. package/dist/server/engine/dispatch/scheduler.d.ts.map +1 -0
  22. package/dist/server/engine/dispatch/scheduler.js +328 -0
  23. package/dist/server/engine/dispatch/scheduler.js.map +1 -0
  24. package/dist/server/engine/dispatch/sqs-poller.d.ts +25 -0
  25. package/dist/server/engine/dispatch/sqs-poller.d.ts.map +1 -0
  26. package/dist/server/engine/dispatch/sqs-poller.js +242 -0
  27. package/dist/server/engine/dispatch/sqs-poller.js.map +1 -0
  28. package/dist/server/engine/dispatch/stream-tailer.d.ts +22 -0
  29. package/dist/server/engine/dispatch/stream-tailer.d.ts.map +1 -0
  30. package/dist/server/engine/dispatch/stream-tailer.js +205 -0
  31. package/dist/server/engine/dispatch/stream-tailer.js.map +1 -0
  32. package/dist/server/engine/emulators/dynamodb/expressions/decimal.d.ts +5 -0
  33. package/dist/server/engine/emulators/dynamodb/expressions/decimal.d.ts.map +1 -0
  34. package/dist/server/engine/emulators/dynamodb/expressions/decimal.js +91 -0
  35. package/dist/server/engine/emulators/dynamodb/expressions/decimal.js.map +1 -0
  36. package/dist/server/engine/emulators/dynamodb/expressions/evaluate.d.ts +24 -0
  37. package/dist/server/engine/emulators/dynamodb/expressions/evaluate.d.ts.map +1 -0
  38. package/dist/server/engine/emulators/dynamodb/expressions/evaluate.js +183 -0
  39. package/dist/server/engine/emulators/dynamodb/expressions/evaluate.js.map +1 -0
  40. package/dist/server/engine/emulators/dynamodb/expressions/expression-types.d.ts +29 -0
  41. package/dist/server/engine/emulators/dynamodb/expressions/expression-types.d.ts.map +1 -0
  42. package/dist/server/engine/emulators/dynamodb/expressions/expression-types.js +12 -0
  43. package/dist/server/engine/emulators/dynamodb/expressions/expression-types.js.map +1 -0
  44. package/dist/server/engine/emulators/dynamodb/expressions/index.d.ts +13 -0
  45. package/dist/server/engine/emulators/dynamodb/expressions/index.d.ts.map +1 -0
  46. package/dist/server/engine/emulators/dynamodb/expressions/index.js +244 -0
  47. package/dist/server/engine/emulators/dynamodb/expressions/index.js.map +1 -0
  48. package/dist/server/engine/emulators/dynamodb/expressions/lexer.d.ts +11 -0
  49. package/dist/server/engine/emulators/dynamodb/expressions/lexer.d.ts.map +1 -0
  50. package/dist/server/engine/emulators/dynamodb/expressions/lexer.js +109 -0
  51. package/dist/server/engine/emulators/dynamodb/expressions/lexer.js.map +1 -0
  52. package/dist/server/engine/emulators/dynamodb/expressions/parser.d.ts +23 -0
  53. package/dist/server/engine/emulators/dynamodb/expressions/parser.d.ts.map +1 -0
  54. package/dist/server/engine/emulators/dynamodb/expressions/parser.js +541 -0
  55. package/dist/server/engine/emulators/dynamodb/expressions/parser.js.map +1 -0
  56. package/dist/server/engine/emulators/dynamodb/index.d.ts +46 -0
  57. package/dist/server/engine/emulators/dynamodb/index.d.ts.map +1 -0
  58. package/dist/server/engine/emulators/dynamodb/index.js +572 -0
  59. package/dist/server/engine/emulators/dynamodb/index.js.map +1 -0
  60. package/dist/server/engine/emulators/dynamodb/query.d.ts +12 -0
  61. package/dist/server/engine/emulators/dynamodb/query.d.ts.map +1 -0
  62. package/dist/server/engine/emulators/dynamodb/query.js +237 -0
  63. package/dist/server/engine/emulators/dynamodb/query.js.map +1 -0
  64. package/dist/server/engine/emulators/dynamodb/schema.d.ts +58 -0
  65. package/dist/server/engine/emulators/dynamodb/schema.d.ts.map +1 -0
  66. package/dist/server/engine/emulators/dynamodb/schema.js +284 -0
  67. package/dist/server/engine/emulators/dynamodb/schema.js.map +1 -0
  68. package/dist/server/engine/emulators/dynamodb/streams.d.ts +28 -0
  69. package/dist/server/engine/emulators/dynamodb/streams.d.ts.map +1 -0
  70. package/dist/server/engine/emulators/dynamodb/streams.js +100 -0
  71. package/dist/server/engine/emulators/dynamodb/streams.js.map +1 -0
  72. package/dist/server/engine/emulators/events/index.d.ts +53 -0
  73. package/dist/server/engine/emulators/events/index.d.ts.map +1 -0
  74. package/dist/server/engine/emulators/events/index.js +417 -0
  75. package/dist/server/engine/emulators/events/index.js.map +1 -0
  76. package/dist/server/engine/emulators/events/pattern.d.ts +3 -0
  77. package/dist/server/engine/emulators/events/pattern.d.ts.map +1 -0
  78. package/dist/server/engine/emulators/events/pattern.js +125 -0
  79. package/dist/server/engine/emulators/events/pattern.js.map +1 -0
  80. package/dist/server/engine/emulators/lambda-ctl/index.d.ts +55 -0
  81. package/dist/server/engine/emulators/lambda-ctl/index.d.ts.map +1 -0
  82. package/dist/server/engine/emulators/lambda-ctl/index.js +461 -0
  83. package/dist/server/engine/emulators/lambda-ctl/index.js.map +1 -0
  84. package/dist/server/engine/emulators/s3/index.d.ts +45 -0
  85. package/dist/server/engine/emulators/s3/index.d.ts.map +1 -0
  86. package/dist/server/engine/emulators/s3/index.js +591 -0
  87. package/dist/server/engine/emulators/s3/index.js.map +1 -0
  88. package/dist/server/engine/emulators/sns/index.d.ts +22 -0
  89. package/dist/server/engine/emulators/sns/index.d.ts.map +1 -0
  90. package/dist/server/engine/emulators/sns/index.js +135 -0
  91. package/dist/server/engine/emulators/sns/index.js.map +1 -0
  92. package/dist/server/engine/emulators/sqs/index.d.ts +66 -0
  93. package/dist/server/engine/emulators/sqs/index.d.ts.map +1 -0
  94. package/dist/server/engine/emulators/sqs/index.js +552 -0
  95. package/dist/server/engine/emulators/sqs/index.js.map +1 -0
  96. package/dist/server/engine/emulators/sqs/md5.d.ts +8 -0
  97. package/dist/server/engine/emulators/sqs/md5.d.ts.map +1 -0
  98. package/dist/server/engine/emulators/sqs/md5.js +36 -0
  99. package/dist/server/engine/emulators/sqs/md5.js.map +1 -0
  100. package/dist/server/engine/emulators/sqs/queue.d.ts +105 -0
  101. package/dist/server/engine/emulators/sqs/queue.d.ts.map +1 -0
  102. package/dist/server/engine/emulators/sqs/queue.js +371 -0
  103. package/dist/server/engine/emulators/sqs/queue.js.map +1 -0
  104. package/dist/server/engine/emulators/sts.d.ts +8 -0
  105. package/dist/server/engine/emulators/sts.d.ts.map +1 -0
  106. package/dist/server/engine/emulators/sts.js +23 -0
  107. package/dist/server/engine/emulators/sts.js.map +1 -0
  108. package/dist/server/engine/engine-backend.d.ts +17 -0
  109. package/dist/server/engine/engine-backend.d.ts.map +1 -0
  110. package/dist/server/engine/engine-backend.js +7 -0
  111. package/dist/server/engine/engine-backend.js.map +1 -0
  112. package/dist/server/engine/engine-manager.d.ts +22 -0
  113. package/dist/server/engine/engine-manager.d.ts.map +1 -0
  114. package/dist/server/engine/engine-manager.js +74 -0
  115. package/dist/server/engine/engine-manager.js.map +1 -0
  116. package/dist/server/engine/http/aws-chunked.d.ts +3 -0
  117. package/dist/server/engine/http/aws-chunked.d.ts.map +1 -0
  118. package/dist/server/engine/http/aws-chunked.js +50 -0
  119. package/dist/server/engine/http/aws-chunked.js.map +1 -0
  120. package/dist/server/engine/http/errors.d.ts +20 -0
  121. package/dist/server/engine/http/errors.d.ts.map +1 -0
  122. package/dist/server/engine/http/errors.js +43 -0
  123. package/dist/server/engine/http/errors.js.map +1 -0
  124. package/dist/server/engine/http/protocols/aws-json.d.ts +8 -0
  125. package/dist/server/engine/http/protocols/aws-json.d.ts.map +1 -0
  126. package/dist/server/engine/http/protocols/aws-json.js +42 -0
  127. package/dist/server/engine/http/protocols/aws-json.js.map +1 -0
  128. package/dist/server/engine/http/protocols/query-xml.d.ts +7 -0
  129. package/dist/server/engine/http/protocols/query-xml.d.ts.map +1 -0
  130. package/dist/server/engine/http/protocols/query-xml.js +35 -0
  131. package/dist/server/engine/http/protocols/query-xml.js.map +1 -0
  132. package/dist/server/engine/http/router.d.ts +8 -0
  133. package/dist/server/engine/http/router.d.ts.map +1 -0
  134. package/dist/server/engine/http/router.js +391 -0
  135. package/dist/server/engine/http/router.js.map +1 -0
  136. package/dist/server/engine/http/sigv4.d.ts +9 -0
  137. package/dist/server/engine/http/sigv4.d.ts.map +1 -0
  138. package/dist/server/engine/http/sigv4.js +33 -0
  139. package/dist/server/engine/http/sigv4.js.map +1 -0
  140. package/dist/server/engine/http/xml.d.ts +16 -0
  141. package/dist/server/engine/http/xml.d.ts.map +1 -0
  142. package/dist/server/engine/http/xml.js +92 -0
  143. package/dist/server/engine/http/xml.js.map +1 -0
  144. package/dist/server/engine/store/atomic.d.ts +4 -0
  145. package/dist/server/engine/store/atomic.d.ts.map +1 -0
  146. package/dist/server/engine/store/atomic.js +35 -0
  147. package/dist/server/engine/store/atomic.js.map +1 -0
  148. package/dist/server/engine/store/engine-store.d.ts +9 -0
  149. package/dist/server/engine/store/engine-store.d.ts.map +1 -0
  150. package/dist/server/engine/store/engine-store.js +218 -0
  151. package/dist/server/engine/store/engine-store.js.map +1 -0
  152. package/dist/server/engine/store/store-types.d.ts +37 -0
  153. package/dist/server/engine/store/store-types.d.ts.map +1 -0
  154. package/dist/server/engine/store/store-types.js +17 -0
  155. package/dist/server/engine/store/store-types.js.map +1 -0
  156. package/dist/server/engine/store/wal.d.ts +49 -0
  157. package/dist/server/engine/store/wal.d.ts.map +1 -0
  158. package/dist/server/engine/store/wal.js +328 -0
  159. package/dist/server/engine/store/wal.js.map +1 -0
  160. package/dist/server/engine/types.d.ts +137 -0
  161. package/dist/server/engine/types.d.ts.map +1 -0
  162. package/dist/server/engine/types.js +8 -0
  163. package/dist/server/engine/types.js.map +1 -0
  164. package/dist/server/index.js +12 -9
  165. package/dist/server/index.js.map +1 -1
  166. package/dist/server/routes/services.d.ts.map +1 -1
  167. package/dist/server/routes/services.js +2 -0
  168. package/dist/server/routes/services.js.map +1 -1
  169. package/dist/server/services/cloudformation-parser.d.ts +26 -2
  170. package/dist/server/services/cloudformation-parser.d.ts.map +1 -1
  171. package/dist/server/services/cloudformation-parser.js +60 -3
  172. package/dist/server/services/cloudformation-parser.js.map +1 -1
  173. package/dist/server/services/config-manager.d.ts +27 -0
  174. package/dist/server/services/config-manager.d.ts.map +1 -1
  175. package/dist/server/services/config-manager.js +67 -7
  176. package/dist/server/services/config-manager.js.map +1 -1
  177. package/dist/server/services/localstack-manager.d.ts +0 -7
  178. package/dist/server/services/localstack-manager.d.ts.map +1 -1
  179. package/dist/server/services/localstack-manager.js +11 -173
  180. package/dist/server/services/localstack-manager.js.map +1 -1
  181. package/dist/server/services/resource-provisioner.d.ts +6 -0
  182. package/dist/server/services/resource-provisioner.d.ts.map +1 -1
  183. package/dist/server/services/resource-provisioner.js +171 -7
  184. package/dist/server/services/resource-provisioner.js.map +1 -1
  185. package/dist/server/services/service-registrar.js +2 -2
  186. package/dist/server/services/service-registrar.js.map +1 -1
  187. package/dist/ui/assets/ApisPage-a9i2qD0Z.js +1 -0
  188. package/dist/ui/assets/BucketDetailPage-Cz27N0KV.js +1 -0
  189. package/dist/ui/assets/BucketsPage-CZ7wGmUN.js +1 -0
  190. package/dist/ui/assets/{DynamoPage-B1__Rjr2.js → DynamoPage-BZMs1iW8.js} +1 -1
  191. package/dist/ui/assets/DynamoTablePage-BQsGkhtO.js +3 -0
  192. package/dist/ui/assets/LambdaDetailPage-DJb7xqRB.js +4 -0
  193. package/dist/ui/assets/LambdasPage-Ctg1l-Lg.js +1 -0
  194. package/dist/ui/assets/OverviewPage-DiijblCA.js +1 -0
  195. package/dist/ui/assets/QueueDetailPage-DRpSqost.js +1 -0
  196. package/dist/ui/assets/QueuesPage-CY0bDGwV.js +1 -0
  197. package/dist/ui/assets/ServiceDetailPage-CYTEdtts.js +2 -0
  198. package/dist/ui/assets/ServicesPage-OlV_rr-q.js +2 -0
  199. package/dist/ui/assets/index-C1KdKPDS.css +1 -0
  200. package/dist/ui/assets/index-DA_C1HOg.js +36 -0
  201. package/dist/ui/index.html +2 -2
  202. package/package.json +2 -1
  203. package/dist/ui/assets/ApisPage-DdgKjEdS.js +0 -1
  204. package/dist/ui/assets/BucketDetailPage-DZQGxm0y.js +0 -1
  205. package/dist/ui/assets/BucketsPage-xYXzxCvd.js +0 -1
  206. package/dist/ui/assets/DynamoTablePage-BCoxPdXW.js +0 -3
  207. package/dist/ui/assets/LambdaDetailPage-B85CA8tb.js +0 -4
  208. package/dist/ui/assets/LambdasPage-_TtxQMT_.js +0 -1
  209. package/dist/ui/assets/OverviewPage-CqTKTY64.js +0 -1
  210. package/dist/ui/assets/QueueDetailPage-BOr_sImA.js +0 -1
  211. package/dist/ui/assets/QueuesPage-DpByfcM5.js +0 -1
  212. package/dist/ui/assets/ServiceDetailPage-DsWgV-ad.js +0 -2
  213. package/dist/ui/assets/ServicesPage-CGlpsj98.js +0 -2
  214. package/dist/ui/assets/index-BWlB542w.js +0 -36
  215. package/dist/ui/assets/index-EYnsoJOq.css +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern.d.ts","sourceRoot":"","sources":["../../../../../src/server/engine/emulators/events/pattern.ts"],"names":[],"mappings":"AAcA,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGjF;AAsDD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAO1D"}
@@ -0,0 +1,125 @@
1
+ // Pure EventBridge event-pattern matcher and validator (v1 subset — see
2
+ // docs/PRD_SELF_ENGINE.md §RF6.1). Leaf arrays are an OR of exact values
3
+ // (numbers compared numerically, everything else type-sensitive), {"prefix"}
4
+ // and {"exists"}; nested pattern objects recurse into event fields. Operators
5
+ // outside the v1 set are rejected up front by validatePattern (PutRule turns
6
+ // them into InvalidEventPatternException) — matching never fails silently on
7
+ // an operator the validator would have refused.
8
+ const V1_OPERATORS = new Set(['prefix', 'exists']);
9
+ function isPlainObject(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ export function matches(pattern, event) {
13
+ if (!isPlainObject(pattern))
14
+ return false;
15
+ return matchObject(pattern, event);
16
+ }
17
+ function matchObject(pattern, event) {
18
+ // An event array satisfies a nested pattern when any element does.
19
+ if (Array.isArray(event))
20
+ return event.some(element => matchObject(pattern, element));
21
+ for (const [key, expected] of Object.entries(pattern)) {
22
+ const present = isPlainObject(event) && key in event;
23
+ const actual = present ? event[key] : undefined;
24
+ if (Array.isArray(expected)) {
25
+ if (!matchLeaf(expected, actual, present))
26
+ return false;
27
+ }
28
+ else if (isPlainObject(expected)) {
29
+ if (!present || !matchObject(expected, actual))
30
+ return false;
31
+ }
32
+ else {
33
+ // Bare primitives are invalid pattern shapes (validatePattern throws on
34
+ // them); a rule that somehow carries one must never match.
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ function matchLeaf(alternatives, actual, present) {
41
+ const candidates = Array.isArray(actual) ? actual : [actual];
42
+ return alternatives.some(alternative => {
43
+ if (isPlainObject(alternative)) {
44
+ const keys = Object.keys(alternative);
45
+ if (keys.length !== 1)
46
+ return false;
47
+ const operator = keys[0];
48
+ const argument = alternative[operator];
49
+ if (operator === 'exists')
50
+ return argument === true ? present : !present;
51
+ if (operator === 'prefix') {
52
+ return present && typeof argument === 'string'
53
+ && candidates.some(value => typeof value === 'string' && value.startsWith(argument));
54
+ }
55
+ return false; // unsupported operator — rejected at PutRule time
56
+ }
57
+ return present && candidates.some(value => valueEquals(alternative, value));
58
+ });
59
+ }
60
+ // Exact-match semantics: numbers compare numerically (5 matches 5.0), every
61
+ // other JSON primitive is type-sensitive strict equality ("5" never matches 5).
62
+ function valueEquals(expected, actual) {
63
+ if (typeof expected === 'number')
64
+ return typeof actual === 'number' && expected === actual;
65
+ return expected === actual;
66
+ }
67
+ // Structural validation for PutRule. Returns the (deduplicated) operator names
68
+ // used by the pattern that the v1 matcher does not support — suffix,
69
+ // anything-but, numeric, cidr, equals-ignore-case, wildcard, or anything else
70
+ // unknown — and an empty array when the pattern is fully supported. Throws a
71
+ // plain Error on structurally invalid patterns (non-object root, empty
72
+ // objects/arrays, bare primitive leaves, nested arrays, malformed operator
73
+ // objects); callers wrap the message into InvalidEventPatternException.
74
+ export function validatePattern(pattern) {
75
+ if (!isPlainObject(pattern)) {
76
+ throw new Error('Event pattern must be a JSON object.');
77
+ }
78
+ const unsupported = new Set();
79
+ walkPattern(pattern, '', unsupported);
80
+ return [...unsupported];
81
+ }
82
+ function walkPattern(pattern, path, unsupported) {
83
+ const entries = Object.entries(pattern);
84
+ if (entries.length === 0) {
85
+ throw new Error(`Event pattern must not contain an empty object${path ? ` at "${path}"` : ''}.`);
86
+ }
87
+ for (const [key, value] of entries) {
88
+ const keyPath = path ? `${path}.${key}` : key;
89
+ if (Array.isArray(value)) {
90
+ if (value.length === 0)
91
+ throw new Error(`Match value list at "${keyPath}" must not be empty.`);
92
+ for (const alternative of value)
93
+ validateAlternative(alternative, keyPath, unsupported);
94
+ }
95
+ else if (isPlainObject(value)) {
96
+ walkPattern(value, keyPath, unsupported);
97
+ }
98
+ else {
99
+ throw new Error(`Pattern value at "${keyPath}" must be an array of match values or a nested object.`);
100
+ }
101
+ }
102
+ }
103
+ function validateAlternative(alternative, keyPath, unsupported) {
104
+ if (Array.isArray(alternative)) {
105
+ throw new Error(`Nested arrays are not allowed in the match value list at "${keyPath}".`);
106
+ }
107
+ if (!isPlainObject(alternative))
108
+ return; // exact-match primitive — always supported
109
+ const keys = Object.keys(alternative);
110
+ if (keys.length !== 1) {
111
+ throw new Error(`Match operator objects must have exactly one key (at "${keyPath}").`);
112
+ }
113
+ const operator = keys[0];
114
+ if (!V1_OPERATORS.has(operator)) {
115
+ unsupported.add(operator);
116
+ return;
117
+ }
118
+ if (operator === 'prefix' && typeof alternative[operator] !== 'string') {
119
+ throw new Error(`Operator "prefix" at "${keyPath}" requires a string argument.`);
120
+ }
121
+ if (operator === 'exists' && typeof alternative[operator] !== 'boolean') {
122
+ throw new Error(`Operator "exists" at "${keyPath}" requires a boolean argument.`);
123
+ }
124
+ }
125
+ //# sourceMappingURL=pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern.js","sourceRoot":"","sources":["../../../../../src/server/engine/emulators/events/pattern.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { AwsRequest, AwsResponse, EngineContext, EngineEventSourceMappingRecord, EngineFunctionRecord, EngineServiceName, RestEmulator } from '../../types.js';
2
+ export interface LambdaPolicyStatement {
3
+ Sid: string;
4
+ Effect: 'Allow';
5
+ Action?: string;
6
+ Resource: string;
7
+ Principal?: {
8
+ Service: string;
9
+ };
10
+ Condition?: {
11
+ ArnLike: {
12
+ 'AWS:SourceArn': string;
13
+ };
14
+ };
15
+ }
16
+ export interface LambdaFunctionRecord extends EngineFunctionRecord {
17
+ statements?: Record<string, LambdaPolicyStatement>;
18
+ }
19
+ export declare class LambdaCtlEmulator implements RestEmulator {
20
+ readonly service: EngineServiceName;
21
+ private readonly ctx;
22
+ private readonly esmChangedListeners;
23
+ constructor(ctx: EngineContext);
24
+ listEventSourceMappings(region: string): EngineEventSourceMappingRecord[];
25
+ getFunctionRecord(region: string, name: string): LambdaFunctionRecord | undefined;
26
+ onEsmChanged(cb: (region: string) => void): void;
27
+ ensureLoaded(region: string): Promise<void>;
28
+ handle(req: AwsRequest): Promise<AwsResponse>;
29
+ private handleFunctionRoutes;
30
+ private handleMappingRoutes;
31
+ private createFunction;
32
+ private getFunction;
33
+ private updateFunctionConfiguration;
34
+ private deleteFunction;
35
+ private listFunctions;
36
+ private addPermission;
37
+ private removePermission;
38
+ private createEventSourceMapping;
39
+ private listEventSourceMappingsRoute;
40
+ private updateEventSourceMapping;
41
+ private deleteEventSourceMapping;
42
+ private invoke;
43
+ private isUnresolved;
44
+ private functions;
45
+ private mappings;
46
+ private resolveFunctionName;
47
+ private functionArn;
48
+ private requireFunction;
49
+ private functionNotFound;
50
+ private requireMapping;
51
+ private functionConfiguration;
52
+ private mappingJson;
53
+ private fireEsmChanged;
54
+ }
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/server/engine/emulators/lambda-ctl/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,8BAA8B,EAC9B,oBAAoB,EAEpB,iBAAiB,EACjB,YAAY,EACb,MAAM,gBAAgB,CAAC;AAIxB,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CACpD;AA+ED,qBAAa,iBAAkB,YAAW,YAAY;IACpD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAY;IAE/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuC;gBAE/D,GAAG,EAAE,aAAa;IAU9B,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,8BAA8B,EAAE;IAIzE,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAMjF,YAAY,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAI1C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3C,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;YAYrC,oBAAoB;IAmBlC,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,2BAA2B;IAgBnC,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,gBAAgB;IAexB,OAAO,CAAC,wBAAwB;IA8ChC,OAAO,CAAC,4BAA4B;IAUpC,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,wBAAwB;YAWlB,MAAM;IA4CpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,cAAc;CASvB"}
@@ -0,0 +1,461 @@
1
+ // Lambda control-plane emulator for the self engine. Real functions run in
2
+ // the LSS Lambda runtime; this emulator absorbs the provisioner's
3
+ // LocalStack-era proxy functions as pure metadata (Code.ZipFile is discarded,
4
+ // the INVOKE_URL env survives as the dispatcher's HTTP fallback), owns the
5
+ // event source mapping registry the dispatcher delivers from, and passes
6
+ // Invoke through to the dispatcher. REST-JSON on the /2015-03-31 paths the
7
+ // LSS provisioner and QueueInspector call — see docs/PRD_SELF_ENGINE.md §6.
8
+ import { randomUUID } from 'crypto';
9
+ import { AwsError, notImplemented } from '../../http/errors.js';
10
+ // Failed dispatcher results with these errorType values mean "the ref could
11
+ // not be resolved at all" (vs a handler error, which stays a 200 +
12
+ // X-Amz-Function-Error). The dispatcher contract never throws, so this is the
13
+ // only signal available to turn an unknown function into the 404 the
14
+ // provisioner's ensureLambdaProxyExists branches on.
15
+ // 'FunctionNotResolvable' is what the round-2 dispatcher actually emits for
16
+ // refs it cannot resolve anywhere (registry, catalog, INVOKE_URL).
17
+ const UNRESOLVED_ERROR_TYPES = new Set([
18
+ 'ResourceNotFoundException',
19
+ 'FunctionNotFound',
20
+ 'Function.NotFound',
21
+ 'FunctionNotResolvable',
22
+ ]);
23
+ const SOURCE_LABELS = { sqs: 'SQS', dynamodb: 'DynamoDB', kinesis: 'Kinesis' };
24
+ function sourceLabel(arn) {
25
+ const service = arn.split(':')[2] ?? '';
26
+ return SOURCE_LABELS[service] ?? service.toUpperCase();
27
+ }
28
+ function decodeSegment(segment) {
29
+ try {
30
+ return decodeURIComponent(segment);
31
+ }
32
+ catch {
33
+ return segment;
34
+ }
35
+ }
36
+ function parseJsonBody(req) {
37
+ if (req.body.length === 0)
38
+ return {};
39
+ try {
40
+ return JSON.parse(req.body.toString('utf8'));
41
+ }
42
+ catch {
43
+ throw new AwsError('InvalidRequestContentException', 'Could not parse request body into json', { status: 400 });
44
+ }
45
+ }
46
+ function jsonResponse(status, payload) {
47
+ return { status, headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) };
48
+ }
49
+ // Lambda's LastModified format ("2026-07-10T12:00:00.000+0000").
50
+ function awsTimestamp(iso) {
51
+ return new Date(iso).toISOString().replace('Z', '+0000');
52
+ }
53
+ export class LambdaCtlEmulator {
54
+ service = 'lambda';
55
+ ctx;
56
+ esmChangedListeners = [];
57
+ constructor(ctx) {
58
+ this.ctx = ctx;
59
+ }
60
+ // -------------------------------------------------------------------------
61
+ // Dispatcher-facing API (round-2 wiring)
62
+ // -------------------------------------------------------------------------
63
+ // Both sync accessors read the loaded catalog state; callers outside a
64
+ // request (dispatcher boot re-arm) must `await ensureLoaded(region)` first.
65
+ listEventSourceMappings(region) {
66
+ return this.mappings(region).values();
67
+ }
68
+ getFunctionRecord(region, name) {
69
+ return this.functions(region).get(this.resolveFunctionName(name));
70
+ }
71
+ // Fired after every ESM create/update/delete (including the cascade drop on
72
+ // DeleteFunction) so the dispatcher re-syncs its delivery loops.
73
+ onEsmChanged(cb) {
74
+ this.esmChangedListeners.push(cb);
75
+ }
76
+ async ensureLoaded(region) {
77
+ await Promise.all([this.functions(region).load(), this.mappings(region).load()]);
78
+ }
79
+ // -------------------------------------------------------------------------
80
+ // Routing
81
+ // -------------------------------------------------------------------------
82
+ async handle(req) {
83
+ const segments = req.rawPath.split('/').filter(s => s.length > 0).map(decodeSegment);
84
+ if (segments[0] !== '2015-03-31') {
85
+ throw notImplemented('lambda', `${req.method} ${req.rawPath}`);
86
+ }
87
+ await this.ensureLoaded(req.region);
88
+ const [, root, ...rest] = segments;
89
+ if (root === 'functions')
90
+ return this.handleFunctionRoutes(req, rest);
91
+ if (root === 'event-source-mappings')
92
+ return this.handleMappingRoutes(req, rest);
93
+ throw notImplemented('lambda', `${req.method} ${req.rawPath}`);
94
+ }
95
+ async handleFunctionRoutes(req, rest) {
96
+ if (rest.length === 0) {
97
+ if (req.method === 'POST')
98
+ return this.createFunction(req);
99
+ if (req.method === 'GET')
100
+ return this.listFunctions(req);
101
+ }
102
+ else if (rest.length === 1) {
103
+ if (req.method === 'GET')
104
+ return this.getFunction(req, rest[0]);
105
+ if (req.method === 'DELETE')
106
+ return this.deleteFunction(req, rest[0]);
107
+ }
108
+ else if (rest.length === 2 && rest[1] === 'configuration' && req.method === 'PUT') {
109
+ return this.updateFunctionConfiguration(req, rest[0]);
110
+ }
111
+ else if (rest.length === 2 && rest[1] === 'policy' && req.method === 'POST') {
112
+ return this.addPermission(req, rest[0]);
113
+ }
114
+ else if (rest.length === 3 && rest[1] === 'policy' && req.method === 'DELETE') {
115
+ return this.removePermission(req, rest[0], rest[2]);
116
+ }
117
+ else if (rest.length === 2 && rest[1] === 'invocations' && req.method === 'POST') {
118
+ return this.invoke(req, rest[0]);
119
+ }
120
+ throw notImplemented('lambda', `${req.method} ${req.rawPath}`);
121
+ }
122
+ handleMappingRoutes(req, rest) {
123
+ if (rest.length === 0) {
124
+ if (req.method === 'POST')
125
+ return this.createEventSourceMapping(req);
126
+ if (req.method === 'GET')
127
+ return this.listEventSourceMappingsRoute(req);
128
+ }
129
+ else if (rest.length === 1) {
130
+ if (req.method === 'GET')
131
+ return jsonResponse(200, this.mappingJson(req.region, this.requireMapping(req.region, rest[0])));
132
+ if (req.method === 'PUT')
133
+ return this.updateEventSourceMapping(req, rest[0]);
134
+ if (req.method === 'DELETE')
135
+ return this.deleteEventSourceMapping(req, rest[0]);
136
+ }
137
+ throw notImplemented('lambda', `${req.method} ${req.rawPath}`);
138
+ }
139
+ // -------------------------------------------------------------------------
140
+ // Functions
141
+ // -------------------------------------------------------------------------
142
+ createFunction(req) {
143
+ const input = parseJsonBody(req);
144
+ const name = input.FunctionName;
145
+ if (!name) {
146
+ throw new AwsError('ValidationException', 'FunctionName is required', { status: 400 });
147
+ }
148
+ const catalog = this.functions(req.region);
149
+ if (catalog.get(name)) {
150
+ throw new AwsError('ResourceConflictException', `Function already exist: ${name}`, { status: 409 });
151
+ }
152
+ // Code.ZipFile is intentionally never read: absorbed proxies are metadata.
153
+ const record = {
154
+ functionName: name,
155
+ arn: this.functionArn(req.region, name),
156
+ runtime: input.Runtime,
157
+ handler: input.Handler,
158
+ memorySize: input.MemorySize,
159
+ timeout: input.Timeout,
160
+ environment: input.Environment?.Variables ?? {},
161
+ lastModified: new Date().toISOString(),
162
+ };
163
+ catalog.set(name, record);
164
+ return jsonResponse(201, this.functionConfiguration(record));
165
+ }
166
+ getFunction(req, ref) {
167
+ const record = this.requireFunction(req.region, this.resolveFunctionName(ref));
168
+ return jsonResponse(200, { Configuration: this.functionConfiguration(record), Code: {} });
169
+ }
170
+ updateFunctionConfiguration(req, ref) {
171
+ const name = this.resolveFunctionName(ref);
172
+ const record = this.requireFunction(req.region, name);
173
+ const input = parseJsonBody(req);
174
+ // AWS replaces Environment.Variables wholesale when Environment is sent —
175
+ // this is how the provisioner re-points stale INVOKE_URLs.
176
+ if (input.Environment !== undefined)
177
+ record.environment = input.Environment.Variables ?? {};
178
+ if (input.Runtime !== undefined)
179
+ record.runtime = input.Runtime;
180
+ if (input.Handler !== undefined)
181
+ record.handler = input.Handler;
182
+ if (input.MemorySize !== undefined)
183
+ record.memorySize = input.MemorySize;
184
+ if (input.Timeout !== undefined)
185
+ record.timeout = input.Timeout;
186
+ record.lastModified = new Date().toISOString();
187
+ this.functions(req.region).set(name, record);
188
+ return jsonResponse(200, this.functionConfiguration(record));
189
+ }
190
+ deleteFunction(req, ref) {
191
+ const name = this.resolveFunctionName(ref);
192
+ this.requireFunction(req.region, name);
193
+ this.functions(req.region).delete(name);
194
+ const mappings = this.mappings(req.region);
195
+ let dropped = false;
196
+ for (const mapping of mappings.values()) {
197
+ if (mapping.functionName === name) {
198
+ mappings.delete(mapping.uuid);
199
+ dropped = true;
200
+ }
201
+ }
202
+ if (dropped)
203
+ this.fireEsmChanged(req.region);
204
+ return { status: 204 };
205
+ }
206
+ listFunctions(req) {
207
+ const functions = this.functions(req.region).values().map(record => this.functionConfiguration(record));
208
+ return jsonResponse(200, { Functions: functions });
209
+ }
210
+ // -------------------------------------------------------------------------
211
+ // Permissions (recorded no-ops — nothing enforces them locally)
212
+ // -------------------------------------------------------------------------
213
+ addPermission(req, ref) {
214
+ const name = this.resolveFunctionName(ref);
215
+ const record = this.requireFunction(req.region, name);
216
+ const input = parseJsonBody(req);
217
+ const sid = input.StatementId;
218
+ if (!sid) {
219
+ throw new AwsError('ValidationException', 'StatementId is required', { status: 400 });
220
+ }
221
+ const statements = record.statements ?? {};
222
+ if (statements[sid]) {
223
+ throw new AwsError('ResourceConflictException', `The statement id (${sid}) provided already exists. Please provide new statement id, or remove existing statement.`, { status: 409 });
224
+ }
225
+ const statement = {
226
+ Sid: sid,
227
+ Effect: 'Allow',
228
+ Resource: record.arn,
229
+ ...(input.Action !== undefined ? { Action: input.Action } : {}),
230
+ ...(input.Principal !== undefined ? { Principal: { Service: input.Principal } } : {}),
231
+ ...(input.SourceArn !== undefined ? { Condition: { ArnLike: { 'AWS:SourceArn': input.SourceArn } } } : {}),
232
+ };
233
+ statements[sid] = statement;
234
+ record.statements = statements;
235
+ this.functions(req.region).set(name, record);
236
+ return jsonResponse(201, { Statement: JSON.stringify(statement) });
237
+ }
238
+ removePermission(req, ref, sid) {
239
+ const name = this.resolveFunctionName(ref);
240
+ const record = this.requireFunction(req.region, name);
241
+ if (!record.statements || !record.statements[sid]) {
242
+ throw new AwsError('ResourceNotFoundException', `Statement ${sid} is not found in resource policy.`, { status: 404 });
243
+ }
244
+ delete record.statements[sid];
245
+ this.functions(req.region).set(name, record);
246
+ return { status: 204 };
247
+ }
248
+ // -------------------------------------------------------------------------
249
+ // Event source mappings
250
+ // -------------------------------------------------------------------------
251
+ createEventSourceMapping(req) {
252
+ const input = parseJsonBody(req);
253
+ if (!input.FunctionName) {
254
+ throw new AwsError('ValidationException', 'FunctionName is required', { status: 400 });
255
+ }
256
+ const functionName = this.resolveFunctionName(input.FunctionName);
257
+ const eventSourceArn = input.EventSourceArn;
258
+ if (!eventSourceArn) {
259
+ throw new AwsError('InvalidParameterValueException', 'Unrecognized event source, must be kinesis, sqs or dynamodb stream.', { status: 400 });
260
+ }
261
+ const catalog = this.mappings(req.region);
262
+ const existing = catalog.values().find(m => m.eventSourceArn === eventSourceArn && m.functionName === functionName);
263
+ if (existing) {
264
+ throw new AwsError('ResourceConflictException', `An event source mapping with ${sourceLabel(eventSourceArn)} arn (" ${eventSourceArn} ") and function ` +
265
+ `(" ${functionName} ") already exists. Please use the existing mapping with UUID ${existing.uuid}`, { status: 409 });
266
+ }
267
+ const now = new Date().toISOString();
268
+ const enabled = input.Enabled !== false;
269
+ const record = {
270
+ uuid: randomUUID(),
271
+ functionName,
272
+ eventSourceArn,
273
+ enabled,
274
+ batchSize: input.BatchSize ?? 10,
275
+ maximumBatchingWindowInSeconds: input.MaximumBatchingWindowInSeconds,
276
+ maximumRetryAttempts: input.MaximumRetryAttempts,
277
+ startingPosition: input.StartingPosition,
278
+ onFailureDestinationArn: input.DestinationConfig?.OnFailure?.Destination,
279
+ filterCriteria: input.FilterCriteria,
280
+ createdAt: now,
281
+ lastModified: now,
282
+ state: enabled ? 'Enabled' : 'Disabled',
283
+ };
284
+ catalog.set(record.uuid, record);
285
+ this.fireEsmChanged(req.region);
286
+ return jsonResponse(202, this.mappingJson(req.region, record));
287
+ }
288
+ listEventSourceMappingsRoute(req) {
289
+ const arnFilter = req.query['EventSourceArn'];
290
+ const rawFunctionFilter = req.query['FunctionName'];
291
+ const functionFilter = rawFunctionFilter ? this.resolveFunctionName(rawFunctionFilter) : undefined;
292
+ let records = this.mappings(req.region).values();
293
+ if (arnFilter)
294
+ records = records.filter(m => m.eventSourceArn === arnFilter);
295
+ if (functionFilter)
296
+ records = records.filter(m => m.functionName === functionFilter);
297
+ return jsonResponse(200, { EventSourceMappings: records.map(m => this.mappingJson(req.region, m)) });
298
+ }
299
+ updateEventSourceMapping(req, uuid) {
300
+ const record = this.requireMapping(req.region, uuid);
301
+ const input = parseJsonBody(req);
302
+ if (typeof input.Enabled === 'boolean') {
303
+ record.enabled = input.Enabled;
304
+ record.state = input.Enabled ? 'Enabled' : 'Disabled';
305
+ }
306
+ if (input.BatchSize !== undefined)
307
+ record.batchSize = input.BatchSize;
308
+ if (input.MaximumBatchingWindowInSeconds !== undefined)
309
+ record.maximumBatchingWindowInSeconds = input.MaximumBatchingWindowInSeconds;
310
+ if (input.MaximumRetryAttempts !== undefined)
311
+ record.maximumRetryAttempts = input.MaximumRetryAttempts;
312
+ if (input.FunctionName !== undefined)
313
+ record.functionName = this.resolveFunctionName(input.FunctionName);
314
+ if (input.FilterCriteria !== undefined)
315
+ record.filterCriteria = input.FilterCriteria;
316
+ if (input.DestinationConfig !== undefined)
317
+ record.onFailureDestinationArn = input.DestinationConfig.OnFailure?.Destination;
318
+ record.lastModified = new Date().toISOString();
319
+ this.mappings(req.region).set(uuid, record);
320
+ this.fireEsmChanged(req.region);
321
+ return jsonResponse(202, this.mappingJson(req.region, record));
322
+ }
323
+ deleteEventSourceMapping(req, uuid) {
324
+ const record = this.requireMapping(req.region, uuid);
325
+ this.mappings(req.region).delete(uuid);
326
+ this.fireEsmChanged(req.region);
327
+ return jsonResponse(202, this.mappingJson(req.region, record));
328
+ }
329
+ // -------------------------------------------------------------------------
330
+ // Invoke passthrough
331
+ // -------------------------------------------------------------------------
332
+ async invoke(req, ref) {
333
+ const name = this.resolveFunctionName(ref);
334
+ const invocationType = req.headers['x-amz-invocation-type'] || 'RequestResponse';
335
+ const record = this.functions(req.region).get(name);
336
+ if (invocationType === 'DryRun') {
337
+ // No registry probe without invoking, so catalog presence is the check.
338
+ if (!record)
339
+ throw this.functionNotFound(req.region, name);
340
+ return { status: 204 };
341
+ }
342
+ let event = {};
343
+ if (req.body.length > 0) {
344
+ try {
345
+ event = JSON.parse(req.body.toString('utf8'));
346
+ }
347
+ catch {
348
+ throw new AwsError('InvalidRequestContentException', 'Could not parse request body into json', { status: 400 });
349
+ }
350
+ }
351
+ const result = await this.ctx.dispatcher.invokeFunction(name, event, { async: invocationType === 'Event' });
352
+ if (!record && this.isUnresolved(result)) {
353
+ throw this.functionNotFound(req.region, name);
354
+ }
355
+ if (invocationType === 'Event') {
356
+ return { status: 202, headers: { 'content-type': 'application/json' } };
357
+ }
358
+ if (!result.ok) {
359
+ return {
360
+ status: 200,
361
+ headers: { 'content-type': 'application/json', 'x-amz-function-error': 'Unhandled' },
362
+ body: JSON.stringify({
363
+ errorType: result.errorType ?? 'Error',
364
+ errorMessage: result.errorMessage ?? 'Unhandled error',
365
+ }),
366
+ };
367
+ }
368
+ return {
369
+ status: 200,
370
+ headers: { 'content-type': 'application/json' },
371
+ body: result.payload === undefined || result.payload === null ? 'null' : JSON.stringify(result.payload),
372
+ };
373
+ }
374
+ isUnresolved(result) {
375
+ return !result.ok && result.errorType !== undefined && UNRESOLVED_ERROR_TYPES.has(result.errorType);
376
+ }
377
+ // -------------------------------------------------------------------------
378
+ // Helpers
379
+ // -------------------------------------------------------------------------
380
+ functions(region) {
381
+ return this.ctx.store.catalog(`lambda/${region}/functions`);
382
+ }
383
+ mappings(region) {
384
+ return this.ctx.store.catalog(`lambda/${region}/event-source-mappings`);
385
+ }
386
+ // Accepts a bare name, "name:qualifier", or a full/partial function ARN and
387
+ // returns the bare name — records are always keyed by name.
388
+ resolveFunctionName(ref) {
389
+ if (!ref.startsWith('arn:'))
390
+ return ref.split(':')[0];
391
+ const suffix = ref.split(':function:')[1];
392
+ return suffix ? suffix.split(':')[0] : ref;
393
+ }
394
+ functionArn(region, name) {
395
+ return `arn:aws:lambda:${region}:${this.ctx.config.account}:function:${name}`;
396
+ }
397
+ requireFunction(region, name) {
398
+ const record = this.functions(region).get(name);
399
+ if (!record)
400
+ throw this.functionNotFound(region, name);
401
+ return record;
402
+ }
403
+ functionNotFound(region, name) {
404
+ return new AwsError('ResourceNotFoundException', `Function not found: ${this.functionArn(region, name)}`, { status: 404 });
405
+ }
406
+ requireMapping(region, uuid) {
407
+ const record = this.mappings(region).get(uuid);
408
+ if (!record) {
409
+ throw new AwsError('ResourceNotFoundException', 'The resource you requested does not exist.', { status: 404 });
410
+ }
411
+ return record;
412
+ }
413
+ functionConfiguration(record) {
414
+ return {
415
+ FunctionName: record.functionName,
416
+ FunctionArn: record.arn,
417
+ Runtime: record.runtime,
418
+ Handler: record.handler,
419
+ MemorySize: record.memorySize,
420
+ Timeout: record.timeout,
421
+ Environment: { Variables: record.environment },
422
+ Version: '$LATEST',
423
+ State: 'Active',
424
+ LastUpdateStatus: 'Successful',
425
+ LastModified: awsTimestamp(record.lastModified),
426
+ };
427
+ }
428
+ mappingJson(region, record) {
429
+ const json = {
430
+ UUID: record.uuid,
431
+ BatchSize: record.batchSize,
432
+ MaximumBatchingWindowInSeconds: record.maximumBatchingWindowInSeconds ?? 0,
433
+ EventSourceArn: record.eventSourceArn,
434
+ FunctionArn: this.functionArn(region, record.functionName),
435
+ LastModified: Math.floor(Date.parse(record.lastModified) / 1000),
436
+ State: record.state,
437
+ StateTransitionReason: 'USER_INITIATED',
438
+ };
439
+ if (record.startingPosition !== undefined)
440
+ json.StartingPosition = record.startingPosition;
441
+ if (record.maximumRetryAttempts !== undefined)
442
+ json.MaximumRetryAttempts = record.maximumRetryAttempts;
443
+ if (record.onFailureDestinationArn !== undefined) {
444
+ json.DestinationConfig = { OnFailure: { Destination: record.onFailureDestinationArn } };
445
+ }
446
+ if (record.filterCriteria !== undefined)
447
+ json.FilterCriteria = record.filterCriteria;
448
+ return json;
449
+ }
450
+ fireEsmChanged(region) {
451
+ for (const listener of this.esmChangedListeners) {
452
+ try {
453
+ listener(region);
454
+ }
455
+ catch (err) {
456
+ console.warn(`lambda-ctl: onEsmChanged listener failed: ${err instanceof Error ? err.message : String(err)}`);
457
+ }
458
+ }
459
+ }
460
+ }
461
+ //# sourceMappingURL=index.js.map