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
package/CHANGELOG.md CHANGED
@@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.0] - 2026-07-10
9
+
10
+ The self engine: an in-process AWS emulator that replaces LocalStack for the typical serverless dev loop — DynamoDB, SQS, S3, EventBridge, SNS (minimal), Lambda control plane and STS served by the orchestrator itself on one port. No Docker, no container, no auth token; boots in milliseconds; data persisted in local files. Opt-in (`engine: "self"` / `lss start --self-engine`) — LocalStack mode remains the default and fully supported. Design: `docs/PRD_SELF_ENGINE.md`; coverage matrix: `docs/SELF_ENGINE.md`.
11
+
12
+ ### Added
13
+ - **Engine selection**: `engine: "localstack" | "self"` and a `selfEngine` config block (`port` default 14566 — deliberately outside the 4566–4599 range a real LocalStack intercepts on some hosts —, `dataDir`, `account`, `idleUnloadMs`, `memoryBudgetMb`, `fsync`, `fallbackEndpoint`); env overrides `LSS_ENGINE`/`LSS_ENGINE_PORT`; CLI `lss start --self-engine` (rejected when combined with the LocalStack-only `--external`/`--pro`/`--localstack-token`).
14
+ - **AWS wire front door** (`src/server/engine/http/`): one `node:http` listener routing by SigV4 credential scope → `X-Amz-Target` → path heuristics; per-protocol error serialization (JSON `__type`, Query XML, S3 XML with body-less HEAD errors, Lambda REST + `x-amzn-ErrorType`); the `x-amzn-query-error` SQS compat header; `aws-chunked` request decoding (SDK v3 streaming PutObject); `x-amzn-RequestId` on every response; `/_localstack/health` alias so existing readiness polls keep working; `selfEngine.fallbackEndpoint` reverse-proxies any unimplemented service/operation verbatim to a LocalStack during migration — unsupported calls otherwise fail loudly, never silently succeed.
15
+ - **File-backed store** (`src/server/engine/store/`): JSONL snapshot + append-only WAL per data table (torn-tail-safe replay, seq-based compaction), atomic JSON catalogs for metadata, content-addressed blob storage for S3 bodies (never held in the heap); registration writes metadata only — data hydrates on first touch (streamed line-by-line) and dehydrates after `idleUnloadMs` or under the `memoryBudgetMb` LRU budget; debounced flushes (20 ms / 256 KB) with opt-in fsync.
16
+ - **DynamoDB emulator** with a hand-rolled expression engine (KeyCondition/Condition/Filter/Update/Projection: comparators, `BETWEEN`/`IN`, boolean precedence, document paths, `SET`/`REMOVE`/`ADD`/`DELETE` with exact decimal-string `N` arithmetic), GSI/LSI queries with projection and sparse-index semantics, `Limit`-before-filter parity, `LastEvaluatedKey` paging (including index LEKs), lazy TTL (filtered at read, purged at compaction with Service-identity stream REMOVEs), stream records (`INSERT`/`MODIFY`/`REMOVE` per `StreamViewType`), batch ops, and the full `DescribeTable` introspection shape (`LatestStreamArn` present immediately).
17
+ - **SQS emulator**: FIFO (per-group ordering, 5-minute dedup window), event-driven long polling (parked promises — no polling loops), visibility-timeout redelivery, live `ApproximateNumberOfMessages*` counters (QueueInspector `await-idle`/metrics work unchanged), `MD5OfMessageBody`/`MD5OfMessageAttributes`, CreateQueue idempotent-success on identical attributes; messages are memory-only with a graceful-shutdown snapshot under `persistence`.
18
+ - **S3 emulator**: byte-exact object round trips, `Range` reads, `ListObjectsV2` (prefix/delimiter/CommonPrefixes/continuation/`encoding-type=url`), `DeleteObjects`, `CopyObject`, versioning flag, notification configuration (including the legacy `CloudFunctionConfiguration` XML names), quoted-MD5 ETags, `GetBucketLocation` us-east-1 quirk; multipart answers an explicit `NotImplemented` until the hardening phase.
19
+ - **EventBridge emulator**: buses, rules with validated patterns (exact, array-OR, `prefix`, `exists`, nested keys; unsupported operators rejected at `PutRule` with `InvalidEventPatternException`), targets, `PutEvents` with per-entry results, plus minimal **SNS** (topics + logged `Publish`) and **STS** (`GetCallerIdentity`).
20
+ - **Lambda control plane**: the provisioner's proxy functions are absorbed as metadata (zip discarded, `INVOKE_URL` kept — it doubles as the HTTP fallback for services still on serverless-offline), event source mappings with UUIDs persisted across restarts, `UpdateEventSourceMapping Enabled` = QueueInspector hold/release, `Invoke` honoring `X-Amz-Invocation-Type`.
21
+ - **In-process event dispatch** (`src/server/engine/dispatch/`): SQS delivery loops (batch size + `MaximumBatchingWindowInSeconds`, failure → visibility redelivery, capped backoff on runtime-unavailable), DynamoDB stream tailers (`TRIM_HORIZON`/`LATEST`, retry-then-advance with the `DDBStreamBatchInfo` OnFailure SQS envelope), S3 notification fan-out (event globs + prefix/suffix filters, `eventVersion 2.1` records), EventBridge target invocation (`Input`/`InputPath`) and **schedule triggering** (`rate()` + 6-field AWS cron, single timer wheel) — all delivered straight into the LSS Lambda runtime: no proxy Lambdas, no HTTP hop, no polling containers. Closes the long-standing scheduled-triggers TODO for self-engine mode.
22
+ - **Example `examples/self-engine-sample`**: three microservices (orders → billing → notifications) exercising DynamoDB + SQS (cross-service ESM by ARN) + S3 + EventBridge on the self engine, registered with `sls package` only. Measured: engine boot ~10 ms; the full pipeline across the three services completes in ~170 ms — no Docker.
23
+ - **Docs**: `docs/SELF_ENGINE.md` (coverage matrix, storage model, known divergences), `docs/PRD_SELF_ENGINE.md` (design), FEATURES.md §13, CONFIGURATION.md and README (self engine as the headline feature).
24
+ - **VS Code tasks** (`.vscode/tasks.json`, now versioned via a `.gitignore` exception): build/dev, unit/coverage/integration tests, lint, cross-project typecheck, a `validate: pre-prod` sequence (lint → typecheck → tests → build), and one-click flows for the self-engine and eventbridge examples (start/register/drive pipeline/stop).
25
+
26
+ ### Changed
27
+ - **Dashboard component library updated**: `@treeui/vue` `^0.6.1` → `^0.10.0` (latest). No API changes required in the LSS components — `vue-tsc` and the Vite build pass unchanged.
28
+ - **Engine seam**: the LocalStack container lifecycle moved to `src/server/engine/backends/localstack-backend.ts`; `services/localstack-manager.ts` is now a thin facade over the new `EngineManager`, so the provisioner/explorers/seeds keep their imports and work against whichever engine is active (they always speak AWS SDK to `getConfig().endpoint`). Self-engine code is loaded via dynamic `import()` — LocalStack-mode memory footprint is unchanged.
29
+ - `lambdaRuntime.invokeHost` defaults to `127.0.0.1` in self-engine mode (nothing runs inside Docker); explicit config still wins.
30
+ - `GET /api/health` now includes an `engine: {kind, running, endpoint, ...}` block; the `localstack` boolean stays truthy when the active engine is healthy (client/UI compatibility).
31
+ - The root package version is now `0.8.0`. The `serverless-lss` plugin package was not changed.
32
+
33
+ ### Fixed
34
+ - **Branch-coverage gate restored to 100%**: closed the six uncovered branches left by the 0.7.0 EventBridge work (`cloudformation-parser.ts` — bus/rule with no `Properties`, rule with no `Targets`; `resource-provisioner.ts` — non-Error rejections in the event-rule pass and eventbus cleanup, `AddPermission` conflict/message-less fallbacks while wiring rule targets). `npm run test:coverage` passes again.
35
+
36
+ ### Tests
37
+ - ~700 new unit tests (1802 total, up from 1100): wire router/SigV4/aws-chunked, store WAL/compaction/hydration/LRU-budget, DynamoDB expression engine (223 assertions) + emulator core, SQS/S3/EventBridge/SNS/STS/lambda-ctl emulators, dispatcher loops/stream tailers/scheduler, self-backend boot; engine selection covered in `config-manager` and `cli` suites. End-to-end smoke with real AWS SDK v3 clients against a booted engine (CRUD/Query/GSI, queue lifecycle, binary S3 round trip, per-entry PutEvents, ESM hold, restart persistence).
38
+
39
+ ## [0.7.0] - 2026-07-10
40
+
41
+ EventBridge support: LSS can now provision the shared event bus and rule→Lambda triggers that previously required the last remaining `serverless deploy` (CloudFormation) step in local monorepo setups.
42
+
43
+ ### Added
44
+ - **`AWS::Events::EventBus` provisioning**: buses declared in the `resources:` section are created in LocalStack on registration (idempotent — re-registration tolerates an existing bus). `PutEvents` from handlers already worked via the SDK; the bus just had to exist.
45
+ - **`AWS::Events::Rule` triggers**: rules (event pattern or schedule, `ENABLED`/`DISABLED` state honored) are created on the declared bus and their Lambda targets wired through the same proxy model used by streams/SQS — EventBridge → proxy Lambda in LocalStack → LSS invoke API (130xx). Target `Input`/`InputPath` pass through; the bus is resolved from the template's logical ids, a literal name, or an ARN. Rules whose `EventBusName` can't be resolved from the template (e.g. `Fn::ImportValue`) are skipped with a warning instead of silently landing on the default bus.
46
+ - **Resources-only stacks**: a registered service that declares only `resources:` (no functions, no ports) provisions its resources without starting a runtime worker, gateway listeners or watcher — this already worked structurally since 0.5.0 gated those on declared functions/ports, and is now covered by tests for the EventBus-only infra-stack case.
47
+ - **Cleanup parity**: unregistering a service removes rule targets and rules first, then deletes its event buses.
48
+ - **Dashboard visibility**: EventBridge buses and rules show up in the service detail page (own sections) and as counters in the services list (`resourceBreakdown.buses`/`eventRules`), so a resources-only infra stack no longer renders as "N total" with an empty body.
49
+ - **Example `examples/eventbridge-sample`**: resources-only `events-stack` owning the shared bus (+ skipped Archive), a producer publishing `UserSignedUp` via `PutEvents`, and a consumer with an `events: eventBridge` trigger storing events in DynamoDB — registered with `sls package` only, no deploy. Its LocalStack runs on port `14566`, outside the standard 4566–4599 range, so a real LocalStack publishing that range (docker-compose defaults) can't intercept the example's `localhost` traffic on Docker Desktop/WSL2.
50
+
51
+ ### Fixed
52
+ - **EventBridge event shape through proxies**: the generated LocalStack proxy Lambda no longer wraps EventBridge events in a `Records` array — handlers receive `source`/`detail-type`/`detail` at the top level, exactly like on AWS. SQS/DynamoDB/SNS batches keep their `Records` shape. Proxies created by earlier versions keep the old code until recreated (only their `INVOKE_URL` is updated in place); delete the proxy function or reset LocalStack to pick up the fix.
53
+
54
+ ### Changed
55
+ - `AWS::Events::Archive` resources are accepted and skipped with a registration warning: LocalStack mocks Archives (CloudFormation reports `CREATE_COMPLETE` but `ListArchives` stays empty), so provisioning one locally would only fake success.
56
+ - `CloudFormationParser.parse()` accepts an optional warnings sink; the registrar forwards template-level warnings (like skipped Archives) to the registering client alongside state-file warnings.
57
+ - New runtime dependency: `@aws-sdk/client-eventbridge`.
58
+
59
+ ### Tests
60
+ - Parser coverage for `AWS::Events::EventBus`/`Rule`/`Archive` (bus name fallback, pattern/schedule/state, `Fn::ImportValue` flagging, literal-ARN targets, warning sink) and ResourceProvisioner coverage for bus creation, rule wiring (proxy reuse, permissions, default vs named vs literal bus), unresolvable-bus/unsupported-target skips, and cleanup ordering.
61
+
8
62
  ## [0.6.0] - 2026-07-09
