rightsize 0.1.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 (216) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +255 -0
  4. package/dist/backend-docker/backend.d.ts +53 -0
  5. package/dist/backend-docker/backend.d.ts.map +1 -0
  6. package/dist/backend-docker/backend.js +336 -0
  7. package/dist/backend-docker/backend.js.map +1 -0
  8. package/dist/backend-docker/client.d.ts +82 -0
  9. package/dist/backend-docker/client.d.ts.map +1 -0
  10. package/dist/backend-docker/client.js +190 -0
  11. package/dist/backend-docker/client.js.map +1 -0
  12. package/dist/backend-docker/frames.d.ts +44 -0
  13. package/dist/backend-docker/frames.d.ts.map +1 -0
  14. package/dist/backend-docker/frames.js +105 -0
  15. package/dist/backend-docker/frames.js.map +1 -0
  16. package/dist/backend-docker/index.d.ts +5 -0
  17. package/dist/backend-docker/index.d.ts.map +1 -0
  18. package/dist/backend-docker/index.js +20 -0
  19. package/dist/backend-docker/index.js.map +1 -0
  20. package/dist/backend-docker/json.d.ts +13 -0
  21. package/dist/backend-docker/json.d.ts.map +1 -0
  22. package/dist/backend-docker/json.js +47 -0
  23. package/dist/backend-docker/json.js.map +1 -0
  24. package/dist/backend-docker/labels.d.ts +11 -0
  25. package/dist/backend-docker/labels.d.ts.map +1 -0
  26. package/dist/backend-docker/labels.js +13 -0
  27. package/dist/backend-docker/labels.js.map +1 -0
  28. package/dist/backend-docker/port-conflict.d.ts +9 -0
  29. package/dist/backend-docker/port-conflict.d.ts.map +1 -0
  30. package/dist/backend-docker/port-conflict.js +12 -0
  31. package/dist/backend-docker/port-conflict.js.map +1 -0
  32. package/dist/backend-docker/provider.d.ts +22 -0
  33. package/dist/backend-docker/provider.d.ts.map +1 -0
  34. package/dist/backend-docker/provider.js +36 -0
  35. package/dist/backend-docker/provider.js.map +1 -0
  36. package/dist/backend-msb/backend.d.ts +169 -0
  37. package/dist/backend-msb/backend.d.ts.map +1 -0
  38. package/dist/backend-msb/backend.js +664 -0
  39. package/dist/backend-msb/backend.js.map +1 -0
  40. package/dist/backend-msb/commands.d.ts +28 -0
  41. package/dist/backend-msb/commands.d.ts.map +1 -0
  42. package/dist/backend-msb/commands.js +67 -0
  43. package/dist/backend-msb/commands.js.map +1 -0
  44. package/dist/backend-msb/exec-tunnel.d.ts +33 -0
  45. package/dist/backend-msb/exec-tunnel.d.ts.map +1 -0
  46. package/dist/backend-msb/exec-tunnel.js +163 -0
  47. package/dist/backend-msb/exec-tunnel.js.map +1 -0
  48. package/dist/backend-msb/follow-replay.d.ts +14 -0
  49. package/dist/backend-msb/follow-replay.d.ts.map +1 -0
  50. package/dist/backend-msb/follow-replay.js +20 -0
  51. package/dist/backend-msb/follow-replay.js.map +1 -0
  52. package/dist/backend-msb/image-cache.d.ts +25 -0
  53. package/dist/backend-msb/image-cache.d.ts.map +1 -0
  54. package/dist/backend-msb/image-cache.js +27 -0
  55. package/dist/backend-msb/image-cache.js.map +1 -0
  56. package/dist/backend-msb/index.d.ts +6 -0
  57. package/dist/backend-msb/index.d.ts.map +1 -0
  58. package/dist/backend-msb/index.js +21 -0
  59. package/dist/backend-msb/index.js.map +1 -0
  60. package/dist/backend-msb/invoke.d.ts +24 -0
  61. package/dist/backend-msb/invoke.d.ts.map +1 -0
  62. package/dist/backend-msb/invoke.js +73 -0
  63. package/dist/backend-msb/invoke.js.map +1 -0
  64. package/dist/backend-msb/ls-json.d.ts +23 -0
  65. package/dist/backend-msb/ls-json.d.ts.map +1 -0
  66. package/dist/backend-msb/ls-json.js +113 -0
  67. package/dist/backend-msb/ls-json.js.map +1 -0
  68. package/dist/backend-msb/network-links.d.ts +7 -0
  69. package/dist/backend-msb/network-links.d.ts.map +1 -0
  70. package/dist/backend-msb/network-links.js +31 -0
  71. package/dist/backend-msb/network-links.js.map +1 -0
  72. package/dist/backend-msb/platform.d.ts +29 -0
  73. package/dist/backend-msb/platform.d.ts.map +1 -0
  74. package/dist/backend-msb/platform.js +128 -0
  75. package/dist/backend-msb/platform.js.map +1 -0
  76. package/dist/backend-msb/port-conflict.d.ts +10 -0
  77. package/dist/backend-msb/port-conflict.d.ts.map +1 -0
  78. package/dist/backend-msb/port-conflict.js +16 -0
  79. package/dist/backend-msb/port-conflict.js.map +1 -0
  80. package/dist/backend-msb/provider.d.ts +10 -0
  81. package/dist/backend-msb/provider.d.ts.map +1 -0
  82. package/dist/backend-msb/provider.js +40 -0
  83. package/dist/backend-msb/provider.js.map +1 -0
  84. package/dist/backend-msb/provisioner.d.ts +19 -0
  85. package/dist/backend-msb/provisioner.d.ts.map +1 -0
  86. package/dist/backend-msb/provisioner.js +332 -0
  87. package/dist/backend-msb/provisioner.js.map +1 -0
  88. package/dist/backend-msb/reaper.d.ts +10 -0
  89. package/dist/backend-msb/reaper.d.ts.map +1 -0
  90. package/dist/backend-msb/reaper.js +73 -0
  91. package/dist/backend-msb/reaper.js.map +1 -0
  92. package/dist/core/backend.d.ts +98 -0
  93. package/dist/core/backend.d.ts.map +1 -0
  94. package/dist/core/backend.js +2 -0
  95. package/dist/core/backend.js.map +1 -0
  96. package/dist/core/backends.d.ts +37 -0
  97. package/dist/core/backends.d.ts.map +1 -0
  98. package/dist/core/backends.js +127 -0
  99. package/dist/core/backends.js.map +1 -0
  100. package/dist/core/cleanup.d.ts +37 -0
  101. package/dist/core/cleanup.d.ts.map +1 -0
  102. package/dist/core/cleanup.js +86 -0
  103. package/dist/core/cleanup.js.map +1 -0
  104. package/dist/core/errors.d.ts +51 -0
  105. package/dist/core/errors.d.ts.map +1 -0
  106. package/dist/core/errors.js +66 -0
  107. package/dist/core/errors.js.map +1 -0
  108. package/dist/core/free-ports.d.ts +23 -0
  109. package/dist/core/free-ports.d.ts.map +1 -0
  110. package/dist/core/free-ports.js +63 -0
  111. package/dist/core/free-ports.js.map +1 -0
  112. package/dist/core/generic-container.d.ts +145 -0
  113. package/dist/core/generic-container.d.ts.map +1 -0
  114. package/dist/core/generic-container.js +385 -0
  115. package/dist/core/generic-container.js.map +1 -0
  116. package/dist/core/model.d.ts +60 -0
  117. package/dist/core/model.d.ts.map +1 -0
  118. package/dist/core/model.js +2 -0
  119. package/dist/core/model.js.map +1 -0
  120. package/dist/core/mountable-file.d.ts +25 -0
  121. package/dist/core/mountable-file.d.ts.map +1 -0
  122. package/dist/core/mountable-file.js +64 -0
  123. package/dist/core/mountable-file.js.map +1 -0
  124. package/dist/core/network.d.ts +58 -0
  125. package/dist/core/network.d.ts.map +1 -0
  126. package/dist/core/network.js +84 -0
  127. package/dist/core/network.js.map +1 -0
  128. package/dist/core/run-id.d.ts +18 -0
  129. package/dist/core/run-id.d.ts.map +1 -0
  130. package/dist/core/run-id.js +18 -0
  131. package/dist/core/run-id.js.map +1 -0
  132. package/dist/core/wait.d.ts +59 -0
  133. package/dist/core/wait.d.ts.map +1 -0
  134. package/dist/core/wait.js +239 -0
  135. package/dist/core/wait.js.map +1 -0
  136. package/dist/index.d.ts +26 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +22 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/modules/arango.d.ts +14 -0
  141. package/dist/modules/arango.d.ts.map +1 -0
  142. package/dist/modules/arango.js +37 -0
  143. package/dist/modules/arango.js.map +1 -0
  144. package/dist/modules/clickhouse.d.ts +37 -0
  145. package/dist/modules/clickhouse.d.ts.map +1 -0
  146. package/dist/modules/clickhouse.js +69 -0
  147. package/dist/modules/clickhouse.js.map +1 -0
  148. package/dist/modules/flink.d.ts +65 -0
  149. package/dist/modules/flink.d.ts.map +1 -0
  150. package/dist/modules/flink.js +125 -0
  151. package/dist/modules/flink.js.map +1 -0
  152. package/dist/modules/floci.d.ts +46 -0
  153. package/dist/modules/floci.d.ts.map +1 -0
  154. package/dist/modules/floci.js +62 -0
  155. package/dist/modules/floci.js.map +1 -0
  156. package/dist/modules/index.d.ts +31 -0
  157. package/dist/modules/index.d.ts.map +1 -0
  158. package/dist/modules/index.js +31 -0
  159. package/dist/modules/index.js.map +1 -0
  160. package/dist/modules/kafka.d.ts +11 -0
  161. package/dist/modules/kafka.d.ts.map +1 -0
  162. package/dist/modules/kafka.js +41 -0
  163. package/dist/modules/kafka.js.map +1 -0
  164. package/dist/modules/keycloak.d.ts +40 -0
  165. package/dist/modules/keycloak.d.ts.map +1 -0
  166. package/dist/modules/keycloak.js +67 -0
  167. package/dist/modules/keycloak.js.map +1 -0
  168. package/dist/modules/mariadb.d.ts +56 -0
  169. package/dist/modules/mariadb.d.ts.map +1 -0
  170. package/dist/modules/mariadb.js +89 -0
  171. package/dist/modules/mariadb.js.map +1 -0
  172. package/dist/modules/memcached.d.ts +24 -0
  173. package/dist/modules/memcached.d.ts.map +1 -0
  174. package/dist/modules/memcached.js +105 -0
  175. package/dist/modules/memcached.js.map +1 -0
  176. package/dist/modules/mongodb.d.ts +19 -0
  177. package/dist/modules/mongodb.d.ts.map +1 -0
  178. package/dist/modules/mongodb.js +69 -0
  179. package/dist/modules/mongodb.js.map +1 -0
  180. package/dist/modules/mysql.d.ts +61 -0
  181. package/dist/modules/mysql.d.ts.map +1 -0
  182. package/dist/modules/mysql.js +101 -0
  183. package/dist/modules/mysql.js.map +1 -0
  184. package/dist/modules/neo4j.d.ts +60 -0
  185. package/dist/modules/neo4j.d.ts.map +1 -0
  186. package/dist/modules/neo4j.js +83 -0
  187. package/dist/modules/neo4j.js.map +1 -0
  188. package/dist/modules/pinot.d.ts +30 -0
  189. package/dist/modules/pinot.d.ts.map +1 -0
  190. package/dist/modules/pinot.js +49 -0
  191. package/dist/modules/pinot.js.map +1 -0
  192. package/dist/modules/postgres.d.ts +29 -0
  193. package/dist/modules/postgres.d.ts.map +1 -0
  194. package/dist/modules/postgres.js +73 -0
  195. package/dist/modules/postgres.js.map +1 -0
  196. package/dist/modules/rabbitmq.d.ts +46 -0
  197. package/dist/modules/rabbitmq.d.ts.map +1 -0
  198. package/dist/modules/rabbitmq.js +68 -0
  199. package/dist/modules/rabbitmq.js.map +1 -0
  200. package/dist/modules/redis.d.ts +15 -0
  201. package/dist/modules/redis.d.ts.map +1 -0
  202. package/dist/modules/redis.js +24 -0
  203. package/dist/modules/redis.js.map +1 -0
  204. package/dist/modules/redpanda.d.ts +27 -0
  205. package/dist/modules/redpanda.d.ts.map +1 -0
  206. package/dist/modules/redpanda.js +58 -0
  207. package/dist/modules/redpanda.js.map +1 -0
  208. package/dist/modules/spring-cloud-config.d.ts +18 -0
  209. package/dist/modules/spring-cloud-config.d.ts.map +1 -0
  210. package/dist/modules/spring-cloud-config.js +28 -0
  211. package/dist/modules/spring-cloud-config.js.map +1 -0
  212. package/dist/modules/wiremock.d.ts +21 -0
  213. package/dist/modules/wiremock.d.ts.map +1 -0
  214. package/dist/modules/wiremock.js +32 -0
  215. package/dist/modules/wiremock.js.map +1 -0
  216. package/package.json +104 -0
