@rebasepro/cli 0.25.0-canary.ge06257e → 0.25.1

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 (142) hide show
  1. package/bin/rebase.js +9 -5
  2. package/dist/{agent-setup-C83DXnlx.js → agent-setup-BYb2Tbdz.js} +2 -2
  3. package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
  4. package/dist/{api-keys-Bc4FJKYp.js → api-keys-CSMbsmAn.js} +28 -22
  5. package/dist/api-keys-CSMbsmAn.js.map +1 -0
  6. package/dist/{apps-wG-ZTryA.js → apps-iBXNZhVN.js} +6 -7
  7. package/dist/{apps-wG-ZTryA.js.map → apps-iBXNZhVN.js.map} +1 -1
  8. package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-BusbHP6n.js} +4 -4
  9. package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
  10. package/dist/{auth-BL7bNQVu.js → auth-DRsTWPky.js} +77 -11
  11. package/dist/auth-DRsTWPky.js.map +1 -0
  12. package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-Bn_FjNld.js} +1 -1
  13. package/dist/{build-h_7Re-67.js → build-et7Rccxi.js} +6 -7
  14. package/dist/{build-h_7Re-67.js.map → build-et7Rccxi.js.map} +1 -1
  15. package/dist/{bundle-CqURVN-K.js → bundle-DlqkDYSe.js} +6 -21
  16. package/dist/bundle-DlqkDYSe.js.map +1 -0
  17. package/dist/{cloud-DVV_5GPG.js → cloud-B-ZIjHrA.js} +10 -11
  18. package/dist/{cloud-DVV_5GPG.js.map → cloud-B-ZIjHrA.js.map} +1 -1
  19. package/dist/commands/api-keys.d.ts +4 -1
  20. package/dist/commands/auth.d.ts +3 -2
  21. package/dist/commands/dev.d.ts +12 -0
  22. package/dist/commands/generate_sdk.d.ts +4 -2
  23. package/dist/commands/init.d.ts +20 -0
  24. package/dist/commands/start.d.ts +10 -0
  25. package/dist/commands/upgrade.d.ts +12 -0
  26. package/dist/connection-banner-DbLJzWf4.js +128 -0
  27. package/dist/connection-banner-DbLJzWf4.js.map +1 -0
  28. package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
  29. package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
  30. package/dist/{console-Dw3HLdUs.js → console-BlD9kjFW.js} +4 -4
  31. package/dist/{console-Dw3HLdUs.js.map → console-BlD9kjFW.js.map} +1 -1
  32. package/dist/{context-BM-oOMNX.js → context-DHiiiJ5C.js} +3 -4
  33. package/dist/context-DHiiiJ5C.js.map +1 -0
  34. package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
  35. package/dist/daemon-DDMRbz4T.js.map +1 -0
  36. package/dist/{daemon-entry-C6pxdily.js → daemon-entry-zOC1revO.js} +204 -5
  37. package/dist/daemon-entry-zOC1revO.js.map +1 -0
  38. package/dist/{db-Dkn9XhT-.js → db-9P6MnmdC.js} +237 -86
  39. package/dist/db-9P6MnmdC.js.map +1 -0
  40. package/dist/{derive-Dl0z2OWi.js → derive-MTdClnkc.js} +29 -14
  41. package/dist/{derive-Dl0z2OWi.js.map → derive-MTdClnkc.js.map} +1 -1
  42. package/dist/{dev-DAcZR4Pp.js → dev-BDN_OKXA.js} +183 -23
  43. package/dist/dev-BDN_OKXA.js.map +1 -0
  44. package/dist/dev-db/connection-banner.d.ts +47 -0
  45. package/dist/dev-db/daemon.d.ts +41 -0
  46. package/dist/dev-db/notification-proxy.d.ts +82 -1
  47. package/dist/dev-db/pull.d.ts +33 -5
  48. package/dist/dev-db/state.d.ts +9 -0
  49. package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-FT9GbJVj.js} +73 -4
  50. package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
  51. package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
  52. package/dist/dev-server-CIZwAfZa.js.map +1 -0
  53. package/dist/{doctor-Ds_lzc9p.js → doctor-D8YXz7df.js} +6 -6
  54. package/dist/doctor-D8YXz7df.js.map +1 -0
  55. package/dist/{eject-CJHkfnat.js → eject-izG2KjMo.js} +3 -4
  56. package/dist/{eject-CJHkfnat.js.map → eject-izG2KjMo.js.map} +1 -1
  57. package/dist/{fold-static-BC9CYicw.js → fold-static-CybSay_W.js} +3 -3
  58. package/dist/{fold-static-BC9CYicw.js.map → fold-static-CybSay_W.js.map} +1 -1
  59. package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-B4R65oUV.js} +22 -12
  60. package/dist/generate_sdk-B4R65oUV.js.map +1 -0
  61. package/dist/index.es.js +36 -37
  62. package/dist/index.es.js.map +1 -1
  63. package/dist/{init-CjoJoThg.js → init-DYaeppEN.js} +52 -63
  64. package/dist/init-DYaeppEN.js.map +1 -0
  65. package/dist/invoked-command-CG_rIa_o.js +55 -0
  66. package/dist/invoked-command-CG_rIa_o.js.map +1 -0
  67. package/dist/last-dev-database-B_75fxZC.js +2 -0
  68. package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
  69. package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
  70. package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BYw8ad87.js} +3 -3
  71. package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
  72. package/dist/precompress.d.ts +0 -7
  73. package/dist/{prepare-C1iaiTC4.js → prepare-1rVNd2PF.js} +7 -6
  74. package/dist/prepare-1rVNd2PF.js.map +1 -0
  75. package/dist/project-g4K6kV1t.js +701 -0
  76. package/dist/project-g4K6kV1t.js.map +1 -0
  77. package/dist/{pull-COpNJFzY.js → pull-DDMpsZ5r.js} +143 -63
  78. package/dist/pull-DDMpsZ5r.js.map +1 -0
  79. package/dist/refusal-ReeeGP8a.js +26 -0
  80. package/dist/refusal-ReeeGP8a.js.map +1 -0
  81. package/dist/resolve-BQUAC3Wi.js +2 -0
  82. package/dist/resources/derive.d.ts +9 -0
  83. package/dist/{resources-CLm6g0br.js → resources-CovP8AZ-.js} +4 -5
  84. package/dist/{resources-CLm6g0br.js.map → resources-CovP8AZ-.js.map} +1 -1
  85. package/dist/{schema-BKdh6UoE.js → schema-nuxvvc50.js} +5 -6
  86. package/dist/{schema-BKdh6UoE.js.map → schema-nuxvvc50.js.map} +1 -1
  87. package/dist/{skills-DEhYiPeO.js → skills-DeUIsZcT.js} +3 -4
  88. package/dist/{skills-DEhYiPeO.js.map → skills-DeUIsZcT.js.map} +1 -1
  89. package/dist/{start-BsPJedPQ.js → start-RzZqDroW.js} +20 -7
  90. package/dist/start-RzZqDroW.js.map +1 -0
  91. package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
  92. package/dist/state-CYdBvnwA.js.map +1 -0
  93. package/dist/{status-D_B3tlDy.js → status-D9Zm88NZ.js} +11 -12
  94. package/dist/{status-D_B3tlDy.js.map → status-D9Zm88NZ.js.map} +1 -1
  95. package/dist/{storage-CtB8polS.js → storage-BB3umRfw.js} +3 -4
  96. package/dist/{storage-CtB8polS.js.map → storage-BB3umRfw.js.map} +1 -1
  97. package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
  98. package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
  99. package/dist/{telemetry-BYmPU-Ji.js → telemetry-TseNLk6X.js} +4 -4
  100. package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-TseNLk6X.js.map} +1 -1
  101. package/dist/{upgrade-DbuKX9lv.js → upgrade-apiPKKE_.js} +29 -6
  102. package/dist/{upgrade-DbuKX9lv.js.map → upgrade-apiPKKE_.js.map} +1 -1
  103. package/dist/utils/args.d.ts +45 -3
  104. package/dist/utils/boot-retry.d.ts +41 -0
  105. package/dist/utils/dev-preflight.d.ts +34 -1
  106. package/dist/utils/dev-server.d.ts +11 -0
  107. package/dist/utils/project.d.ts +5 -1
  108. package/dist/utils/redact.d.ts +10 -0
  109. package/dist/utils/refusal.d.ts +18 -0
  110. package/dist/utils/unknown-command.d.ts +4 -0
  111. package/package.json +7 -7
  112. package/templates/overlays/baas/README.md +2 -1
  113. package/templates/resource-graphs/blank.json +25 -0
  114. package/templates/resource-graphs/blog.json +28 -0
  115. package/templates/resource-graphs/ecommerce.json +28 -0
  116. package/templates/resource-graphs/headless.json +22 -0
  117. package/templates/template/.env.example +11 -4
  118. package/templates/template/frontend/src/App.tsx +10 -9
  119. package/templates/template/frontend/vite.config.ts +7 -73
  120. package/dist/api-keys-Bc4FJKYp.js.map +0 -1
  121. package/dist/args-CcBphFdj.js +0 -237
  122. package/dist/args-CcBphFdj.js.map +0 -1
  123. package/dist/auth-BL7bNQVu.js.map +0 -1
  124. package/dist/bundle-CqURVN-K.js.map +0 -1
  125. package/dist/context-BM-oOMNX.js.map +0 -1
  126. package/dist/daemon-CknkDjd6.js.map +0 -1
  127. package/dist/daemon-entry-C6pxdily.js.map +0 -1
  128. package/dist/db-Dkn9XhT-.js.map +0 -1
  129. package/dist/dev-DAcZR4Pp.js.map +0 -1
  130. package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
  131. package/dist/dev-server-B1UYifZJ.js.map +0 -1
  132. package/dist/doctor-Ds_lzc9p.js.map +0 -1
  133. package/dist/generate_sdk-kfkZmG-d.js.map +0 -1
  134. package/dist/init-CjoJoThg.js.map +0 -1
  135. package/dist/last-dev-database-BEBPfAMY.js +0 -2
  136. package/dist/prepare-C1iaiTC4.js.map +0 -1
  137. package/dist/project-Wp2zoyeN.js +0 -349
  138. package/dist/project-Wp2zoyeN.js.map +0 -1
  139. package/dist/pull-COpNJFzY.js.map +0 -1
  140. package/dist/resolve-b_p9bmdH.js +0 -2
  141. package/dist/start-BsPJedPQ.js.map +0 -1
  142. package/dist/state-CCB-FAQ9.js.map +0 -1