9
63
 
10
64
  Corrections from integrating LSS 0.5.0 in a real monorepo using osls 4, serverless-esbuild ESM artifacts, HTTP API v2 authorizers, DynamoDB Streams, and Docker-in-Docker devcontainers.
package/README.md CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/local-serverless-stack.svg)](https://www.npmjs.com/package/local-serverless-stack)
4
4
 
5
- **Local control plane for serverless development with LocalStack orchestration**
5
+ **Local control plane for serverless development with its own in-process AWS engine (no Docker) or LocalStack orchestration**
6
6
 
7
- LSS provides a unified local development environment for serverless microservices, eliminating the need to run separate LocalStack instances for each service.
7
+ LSS provides a unified local development environment for serverless microservices: one orchestrator provisions and serves every AWS resource your services declare, eliminating the need to run separate LocalStack instances (or, with the **self engine**, any LocalStack at all).
8
8
 
9
9
  ## Features
10
10
 
11
- - **Centralized LocalStack**: Single LocalStack instance manages DynamoDB, SQS, SNS, S3, and Lambda
12
- - **Auto-provisioning**: Parses CloudFormation templates from `sls package` and provisions resources automatically
13
- - **Event source mappings**: Automatically connects SQS queues, streams, and S3 notifications to Lambda handlers via LocalStack
14
- - **Lambda runtime & API emulation**: LSS can answer on the same API (30xx) and Lambda invoke (130xx) ports that serverless-offline used
11
+ - **⚡ Self engine (the LSS differentiator)**: DynamoDB, SQS, S3, EventBridge, SNS and the Lambda control plane emulated **in-process** by the orchestrator — no Docker, no LocalStack, no auth token. Boots in milliseconds, stores data in local files, delivers events straight into your handlers. See [Self engine](#self-engine--no-docker-no-localstack-no-auth-token) below.
12
+ - **Centralized provisioning**: Parses CloudFormation templates from `sls package` and provisions resources automatically — into the self engine or a single shared LocalStack, your choice per instance
13
+ - **Event source mappings**: Automatically connects SQS queues, streams, S3 notifications and EventBridge rules to Lambda handlers
14
+ - **Lambda runtime & API emulation**: LSS answers on the same API (30xx) and Lambda invoke (130xx) ports that serverless-offline used
15
15
  - **Web UI**: Vue 3 dashboard to monitor services, resources, and event mappings
16
16
  - **Hot reload**: Watch for code changes and auto-rebuild/reprovision
17
17
  - **Process management**: Start/stop microservices from the orchestrator
@@ -19,6 +19,57 @@ LSS provides a unified local development environment for serverless microservice
19
19
 
20
20
  See [docs/FEATURES.md](docs/FEATURES.md) for the complete feature inventory and how each capability is tested.
21
21
 
22
+ ## Self engine — no Docker, no LocalStack, no auth token
23
+
24
+ LocalStack got heavy (a container eating 1 GB+ of RAM) and stopped being free by default
25
+ (community images `>= 2026.5` require an auth token). The **self engine** replaces it for
26
+ the typical serverless dev loop: the orchestrator itself serves the real AWS wire
27
+ protocols on one port, so your application code, the AWS SDK, the dashboard and `lss seed`
28
+ all work unchanged — there is simply no container underneath.
29
+
30
+ ```bash
31
+ npx lss start --self-engine # or "engine": "self" in lss.config.json
32
+ ```
33
+
34
+ ```
35
+ AWS_ENDPOINT=http://localhost:14566 # point your services here, done
36
+ ```
37
+
38
+ What you get:
39
+
40
+ - **DynamoDB** with the full expression language (KeyCondition/Filter/Update/Projection,
41
+ exact decimal arithmetic), GSIs/LSIs, TTL and streams — items persisted in local
42
+ JSONL files under `~/.lss/engine/`, hydrated lazily and unloaded when idle.
43
+ - **SQS** with FIFO, visibility redelivery and live counters; **S3** with byte-exact
44
+ object round trips and notifications; **EventBridge** with buses, pattern-filtered
45
+ rules and `rate()`/cron schedules; minimal **SNS** and **STS**.
46
+ - **Events delivered in-process**: SQS batches, DynamoDB streams, S3 notifications and
47
+ EventBridge targets go straight from the engine to the LSS Lambda runtime — no proxy
48
+ Lambdas, no polling containers.
49
+
50
+ ```mermaid
51
+ flowchart LR
52
+ APP[Your services<br/>AWS SDK → :14566] --> ENG
53
+ subgraph ORCH["One orchestrator process — no Docker"]
54
+ ENG[Self engine<br/>DynamoDB · SQS · S3<br/>EventBridge · SNS · STS]
55
+ ENG -->|in-process events| RT[LSS Lambda runtime<br/>your handlers, ports 30xx/130xx]
56
+ ENG --- FS[(local files<br/>~/.lss/engine)]
57
+ end
58
+ ```
59
+
60
+ Measured on [examples/self-engine-sample](examples/self-engine-sample/) (3 microservices:
61
+ orders → billing → notifications): engine boot **~10 ms**; a full pipeline crossing
62
+ DynamoDB + SQS + S3 + EventBridge across the three services completes in **~170 ms** —
63
+ with the whole stack being the orchestrator process plus one small worker per service.
64
+
65
+ Migration is gradual: LocalStack mode remains the default and fully supported; a running
66
+ instance picks one engine. Anything the self engine doesn't implement yet answers with an
67
+ explicit error naming the operation — or is forwarded verbatim to a LocalStack via
68
+ `selfEngine.fallbackEndpoint`. Coverage matrix and storage model:
69
+ [docs/SELF_ENGINE.md](docs/SELF_ENGINE.md) · design/PRD:
70
+ [docs/PRD_SELF_ENGINE.md](docs/PRD_SELF_ENGINE.md) · runnable demo:
71
+ [examples/self-engine-sample](examples/self-engine-sample/).
72
+
22
73
  ## Architecture
23
74
 
24
75
  ```
@@ -41,18 +92,18 @@ local-serverless-stack/
41
92
 
42
93
  **Components**:
43
94
  - **CLI** (`bin/cli.js`): Background process management (start/stop/status/logs)
44
- - **Server** (`src/server/`): Express API + LocalStack orchestration
95
+ - **Server** (`src/server/`): Express API + engine orchestration
96
+ - **Engine** (`src/server/engine/`): the AWS provider behind everything — either the in-process **self engine** (port 14566, no Docker) or a managed/external **LocalStack** container (port 4566)
45
97
  - **UI** (`src/ui/`): Vue 3 dashboard for monitoring
46
98
  - **Plugin** (`packages/serverless-plugin/`): Auto-registration for Serverless Framework
47
- - **LocalStack**: Docker container (port 4566) with AWS services
48
99
 
49
100
  ## Quick Start
50
101
 
51
102
  ### Prerequisites
52
103
 
53
104
  - Node.js >= 20
54
- - Docker (for LocalStack)
55
105
  - Serverless Framework 3.40.0
106
+ - Docker — **only for the LocalStack engine**; the self engine (`--self-engine`) needs none
56
107
 
57
108
  ### Installation
58
109
 
@@ -86,9 +137,12 @@ See the [plugin documentation](packages/serverless-plugin/README.md) for configu
86
137
  LSS provides a simple CLI to manage the orchestrator in background mode:
87
138
 
88
139
  ```bash
89
- # Start the orchestrator in background
140
+ # Start the orchestrator in background (managed LocalStack)
90
141
  npx lss start
91
142
 
143
+ # Start with the in-process self engine instead — no Docker
144
+ npx lss start --self-engine
145
+
92
146
  # Check if orchestrator is running
93
147
  npx lss status
94
148
 
@@ -105,6 +159,12 @@ npx lss help
105
159
  ### CLI Output
106
160
 
107
161
  ```bash
162
+ $ npx lss start --self-engine
163
+ 🚀 LSS Orchestrator started (PID: 12345)
164
+ 📊 Server: http://localhost:3100
165
+ 🔧 Self Engine: http://localhost:14566 (no Docker)
166
+ ✅ Service is running
167
+
108
168
  $ npx lss start
109
169
  🚀 LSS Orchestrator started (PID: 12345)
110
170
  📊 Dashboard: http://localhost:3100
@@ -214,13 +274,30 @@ local-serverless-stack/
214
274
  ### Environment Variables
215
275
 
216
276
  - `PORT`: Orchestrator API port (default: 3100)
277
+ - `LSS_ENGINE`: AWS engine — `localstack` (default) or `self` (in-process, no Docker)
278
+ - `LSS_ENGINE_PORT`: Self engine port (default: 14566)
217
279
  - `ENABLE_DYNAMO_PROXY`: Enable DynamoDB proxy on port 8000 (default: false)
218
280
  - `DYNAMO_PROXY_PORT`: DynamoDB proxy port (default: 8000)
219
281
 
282
+ ### Engine selection
283
+
284
+ ```jsonc
285
+ // lss.config.json
286
+ {
287
+ "engine": "self", // "localstack" (default) | "self"
288
+ "selfEngine": { "port": 14566 } // full reference: docs/SELF_ENGINE.md
289
+ }
290
+ ```
291
+
292
+ The self engine's default port sits **outside 4566–4599** on purpose: a real LocalStack
293
+ install intercepts that whole range on some hosts (Docker Desktop/WSL2). `--self-engine`
294
+ cannot be combined with the LocalStack-only flags (`--external`, `--pro`,
295
+ `--localstack-token`).
296
+
220
297
  ### LocalStack Settings
221
298
 
222
299
  LocalStack is configured with:
223
- - Services: `dynamodb,sqs,sns,s3,lambda`
300
+ - Services: `dynamodb,sqs,sns,s3,lambda,events`
224
301
  - Persistence: Enabled (volume: `lss-localstack-data`)
225
302
  - Lambda executor: `local` (no Docker-in-Docker required)
226
303
  - Docker socket: Mounted from host
@@ -479,6 +556,17 @@ sequenceDiagram
479
556
  - Response returned through proxy chain
480
557
  - Message deleted from queue on success, or sent to DLQ on failure
481
558
 
559
+ > **Self-engine mode collapses this whole chain**: the engine's event source mapping
560
+ > delivers the SQS batch **in-process** to the LSS Lambda runtime — no LocalStack
561
+ > polling, no proxy Lambda, no HTTP hop:
562
+ >
563
+ > ```
564
+ > LocalStack mode: SQS → LocalStack ESM → proxy Lambda (container) → HTTP → handler
565
+ > Self engine: SQS (engine) → dispatcher → handler
566
+ > ```
567
+ >
568
+ > The same applies to DynamoDB streams, S3 notifications and EventBridge targets.
569
+
482
570
  ## CLI Implementation Details
483
571
 
484
572
  The `npx lss` CLI is implemented in `/bin/cli.js` and provides:
package/bin/cli.js CHANGED
@@ -96,6 +96,8 @@ function getConfig(config) {
96
96
  localstackImage: config.localstackImage,
97
97
  localstackAuthToken: config.localstackAuthToken,
98
98
  stateDir: config.stateDir,
99
+ engine: config.engine || 'localstack',
100
+ selfEnginePort: (config.selfEngine && config.selfEngine.port) || 14566,
99
101
  };
100
102
  }
101
103
 
@@ -150,7 +152,11 @@ function startOrchestrator() {
150
152
  const cfg = getConfig(config);
151
153
  console.log('✅ LSS Orchestrator already running (PID:', pid + ')');
152
154
  console.log(`📊 Server: http://localhost:${cfg.serverPort}`);
153
- console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort}`);
155
+ if (cfg.engine === 'self') {
156
+ console.log(`🔧 Self Engine: http://localhost:${cfg.selfEnginePort}`);
157
+ } else {
158
+ console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort}`);
159
+ }
154
160
  if (cfg.enableDynamoProxy) {
155
161
  console.log(`🔄 DynamoDB Proxy: http://localhost:${cfg.dynamoProxyPort} (enabled)`);