@@ -0,0 +1,385 @@
1
+ import { FreePorts } from "./free-ports.js";
2
+ import { RunId } from "./run-id.js";
3
+ import { Network } from "./network.js";
4
+ import { PortBindConflictError } from "./errors.js";
5
+ import { Wait } from "./wait.js";
6
+ import { registerSyncCleanup, unregisterSyncCleanup } from "./cleanup.js";
7
+ import { Backends } from "./backends.js";
8
+ const MAX_START_ATTEMPTS = 5;
9
+ let sequence = 0;
10
+ function nextSequence() {
11
+ sequence += 1;
12
+ return sequence;
13
+ }
14
+ /** Typed-first, string-fallback classification: walk the cause chain for a typed conflict, else match the daemon's own wording. */
15
+ function isPortBindConflict(err) {
16
+ let current = err;
17
+ const seen = new Set();
18
+ while (current !== undefined && current !== null && !seen.has(current)) {
19
+ seen.add(current);
20
+ if (current instanceof PortBindConflictError) {
21
+ return true;
22
+ }
23
+ if (current instanceof Error) {
24
+ const message = current.message.toLowerCase();
25
+ if (message.includes("address already in use") || message.includes("already allocated")) {
26
+ return true;
27
+ }
28
+ current = current.cause;
29
+ continue;
30
+ }
31
+ break;
32
+ }
33
+ return false;
34
+ }
35
+ async function swallow(fn) {
36
+ try {
37
+ await fn();
38
+ }
39
+ catch {
40
+ // Best-effort teardown: never let a stop/remove failure escape.
41
+ }
42
+ }
43
+ /**
44
+ * The builder, launcher, and lifecycle guard for a single container — the
45
+ * one class every module (`RedisContainer`, `PostgresContainer`, …)
46
+ * subclasses, and the type you reach for directly for any image without a
47
+ * dedicated module.
48
+ *
49
+ * The instance itself is the guard: after `start()` it implements
50
+ * `Symbol.asyncDispose`, so `await using c = await new GenericContainer(img).start()`
51
+ * tears the container down at scope exit — the direct analog of the Kotlin
52
+ * `AutoCloseable` and Rust's RAII guard, expressed with TC39 explicit
53
+ * resource management. Prefer `await using` in tests; use explicit
54
+ * `start()`/`stop()` where a `using` scope can't span a framework hook
55
+ * (Jest/Vitest `beforeAll`/`afterAll`, `node:test`'s `before`/`after`).
56
+ *
57
+ * Builder methods (`withEnv`, `withExposedPorts`, …) mutate and return
58
+ * `this` for chaining; none of them perform I/O. All I/O happens in
59
+ * `start()`, `stop()`, `exec()`, `logs()`, and `followOutput()`.
60
+ */
61
+ export class GenericContainer {
62
+ image;
63
+ envPairs = [];
64
+ exposedPorts = [];
65
+ command;
66
+ network;
67
+ aliasNames = [];
68
+ mounts = [];
69
+ waitStrategy = Wait.forListeningPort();
70
+ memoryLimitMb;
71
+ backendOverride;
72
+ handle;
73
+ backend;
74
+ mappedPorts = new Map();
75
+ running = false;
76
+ /** Builds against `image` (e.g. `"redis:8.6-alpine"`); no I/O happens until `start()`. */
77
+ constructor(image) {
78
+ this.image = image;
79
+ }
80
+ /** Convenience: `new GenericContainer(image).start()`. */
81
+ static async start(image) {
82
+ return new GenericContainer(image).start();
83
+ }
84
+ /** Sets an environment variable visible to the workload. Last-write-wins if called again with the same key; insertion order is otherwise preserved. */
85
+ withEnv(key, value) {
86
+ // Last-write-wins, insertion-ordered: drop any prior entry for this key
87
+ // before pushing, so re-setting the same key doesn't duplicate it while
88
+ // still preserving the position of first-set for untouched keys.
89
+ this.envPairs = this.envPairs.filter(([k]) => k !== key);
90
+ this.envPairs.push([key, value]);
91
+ return this;
92
+ }
93
+ /** Publishes these guest ports to pre-allocated host ports (see `FreePorts`); read back with `getMappedPort`. */
94
+ withExposedPorts(...ports) {
95
+ this.exposedPorts.push(...ports);
96
+ return this;
97
+ }
98
+ /** Overrides the image's default ENTRYPOINT/CMD. Omit this call entirely to run the image unmodified. */
99
+ withCommand(...cmd) {
100
+ this.command = cmd;
101
+ return this;
102
+ }
103
+ /** Joins a `Network`, making this container reachable from (and to) its running siblings by alias. */
104
+ withNetwork(net) {
105
+ this.network = net;
106
+ return this;
107
+ }
108
+ /** Names this container answers to on its network — resolved via `Network.resolve(alias, port)` by other members. */
109
+ withNetworkAliases(...names) {
110
+ this.aliasNames.push(...names);
111
+ return this;
112
+ }
113
+ /** Copies a host file into the guest before boot, at `guestPath`. Read-only is enforced on docker; advisory only on msb (current microsandbox releases do not enforce guest-side read-only mounts). */
114
+ withCopyFileToContainer(file, guestPath) {
115
+ this.mounts.push({ hostPath: file.path, guestPath, readOnly: true });
116
+ return this;
117
+ }
118
+ /** Sets the readiness check `start()` waits on before returning. Defaults to `Wait.forListeningPort()`. */
119
+ waitingFor(strategy) {
120
+ this.waitStrategy = strategy;
121
+ return this;
122
+ }
123
+ /** Raises the container's memory ceiling (in MB) above the backend default — required by several JVM-heavy modules; see each module's own doc for its pin. */
124
+ withMemoryLimit(megabytes) {
125
+ this.memoryLimitMb = megabytes;
126
+ return this;
127
+ }
128
+ /** Test/advanced seam: pin the backend instead of resolving `Backends.active()`. */
129
+ withBackend(backend) {
130
+ this.backendOverride = backend;
131
+ return this;
132
+ }
133
+ /** Module hook: override to adjust the spec once mapped host ports are known (e.g. advertised listeners). */
134
+ customizeSpec(spec, _mapped) {
135
+ return spec;
136
+ }
137
+ /** Module hook: runs after the container is ready (e.g. `rs.initiate` for a Mongo replica set). */
138
+ containerIsStarted() {
139
+ return Promise.resolve();
140
+ }
141
+ /**
142
+ * Module hook: runs the instant `start()` has resolved a backend — before
143
+ * `ensureNetwork`, port allocation, or the container is created/booted at
144
+ * all. The one hook that lets a module reject a backend-incompatible
145
+ * request (e.g. `FlinkContainer.withTaskManager()` on msb) without paying
146
+ * for a boot first. `containerIsStarted()` (above) fires much later, after
147
+ * this container is already fully up — fine for post-readiness setup, but
148
+ * too late to fail fast on a capability the backend never had.
149
+ */
150
+ containerIsStarting(_backend) {
151
+ return Promise.resolve();
152
+ }
153
+ /**
154
+ * Module hook: the backend this container actually started against (only
155
+ * valid once `start()` has resolved it — i.e. from within
156
+ * `containerIsStarted()` or later). Lets a module's post-start hook branch
157
+ * on backend capability, e.g. `FlinkContainer.withTaskManager()` throwing a
158
+ * typed `UnsupportedByBackendError` on msb instead of attempting a
159
+ * network-link topology the exec-tunnel emulation can't carry.
160
+ */
161
+ currentBackend() {
162
+ return this.requireHandle().backend;
163
+ }
164
+ resolveBackend() {
165
+ return this.backendOverride ?? Backends.active();
166
+ }
167
+ buildSpec(name, ports) {
168
+ const spec = {
169
+ name,
170
+ image: this.image,
171
+ env: this.envPairs.map(([k, v]) => [k, v]),
172
+ command: this.command,
173
+ ports: this.exposedPorts.map((guestPort) => ({
174
+ hostPort: ports.get(guestPort) ?? (() => {
175
+ throw new Error(`no allocated host port for guest port ${guestPort}`);
176
+ })(),
177
+ guestPort,
178
+ })),
179
+ mounts: this.mounts,
180
+ networkId: this.network?.id,
181
+ aliases: this.aliasNames,
182
+ runId: RunId.value,
183
+ memoryLimitMb: this.memoryLimitMb,
184
+ };
185
+ return this.customizeSpec(spec, (guest) => {
186
+ const p = ports.get(guest);
187
+ if (p === undefined) {
188
+ throw new Error(`no allocated host port for guest port ${guest}`);
189
+ }
190
+ return p;
191
+ });
192
+ }
193
+ async allocatePorts() {
194
+ const ports = new Map();
195
+ for (const guestPort of this.exposedPorts) {
196
+ ports.set(guestPort, await FreePorts.allocate());
197
+ }
198
+ return ports;
199
+ }
200
+ releasePorts(ports) {
201
+ for (const hostPort of ports.values()) {
202
+ FreePorts.release(hostPort);
203
+ }
204
+ }
205
+ /**
206
+ * Resolves a backend, allocates host ports, boots the container, installs
207
+ * any network links, and waits for readiness — in that order. On ANY
208
+ * failure partway through (a bad wait strategy, a network-link rejection,
209
+ * a port conflict exhausting its retries), everything already allocated
210
+ * or started is torn down to completion BEFORE this rejects: a
211
+ * half-started container never leaks, and there is no detached
212
+ * fire-and-forget cleanup running after `start()` has already returned
213
+ * control to the caller.
214
+ *
215
+ * A host-port-bind conflict specifically retries (up to 5 attempts) with
216
+ * a fresh port before giving up — pre-allocated ports can still lose an
217
+ * allocate-then-bind race to an unrelated process.
218
+ */
219
+ async start() {
220
+ const backend = this.resolveBackend();
221
+ await this.containerIsStarting(backend);
222
+ if (this.network !== undefined) {
223
+ await backend.ensureNetwork(this.network.id);
224
+ }
225
+ let handle;
226
+ let allocatedPorts;
227
+ let lastConflict;
228
+ for (let attempt = 0; attempt < MAX_START_ATTEMPTS; attempt++) {
229
+ const ports = await this.allocatePorts();
230
+ const name = `rz-${RunId.value}-${nextSequence()}`;
231
+ const spec = this.buildSpec(name, ports);
232
+ let createdHandle;
233
+ try {
234
+ createdHandle = await backend.create(spec);
235
+ await backend.start(createdHandle);
236
+ handle = createdHandle;
237
+ allocatedPorts = ports;
238
+ break;
239
+ }
240
+ catch (err) {
241
+ if (createdHandle !== undefined) {
242
+ await swallow(() => backend.stop(createdHandle));
243
+ await swallow(() => backend.remove(createdHandle));
244
+ }
245
+ this.releasePorts(ports);
246
+ if (isPortBindConflict(err)) {
247
+ lastConflict = err;
248
+ continue;
249
+ }
250
+ throw err;
251
+ }
252
+ }
253
+ if (handle === undefined || allocatedPorts === undefined) {
254
+ throw new Error(`Failed to start '${this.image}' after ${MAX_START_ATTEMPTS} attempts: every attempt hit a host port already in use by another process.`, { cause: lastConflict });
255
+ }
256
+ // Assign guard fields: getMappedPort/exec/logs become valid from here,
257
+ // even though readiness (below) hasn't been confirmed yet.
258
+ this.handle = handle;
259
+ this.backend = backend;
260
+ this.mappedPorts = allocatedPorts;
261
+ this.running = true;
262
+ // Real synchronous teardown, not a placeholder: if this process dies
263
+ // before stop() ever runs (process.exit / SIGINT / SIGTERM), the exit
264
+ // hook in cleanup.ts calls straight into the backend's own blocking
265
+ // cleanupSync — the same mechanism a crash-recovery reaper would find
266
+ // necessary, just invoked proactively instead of on the next process's
267
+ // startup sweep.
268
+ registerSyncCleanup(handle.id, () => backend.cleanupSync(handle.id));
269
+ try {
270
+ const links = this.network?.linksForNewMember() ?? [];
271
+ await backend.installNetworkLinks(handle, links);
272
+ // Register AFTER links are computed/installed — a container must
273
+ // never see itself in its own linksForNewMember() call.
274
+ if (this.network !== undefined) {
275
+ this.network.register(this, this.aliasNames, backend);
276
+ }
277
+ await this.waitStrategy.waitUntilReady(this.asWaitTarget());
278
+ }
279
+ catch (err) {
280
+ // A half-started container never leaks regardless of which step
281
+ // failed: tear down to completion before this rejection surfaces, so
282
+ // start() never returns until cleanup has actually finished.
283
+ await this.stop();
284
+ throw err;
285
+ }
286
+ await this.containerIsStarted();
287
+ return this;
288
+ }
289
+ /**
290
+ * Stops and removes the backend-native resource, releases its host ports,
291
+ * and clears the port map. Idempotent — stopping a container that never
292
+ * started, or stopping twice, is a harmless no-op the second time. Errors
293
+ * from the backend's own stop/remove calls are swallowed (best-effort
294
+ * teardown); this method itself never throws.
295
+ */
296
+ async stop() {
297
+ if (!this.running || this.handle === undefined || this.backend === undefined) {
298
+ return;
299
+ }
300
+ const handle = this.handle;
301
+ const backend = this.backend;
302
+ this.running = false;
303
+ await swallow(() => backend.stop(handle));
304
+ await swallow(() => backend.remove(handle));
305
+ unregisterSyncCleanup(handle.id);
306
+ this.releasePorts(this.mappedPorts);
307
+ this.mappedPorts = new Map();
308
+ }
309
+ /** `= stop()`. What `await using c = await new GenericContainer(img).start()` calls at scope exit; never throws. */
310
+ async [Symbol.asyncDispose]() {
311
+ await this.stop();
312
+ }
313
+ /** Whether `start()` has completed and `stop()` has not yet run. */
314
+ get isRunning() {
315
+ return this.running;
316
+ }
317
+ /** Always `"127.0.0.1"` — every published port is bound loopback-only, on both backends. */
318
+ get host() {
319
+ return "127.0.0.1";
320
+ }
321
+ /** Names this container answers to on its network (set via `withNetworkAliases`). Part of the `NetworkMember` contract `Network` uses to compute links. */
322
+ get aliases() {
323
+ return this.aliasNames;
324
+ }
325
+ /** Every guest port this container published (set via `withExposedPorts`). Part of the `NetworkMember` contract `Network` uses to compute links. */
326
+ get exposedGuestPorts() {
327
+ return this.exposedPorts;
328
+ }
329
+ /**
330
+ * The host port bound to `guestPort`. Throws distinct messages for the
331
+ * two ways this can be wrong: not running at all ("call start() first"),
332
+ * versus running but never exposed via `withExposedPorts` ("call
333
+ * withExposedPorts(N)").
334
+ */
335
+ getMappedPort(guestPort) {
336
+ if (!this.running) {
337
+ throw new Error(`Container is not running — call start() first.`);
338
+ }
339
+ const port = this.mappedPorts.get(guestPort);
340
+ if (port === undefined) {
341
+ throw new Error(`Port ${guestPort} is not exposed — call withExposedPorts(${guestPort}).`);
342
+ }
343
+ return port;
344
+ }
345
+ /** Alias for `getMappedPort`, satisfying the `NetworkMember`/`WaitTarget` shape (which name this method `mappedPort`). */
346
+ mappedPort(guestPort) {
347
+ return this.getMappedPort(guestPort);
348
+ }
349
+ requireHandle() {
350
+ if (!this.running || this.handle === undefined || this.backend === undefined) {
351
+ throw new Error(`Container is not running — call start() first.`);
352
+ }
353
+ return { handle: this.handle, backend: this.backend };
354
+ }
355
+ /** Runs a one-shot command inside the running container and waits for it to exit. Throws if the container is not running. */
356
+ async exec(...cmd) {
357
+ const { handle, backend } = this.requireHandle();
358
+ return backend.exec(handle, cmd);
359
+ }
360
+ /** Fetches the workload's logs so far (a bounded tail), as a single string. Throws if the container is not running. */
361
+ async logs() {
362
+ const { handle, backend } = this.requireHandle();
363
+ return backend.logs(handle);
364
+ }
365
+ /**
366
+ * Streams log lines to `consumer` as they're produced, in order, with no
367
+ * duplicates — including the final unterminated fragment after the
368
+ * workload exits, delivered exactly once. Call `close()` on the returned
369
+ * handle to stop delivery without flushing anything further.
370
+ */
371
+ async followOutput(consumer) {
372
+ const { handle, backend } = this.requireHandle();
373
+ return backend.followLogs(handle, consumer);
374
+ }
375
+ asWaitTarget() {
376
+ return {
377
+ host: this.host,
378
+ mappedPort: (guestPort) => this.getMappedPort(guestPort),
379
+ exposedGuestPorts: this.exposedPorts,
380
+ currentLogs: () => this.logs(),
381
+ describe: () => `container '${this.handle?.spec.name ?? this.image}' (image ${this.image})`,
382
+ };
383
+ }
384
+ }
385
+ //# sourceMappingURL=generic-container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic-container.js","sourceRoot":"","sources":["../../src/core/generic-container.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /** A published container port: a host port already chosen, mapped to the port the workload listens on inside the guest. */
2
+ export interface PortBinding {
3
+ /** The host-side port, pre-allocated by `FreePorts`. */
4
+ readonly hostPort: number;
5
+ /** The port the workload listens on inside the guest. */
6
+ readonly guestPort: number;
7
+ }
8
+ /** A host path copied into the guest before boot. `readOnly` defaults to `true` via the builder (`withCopyFileToContainer`). */
9
+ export interface FileMount {
10
+ /** Absolute host path to copy from. */
11
+ readonly hostPath: string;
12
+ /** Destination path inside the guest. */
13
+ readonly guestPath: string;
14
+ /** Enforced on docker; advisory only on msb (current microsandbox releases do not enforce guest-side read-only mounts). */
15
+ readonly readOnly: boolean;
16
+ }
17
+ /** The result of a one-shot `exec` inside a running container. */
18
+ export interface ExecResult {
19
+ /** The command's process exit code. */
20
+ readonly exitCode: number;
21
+ /** Everything the command wrote to stdout. */
22
+ readonly stdout: string;
23
+ /** Everything the command wrote to stderr. */
24
+ readonly stderr: string;
25
+ }
26
+ /**
27
+ * The immutable, backend-agnostic description of a container to launch.
28
+ * Built by `GenericContainer` from its builder calls and handed to
29
+ * whichever `SandboxBackend` is active — a spec never changes once built,
30
+ * and building one performs no I/O.
31
+ */
32
+ export interface ContainerSpec {
33
+ /** The container's name, `rz-<runId>-<seq>`. */
34
+ readonly name: string;
35
+ /** The image reference, e.g. `"redis:8.6-alpine"`. */
36
+ readonly image: string;
37
+ /**
38
+ * Array of pairs, not a Map: insertion-ordered, with last-write-wins
39
+ * deduping when a builder overwrites a key.
40
+ */
41
+ readonly env: ReadonlyArray<readonly [string, string]>;
42
+ /** `undefined` means the image's own ENTRYPOINT/CMD runs unmodified. */
43
+ readonly command: ReadonlyArray<string> | undefined;
44
+ /**
45
+ * Already-chosen host ports — the core invariant of this library: a
46
+ * backend binds these, it never allocates its own. See `FreePorts`.
47
+ */
48
+ readonly ports: ReadonlyArray<PortBinding>;
49
+ /** Host files/directories to copy into the guest before boot. */
50
+ readonly mounts: ReadonlyArray<FileMount>;
51
+ /** The `Network.id` this container joins, if any. */
52
+ readonly networkId: string | undefined;
53
+ /** Names this container answers to on its network. */
54
+ readonly aliases: ReadonlyArray<string>;
55
+ /** The process-wide `RunId.value` that named this container. */
56
+ readonly runId: string;
57
+ /** An explicit memory ceiling in MB, if the module or caller set one via `withMemoryLimit`. */
58
+ readonly memoryLimitMb: number | undefined;
59
+ }
60
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/core/model.ts"],"names":[],"mappings":"AAAA,2HAA2H;AAC3H,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,gIAAgI;AAChI,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2HAA2H;IAC3H,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC3C,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1C,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/core/model.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A file resolved to an absolute host path, ready to pass to
3
+ * `GenericContainer.withCopyFileToContainer`. `path` is always absolute —
4
+ * `forHostPath` resolves it immediately, and `forResource` copies to a
5
+ * cleaned-on-exit temp file first.
6
+ */
7
+ export declare class MountableFile {
8
+ /** The resolved, absolute host path. */
9
+ readonly path: string;
10
+ private constructor();
11
+ /** Resolves a host filesystem path (relative to `process.cwd()` if not already absolute). */
12
+ static forHostPath(hostPath: string): MountableFile;
13
+ /**
14
+ * JS has no classpath and no cheap "caller's directory": `import.meta.url`
15
+ * only ever gives the DEFINING module's own directory, and recovering the
16
+ * true call site would need a fragile `Error().stack` parse that differs
17
+ * between Node and Bun. So the base is explicit rather than inferred:
18
+ * `relPath` resolves against `baseUrl` (typically the calling module's own
19
+ * `import.meta.url`) when given, and against `process.cwd()` otherwise.
20
+ * There is no classpath-relative resolution here — only an explicit
21
+ * base (an `import.meta.url` you pass in) or the process cwd.
22
+ */
23
+ static forResource(relPath: string, baseUrl?: string): MountableFile;
24
+ }
25
+ //# sourceMappingURL=mountable-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mountable-file.d.ts","sourceRoot":"","sources":["../../src/core/mountable-file.ts"],"names":[],"mappings":"AAiCA;;;;;GAKG;AACH,qBAAa,aAAa;IAEtB,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAFvB,OAAO;IAKP,6FAA6F;IAC7F,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa;IAInD;;;;;;;;;OASG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa;CAKrE"}
@@ -0,0 +1,64 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { randomBytes } from "node:crypto";
6
+ const tempCopies = [];
7
+ let exitHookInstalled = false;
8
+ function installExitCleanup() {
9
+ if (exitHookInstalled) {
10
+ return;
11
+ }
12
+ exitHookInstalled = true;
13
+ process.on("exit", () => {
14
+ for (const p of tempCopies) {
15
+ try {
16
+ fs.rmSync(p, { force: true });
17
+ }
18
+ catch {
19
+ // best-effort; the OS reclaims tmpdir eventually regardless
20
+ }
21
+ }
22
+ });
23
+ }
24
+ function copyToTemp(sourcePath) {
25
+ const dest = path.join(os.tmpdir(), `rightsize-${randomBytes(6).toString("hex")}-${path.basename(sourcePath)}`);
26
+ fs.copyFileSync(sourcePath, dest);
27
+ tempCopies.push(dest);
28
+ installExitCleanup();
29
+ return dest;
30
+ }
31
+ /**
32
+ * A file resolved to an absolute host path, ready to pass to
33
+ * `GenericContainer.withCopyFileToContainer`. `path` is always absolute —
34
+ * `forHostPath` resolves it immediately, and `forResource` copies to a
35
+ * cleaned-on-exit temp file first.
36
+ */
37
+ export class MountableFile {
38
+ path;
39
+ constructor(
40
+ /** The resolved, absolute host path. */
41
+ path) {
42
+ this.path = path;
43
+ }
44
+ /** Resolves a host filesystem path (relative to `process.cwd()` if not already absolute). */
45
+ static forHostPath(hostPath) {
46
+ return new MountableFile(path.resolve(hostPath));
47
+ }
48
+ /**
49
+ * JS has no classpath and no cheap "caller's directory": `import.meta.url`
50
+ * only ever gives the DEFINING module's own directory, and recovering the
51
+ * true call site would need a fragile `Error().stack` parse that differs
52
+ * between Node and Bun. So the base is explicit rather than inferred:
53
+ * `relPath` resolves against `baseUrl` (typically the calling module's own
54
+ * `import.meta.url`) when given, and against `process.cwd()` otherwise.
55
+ * There is no classpath-relative resolution here — only an explicit
56
+ * base (an `import.meta.url` you pass in) or the process cwd.
57
+ */
58
+ static forResource(relPath, baseUrl) {
59
+ const baseDir = baseUrl === undefined ? process.cwd() : path.dirname(fileURLToPath(baseUrl));
60
+ const resolved = path.resolve(baseDir, relPath);
61
+ return new MountableFile(copyToTemp(resolved));
62
+ }
63
+ }
64
+ //# sourceMappingURL=mountable-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mountable-file.js","sourceRoot":"","sources":["../../src/core/mountable-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,GAAa,EAAE,CAAC;AAChC,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B,SAAS,kBAAkB;IACzB,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IACD,iBAAiB,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;YAC9D,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,UAAkB;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,aAAa,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAChH,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAClC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,kBAAkB,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAGb;IAFX;IACE,wCAAwC;IAC/B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IACpB,CAAC;IAEJ,6FAA6F;IAC7F,MAAM,CAAC,WAAW,CAAC,QAAgB;QACjC,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,WAAW,CAAC,OAAe,EAAE,OAAgB;QAClD,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7F,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ import type { SandboxBackend, NetworkLink } from "./backend.js";
2
+ /**
3
+ * The subset of a running container `Network` needs in order to compute
4
+ * links for a newly-joining sibling: its aliases, its exposed guest ports
5
+ * mapped to host ports, and whether it is currently running (a stopped
6
+ * container never gets linked to).
7
+ */
8
+ export interface NetworkMember {
9
+ /** Names this member answers to on the network. */
10
+ readonly aliases: ReadonlyArray<string>;
11
+ /** Whether this member is currently running (a stopped member is never linked to). */
12
+ readonly isRunning: boolean;
13
+ /** Guest ports this member has exposed. */
14
+ readonly exposedGuestPorts: ReadonlyArray<number>;
15
+ /** The host port bound to `guestPort`. */
16
+ mappedPort(guestPort: number): number;
17
+ }
18
+ /**
19
+ * Alias-based connectivity between containers, on both backends. Docker
20
+ * implements it with a native bridge network; msb — where each container is
21
+ * a fully isolated microVM — emulates it with an `/etc/hosts` alias plus a
22
+ * TCP relay over the sandbox's exec channel. `Network.resolve` returns the
23
+ * same `"alias:port"` string either way, so code written against it doesn't
24
+ * need to know which backend is active.
25
+ *
26
+ * Implements `Symbol.asyncDispose`, so `await using net = Network.newNetwork()`
27
+ * tears the network down at scope exit alongside its member containers.
28
+ */
29
+ export declare class Network implements AsyncDisposable {
30
+ /** This network's process-unique id, `rz-net-<8hex>`. */
31
+ readonly id: string;
32
+ private readonly aliasPorts;
33
+ private readonly members;
34
+ private lastBackend;
35
+ private constructor();
36
+ /** Creates a new network with a process-unique id (`rz-net-<8hex>`). Pass it to `withNetwork` on each member container. */
37
+ static newNetwork(): Network;
38
+ /** "alias:guestPort" for a registered (alias, guestPort) pair; throws naming the alias otherwise. */
39
+ resolve(alias: string, guestPort: number): string;
40
+ /**
41
+ * Links a newly-starting container needs installed toward every ALREADY
42
+ * RUNNING sibling, computed before `register` runs — so a container can
43
+ * never end up linked to itself, and only genuinely-running siblings
44
+ * (never a half-started or already-stopped one) contribute links.
45
+ */
46
+ linksForNewMember(): ReadonlyArray<NetworkLink>;
47
+ /**
48
+ * Registers a container as a network member AFTER its links to existing
49
+ * siblings were computed and installed — never before, or the ordering
50
+ * that prevents self-links breaks.
51
+ */
52
+ register(member: NetworkMember, aliases: ReadonlyArray<string>, backend: SandboxBackend): void;
53
+ /** Removes the backend-native network. Best-effort: safe to call even if no member ever started. */
54
+ close(): Promise<void>;
55
+ /** `= close()`. What `await using net = Network.newNetwork()` calls at scope exit. */
56
+ [Symbol.asyncDispose](): Promise<void>;
57
+ }
58
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/core/network.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD,0CAA0C;IAC1C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CACvC;AAMD;;;;;;;;;;GAUG;AACH,qBAAa,OAAQ,YAAW,eAAe;IAC7C,yDAAyD;IACzD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0C;IACrE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,WAAW,CAA6B;IAEhD,OAAO;IAIP,2HAA2H;IAC3H,MAAM,CAAC,UAAU,IAAI,OAAO;IAI5B,qGAAqG;IACrG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAQjD;;;;;OAKG;IACH,iBAAiB,IAAI,aAAa,CAAC,WAAW,CAAC;IAe/C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAe9F,oGAAoG;IAC9F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,sFAAsF;IAChF,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}