@@ -1,5 +1,6 @@
1
+ import { n as cliVersion } from "./version-Buyyo1ut.js";
1
2
  import { r as PGLITE_EXTENSIONS, t as MANAGED_IDLE_TIMEOUT_MS } from "./constraints-CtiylsGf.js";
2
- import { a as dataDir, i as clearState, m as writeState, n as additionalDataDir, r as chooseManagedPort, s as findFreePort, u as recordManagedPorts } from "./state-CCB-FAQ9.js";
3
+ import { a as dataDir, i as clearState, m as writeState, n as additionalDataDir, r as chooseManagedPort, s as findFreePort, u as recordManagedPorts } from "./state-CYdBvnwA.js";
3
4
  import fs from "fs";
4
5
  import { DEFAULT_RESOURCE_KEY } from "@rebasepro/types";
5
6
  import net from "net";
@@ -37,10 +38,17 @@ import net from "net";
37
38
  * has subscribed to, which costs nothing. A client that *did* ask now always
38
39
  * gets it, which is the whole point.
39
40
  *
41
+ * It corrects one more defect on the way: PGlite answers a failed
42
+ * extended-protocol message with a ReadyForQuery of its own and the Sync after
43
+ * it with another, and the proxy drops the extra one (`ReadyForQueryLedger`),
44
+ * because a client that takes it for the next query's answer reads that
45
+ * query's rows as empty and every answer after it one request late.
46
+ *
40
47
  * Two properties make this safe rather than clever:
41
48
  *
42
49
  * - **It never parses SQL and never rewrites a byte.** Frames are forwarded
43
- * verbatim; the only edit is delivering a copy of one to more sockets.
50
+ * verbatim; the only edits are delivering a copy of one to more sockets and
51
+ * holding back that extra ReadyForQuery.
44
52
  * - **Injection only happens on a message boundary.** The server→client stream
45
53
  * is reassembled into whole protocol messages before anything is written on,
46
54
  * so an injected frame can never land inside another message.
@@ -104,6 +112,165 @@ var BackendMessageParser = class {
104
112
  return this.buffered.length;
105
113
  }
106
114
  };
115
+ /** The startup packet's protocol code, which no request code shares. */
116
+ var PROTOCOL_3 = 196608;
117
+ /**
118
+ * Reassembles the client→server stream into whole messages, so the proxy
119
+ * knows what each answer is an answer to.
120
+ *
121
+ * The connection opens untyped — an `SSLRequest` or `GSSENCRequest` (each
122
+ * answered with one byte) and then the startup packet, each a bare `Int32
123
+ * length` and a code — and is `type + Int32 length` after it. Each message is
124
+ * returned as its type character, `"startup"` for the startup packet, and the
125
+ * negotiation requests not at all.
126
+ */
127
+ var FrontendMessageParser = class {
128
+ buffered = Buffer.alloc(0);
129
+ started = false;
130
+ push(chunk) {
131
+ const types = [];
132
+ this.buffered = this.buffered.length === 0 ? chunk : Buffer.concat([this.buffered, chunk]);
133
+ while (true) {
134
+ if (!this.started) {
135
+ if (this.buffered.length < 8) break;
136
+ const length = this.buffered.readInt32BE(0);
137
+ if (length < 8) break;
138
+ if (this.buffered.length < length) break;
139
+ if (this.buffered.readInt32BE(4) === PROTOCOL_3) {
140
+ this.started = true;
141
+ types.push("startup");
142
+ }
143
+ this.buffered = this.buffered.subarray(length);
144
+ continue;
145
+ }
146
+ if (this.buffered.length < 5) break;
147
+ const length = this.buffered.readInt32BE(1);
148
+ if (length < 4) break;
149
+ if (this.buffered.length < length + 1) break;
150
+ types.push(String.fromCharCode(this.buffered[0]));
151
+ this.buffered = this.buffered.subarray(length + 1);
152
+ }
153
+ return types;
154
+ }
155
+ };
156
+ /**
157
+ * The backend messages that complete one extended-protocol message: Parse,
158
+ * Bind, Close, Describe (its row description or NoData) and Execute (its
159
+ * CommandComplete, EmptyQueryResponse or PortalSuspended).
160
+ */
161
+ var EXTENDED_REPLIES = /* @__PURE__ */ new Set([
162
+ "1",
163
+ "2",
164
+ "3",
165
+ "T",
166
+ "n",
167
+ "C",
168
+ "I",
169
+ "s"
170
+ ]);
171
+ /** Frontend messages that are part of an extended-protocol request and are answered. */
172
+ var EXTENDED_MESSAGES = /* @__PURE__ */ new Set([
173
+ "P",
174
+ "B",
175
+ "D",
176
+ "E",
177
+ "C"
178
+ ]);
179
+ /**
180
+ * Keeps a connection's ReadyForQuery messages one to a request.
181
+ *
182
+ * PGlite (0.5.6) answers an extended-protocol message that fails with an
183
+ * ErrorResponse *and* a ReadyForQuery, then answers the Sync after it with a
184
+ * second ReadyForQuery. Real Postgres sends one: after an error it ignores
185
+ * everything up to the Sync and answers the Sync. `pg` absorbs the extra one
186
+ * when both arrive in one read. When they arrive apart — under load, a script
187
+ * beside a running `rebase dev` — it takes the second as the answer to the
188
+ * next query it sent:
189
+ *
190
+ * - a refused write's ROLLBACK "finished" on it, the pool saw the client
191
+ * released in a failed transaction (status 'E'), and the real ROLLBACK's
192
+ * answer arrived as "Received unexpected commandComplete message from
193
+ * backend" — two `[pg-pool]` ERROR lines on every refused write;
194
+ * - on a connection held across queries, the next query "finished" with no
195
+ * rows: a `SELECT` that answered `[]`.
196
+ *
197
+ * So the proxy follows each request and drops the ReadyForQuery that answers
198
+ * a failed message before its Sync. An error raised *by* the Sync — a deferred
199
+ * constraint failing as the implicit transaction commits — comes with the
200
+ * one ReadyForQuery, and that is kept, as is every simple Query's.
201
+ */
202
+ var ReadyForQueryLedger = class {
203
+ pending = [];
204
+ /** Note a message the client sent, before it is forwarded. */
205
+ sent(type) {
206
+ const open = this.pending.at(-1);
207
+ const extendedOpen = open && open.extended && !open.synced ? open : void 0;
208
+ if (type === "startup" || type === "Q" || type === "F") this.pending.push({
209
+ extended: false,
210
+ messages: 0,
211
+ answered: 0,
212
+ synced: true,
213
+ extraReady: false
214
+ });
215
+ else if (EXTENDED_MESSAGES.has(type)) if (extendedOpen) extendedOpen.messages += 1;
216
+ else this.pending.push({
217
+ extended: true,
218
+ messages: 1,
219
+ answered: 0,
220
+ synced: false,
221
+ extraReady: false
222
+ });
223
+ else if (type === "S") if (extendedOpen) extendedOpen.synced = true;
224
+ else this.pending.push({
225
+ extended: true,
226
+ messages: 0,
227
+ answered: 0,
228
+ synced: true,
229
+ extraReady: false
230
+ });
231
+ }
232
+ /** Whether a message the backend sent goes on to the client. */
233
+ deliver(message) {
234
+ const type = String.fromCharCode(message[0]);
235
+ const request = this.pending[0];
236
+ if (!request) return true;
237
+ if (request.extended && EXTENDED_REPLIES.has(type)) request.answered += 1;
238
+ else if (type === "E" && request.extended && request.answered < request.messages) request.extraReady = true;
239
+ else if (type === "Z") {
240
+ if (request.extraReady) {
241
+ request.extraReady = false;
242
+ return false;
243
+ }
244
+ this.pending.shift();
245
+ }
246
+ return true;
247
+ }
248
+ };
249
+ /** What a client over the managed database's limit is told, and what to do. */
250
+ var NO_FREE_CONNECTION = {
251
+ message: "The managed development database has no free connection: it accepts only a few at a time, and other clients hold them — a running `rebase dev` holds some.",
252
+ hint: "Stop another process that uses it (a second backend, `rebase console`, psql or a database GUI) and connect again. For several backends at once, run Postgres in Docker: `rebase dev --docker`."
253
+ };
254
+ /**
255
+ * The refusal as Postgres sends one to a client over `max_connections`: a
256
+ * FATAL ErrorResponse with SQLSTATE 53300 (too_many_connections).
257
+ */
258
+ function noFreeConnectionResponse() {
259
+ const field = (code, value) => Buffer.concat([Buffer.from(code, "ascii"), Buffer.from(`${value}\0`, "utf8")]);
260
+ const body = Buffer.concat([
261
+ field("S", "FATAL"),
262
+ field("V", "FATAL"),
263
+ field("C", "53300"),
264
+ field("M", NO_FREE_CONNECTION.message),
265
+ field("H", NO_FREE_CONNECTION.hint),
266
+ Buffer.from([0])
267
+ ]);
268
+ const message = Buffer.alloc(5 + body.length);
269
+ message.write("E", 0, "ascii");
270
+ message.writeInt32BE(4 + body.length, 1);
271
+ body.copy(message, 5);
272
+ return message;
273
+ }
107
274
  function isNotificationFrame(message) {
108
275
  return message.length > 0 && message[0] === NOTIFICATION_RESPONSE;
109
276
  }
@@ -149,21 +316,29 @@ var NotificationProxy = class {
149
316
  });
150
317
  }