156
162
  }
@@ -183,6 +189,13 @@ function startOrchestrator() {
183
189
  const useExternal = process.argv.includes('--external');
184
190
  const usePro = process.argv.includes('--pro');
185
191
  const cliToken = getArgValue('--localstack-token');
192
+ const useSelfEngine = process.argv.includes('--self-engine');
193
+
194
+ const engine = useSelfEngine ? 'self' : cfg.engine;
195
+ if (engine === 'self' && (useExternal || usePro || cliToken)) {
196
+ console.error('❌ --self-engine cannot be combined with --external, --pro or --localstack-token (those are LocalStack options).');
197
+ process.exit(1);
198
+ }
186
199
 
187
200
  const mode = useExternal ? 'external' : cfg.mode;
188
201
  const edition = usePro ? 'pro' : cfg.localstackEdition;
@@ -223,6 +236,9 @@ function startOrchestrator() {
223
236
  if (authToken) {
224
237
  env.LOCALSTACK_AUTH_TOKEN = authToken;
225
238
  }
239
+ if (engine === 'self') {
240
+ env.LSS_ENGINE = 'self';
241
+ }
226
242
  // Hand the same config file to the server so its ConfigManager reads the
227
243
  // identical serverPort/localstackPort/seedsDir/region/mode (not just the
228
244
  // hand-translated subset above). Keeps the two config loaders in agreement.
@@ -243,7 +259,11 @@ function startOrchestrator() {
243
259
 
244
260
  console.log('🚀 LSS Orchestrator started (PID:', child.pid + ')');
245
261
  console.log(`📊 Server: http://localhost:${cfg.serverPort}`);
246
- console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort} (mode: ${mode}, edition: ${edition})`);
262
+ if (engine === 'self') {
263
+ console.log(`🔧 Self Engine: http://localhost:${cfg.selfEnginePort} (no Docker)`);
264
+ } else {
265
+ console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort} (mode: ${mode}, edition: ${edition})`);
266
+ }
247
267
  if (enableDynamoProxy) {
248
268
  console.log(`🔄 DynamoDB Proxy: http://localhost:${cfg.dynamoProxyPort} (enabled)`);
249
269
  }
@@ -269,7 +289,9 @@ function waitForExit(pid, timeoutMs = 10000, intervalMs = 200) {
269
289
  return new Promise(resolve => {
270
290
  const deadline = Date.now() + timeoutMs;
271
291
  let timer;
292
+ let done = false;
272
293
  const finish = (value) => {
294
+ done = true;
273
295
  if (timer) clearInterval(timer);
274
296
  resolve(value);
275
297
  };
@@ -284,8 +306,12 @@ function waitForExit(pid, timeoutMs = 10000, intervalMs = 200) {
284
306
  finish(false);
285
307
  }
286
308
  };
287
- timer = setInterval(check, intervalMs);
309
+ // Fast path: the process is often already gone — check synchronously so
310
+ // `stop` resolves without ever scheduling (and leaking) an interval.
288
311
  check();
312
+ if (!done) {
313
+ timer = setInterval(check, intervalMs);
314
+ }
289
315
  });
290
316
  }
291
317
 
@@ -632,6 +658,7 @@ Options:
632
658
  Applies to start/stop/status/logs so an isolated instance
633
659
  can be addressed without cd-ing into its folder.
634
660
  --enable-dynamo-proxy Enable DynamoDB proxy on port 8000 (for start command)
661
+ --self-engine Use the in-process AWS engine instead of LocalStack (no Docker)
635
662
  --external Connect to a LocalStack already running, do not spawn a container