151
318
  accept(client, host) {
319
+ if (this.connections.size >= (this.options.maxConnections ?? 4)) {
320
+ this.refuse(client);
321
+ return;
322
+ }
152
323
  const upstream = net.connect(this.options.upstreamPort, host);
153
324
  const connection = {
154
325
  client,
155
326
  upstream,
156
- parser: new BackendMessageParser()
327
+ parser: new BackendMessageParser(),
328
+ frontend: new FrontendMessageParser(),
329
+ ledger: new ReadyForQueryLedger()
157
330
  };
158
331
  this.connections.add(connection);
159
332
  client.setNoDelay(true);
160
333
  upstream.setNoDelay(true);
161
334
  client.on("data", (chunk) => {
162
335
  if (isSslRequest(chunk)) connection.parser.expectSslReply();
336
+ for (const type of connection.frontend.push(chunk)) connection.ledger.sent(type);
163
337
  upstream.write(chunk);
164
338
  });
165
339
  upstream.on("data", (chunk) => {
166
340
  for (const message of connection.parser.push(chunk)) {
341
+ if (!connection.ledger.deliver(message)) continue;
167
342
  if (!client.destroyed) client.write(message);
168
343
  if (isNotificationFrame(message)) this.broadcast(message, connection);
169
344
  }
@@ -179,6 +354,28 @@ var NotificationProxy = class {
179
354
  upstream.on("error", close);
180
355
  }
181
356
  /**
357
+ * Turn a client away, in words, when every connection the socket server
358
+ * accepts is taken.
359
+ *
360
+ * The socket server sets its limit on the listening socket, so Node closes
361
+ * a connection over it the moment it is accepted, before a byte is read:
362
+ * every client read that as "Connection terminated unexpectedly", which
363
+ * says the database is down. Each client has its own connection upstream,
364
+ * so the proxy's count is the server's, and it answers as Postgres answers
365
+ * a client over `max_connections`, once the client has said hello: "no TLS"
366
+ * to an SSLRequest, and the refusal to the startup packet.
367
+ */
368
+ refuse(client) {
369
+ client.on("error", () => client.destroy());
370
+ client.on("data", (chunk) => {
371
+ if (isSslRequest(chunk)) {
372
+ client.write("N");
373
+ return;
374
+ }
375
+ client.end(noFreeConnectionResponse());
376
+ });
377
+ }
378
+ /**
182
379
  * Copy a notification to every other client.
183
380
  *
184
381
  * Written directly rather than through a parser: it is already a whole
@@ -383,7 +580,8 @@ async function runDaemon(args) {
383
580
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
384
581
  token: args.token,
385
582
  identityPort,
386
- ...additional.size > 0 ? { databases } : {}
583
+ ...additional.size > 0 ? { databases } : {},
584
+ cliVersion: cliVersion()
387
585
  });
388
586
  };
389
587
  let identityPort = 0;
@@ -409,6 +607,7 @@ async function runDaemon(args) {
409
607
  const identity = await startIdentityServer(args.token, () => {
410
608
  idleSince = null;
411
609
  }, async (command) => {
610
+ if (command === "clients") return `ok ${[primary, ...additional.values()].reduce((sum, instance) => sum + instance.proxy.connectionCount, 0)}`;
412
611
  const match = /^ensure\s+(\S+)$/.exec(command);
413
612
  if (!match) return `error unknown command "${command}"`;
414
613
  const key = match[1];
@@ -460,4 +659,4 @@ async function runDaemon(args) {
460
659
  //#endregion
461
660
  export { loadExtensions, parseDaemonArgs, runDaemon };
462
661
 
463
- //# sourceMappingURL=daemon-entry-C6pxdily.js.map
662
+ //# sourceMappingURL=daemon-entry-zOC1revO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-entry-zOC1revO.js","names":[],"sources":["../src/dev-db/notification-proxy.ts","../src/dev-db/daemon-entry.ts"],"sourcesContent":["/**\n * A transparent Postgres proxy that puts LISTEN/NOTIFY back.\n *\n * Without this, realtime does not work against the managed database — and it\n * fails silently, which is worse than failing. The reason is specific and\n * measurable:\n *\n * PGlite is a *single* backend session, and `PGLiteSocketServer` multiplexes\n * every client connection onto it. `LISTEN` is therefore session-wide: whichever\n * client issues it arms the whole database. But a `NotificationResponse` is an\n * asynchronous message with no request to answer, so the multiplexer hands it to\n * whichever socket happens to be reading the protocol stream at that moment —\n * which is the client that *caused* the notification, not the one that asked for\n * it.\n *\n * Measured against pglite-socket 0.2.9:\n *\n * LISTEN and NOTIFY on one connection → delivered\n * trigger-fired pg_notify, same connection → delivered\n * another connection causes the notify → NOT delivered to the listener\n * …and the same notification IS delivered to the notifier, which never asked\n *\n * The realtime engine listens on a dedicated connection and the writes come\n * from request connections, so it is exactly the broken case, every time.\n *\n * The fix is to stop treating a notification as belonging to one connection,\n * which for a single-session database is the truth anyway: this proxy watches\n * the server→client direction, and every `NotificationResponse` frame it sees is\n * copied to every other connected client. A client that never issued `LISTEN`\n * may receive one it did not ask for; `pg` raises a `notification` event nobody\n * has subscribed to, which costs nothing. A client that *did* ask now always\n * gets it, which is the whole point.\n *\n * It corrects one more defect on the way: PGlite answers a failed\n * extended-protocol message with a ReadyForQuery of its own and the Sync after\n * it with another, and the proxy drops the extra one (`ReadyForQueryLedger`),\n * because a client that takes it for the next query's answer reads that\n * query's rows as empty and every answer after it one request late.\n *\n * Two properties make this safe rather than clever:\n *\n * - **It never parses SQL and never rewrites a byte.** Frames are forwarded\n * verbatim; the only edits are delivering a copy of one to more sockets and\n * holding back that extra ReadyForQuery.\n * - **Injection only happens on a message boundary.** The server→client stream\n * is reassembled into whole protocol messages before anything is written on,\n * so an injected frame can never land inside another message.\n *\n * This exists only for the managed development database. Against a real Postgres\n * there is no proxy, because there is no defect to correct.\n */\n\nimport net from \"net\";\n\nimport { MANAGED_SERVER_MAX_CONNECTIONS } from \"./constraints\";\n\n/** `NotificationResponse`. The one message type this proxy treats specially. */\nconst NOTIFICATION_RESPONSE = 0x41; // 'A'\n\n/**\n * The SSL negotiation request, which is the one thing on the wire that is not\n * a typed message.\n *\n * A client may open with an 8-byte `SSLRequest` (length 8, code 80877103), and\n * the server answers with a *single untyped byte* — `N` or `S`. Feeding that\n * byte to a parser expecting `type + Int32 length` would desynchronise the\n * stream for the rest of the connection, so it is recognised and passed through.\n */\nconst SSL_REQUEST_LENGTH = 8;\nconst SSL_REQUEST_CODE = 80877103;\n\nfunction isSslRequest(chunk: Buffer): boolean {\n return (\n chunk.length >= SSL_REQUEST_LENGTH &&\n chunk.readInt32BE(0) === SSL_REQUEST_LENGTH &&\n chunk.readInt32BE(4) === SSL_REQUEST_CODE\n );\n}\n\n/**\n * Reassembles a server→client byte stream into whole protocol messages.\n *\n * Every backend message is `Int8 type` + `Int32 length` + payload, where the\n * length counts itself but not the type byte. Anything shorter than a full\n * message is held until the rest arrives — TCP offers no guarantee that a\n * message arrives in one chunk, and a proxy that assumed otherwise would inject\n * into the middle of a row description under load.\n */\nexport class BackendMessageParser {\n private buffered: Buffer = Buffer.alloc(0);\n /** Set once the untyped SSL negotiation byte has been dealt with. */\n private awaitingSslReply = false;\n\n expectSslReply(): void {\n this.awaitingSslReply = true;\n }\n\n /** Feed bytes in; get whole messages out, in order. */\n push(chunk: Buffer): Buffer[] {\n const messages: Buffer[] = [];\n this.buffered = this.buffered.length === 0 ? chunk : Buffer.concat([this.buffered, chunk]);\n\n if (this.awaitingSslReply && this.buffered.length >= 1) {\n // Single untyped byte: 'N' (no SSL) or 'S' (proceed).\n messages.push(this.buffered.subarray(0, 1));\n this.buffered = this.buffered.subarray(1);\n this.awaitingSslReply = false;\n }\n\n while (this.buffered.length >= 5) {\n const length = this.buffered.readInt32BE(1);\n // A length below 4 cannot describe itself; the stream is not one we\n // understand, so stop parsing and let the rest through untouched\n // rather than guessing.\n if (length < 4) break;\n const total = length + 1;\n if (this.buffered.length < total) break;\n messages.push(this.buffered.subarray(0, total));\n this.buffered = this.buffered.subarray(total);\n }\n\n return messages;\n }\n\n /** Bytes held back because they are not yet a whole message. */\n get pending(): number {\n return this.buffered.length;\n }\n}\n\n/** The startup packet's protocol code, which no request code shares. */\nconst PROTOCOL_3 = 196608;\n\n/**\n * Reassembles the client→server stream into whole messages, so the proxy\n * knows what each answer is an answer to.\n *\n * The connection opens untyped — an `SSLRequest` or `GSSENCRequest` (each\n * answered with one byte) and then the startup packet, each a bare `Int32\n * length` and a code — and is `type + Int32 length` after it. Each message is\n * returned as its type character, `\"startup\"` for the startup packet, and the\n * negotiation requests not at all.\n */\nexport class FrontendMessageParser {\n private buffered: Buffer = Buffer.alloc(0);\n private started = false;\n\n push(chunk: Buffer): string[] {\n const types: string[] = [];\n this.buffered = this.buffered.length === 0 ? chunk : Buffer.concat([this.buffered, chunk]);\n\n while (true) {\n if (!this.started) {\n if (this.buffered.length < 8) break;\n const length = this.buffered.readInt32BE(0);\n if (length < 8) break;\n if (this.buffered.length < length) break;\n if (this.buffered.readInt32BE(4) === PROTOCOL_3) {\n this.started = true;\n types.push(\"startup\");\n }\n this.buffered = this.buffered.subarray(length);\n continue;\n }\n if (this.buffered.length < 5) break;\n const length = this.buffered.readInt32BE(1);\n if (length < 4) break;\n if (this.buffered.length < length + 1) break;\n types.push(String.fromCharCode(this.buffered[0]));\n this.buffered = this.buffered.subarray(length + 1);\n }\n\n return types;\n }\n}\n\n/** One request the backend ends with a ReadyForQuery. */\ninterface PendingRequest {\n /** Parse/Bind/Describe/Execute/Close messages up to a Sync, rather than one Query. */\n extended: boolean;\n /** Its extended messages that answer with a message of their own. */\n messages: number;\n /** How many of them have been answered. */\n answered: number;\n /** The Sync that closes it has been sent. */\n synced: boolean;\n /** PGlite answered a failed message with a ReadyForQuery of its own, owed before the Sync's. */\n extraReady: boolean;\n}\n\n/**\n * The backend messages that complete one extended-protocol message: Parse,\n * Bind, Close, Describe (its row description or NoData) and Execute (its\n * CommandComplete, EmptyQueryResponse or PortalSuspended).\n */\nconst EXTENDED_REPLIES = new Set([\"1\", \"2\", \"3\", \"T\", \"n\", \"C\", \"I\", \"s\"]);\n\n/** Frontend messages that are part of an extended-protocol request and are answered. */\nconst EXTENDED_MESSAGES = new Set([\"P\", \"B\", \"D\", \"E\", \"C\"]);\n\n/**\n * Keeps a connection's ReadyForQuery messages one to a request.\n *\n * PGlite (0.5.6) answers an extended-protocol message that fails with an\n * ErrorResponse *and* a ReadyForQuery, then answers the Sync after it with a\n * second ReadyForQuery. Real Postgres sends one: after an error it ignores\n * everything up to the Sync and answers the Sync. `pg` absorbs the extra one\n * when both arrive in one read. When they arrive apart — under load, a script\n * beside a running `rebase dev` — it takes the second as the answer to the\n * next query it sent:\n *\n * - a refused write's ROLLBACK \"finished\" on it, the pool saw the client\n * released in a failed transaction (status 'E'), and the real ROLLBACK's\n * answer arrived as \"Received unexpected commandComplete message from\n * backend\" — two `[pg-pool]` ERROR lines on every refused write;\n * - on a connection held across queries, the next query \"finished\" with no\n * rows: a `SELECT` that answered `[]`.\n *\n * So the proxy follows each request and drops the ReadyForQuery that answers\n * a failed message before its Sync. An error raised *by* the Sync — a deferred\n * constraint failing as the implicit transaction commits — comes with the\n * one ReadyForQuery, and that is kept, as is every simple Query's.\n */\nexport class ReadyForQueryLedger {\n private readonly pending: PendingRequest[] = [];\n\n /** Note a message the client sent, before it is forwarded. */\n sent(type: string): void {\n const open = this.pending.at(-1);\n const extendedOpen = open && open.extended && !open.synced ? open : undefined;\n if (type === \"startup\" || type === \"Q\" || type === \"F\") {\n this.pending.push({ extended: false, messages: 0, answered: 0, synced: true, extraReady: false });\n } else if (EXTENDED_MESSAGES.has(type)) {\n if (extendedOpen) extendedOpen.messages += 1;\n else this.pending.push({ extended: true, messages: 1, answered: 0, synced: false, extraReady: false });\n } else if (type === \"S\") {\n if (extendedOpen) extendedOpen.synced = true;\n else this.pending.push({ extended: true, messages: 0, answered: 0, synced: true, extraReady: false });\n }\n }\n\n /** Whether a message the backend sent goes on to the client. */\n deliver(message: Buffer): boolean {\n const type = String.fromCharCode(message[0]);\n const request = this.pending[0];\n if (!request) return true;\n if (request.extended && EXTENDED_REPLIES.has(type)) {\n request.answered += 1;\n } else if (type === \"E\" && request.extended && request.answered < request.messages) {\n request.extraReady = true;\n } else if (type === \"Z\") {\n if (request.extraReady) {\n request.extraReady = false;\n return false;\n }\n this.pending.shift();\n }\n return true;\n }\n}\n\n/** What a client over the managed database's limit is told, and what to do. */\nexport const NO_FREE_CONNECTION = {\n message: \"The managed development database has no free connection: it accepts only a few at a time, \" +\n \"and other clients hold them — a running `rebase dev` holds some.\",\n hint: \"Stop another process that uses it (a second backend, `rebase console`, psql or a database GUI) \" +\n \"and connect again. For several backends at once, run Postgres in Docker: `rebase dev --docker`.\"\n};\n\n/**\n * The refusal as Postgres sends one to a client over `max_connections`: a\n * FATAL ErrorResponse with SQLSTATE 53300 (too_many_connections).\n */\nexport function noFreeConnectionResponse(): Buffer {\n const field = (code: string, value: string) => Buffer.concat([Buffer.from(code, \"ascii\"), Buffer.from(`${value}\\0`, \"utf8\")]);\n const body = Buffer.concat([\n field(\"S\", \"FATAL\"),\n field(\"V\", \"FATAL\"),\n field(\"C\", \"53300\"),\n field(\"M\", NO_FREE_CONNECTION.message),\n field(\"H\", NO_FREE_CONNECTION.hint),\n Buffer.from([0])\n ]);\n const message = Buffer.alloc(5 + body.length);\n message.write(\"E\", 0, \"ascii\");\n message.writeInt32BE(4 + body.length, 1);\n body.copy(message, 5);\n return message;\n}\n\nexport function isNotificationFrame(message: Buffer): boolean {\n return message.length > 0 && message[0] === NOTIFICATION_RESPONSE;\n}\n\n/** Channel and payload of a NotificationResponse, for logging and tests. */\nexport function decodeNotification(message: Buffer): { channel: string; payload: string } | null {\n if (!isNotificationFrame(message) || message.length < 10) return null;\n // 1 type byte + 4 length + 4 process id, then two null-terminated strings.\n const body = message.subarray(9);\n const split = body.indexOf(0);\n if (split === -1) return null;\n const channel = body.subarray(0, split).toString(\"utf8\");\n const rest = body.subarray(split + 1);\n const end = rest.indexOf(0);\n\n return { channel, payload: (end === -1 ? rest : rest.subarray(0, end)).toString(\"utf8\") };\n}\n\nexport interface NotificationProxyOptions {\n /** Port clients connect to. */\n listenPort: number;\n /** Port the real PGlite socket server is on. */\n upstreamPort: number;\n host?: string;\n /** Called for every notification broadcast. For diagnostics and tests. */\n onNotification?: (channel: string, payload: string, copies: number) => void;\n /**\n * Connections the socket server behind accepts. One more is refused here,\n * with a message, rather than dropped there without one. Default\n * {@link MANAGED_SERVER_MAX_CONNECTIONS}, what the daemon gives it.\n */\n maxConnections?: number;\n}\n\ninterface Connection {\n client: net.Socket;\n upstream: net.Socket;\n parser: BackendMessageParser;\n frontend: FrontendMessageParser;\n ledger: ReadyForQueryLedger;\n}\n\n/**\n * The proxy itself.\n *\n * One upstream connection per client connection, so the multiplexer downstream\n * sees exactly what it would have seen without the proxy.\n */\nexport class NotificationProxy {\n private server: net.Server | null = null;\n private readonly connections = new Set<Connection>();\n\n constructor(private readonly options: NotificationProxyOptions) {}\n\n get connectionCount(): number {\n return this.connections.size;\n }\n\n start(): Promise<void> {\n const host = this.options.host ?? \"127.0.0.1\";\n\n return new Promise((resolve, reject) => {\n const server = net.createServer((client) => this.accept(client, host));\n server.once(\"error\", reject);\n server.listen(this.options.listenPort, host, () => {\n this.server = server;\n resolve();\n });\n });\n }\n\n private accept(client: net.Socket, host: string): void {\n if (this.connections.size >= (this.options.maxConnections ?? MANAGED_SERVER_MAX_CONNECTIONS)) {\n this.refuse(client);\n return;\n }\n const upstream = net.connect(this.options.upstreamPort, host);\n const connection: Connection = {\n client,\n upstream,\n parser: new BackendMessageParser(),\n frontend: new FrontendMessageParser(),\n ledger: new ReadyForQueryLedger()\n };\n this.connections.add(connection);\n\n // Nagle would batch a notification behind nothing at all, adding latency\n // to the one message whose entire value is arriving promptly.\n client.setNoDelay(true);\n upstream.setNoDelay(true);\n\n client.on(\"data\", (chunk: Buffer) => {\n if (isSslRequest(chunk)) connection.parser.expectSslReply();\n // Noted before it is forwarded, so every answer finds its request.\n for (const type of connection.frontend.push(chunk)) connection.ledger.sent(type);\n upstream.write(chunk);\n });\n\n upstream.on(\"data\", (chunk: Buffer) => {\n for (const message of connection.parser.push(chunk)) {\n // PGlite's second ReadyForQuery for one failed request; see\n // `ReadyForQueryLedger`.\n if (!connection.ledger.deliver(message)) continue;\n // The rest of a response to a client that has gone is read\n // and dropped — see `close`.\n if (!client.destroyed) client.write(message);\n if (isNotificationFrame(message)) this.broadcast(message, connection);\n }\n });\n\n // The upstream socket is ended, never destroyed. A client that goes\n // away mid-response (Atlas exiting on a failed migration) leaves the\n // rest of that response in flight, and closing a socket with data\n // unread makes the kernel answer with a reset. `PGLiteSocketServer`\n // (0.2.9) handles a reset by detaching the connection's listeners\n // before its `close`, the one event it stops counting a connection\n // on, so every such exit cost one of the few connections it accepts\n // until the daemon restarted, and the next `rebase db migrate` beside\n // a running `rebase dev` got `Error: EOF`. Ended, the server reads an\n // ordinary close, and the proxy's upstream closes once it has.\n const close = () => {\n this.connections.delete(connection);\n client.destroy();\n upstream.end();\n };\n client.on(\"close\", close);\n client.on(\"error\", close);\n upstream.on(\"close\", close);\n upstream.on(\"error\", close);\n }\n\n /**\n * Turn a client away, in words, when every connection the socket server\n * accepts is taken.\n *\n * The socket server sets its limit on the listening socket, so Node closes\n * a connection over it the moment it is accepted, before a byte is read:\n * every client read that as \"Connection terminated unexpectedly\", which\n * says the database is down. Each client has its own connection upstream,\n * so the proxy's count is the server's, and it answers as Postgres answers\n * a client over `max_connections`, once the client has said hello: \"no TLS\"\n * to an SSLRequest, and the refusal to the startup packet.\n */\n private refuse(client: net.Socket): void {\n client.on(\"error\", () => client.destroy());\n client.on(\"data\", (chunk: Buffer) => {\n if (isSslRequest(chunk)) {\n client.write(\"N\");\n return;\n }\n client.end(noFreeConnectionResponse());\n });\n }\n\n /**\n * Copy a notification to every other client.\n *\n * Written directly rather than through a parser: it is already a whole\n * message, and every other socket is only ever written whole messages, so\n * there is no boundary to land inside.\n */\n private broadcast(message: Buffer, origin: Connection): void {\n let copies = 0;\n for (const connection of this.connections) {\n if (connection === origin) continue;\n if (connection.client.destroyed || !connection.client.writable) continue;\n connection.client.write(message);\n copies += 1;\n }\n\n const decoded = decodeNotification(message);\n if (decoded) this.options.onNotification?.(decoded.channel, decoded.payload, copies);\n }\n\n async stop(): Promise<void> {\n for (const connection of [...this.connections]) {\n connection.client.destroy();\n connection.upstream.destroy();\n }\n this.connections.clear();\n\n const server = this.server;\n this.server = null;\n if (!server) return;\n\n await new Promise<void>((resolve) => server.close(() => resolve()));\n }\n}\n","/**\n * The managed database process: one PGlite instance behind a Postgres socket.\n *\n * Runs as `rebase __dev-db-daemon`, a hidden subcommand rather than a separate\n * build entry point, so the same resolution works from `src` under tsx and from\n * the bundled `dist` a published CLI ships — there is no second file for a\n * build config to forget.\n *\n * It is deliberately detached from whoever started it. `rebase db push` in one\n * terminal and `rebase dev` in another must reach the same database, because\n * two processes opening one PGlite data directory would corrupt it, so the\n * daemon belongs to the *project* rather than to a command. What starts it is\n * incidental; what stops it is an explicit `rebase db stop`, an idle timeout,\n * or the machine going away.\n *\n * PGlite is imported dynamically. It is an optional dependency carrying a 25MB\n * WASM build, and the cost of that must fall only on someone who actually uses\n * the managed database — never on `rebase init`, and never on a CLI startup\n * that is about to print help.\n */\n\nimport fs from \"fs\";\nimport net from \"net\";\nimport { DEFAULT_RESOURCE_KEY } from \"@rebasepro/types\";\n\nimport {\n MANAGED_IDLE_TIMEOUT_MS,\n MANAGED_SERVER_MAX_CONNECTIONS,\n PGLITE_EXTENSIONS\n} from \"./constraints\";\nimport { cliVersion } from \"../utils/version\";\nimport { NotificationProxy } from \"./notification-proxy\";\nimport { additionalDataDir, chooseManagedPort, clearState, dataDir, findFreePort, recordManagedPorts, writeState } from \"./state\";\n\n\n/** How often to check for idleness. */\nconst IDLE_CHECK_INTERVAL_MS = 60_000;\n\nexport interface DaemonArgs {\n projectRoot: string;\n port: number;\n token: string;\n idleTimeoutMs: number;\n}\n\n/**\n * `--project <dir> --port <n> --token <t> [--idle-timeout <ms>]`.\n *\n * Every field is required and unvalidated input is fatal: this process is\n * spawned by the CLI, never typed by a person, so a malformed argument is a bug\n * in the caller and guessing would hide it.\n */\nexport function parseDaemonArgs(argv: readonly string[]): DaemonArgs {\n const take = (flag: string): string | null => {\n const index = argv.indexOf(flag);\n\n return index >= 0 && index + 1 < argv.length ? argv[index + 1] : null;\n };\n\n const projectRoot = take(\"--project\");\n const port = Number(take(\"--port\"));\n const token = take(\"--token\");\n const idleRaw = take(\"--idle-timeout\");\n\n if (!projectRoot) throw new Error(\"__dev-db-daemon: --project is required\");\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(\"__dev-db-daemon: --port must be a valid port\");\n }\n if (!token) throw new Error(\"__dev-db-daemon: --token is required\");\n\n const idleTimeoutMs = idleRaw === null ? MANAGED_IDLE_TIMEOUT_MS : Number(idleRaw);\n if (!Number.isFinite(idleTimeoutMs) || idleTimeoutMs < 0) {\n throw new Error(\"__dev-db-daemon: --idle-timeout must be a non-negative number of milliseconds\");\n }\n\n return { projectRoot, port, token, idleTimeoutMs };\n}\n\n/**\n * Load the extension bundles PGlite needs.\n *\n * `CREATE EXTENSION pg_trgm` cannot install anything on its own here — PGlite\n * resolves extensions from bundles handed to the constructor, and a missing one\n * fails at migration time with `extension \"pg_trgm\" is not available`, which\n * reads like a broken database rather than a missing import.\n *\n * Every failure here is loud for that reason, including the one that is new:\n * pgvector arrives from a package of its own, so unlike the contrib bundles it\n * can be absent while PGlite itself is fine.\n */\nexport async function loadExtensions(): Promise<Record<string, unknown>> {\n const extensions: Record<string, unknown> = {};\n for (const extension of PGLITE_EXTENSIONS) {\n let module: Record<string, unknown>;\n try {\n module = (await import(extension.module)) as Record<string, unknown>;\n } catch (err) {\n throw new Error(\n `Could not load \"${extension.module}\", which supplies the ${extension.name} extension: ` +\n `${err instanceof Error ? err.message : String(err)}\\n` +\n \"It is an optional dependency of @rebasepro/cli — reinstall the project, \" +\n \"or run `rebase dev --docker` to use a real Postgres instead.\"\n );\n }\n const bundle = module[extension.export];\n if (!bundle) {\n throw new Error(\n `${extension.module} did not export \"${extension.export}\". ` +\n \"The installed PGlite version may not ship this extension.\"\n );\n }\n extensions[extension.name] = bundle;\n }\n\n return extensions;\n}\n\n/**\n * A tiny sidecar listener that answers one question: \"are you the daemon this\n * state file describes?\"\n *\n * Liveness cannot be answered by the pid — after a reboot the number belongs to\n * something else — nor by the port alone, for the same reason. Both would let\n * Rebase send a migration to a stranger. So the daemon publishes a token on a\n * second loopback port and the answer is only yes when the token matches.\n */\n/**\n * How long a client has to send a command after the identity line, before\n * the daemon closes the socket. The liveness check never sends one and\n * closes on its own; this is for a client that connected and hung.\n */\nconst COMMAND_TIMEOUT_MS = 2_000;\n\n/**\n * One command a client may send after reading the identity line.\n *\n * `ensure <key>` answers `ok <port>` once a PGlite instance for that declared\n * database is serving, starting it if it is not. A daemon started before a\n * second database was declared serves it without a restart, which matters\n * because `rebase studio` in another terminal is connected to the first.\n *\n * `clients` answers `ok <n>`: the clients connected to every database it\n * serves. A newer CLI asks before restarting a daemon an older one started.\n */\ntype CommandHandler = (command: string) => Promise<string>;\n\nfunction startIdentityServer(token: string, onConnection: () => void, onCommand: CommandHandler): Promise<net.Server> {\n return new Promise((resolve, reject) => {\n const server = net.createServer((socket) => {\n onConnection();\n socket.write(`rebase-dev-db ${token}\\n`);\n let buffered = \"\";\n const timer = setTimeout(() => socket.end(), COMMAND_TIMEOUT_MS);\n socket.on(\"data\", (chunk) => {\n buffered += chunk.toString(\"utf8\");\n const newline = buffered.indexOf(\"\\n\");\n if (newline === -1) return;\n clearTimeout(timer);\n const command = buffered.slice(0, newline).trim();\n buffered = \"\";\n onCommand(command)\n .then((reply) => socket.end(`${reply}\\n`))\n .catch((err) => socket.end(`error ${err instanceof Error ? err.message : String(err)}\\n`));\n });\n socket.on(\"error\", () => clearTimeout(timer));\n socket.on(\"close\", () => clearTimeout(timer));\n });\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => resolve(server));\n });\n}\n\nexport async function runDaemon(args: DaemonArgs): Promise<void> {\n const directory = dataDir(args.projectRoot);\n fs.mkdirSync(directory, { recursive: true });\n\n const { PGlite } = (await import(\"@electric-sql/pglite\")) as {\n PGlite: { create(options: unknown): Promise<unknown> };\n };\n const { PGLiteSocketServer } = (await import(\"@electric-sql/pglite-socket\")) as {\n PGLiteSocketServer: new (options: unknown) => {\n start(): Promise<void>;\n stop(): Promise<void>;\n getStats(): { activeConnections: number; queuedQueries: number };\n };\n };\n\n const extensions = await loadExtensions();\n\n /** One PGlite instance, its socket server and the proxy clients reach it through. */\n interface Instance {\n db: { close(): Promise<void> };\n server: { stop(): Promise<void>; getStats(): { activeConnections: number; queuedQueries: number } };\n proxy: NotificationProxy;\n port: number;\n dataDir: string;\n }\n\n // The socket server listens privately; clients reach it through the\n // notification proxy on the public port. Realtime does not work\n // otherwise — PGlite is one session, so a NotificationResponse is handed\n // to whichever socket is reading rather than to the one that issued\n // LISTEN. See `notification-proxy.ts` for the measurements.\n const startInstance = async (instanceDir: string, listenPort: number, label: string): Promise<Instance> => {\n fs.mkdirSync(instanceDir, { recursive: true });\n const db = (await PGlite.create({ dataDir: instanceDir, extensions })) as { close(): Promise<void> };\n const upstreamPort = await findFreePort();\n const server = new PGLiteSocketServer({\n db,\n port: upstreamPort,\n host: \"127.0.0.1\",\n // Above the client pool limit so a second *non-transactional* client is\n // refused with a connection error rather than deadlocking the\n // multiplexer. See `constraints.ts` — the pool limit is what actually\n // prevents overlapping transactions.\n maxConnections: MANAGED_SERVER_MAX_CONNECTIONS\n });\n await server.start();\n const proxy = new NotificationProxy({\n listenPort,\n upstreamPort,\n onNotification: (channel, _payload, copies) => {\n if (copies > 0) process.stdout.write(`dev-db${label}: relayed notification on ${channel} to ${copies} client(s)\\n`);\n }\n });\n await proxy.start();\n return { db, server, proxy, port: listenPort, dataDir: instanceDir };\n };\n\n const primary = await startInstance(directory, args.port, \"\");\n /** Additional declared databases, by key. Started on demand. */\n const additional = new Map<string, Instance>();\n /** In-flight starts, so two commands asking for one key start one instance. */\n const starting = new Map<string, Promise<Instance>>();\n\n // \"Idle\" means nothing is connected to the *database*. An earlier version\n // tracked identity pings instead, which meant a daemon serving queries\n // steadily for an hour would decide it was idle and shut down under a\n // running dev server.\n let idleSince: number | null = Date.now();\n\n const publishState = (identityPort: number) => {\n const databases: Record<string, { port: number; dataDir: string }> = {};\n for (const [key, instance] of additional) databases[key] = { port: instance.port, dataDir: instance.dataDir };\n // Kept after this daemon is gone, so the next one listens where this\n // one did (`chooseManagedPort`).\n const ports: Record<string, number> = { [DEFAULT_RESOURCE_KEY]: args.port };\n for (const [key, entry] of Object.entries(databases)) ports[key] = entry.port;\n recordManagedPorts(args.projectRoot, ports);\n writeState(args.projectRoot, {\n port: args.port,\n pid: process.pid,\n dataDir: directory,\n startedAt: new Date().toISOString(),\n token: args.token,\n identityPort,\n ...(additional.size > 0 ? { databases } : {}),\n // Which CLI's proxy this is, for the next CLI to compare against\n // its own (`daemonOutdated`).\n cliVersion: cliVersion()\n });\n };\n\n let identityPort = 0;\n const ensureAdditional = async (key: string): Promise<Instance> => {\n const existing = additional.get(key);\n if (existing) return existing;\n const inFlight = starting.get(key);\n if (inFlight) return inFlight;\n const promise = (async () => {\n // Its own stable port, for the same reason the default has one.\n const instance = await startInstance(additionalDataDir(args.projectRoot, key), await chooseManagedPort(args.projectRoot, key), ` [${key}]`);\n additional.set(key, instance);\n publishState(identityPort);\n process.stdout.write(`dev-db [${key}]: ready on 127.0.0.1:${instance.port}\\n`);\n return instance;\n })();\n starting.set(key, promise);\n try {\n return await promise;\n } finally {\n starting.delete(key);\n }\n };\n\n const identity = await startIdentityServer(args.token, () => {\n idleSince = null;\n }, async (command) => {\n if (command === \"clients\") {\n return `ok ${[primary, ...additional.values()].reduce((sum, instance) => sum + instance.proxy.connectionCount, 0)}`;\n }\n const match = /^ensure\\s+(\\S+)$/.exec(command);\n if (!match) return `error unknown command \"${command}\"`;\n const key = match[1];\n // The key becomes a directory name and a variable suffix; the same\n // rule the declaration passed applies here, so nothing a declaration\n // could not say reaches the filesystem.\n if (!/[a-z0-9]/i.test(key) || key.includes(\"/\") || key.includes(\"..\")) return `error invalid key \"${key}\"`;\n const instance = await ensureAdditional(key);\n return `ok ${instance.port}`;\n });\n const identityAddress = identity.address();\n identityPort = identityAddress !== null && typeof identityAddress !== \"string\" ? identityAddress.port : 0;\n\n publishState(identityPort);\n\n let shuttingDown = false;\n const shutdown = async (reason: string) => {\n if (shuttingDown) return;\n shuttingDown = true;\n process.stdout.write(`dev-db: stopping (${reason})\\n`);\n // The state file goes first: a command that reads it during shutdown\n // should conclude \"not running\" and start a fresh daemon, rather than\n // connect to a socket that is closing under it.\n clearState(args.projectRoot);\n identity.close();\n for (const instance of [primary, ...additional.values()]) {\n try {\n await instance.proxy.stop();\n } catch { /* already down */ }\n try {\n await instance.server.stop();\n } catch { /* already down */ }\n try {\n await instance.db.close();\n } catch { /* already closed */ }\n }\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => void shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => void shutdown(\"SIGTERM\"));\n // The parent going away must not take the database with it — the daemon\n // belongs to the project. But an orphan with nobody left to serve should\n // not outlive the session either, which is what the idle timer is for.\n process.on(\"disconnect\", () => { /* detached on purpose */ });\n\n if (args.idleTimeoutMs > 0) {\n const timer = setInterval(() => {\n const busy = [primary, ...additional.values()].some((instance) => {\n const stats = instance.server.getStats();\n return stats.activeConnections > 0 || stats.queuedQueries > 0;\n });\n if (busy) {\n idleSince = null;\n\n return;\n }\n if (idleSince === null) {\n idleSince = Date.now();\n\n return;\n }\n if (Date.now() - idleSince >= args.idleTimeoutMs) {\n void shutdown(`idle for ${Math.round(args.idleTimeoutMs / 60_000)} minutes`);\n }\n }, IDLE_CHECK_INTERVAL_MS);\n timer.unref();\n }\n\n process.stdout.write(`dev-db: ready on 127.0.0.1:${args.port}\\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAM,wBAAwB;;;;;;;;;;AAW9B,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AAEzB,SAAS,aAAa,OAAwB;CAC1C,OACI,MAAM,UAAU,sBAChB,MAAM,YAAY,CAAC,MAAM,sBACzB,MAAM,YAAY,CAAC,MAAM;AAEjC;;;;;;;;;;AAWA,IAAa,uBAAb,MAAkC;CAC9B,WAA2B,OAAO,MAAM,CAAC;;CAEzC,mBAA2B;CAE3B,iBAAuB;EACnB,KAAK,mBAAmB;CAC5B;;CAGA,KAAK,OAAyB;EAC1B,MAAM,WAAqB,CAAC;EAC5B,KAAK,WAAW,KAAK,SAAS,WAAW,IAAI,QAAQ,OAAO,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC;EAEzF,IAAI,KAAK,oBAAoB,KAAK,SAAS,UAAU,GAAG;GAEpD,SAAS,KAAK,KAAK,SAAS,SAAS,GAAG,CAAC,CAAC;GAC1C,KAAK,WAAW,KAAK,SAAS,SAAS,CAAC;GACxC,KAAK,mBAAmB;EAC5B;EAEA,OAAO,KAAK,SAAS,UAAU,GAAG;GAC9B,MAAM,SAAS,KAAK,SAAS,YAAY,CAAC;GAI1C,IAAI,SAAS,GAAG;GAChB,MAAM,QAAQ,SAAS;GACvB,IAAI,KAAK,SAAS,SAAS,OAAO;GAClC,SAAS,KAAK,KAAK,SAAS,SAAS,GAAG,KAAK,CAAC;GAC9C,KAAK,WAAW,KAAK,SAAS,SAAS,KAAK;EAChD;EAEA,OAAO;CACX;;CAGA,IAAI,UAAkB;EAClB,OAAO,KAAK,SAAS;CACzB;AACJ;;AAGA,IAAM,aAAa;;;;;;;;;;;AAYnB,IAAa,wBAAb,MAAmC;CAC/B,WAA2B,OAAO,MAAM,CAAC;CACzC,UAAkB;CAElB,KAAK,OAAyB;EAC1B,MAAM,QAAkB,CAAC;EACzB,KAAK,WAAW,KAAK,SAAS,WAAW,IAAI,QAAQ,OAAO,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC;EAEzF,OAAO,MAAM;GACT,IAAI,CAAC,KAAK,SAAS;IACf,IAAI,KAAK,SAAS,SAAS,GAAG;IAC9B,MAAM,SAAS,KAAK,SAAS,YAAY,CAAC;IAC1C,IAAI,SAAS,GAAG;IAChB,IAAI,KAAK,SAAS,SAAS,QAAQ;IACnC,IAAI,KAAK,SAAS,YAAY,CAAC,MAAM,YAAY;KAC7C,KAAK,UAAU;KACf,MAAM,KAAK,SAAS;IACxB;IACA,KAAK,WAAW,KAAK,SAAS,SAAS,MAAM;IAC7C;GACJ;GACA,IAAI,KAAK,SAAS,SAAS,GAAG;GAC9B,MAAM,SAAS,KAAK,SAAS,YAAY,CAAC;GAC1C,IAAI,SAAS,GAAG;GAChB,IAAI,KAAK,SAAS,SAAS,SAAS,GAAG;GACvC,MAAM,KAAK,OAAO,aAAa,KAAK,SAAS,EAAE,CAAC;GAChD,KAAK,WAAW,KAAK,SAAS,SAAS,SAAS,CAAC;EACrD;EAEA,OAAO;CACX;AACJ;;;;;;AAqBA,IAAM,mCAAmB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;;AAGzE,IAAM,oCAAoB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyB3D,IAAa,sBAAb,MAAiC;CAC7B,UAA6C,CAAC;;CAG9C,KAAK,MAAoB;EACrB,MAAM,OAAO,KAAK,QAAQ,GAAG,EAAE;EAC/B,MAAM,eAAe,QAAQ,KAAK,YAAY,CAAC,KAAK,SAAS,OAAO,KAAA;EACpE,IAAI,SAAS,aAAa,SAAS,OAAO,SAAS,KAC/C,KAAK,QAAQ,KAAK;GAAE,UAAU;GAAO,UAAU;GAAG,UAAU;GAAG,QAAQ;GAAM,YAAY;EAAM,CAAC;OAC7F,IAAI,kBAAkB,IAAI,IAAI,GACjC,IAAI,cAAc,aAAa,YAAY;OACtC,KAAK,QAAQ,KAAK;GAAE,UAAU;GAAM,UAAU;GAAG,UAAU;GAAG,QAAQ;GAAO,YAAY;EAAM,CAAC;OAClG,IAAI,SAAS,KAChB,IAAI,cAAc,aAAa,SAAS;OACnC,KAAK,QAAQ,KAAK;GAAE,UAAU;GAAM,UAAU;GAAG,UAAU;GAAG,QAAQ;GAAM,YAAY;EAAM,CAAC;CAE5G;;CAGA,QAAQ,SAA0B;EAC9B,MAAM,OAAO,OAAO,aAAa,QAAQ,EAAE;EAC3C,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,CAAC,SAAS,OAAO;EACrB,IAAI,QAAQ,YAAY,iBAAiB,IAAI,IAAI,GAC7C,QAAQ,YAAY;OACjB,IAAI,SAAS,OAAO,QAAQ,YAAY,QAAQ,WAAW,QAAQ,UACtE,QAAQ,aAAa;OAClB,IAAI,SAAS,KAAK;GACrB,IAAI,QAAQ,YAAY;IACpB,QAAQ,aAAa;IACrB,OAAO;GACX;GACA,KAAK,QAAQ,MAAM;EACvB;EACA,OAAO;CACX;AACJ;;AAGA,IAAa,qBAAqB;CAC9B,SAAS;CAET,MAAM;AAEV;;;;;AAMA,SAAgB,2BAAmC;CAC/C,MAAM,SAAS,MAAc,UAAkB,OAAO,OAAO,CAAC,OAAO,KAAK,MAAM,OAAO,GAAG,OAAO,KAAK,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC;CAC5H,MAAM,OAAO,OAAO,OAAO;EACvB,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,mBAAmB,OAAO;EACrC,MAAM,KAAK,mBAAmB,IAAI;EAClC,OAAO,KAAK,CAAC,CAAC,CAAC;CACnB,CAAC;CACD,MAAM,UAAU,OAAO,MAAM,IAAI,KAAK,MAAM;CAC5C,QAAQ,MAAM,KAAK,GAAG,OAAO;CAC7B,QAAQ,aAAa,IAAI,KAAK,QAAQ,CAAC;CACvC,KAAK,KAAK,SAAS,CAAC;CACpB,OAAO;AACX;AAEA,SAAgB,oBAAoB,SAA0B;CAC1D,OAAO,QAAQ,SAAS,KAAK,QAAQ,OAAO;AAChD;;AAGA,SAAgB,mBAAmB,SAA8D;CAC7F,IAAI,CAAC,oBAAoB,OAAO,KAAK,QAAQ,SAAS,IAAI,OAAO;CAEjE,MAAM,OAAO,QAAQ,SAAS,CAAC;CAC/B,MAAM,QAAQ,KAAK,QAAQ,CAAC;CAC5B,IAAI,UAAU,IAAI,OAAO;CACzB,MAAM,UAAU,KAAK,SAAS,GAAG,KAAK,CAAC,CAAC,SAAS,MAAM;CACvD,MAAM,OAAO,KAAK,SAAS,QAAQ,CAAC;CACpC,MAAM,MAAM,KAAK,QAAQ,CAAC;CAE1B,OAAO;EAAE;EAAS,UAAU,QAAQ,KAAK,OAAO,KAAK,SAAS,GAAG,GAAG,EAAA,CAAG,SAAS,MAAM;CAAE;AAC5F;;;;;;;AAgCA,IAAa,oBAAb,MAA+B;CAIE;CAH7B,SAAoC;CACpC,8BAA+B,IAAI,IAAgB;CAEnD,YAAY,SAAoD;EAAnC,KAAA,UAAA;CAAoC;CAEjE,IAAI,kBAA0B;EAC1B,OAAO,KAAK,YAAY;CAC5B;CAEA,QAAuB;EACnB,MAAM,OAAO,KAAK,QAAQ,QAAQ;EAElC,OAAO,IAAI,SAAS,SAAS,WAAW;GACpC,MAAM,SAAS,IAAI,cAAc,WAAW,KAAK,OAAO,QAAQ,IAAI,CAAC;GACrE,OAAO,KAAK,SAAS,MAAM;GAC3B,OAAO,OAAO,KAAK,QAAQ,YAAY,YAAY;IAC/C,KAAK,SAAS;IACd,QAAQ;GACZ,CAAC;EACL,CAAC;CACL;CAEA,OAAe,QAAoB,MAAoB;EACnD,IAAI,KAAK,YAAY,SAAS,KAAK,QAAQ,kBAAA,IAAmD;GAC1F,KAAK,OAAO,MAAM;GAClB;EACJ;EACA,MAAM,WAAW,IAAI,QAAQ,KAAK,QAAQ,cAAc,IAAI;EAC5D,MAAM,aAAyB;GAC3B;GACA;GACA,QAAQ,IAAI,qBAAqB;GACjC,UAAU,IAAI,sBAAsB;GACpC,QAAQ,IAAI,oBAAoB;EACpC;EACA,KAAK,YAAY,IAAI,UAAU;EAI/B,OAAO,WAAW,IAAI;EACtB,SAAS,WAAW,IAAI;EAExB,OAAO,GAAG,SAAS,UAAkB;GACjC,IAAI,aAAa,KAAK,GAAG,WAAW,OAAO,eAAe;GAE1D,KAAK,MAAM,QAAQ,WAAW,SAAS,KAAK,KAAK,GAAG,WAAW,OAAO,KAAK,IAAI;GAC/E,SAAS,MAAM,KAAK;EACxB,CAAC;EAED,SAAS,GAAG,SAAS,UAAkB;GACnC,KAAK,MAAM,WAAW,WAAW,OAAO,KAAK,KAAK,GAAG;IAGjD,IAAI,CAAC,WAAW,OAAO,QAAQ,OAAO,GAAG;IAGzC,IAAI,CAAC,OAAO,WAAW,OAAO,MAAM,OAAO;IAC3C,IAAI,oBAAoB,OAAO,GAAG,KAAK,UAAU,SAAS,UAAU;GACxE;EACJ,CAAC;EAYD,MAAM,cAAc;GAChB,KAAK,YAAY,OAAO,UAAU;GAClC,OAAO,QAAQ;GACf,SAAS,IAAI;EACjB;EACA,OAAO,GAAG,SAAS,KAAK;EACxB,OAAO,GAAG,SAAS,KAAK;EACxB,SAAS,GAAG,SAAS,KAAK;EAC1B,SAAS,GAAG,SAAS,KAAK;CAC9B;;;;;;;;;;;;;CAcA,OAAe,QAA0B;EACrC,OAAO,GAAG,eAAe,OAAO,QAAQ,CAAC;EACzC,OAAO,GAAG,SAAS,UAAkB;GACjC,IAAI,aAAa,KAAK,GAAG;IACrB,OAAO,MAAM,GAAG;IAChB;GACJ;GACA,OAAO,IAAI,yBAAyB,CAAC;EACzC,CAAC;CACL;;;;;;;;CASA,UAAkB,SAAiB,QAA0B;EACzD,IAAI,SAAS;EACb,KAAK,MAAM,cAAc,KAAK,aAAa;GACvC,IAAI,eAAe,QAAQ;GAC3B,IAAI,WAAW,OAAO,aAAa,CAAC,WAAW,OAAO,UAAU;GAChE,WAAW,OAAO,MAAM,OAAO;GAC/B,UAAU;EACd;EAEA,MAAM,UAAU,mBAAmB,OAAO;EAC1C,IAAI,SAAS,KAAK,QAAQ,iBAAiB,QAAQ,SAAS,QAAQ,SAAS,MAAM;CACvF;CAEA,MAAM,OAAsB;EACxB,KAAK,MAAM,cAAc,CAAC,GAAG,KAAK,WAAW,GAAG;GAC5C,WAAW,OAAO,QAAQ;GAC1B,WAAW,SAAS,QAAQ;EAChC;EACA,KAAK,YAAY,MAAM;EAEvB,MAAM,SAAS,KAAK;EACpB,KAAK,SAAS;EACd,IAAI,CAAC,QAAQ;EAEb,MAAM,IAAI,SAAe,YAAY,OAAO,YAAY,QAAQ,CAAC,CAAC;CACtE;AACJ;;;;;;;;;;;;;;;;;;;;;;;;ACzbA,IAAM,yBAAyB;;;;;;;;AAgB/B,SAAgB,gBAAgB,MAAqC;CACjE,MAAM,QAAQ,SAAgC;EAC1C,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAE/B,OAAO,SAAS,KAAK,QAAQ,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK;CACrE;CAEA,MAAM,cAAc,KAAK,WAAW;CACpC,MAAM,OAAO,OAAO,KAAK,QAAQ,CAAC;CAClC,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,UAAU,KAAK,gBAAgB;CAErC,IAAI,CAAC,aAAa,MAAM,IAAI,MAAM,wCAAwC;CAC1E,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,QAAQ,KAAK,OAAO,OAC/C,MAAM,IAAI,MAAM,8CAA8C;CAElE,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,sCAAsC;CAElE,MAAM,gBAAgB,YAAY,OAAO,0BAA0B,OAAO,OAAO;CACjF,IAAI,CAAC,OAAO,SAAS,aAAa,KAAK,gBAAgB,GACnD,MAAM,IAAI,MAAM,+EAA+E;CAGnG,OAAO;EAAE;EAAa;EAAM;EAAO;CAAc;AACrD;;;;;;;;;;;;;AAcA,eAAsB,iBAAmD;CACrE,MAAM,aAAsC,CAAC;CAC7C,KAAK,MAAM,aAAa,mBAAmB;EACvC,IAAI;EACJ,IAAI;GACA,SAAU,MAAM,OAAO,UAAU;EACrC,SAAS,KAAK;GACV,MAAM,IAAI,MACN,mBAAmB,UAAU,OAAO,wBAAwB,UAAU,KAAK,cACxE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,yIAGxD;EACJ;EACA,MAAM,SAAS,OAAO,UAAU;EAChC,IAAI,CAAC,QACD,MAAM,IAAI,MACN,GAAG,UAAU,OAAO,mBAAmB,UAAU,OAAO,6DAE5D;EAEJ,WAAW,UAAU,QAAQ;CACjC;CAEA,OAAO;AACX;;;;;;;;;;;;;;;AAgBA,IAAM,qBAAqB;AAe3B,SAAS,oBAAoB,OAAe,cAA0B,WAAgD;CAClH,OAAO,IAAI,SAAS,SAAS,WAAW;EACpC,MAAM,SAAS,IAAI,cAAc,WAAW;GACxC,aAAa;GACb,OAAO,MAAM,iBAAiB,MAAM,GAAG;GACvC,IAAI,WAAW;GACf,MAAM,QAAQ,iBAAiB,OAAO,IAAI,GAAG,kBAAkB;GAC/D,OAAO,GAAG,SAAS,UAAU;IACzB,YAAY,MAAM,SAAS,MAAM;IACjC,MAAM,UAAU,SAAS,QAAQ,IAAI;IACrC,IAAI,YAAY,IAAI;IACpB,aAAa,KAAK;IAClB,MAAM,UAAU,SAAS,MAAM,GAAG,OAAO,CAAC,CAAC,KAAK;IAChD,WAAW;IACX,UAAU,OAAO,CAAC,CACb,MAAM,UAAU,OAAO,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,CACzC,OAAO,QAAQ,OAAO,IAAI,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GAAG,CAAC;GACjG,CAAC;GACD,OAAO,GAAG,eAAe,aAAa,KAAK,CAAC;GAC5C,OAAO,GAAG,eAAe,aAAa,KAAK,CAAC;EAChD,CAAC;EACD,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,GAAG,mBAAmB,QAAQ,MAAM,CAAC;CACvD,CAAC;AACL;AAEA,eAAsB,UAAU,MAAiC;CAC7D,MAAM,YAAY,QAAQ,KAAK,WAAW;CAC1C,GAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;CAE3C,MAAM,EAAE,WAAY,MAAM,OAAO;CAGjC,MAAM,EAAE,uBAAwB,MAAM,OAAO;CAQ7C,MAAM,aAAa,MAAM,eAAe;CAgBxC,MAAM,gBAAgB,OAAO,aAAqB,YAAoB,UAAqC;EACvG,GAAG,UAAU,aAAa,EAAE,WAAW,KAAK,CAAC;EAC7C,MAAM,KAAM,MAAM,OAAO,OAAO;GAAE,SAAS;GAAa;EAAW,CAAC;EACpE,MAAM,eAAe,MAAM,aAAa;EACxC,MAAM,SAAS,IAAI,mBAAmB;GAClC;GACA,MAAM;GACN,MAAM;GAKN,gBAAA;EACJ,CAAC;EACD,MAAM,OAAO,MAAM;EACnB,MAAM,QAAQ,IAAI,kBAAkB;GAChC;GACA;GACA,iBAAiB,SAAS,UAAU,WAAW;IAC3C,IAAI,SAAS,GAAG,QAAQ,OAAO,MAAM,SAAS,MAAM,4BAA4B,QAAQ,MAAM,OAAO,aAAa;GACtH;EACJ,CAAC;EACD,MAAM,MAAM,MAAM;EAClB,OAAO;GAAE;GAAI;GAAQ;GAAO,MAAM;GAAY,SAAS;EAAY;CACvE;CAEA,MAAM,UAAU,MAAM,cAAc,WAAW,KAAK,MAAM,EAAE;;CAE5D,MAAM,6BAAa,IAAI,IAAsB;;CAE7C,MAAM,2BAAW,IAAI,IAA+B;CAMpD,IAAI,YAA2B,KAAK,IAAI;CAExC,MAAM,gBAAgB,iBAAyB;EAC3C,MAAM,YAA+D,CAAC;EACtE,KAAK,MAAM,CAAC,KAAK,aAAa,YAAY,UAAU,OAAO;GAAE,MAAM,SAAS;GAAM,SAAS,SAAS;EAAQ;EAG5G,MAAM,QAAgC,GAAG,uBAAuB,KAAK,KAAK;EAC1E,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,GAAG,MAAM,OAAO,MAAM;EACzE,mBAAmB,KAAK,aAAa,KAAK;EAC1C,WAAW,KAAK,aAAa;GACzB,MAAM,KAAK;GACX,KAAK,QAAQ;GACb,SAAS;GACT,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GAClC,OAAO,KAAK;GACZ;GACA,GAAI,WAAW,OAAO,IAAI,EAAE,UAAU,IAAI,CAAC;GAG3C,YAAY,WAAW;EAC3B,CAAC;CACL;CAEA,IAAI,eAAe;CACnB,MAAM,mBAAmB,OAAO,QAAmC;EAC/D,MAAM,WAAW,WAAW,IAAI,GAAG;EACnC,IAAI,UAAU,OAAO;EACrB,MAAM,WAAW,SAAS,IAAI,GAAG;EACjC,IAAI,UAAU,OAAO;EACrB,MAAM,WAAW,YAAY;GAEzB,MAAM,WAAW,MAAM,cAAc,kBAAkB,KAAK,aAAa,GAAG,GAAG,MAAM,kBAAkB,KAAK,aAAa,GAAG,GAAG,KAAK,IAAI,EAAE;GAC1I,WAAW,IAAI,KAAK,QAAQ;GAC5B,aAAa,YAAY;GACzB,QAAQ,OAAO,MAAM,WAAW,IAAI,wBAAwB,SAAS,KAAK,GAAG;GAC7E,OAAO;EACX,EAAA,CAAG;EACH,SAAS,IAAI,KAAK,OAAO;EACzB,IAAI;GACA,OAAO,MAAM;EACjB,UAAU;GACN,SAAS,OAAO,GAAG;EACvB;CACJ;CAEA,MAAM,WAAW,MAAM,oBAAoB,KAAK,aAAa;EACzD,YAAY;CAChB,GAAG,OAAO,YAAY;EAClB,IAAI,YAAY,WACZ,OAAO,MAAM,CAAC,SAAS,GAAG,WAAW,OAAO,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,MAAM,SAAS,MAAM,iBAAiB,CAAC;EAEpH,MAAM,QAAQ,mBAAmB,KAAK,OAAO;EAC7C,IAAI,CAAC,OAAO,OAAO,0BAA0B,QAAQ;EACrD,MAAM,MAAM,MAAM;EAIlB,IAAI,CAAC,YAAY,KAAK,GAAG,KAAK,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,IAAI,GAAG,OAAO,sBAAsB,IAAI;EAExG,OAAO,OAAM,MADU,iBAAiB,GAAG,EAAA,CACrB;CAC1B,CAAC;CACD,MAAM,kBAAkB,SAAS,QAAQ;CACzC,eAAe,oBAAoB,QAAQ,OAAO,oBAAoB,WAAW,gBAAgB,OAAO;CAExG,aAAa,YAAY;CAEzB,IAAI,eAAe;CACnB,MAAM,WAAW,OAAO,WAAmB;EACvC,IAAI,cAAc;EAClB,eAAe;EACf,QAAQ,OAAO,MAAM,qBAAqB,OAAO,IAAI;EAIrD,WAAW,KAAK,WAAW;EAC3B,SAAS,MAAM;EACf,KAAK,MAAM,YAAY,CAAC,SAAS,GAAG,WAAW,OAAO,CAAC,GAAG;GACtD,IAAI;IACA,MAAM,SAAS,MAAM,KAAK;GAC9B,QAAQ,CAAqB;GAC7B,IAAI;IACA,MAAM,SAAS,OAAO,KAAK;GAC/B,QAAQ,CAAqB;GAC7B,IAAI;IACA,MAAM,SAAS,GAAG,MAAM;GAC5B,QAAQ,CAAuB;EACnC;EACA,QAAQ,KAAK,CAAC;CAClB;CAEA,QAAQ,GAAG,gBAAgB,KAAK,SAAS,QAAQ,CAAC;CAClD,QAAQ,GAAG,iBAAiB,KAAK,SAAS,SAAS,CAAC;CAIpD,QAAQ,GAAG,oBAAoB,CAA4B,CAAC;CAE5D,IAAI,KAAK,gBAAgB,GAoBrB,kBAnBgC;EAK5B,IAJa,CAAC,SAAS,GAAG,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM,aAAa;GAC9D,MAAM,QAAQ,SAAS,OAAO,SAAS;GACvC,OAAO,MAAM,oBAAoB,KAAK,MAAM,gBAAgB;EAChE,CACI,GAAM;GACN,YAAY;GAEZ;EACJ;EACA,IAAI,cAAc,MAAM;GACpB,YAAY,KAAK,IAAI;GAErB;EACJ;EACA,IAAI,KAAK,IAAI,IAAI,aAAa,KAAK,eAC/B,SAAc,YAAY,KAAK,MAAM,KAAK,gBAAgB,GAAM,EAAE,SAAS;CAEnF,GAAG,sBACH,CAAA,CAAM,MAAM;CAGhB,QAAQ,OAAO,MAAM,8BAA8B,KAAK,KAAK,GAAG;AACpE"}