636
663
  --pro Use the LocalStack Pro image (requires LOCALSTACK_AUTH_TOKEN)
637
664
  --localstack-token <token> Pass a LOCALSTACK_AUTH_TOKEN to the container
@@ -671,6 +698,7 @@ Configuration:
671
698
 
672
699
  Examples:
673
700
  npx lss start # Start the orchestrator (managed LocalStack)
701
+ npx lss start --self-engine # Use the in-process engine (no Docker, port 14566)
674
702
  npx lss start --enable-dynamo-proxy # Start with DynamoDB proxy enabled
675
703
  npx lss start --external # Connect to an external LocalStack
676
704
  npx lss start --pro # Use LocalStack Pro (token required)
@@ -0,0 +1,25 @@
1
+ import type { EngineBackend } from '../engine-backend.js';
2
+ export declare class LocalStackBackend implements EngineBackend {
3
+ readonly kind: "localstack";
4
+ private process;
5
+ private _isRunning;
6
+ private endpoint;
7
+ private readonly containerName;
8
+ private readonly configManager;
9
+ constructor();
10
+ start(): Promise<void>;
11
+ stop(): Promise<void>;
12
+ private waitForReady;
13
+ isRunning(): boolean;
14
+ getEndpoint(): string;
15
+ getConfig(): {
16
+ endpoint: string;
17
+ region: string;
18
+ credentials: {
19
+ accessKeyId: string;
20
+ secretAccessKey: string;
21
+ };
22
+ };
23
+ healthDetail(): Record<string, unknown>;
24
+ }
25
+ //# sourceMappingURL=localstack-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localstack-backend.d.ts","sourceRoot":"","sources":["../../../../src/server/engine/backends/localstack-backend.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IAKzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;;IASxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+HtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAwBb,YAAY;IAmB1B,SAAS,IAAI,OAAO;IAIpB,WAAW,IAAI,MAAM;IAIrB,SAAS;;;;;;;;IAWT,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQxC"}
@@ -0,0 +1,199 @@
1
+ // LocalStack lifecycle backend (docker container, managed/external). This is
2
+ // the code that used to live in services/localstack-manager.ts — that module
3
+ // is now a thin facade over EngineManager so existing call sites keep working
4
+ // regardless of the active engine.
5
+ import { spawn, exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import { ConfigManager } from '../../services/config-manager.js';
8
+ const execAsync = promisify(exec);
9
+ export class LocalStackBackend {
10
+ kind = 'localstack';
11
+ process = null;
12
+ _isRunning = false;
13
+ endpoint;
14
+ // Container name is scoped to the LocalStack port so multiple LSS instances
15
+ // (one per example/project) can launch their own container side by side.
16
+ // Falls back to "lss-localstack" when the default 4566 is in use to preserve
17
+ // existing teardown scripts that look for that name.
18
+ containerName;
19
+ configManager;
20
+ constructor() {
21
+ this.configManager = ConfigManager.getInstance();
22
+ this.endpoint = this.configManager.getLocalStackEndpoint();
23
+ const port = this.configManager.getLocalStackPort();
24
+ this.containerName = port === 4566 ? 'lss-localstack' : `lss-localstack-${port}`;
25
+ }
26
+ async start() {
27
+ if (this._isRunning) {
28
+ console.log('⚠️ LocalStack already running');
29
+ return;
30
+ }
31
+ if (this.configManager.isExternal()) {
32
+ console.log(`🔗 Using external LocalStack at ${this.endpoint}`);
33
+ await this.waitForReady(30);
34
+ this._isRunning = true;
35
+ console.log('✅ Connected to external LocalStack');
36
+ return;
37
+ }
38
+ try {
39
+ // Check if docker is available
40
+ await execAsync('docker ps -q', { timeout: 5000 });
41
+ }
42
+ catch {
43
+ throw new Error('Docker is not available or not running. Please start Docker first.');
44
+ }
45
+ const edition = this.configManager.getLocalStackEdition();
46
+ const image = this.configManager.getLocalStackImage();
47
+ const authToken = this.configManager.getLocalStackAuthToken();
48
+ if (edition === 'pro' && !authToken) {
49
+ throw new Error('LocalStack Pro requires LOCALSTACK_AUTH_TOKEN. Set the env var or "localstackAuthToken" in your config.');
50
+ }
51
+ if (edition === 'community' && !authToken) {
52
+ console.warn('⚠️ LOCALSTACK_AUTH_TOKEN not set. Recent localstack/localstack images (>= 2026.5) require a token even for the community edition.');
53
+ }
54
+ return new Promise((resolve, reject) => {
55
+ console.log(`🔄 Starting LocalStack (${image})...`);
56
+ const port = this.configManager.getLocalStackPort();
57
+ const services = this.configManager.getServices().join(',');
58
+ const persistence = this.configManager.isPersistence() ? '1' : '0';
59
+ const debug = this.configManager.isDebug() ? '1' : '0';
60
+ // Persistence volume is scoped to the container so two LSS instances
61
+ // don't share (and corrupt) the same on-disk state.
62
+ const volumeName = this.containerName === 'lss-localstack'
63
+ ? 'lss-localstack-data'
64
+ : `${this.containerName}-data`;
65
+ const dockerArgs = [
66
+ 'run',
67
+ '--rm',
68
+ '-p',
69
+ `${port}:4566`,
70
+ '-v',
71
+ `${volumeName}:/var/lib/localstack`,
72
+ '-v',
73
+ '/var/run/docker.sock:/var/run/docker.sock',
74
+ // host.docker.internal resolves natively on Docker Desktop but not on
75
+ // Linux; the generated Lambda proxies target it to reach the invoke
76
+ // listeners on the host, so map it explicitly (no-op elsewhere).
77
+ '--add-host',
78
+ 'host.docker.internal:host-gateway',
79
+ '--name',
80
+ this.containerName,
81
+ '-e',
82
+ `SERVICES=${services}`,
83
+ '-e',
84
+ 'LAMBDA_EXECUTOR=local',
85
+ // Lambda containers spawned BY LocalStack (which run the proxies) need
86
+ // the same host mapping.
87
+ '-e',
88
+ 'LAMBDA_DOCKER_FLAGS=--add-host host.docker.internal:host-gateway',
89
+ '-e',
90
+ `PERSISTENCE=${persistence}`,
91
+ '-e',
92
+ `DEBUG=${debug}`,
93
+ ];
94
+ if (authToken) {
95
+ dockerArgs.push('-e', `LOCALSTACK_AUTH_TOKEN=${authToken}`);
96
+ }
97
+ dockerArgs.push(image);
98
+ this.process = spawn('docker', dockerArgs);
99
+ let stderr = '';
100
+ let stdout = '';
101
+ this.process.stderr?.on('data', data => {
102
+ stderr += data.toString();
103
+ console.log(`[LocalStack stderr] ${data.toString().trim()}`);
104
+ });
105
+ this.process.stdout?.on('data', data => {
106
+ stdout += data.toString();
107
+ console.log(`[LocalStack stdout] ${data.toString().trim()}`);
108
+ });
109
+ this.process.on('error', error => {
110
+ console.error('❌ Failed to start LocalStack process:', error);
111
+ reject(error);
112
+ });
113
+ this.process.on('close', code => {
114
+ if (code !== 0) {
115
+ console.error(`Container exited with code ${code}`);
116
+ console.error('stderr:', stderr);
117
+ console.error('stdout:', stdout);
118
+ }
119
+ });
120
+ // Wait for LocalStack to be ready
121
+ this.waitForReady()
122
+ .then(() => {
123
+ this._isRunning = true;
124
+ console.log('✅ LocalStack ready');
125
+ resolve();
126
+ })
127
+ .catch(err => {
128
+ console.error('LocalStack startup error:', err.message);
129
+ this.process?.kill();
130
+ reject(err);
131
+ });
132
+ });
133
+ }
134
+ async stop() {
135
+ if (!this._isRunning) {
136
+ return;
137
+ }
138
+ if (this.configManager.isExternal()) {
139
+ console.log('🔗 External LocalStack — leaving container untouched');
140
+ this._isRunning = false;
141
+ return;
142
+ }
143
+ console.log('🛑 Stopping LocalStack...');
144
+ try {
145
+ await execAsync(`docker stop ${this.containerName}`, { timeout: 10000 });
146
+ this._isRunning = false;
147
+ this.process = null;
148
+ console.log('✅ LocalStack stopped');
149
+ }
150
+ catch (error) {
151
+ console.error('⚠️ Error stopping LocalStack:', error instanceof Error ? error.message : 'Unknown error');
152
+ this._isRunning = false;
153
+ }
154
+ }
155
+ async waitForReady(maxAttempts = 120) {
156
+ for (let i = 0; i < maxAttempts; i++) {
157
+ try {
158
+ const response = await fetch(`${this.endpoint}/_localstack/health`);
159
+ if (response.ok) {
160
+ console.log(`✓ LocalStack is responsive (attempt ${i + 1}/${maxAttempts})`);
161
+ return;
162
+ }
163
+ }
164
+ catch {
165
+ // Ignore fetch errors during startup
166
+ if (i % 20 === 0) {
167
+ console.log(`⏳ Waiting for LocalStack... (attempt ${i + 1}/${maxAttempts})`);
168
+ }
169
+ }
170
+ await new Promise(resolve => setTimeout(resolve, 1000));
171
+ }
172
+ throw new Error('LocalStack failed to start in time (120s timeout). Check Docker and container logs.');
173
+ }
174
+ isRunning() {
175
+ return this._isRunning;
176
+ }
177
+ getEndpoint() {
178
+ return this.endpoint;
179
+ }
180
+ getConfig() {
181
+ return {
182
+ endpoint: this.endpoint,
183
+ region: this.configManager.getRegion(),
184
+ credentials: {
185
+ accessKeyId: process.env.LOCALSTACK_ACCESS_KEY_ID || 'test',
186
+ secretAccessKey: process.env.LOCALSTACK_SECRET_ACCESS_KEY || 'test',
187
+ },
188
+ };
189
+ }
190
+ healthDetail() {
191
+ return {
192
+ endpoint: this.endpoint,
193
+ running: this._isRunning,
194
+ mode: this.configManager.getMode(),
195
+ image: this.configManager.isExternal() ? undefined : this.configManager.getLocalStackImage(),
196
+ };
197
+ }
198
+ }
199
+ //# sourceMappingURL=localstack-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localstack-backend.js","sourceRoot":"","sources":["../../../../src/server/engine/backends/localstack-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { EngineBackend } from '../engine-backend.js';
2
+ import { type ResolvedSelfEngineConfig } from '../../services/config-manager.js';
3
+ export declare class SelfEngineBackend implements EngineBackend {
4
+ readonly kind: "self";
5
+ private readonly overrides;
6
+ private config;
7
+ private store;
8
+ private sqs;
9
+ private dispatcher;
10
+ private scheduler;
11
+ private server;
12
+ private readonly sockets;
13
+ private boundPort;
14
+ private running;
15
+ constructor(overrides?: Partial<ResolvedSelfEngineConfig>);
16
+ start(): Promise<void>;
17
+ stop(): Promise<void>;
18
+ isRunning(): boolean;
19
+ getEndpoint(): string;
20
+ getConfig(): {
21
+ endpoint: string;
22
+ region: string;
23
+ credentials: {
24
+ accessKeyId: string;
25
+ secretAccessKey: string;
26
+ };
27
+ };
28
+ healthDetail(): Record<string, unknown>;
29
+ private resolveConfig;
30
+ private restoreSqsSnapshot;
31
+ }
32
+ //# sourceMappingURL=self-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-backend.d.ts","sourceRoot":"","sources":["../../../../src/server/engine/backends/self-backend.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAmBhG,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;IAEhC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,OAAO,CAAC,MAAM,CAAyC;IACvD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,MAAM,CAA4B;IAE1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAS;gBAIZ,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM;IAIvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsFtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC3B,SAAS,IAAI,OAAO;IAIpB,WAAW,IAAI,MAAM;IAIrB,SAAS,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;IAQhH,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAYvC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,kBAAkB;CAU3B"}