@spine-event-engine/server 2.0.0-snapshot.2

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 (354) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +569 -0
  3. package/REFERENCE.md +488 -0
  4. package/dist/bus/command-bus.d.ts +61 -0
  5. package/dist/bus/command-bus.d.ts.map +1 -0
  6. package/dist/bus/command-bus.js +201 -0
  7. package/dist/bus/command-bus.js.map +1 -0
  8. package/dist/bus/command-dispatcher-registry.d.ts +33 -0
  9. package/dist/bus/command-dispatcher-registry.d.ts.map +1 -0
  10. package/dist/bus/command-dispatcher-registry.js +75 -0
  11. package/dist/bus/command-dispatcher-registry.js.map +1 -0
  12. package/dist/bus/command-dispatcher.d.ts +25 -0
  13. package/dist/bus/command-dispatcher.d.ts.map +1 -0
  14. package/dist/bus/command-dispatcher.js +15 -0
  15. package/dist/bus/command-dispatcher.js.map +1 -0
  16. package/dist/bus/command-errors.d.ts +23 -0
  17. package/dist/bus/command-errors.d.ts.map +1 -0
  18. package/dist/bus/command-errors.js +52 -0
  19. package/dist/bus/command-errors.js.map +1 -0
  20. package/dist/bus/event-bus.d.ts +113 -0
  21. package/dist/bus/event-bus.d.ts.map +1 -0
  22. package/dist/bus/event-bus.js +518 -0
  23. package/dist/bus/event-bus.js.map +1 -0
  24. package/dist/bus/event-dispatcher-origin-schemas.d.ts +17 -0
  25. package/dist/bus/event-dispatcher-origin-schemas.d.ts.map +1 -0
  26. package/dist/bus/event-dispatcher-origin-schemas.js +32 -0
  27. package/dist/bus/event-dispatcher-origin-schemas.js.map +1 -0
  28. package/dist/bus/event-dispatcher-registry.d.ts +48 -0
  29. package/dist/bus/event-dispatcher-registry.d.ts.map +1 -0
  30. package/dist/bus/event-dispatcher-registry.js +176 -0
  31. package/dist/bus/event-dispatcher-registry.js.map +1 -0
  32. package/dist/bus/event-dispatcher.d.ts +48 -0
  33. package/dist/bus/event-dispatcher.d.ts.map +1 -0
  34. package/dist/bus/event-dispatcher.js +15 -0
  35. package/dist/bus/event-dispatcher.js.map +1 -0
  36. package/dist/context/bounded-context.d.ts +606 -0
  37. package/dist/context/bounded-context.d.ts.map +1 -0
  38. package/dist/context/bounded-context.js +1780 -0
  39. package/dist/context/bounded-context.js.map +1 -0
  40. package/dist/context/entity-inbox.d.ts +63 -0
  41. package/dist/context/entity-inbox.d.ts.map +1 -0
  42. package/dist/context/entity-inbox.js +380 -0
  43. package/dist/context/entity-inbox.js.map +1 -0
  44. package/dist/context/local-inbox-handoff.d.ts +244 -0
  45. package/dist/context/local-inbox-handoff.d.ts.map +1 -0
  46. package/dist/context/local-inbox-handoff.js +334 -0
  47. package/dist/context/local-inbox-handoff.js.map +1 -0
  48. package/dist/context/projection-handoff.d.ts +46 -0
  49. package/dist/context/projection-handoff.d.ts.map +1 -0
  50. package/dist/context/projection-handoff.js +172 -0
  51. package/dist/context/projection-handoff.js.map +1 -0
  52. package/dist/context/tenant-index.d.ts +41 -0
  53. package/dist/context/tenant-index.d.ts.map +1 -0
  54. package/dist/context/tenant-index.js +92 -0
  55. package/dist/context/tenant-index.js.map +1 -0
  56. package/dist/delivery/conditional-pickup.d.ts +25 -0
  57. package/dist/delivery/conditional-pickup.d.ts.map +1 -0
  58. package/dist/delivery/conditional-pickup.js +26 -0
  59. package/dist/delivery/conditional-pickup.js.map +1 -0
  60. package/dist/delivery/delivery-builder.d.ts +216 -0
  61. package/dist/delivery/delivery-builder.d.ts.map +1 -0
  62. package/dist/delivery/delivery-builder.js +323 -0
  63. package/dist/delivery/delivery-builder.js.map +1 -0
  64. package/dist/delivery/delivery-lease.d.ts +15 -0
  65. package/dist/delivery/delivery-lease.d.ts.map +1 -0
  66. package/dist/delivery/delivery-lease.js +44 -0
  67. package/dist/delivery/delivery-lease.js.map +1 -0
  68. package/dist/delivery/delivery-loop.d.ts +113 -0
  69. package/dist/delivery/delivery-loop.d.ts.map +1 -0
  70. package/dist/delivery/delivery-loop.js +119 -0
  71. package/dist/delivery/delivery-loop.js.map +1 -0
  72. package/dist/delivery/delivery-monitor.d.ts +165 -0
  73. package/dist/delivery/delivery-monitor.d.ts.map +1 -0
  74. package/dist/delivery/delivery-monitor.js +168 -0
  75. package/dist/delivery/delivery-monitor.js.map +1 -0
  76. package/dist/delivery/delivery-ports.d.ts +141 -0
  77. package/dist/delivery/delivery-ports.d.ts.map +1 -0
  78. package/dist/delivery/delivery-ports.js +15 -0
  79. package/dist/delivery/delivery-ports.js.map +1 -0
  80. package/dist/delivery/delivery-run-control.d.ts +50 -0
  81. package/dist/delivery/delivery-run-control.d.ts.map +1 -0
  82. package/dist/delivery/delivery-run-control.js +66 -0
  83. package/dist/delivery/delivery-run-control.js.map +1 -0
  84. package/dist/delivery/delivery-run-coordinator.d.ts +218 -0
  85. package/dist/delivery/delivery-run-coordinator.d.ts.map +1 -0
  86. package/dist/delivery/delivery-run-coordinator.js +609 -0
  87. package/dist/delivery/delivery-run-coordinator.js.map +1 -0
  88. package/dist/delivery/delivery-storage-error.d.ts +13 -0
  89. package/dist/delivery/delivery-storage-error.d.ts.map +1 -0
  90. package/dist/delivery/delivery-storage-error.js +30 -0
  91. package/dist/delivery/delivery-storage-error.js.map +1 -0
  92. package/dist/delivery/delivery-supervisor.d.ts +175 -0
  93. package/dist/delivery/delivery-supervisor.d.ts.map +1 -0
  94. package/dist/delivery/delivery-supervisor.js +469 -0
  95. package/dist/delivery/delivery-supervisor.js.map +1 -0
  96. package/dist/delivery/delivery-worker.d.ts +180 -0
  97. package/dist/delivery/delivery-worker.d.ts.map +1 -0
  98. package/dist/delivery/delivery-worker.js +232 -0
  99. package/dist/delivery/delivery-worker.js.map +1 -0
  100. package/dist/delivery/delivery.d.ts +238 -0
  101. package/dist/delivery/delivery.d.ts.map +1 -0
  102. package/dist/delivery/delivery.js +400 -0
  103. package/dist/delivery/delivery.js.map +1 -0
  104. package/dist/delivery/inbox-records.d.ts +19 -0
  105. package/dist/delivery/inbox-records.d.ts.map +1 -0
  106. package/dist/delivery/inbox-records.js +291 -0
  107. package/dist/delivery/inbox-records.js.map +1 -0
  108. package/dist/delivery/inbox-storage.d.ts +81 -0
  109. package/dist/delivery/inbox-storage.d.ts.map +1 -0
  110. package/dist/delivery/inbox-storage.js +353 -0
  111. package/dist/delivery/inbox-storage.js.map +1 -0
  112. package/dist/delivery/inbox.d.ts +263 -0
  113. package/dist/delivery/inbox.d.ts.map +1 -0
  114. package/dist/delivery/inbox.js +172 -0
  115. package/dist/delivery/inbox.js.map +1 -0
  116. package/dist/delivery/parked-delivery-obligations.d.ts +182 -0
  117. package/dist/delivery/parked-delivery-obligations.d.ts.map +1 -0
  118. package/dist/delivery/parked-delivery-obligations.js +390 -0
  119. package/dist/delivery/parked-delivery-obligations.js.map +1 -0
  120. package/dist/delivery/shard-index.d.ts +41 -0
  121. package/dist/delivery/shard-index.d.ts.map +1 -0
  122. package/dist/delivery/shard-index.js +67 -0
  123. package/dist/delivery/shard-index.js.map +1 -0
  124. package/dist/delivery/sharded-work-registry.d.ts +114 -0
  125. package/dist/delivery/sharded-work-registry.d.ts.map +1 -0
  126. package/dist/delivery/sharded-work-registry.js +360 -0
  127. package/dist/delivery/sharded-work-registry.js.map +1 -0
  128. package/dist/entity/entity-metadata.d.ts +173 -0
  129. package/dist/entity/entity-metadata.d.ts.map +1 -0
  130. package/dist/entity/entity-metadata.js +187 -0
  131. package/dist/entity/entity-metadata.js.map +1 -0
  132. package/dist/entity/entity-storage-descriptor.d.ts +64 -0
  133. package/dist/entity/entity-storage-descriptor.d.ts.map +1 -0
  134. package/dist/entity/entity-storage-descriptor.js +227 -0
  135. package/dist/entity/entity-storage-descriptor.js.map +1 -0
  136. package/dist/entity/entity-transaction.d.ts +372 -0
  137. package/dist/entity/entity-transaction.d.ts.map +1 -0
  138. package/dist/entity/entity-transaction.js +413 -0
  139. package/dist/entity/entity-transaction.js.map +1 -0
  140. package/dist/entity/entity-transition-validation.d.ts +47 -0
  141. package/dist/entity/entity-transition-validation.d.ts.map +1 -0
  142. package/dist/entity/entity-transition-validation.js +327 -0
  143. package/dist/entity/entity-transition-validation.js.map +1 -0
  144. package/dist/entity/entity.d.ts +499 -0
  145. package/dist/entity/entity.d.ts.map +1 -0
  146. package/dist/entity/entity.js +829 -0
  147. package/dist/entity/entity.js.map +1 -0
  148. package/dist/entity/implicit-required-id.d.ts +15 -0
  149. package/dist/entity/implicit-required-id.d.ts.map +1 -0
  150. package/dist/entity/implicit-required-id.js +123 -0
  151. package/dist/entity/implicit-required-id.js.map +1 -0
  152. package/dist/entity/spec-scanner.d.ts +14 -0
  153. package/dist/entity/spec-scanner.d.ts.map +1 -0
  154. package/dist/entity/spec-scanner.js +37 -0
  155. package/dist/entity/spec-scanner.js.map +1 -0
  156. package/dist/handler/build-time-handler-analyzer.d.ts +150 -0
  157. package/dist/handler/build-time-handler-analyzer.d.ts.map +1 -0
  158. package/dist/handler/build-time-handler-analyzer.js +1101 -0
  159. package/dist/handler/build-time-handler-analyzer.js.map +1 -0
  160. package/dist/handler/command-registration-readiness.d.ts +99 -0
  161. package/dist/handler/command-registration-readiness.d.ts.map +1 -0
  162. package/dist/handler/command-registration-readiness.js +118 -0
  163. package/dist/handler/command-registration-readiness.js.map +1 -0
  164. package/dist/handler/event-handler-filter.d.ts +43 -0
  165. package/dist/handler/event-handler-filter.d.ts.map +1 -0
  166. package/dist/handler/event-handler-filter.js +128 -0
  167. package/dist/handler/event-handler-filter.js.map +1 -0
  168. package/dist/handler/event-registration-readiness.d.ts +196 -0
  169. package/dist/handler/event-registration-readiness.d.ts.map +1 -0
  170. package/dist/handler/event-registration-readiness.js +187 -0
  171. package/dist/handler/event-registration-readiness.js.map +1 -0
  172. package/dist/handler/external.d.ts +10 -0
  173. package/dist/handler/external.d.ts.map +1 -0
  174. package/dist/handler/external.js +15 -0
  175. package/dist/handler/external.js.map +1 -0
  176. package/dist/handler/generated-handler-registry.d.ts +154 -0
  177. package/dist/handler/generated-handler-registry.d.ts.map +1 -0
  178. package/dist/handler/generated-handler-registry.js +223 -0
  179. package/dist/handler/generated-handler-registry.js.map +1 -0
  180. package/dist/handler/generated-registry-discovery.d.ts +94 -0
  181. package/dist/handler/generated-registry-discovery.d.ts.map +1 -0
  182. package/dist/handler/generated-registry-discovery.js +228 -0
  183. package/dist/handler/generated-registry-discovery.js.map +1 -0
  184. package/dist/handler/generated-registry-writer.d.ts +58 -0
  185. package/dist/handler/generated-registry-writer.d.ts.map +1 -0
  186. package/dist/handler/generated-registry-writer.js +458 -0
  187. package/dist/handler/generated-registry-writer.js.map +1 -0
  188. package/dist/handler/handler-codegen.d.ts +48 -0
  189. package/dist/handler/handler-codegen.d.ts.map +1 -0
  190. package/dist/handler/handler-codegen.js +214 -0
  191. package/dist/handler/handler-codegen.js.map +1 -0
  192. package/dist/handler/handler-decorators.d.ts +134 -0
  193. package/dist/handler/handler-decorators.d.ts.map +1 -0
  194. package/dist/handler/handler-decorators.js +239 -0
  195. package/dist/handler/handler-decorators.js.map +1 -0
  196. package/dist/handler/handler-metadata.d.ts +510 -0
  197. package/dist/handler/handler-metadata.d.ts.map +1 -0
  198. package/dist/handler/handler-metadata.js +391 -0
  199. package/dist/handler/handler-metadata.js.map +1 -0
  200. package/dist/handler/registration-readiness-metadata.d.ts +68 -0
  201. package/dist/handler/registration-readiness-metadata.d.ts.map +1 -0
  202. package/dist/handler/registration-readiness-metadata.js +170 -0
  203. package/dist/handler/registration-readiness-metadata.js.map +1 -0
  204. package/dist/handler/rejection-source.d.ts +11 -0
  205. package/dist/handler/rejection-source.d.ts.map +1 -0
  206. package/dist/handler/rejection-source.js +25 -0
  207. package/dist/handler/rejection-source.js.map +1 -0
  208. package/dist/index.d.ts +46 -0
  209. package/dist/index.d.ts.map +1 -0
  210. package/dist/index.js +54 -0
  211. package/dist/index.js.map +1 -0
  212. package/dist/integration/external-messages.d.ts +49 -0
  213. package/dist/integration/external-messages.d.ts.map +1 -0
  214. package/dist/integration/external-messages.js +112 -0
  215. package/dist/integration/external-messages.js.map +1 -0
  216. package/dist/integration/integration-broker.d.ts +74 -0
  217. package/dist/integration/integration-broker.d.ts.map +1 -0
  218. package/dist/integration/integration-broker.js +444 -0
  219. package/dist/integration/integration-broker.js.map +1 -0
  220. package/dist/integration/third-party-context.d.ts +56 -0
  221. package/dist/integration/third-party-context.d.ts.map +1 -0
  222. package/dist/integration/third-party-context.js +130 -0
  223. package/dist/integration/third-party-context.js.map +1 -0
  224. package/dist/repository/command-errors.d.ts +25 -0
  225. package/dist/repository/command-errors.d.ts.map +1 -0
  226. package/dist/repository/command-errors.js +43 -0
  227. package/dist/repository/command-errors.js.map +1 -0
  228. package/dist/repository/command-routing.d.ts +72 -0
  229. package/dist/repository/command-routing.d.ts.map +1 -0
  230. package/dist/repository/command-routing.js +98 -0
  231. package/dist/repository/command-routing.js.map +1 -0
  232. package/dist/repository/commit-fence.d.ts +22 -0
  233. package/dist/repository/commit-fence.d.ts.map +1 -0
  234. package/dist/repository/commit-fence.js +37 -0
  235. package/dist/repository/commit-fence.js.map +1 -0
  236. package/dist/repository/event-routing.d.ts +71 -0
  237. package/dist/repository/event-routing.d.ts.map +1 -0
  238. package/dist/repository/event-routing.js +96 -0
  239. package/dist/repository/event-routing.js.map +1 -0
  240. package/dist/repository/primitive-id.d.ts +86 -0
  241. package/dist/repository/primitive-id.d.ts.map +1 -0
  242. package/dist/repository/primitive-id.js +110 -0
  243. package/dist/repository/primitive-id.js.map +1 -0
  244. package/dist/repository/repository.d.ts +670 -0
  245. package/dist/repository/repository.d.ts.map +1 -0
  246. package/dist/repository/repository.js +3298 -0
  247. package/dist/repository/repository.js.map +1 -0
  248. package/dist/repository/routing-declarations.d.ts +75 -0
  249. package/dist/repository/routing-declarations.d.ts.map +1 -0
  250. package/dist/repository/routing-declarations.js +101 -0
  251. package/dist/repository/routing-declarations.js.map +1 -0
  252. package/dist/repository/state-update-routing.d.ts +66 -0
  253. package/dist/repository/state-update-routing.d.ts.map +1 -0
  254. package/dist/repository/state-update-routing.js +91 -0
  255. package/dist/repository/state-update-routing.js.map +1 -0
  256. package/dist/runtime/runtime.d.ts +156 -0
  257. package/dist/runtime/runtime.d.ts.map +1 -0
  258. package/dist/runtime/runtime.js +215 -0
  259. package/dist/runtime/runtime.js.map +1 -0
  260. package/dist/runtime/signal-intake.d.ts +84 -0
  261. package/dist/runtime/signal-intake.d.ts.map +1 -0
  262. package/dist/runtime/signal-intake.js +94 -0
  263. package/dist/runtime/signal-intake.js.map +1 -0
  264. package/dist/runtime/signal-metadata.d.ts +242 -0
  265. package/dist/runtime/signal-metadata.d.ts.map +1 -0
  266. package/dist/runtime/signal-metadata.js +368 -0
  267. package/dist/runtime/signal-metadata.js.map +1 -0
  268. package/dist/server/browser-server.d.ts +49 -0
  269. package/dist/server/browser-server.d.ts.map +1 -0
  270. package/dist/server/browser-server.js +633 -0
  271. package/dist/server/browser-server.js.map +1 -0
  272. package/dist/server/durable-subscription-bindings.d.ts +122 -0
  273. package/dist/server/durable-subscription-bindings.d.ts.map +1 -0
  274. package/dist/server/durable-subscription-bindings.js +524 -0
  275. package/dist/server/durable-subscription-bindings.js.map +1 -0
  276. package/dist/server/environment-attachment.d.ts +356 -0
  277. package/dist/server/environment-attachment.d.ts.map +1 -0
  278. package/dist/server/environment-attachment.js +2061 -0
  279. package/dist/server/environment-attachment.js.map +1 -0
  280. package/dist/server/environment-delivery-records.d.ts +77 -0
  281. package/dist/server/environment-delivery-records.d.ts.map +1 -0
  282. package/dist/server/environment-delivery-records.js +282 -0
  283. package/dist/server/environment-delivery-records.js.map +1 -0
  284. package/dist/server/environment-delivery-worker.d.ts +161 -0
  285. package/dist/server/environment-delivery-worker.d.ts.map +1 -0
  286. package/dist/server/environment-delivery-worker.js +664 -0
  287. package/dist/server/environment-delivery-worker.js.map +1 -0
  288. package/dist/server/environment.d.ts +43 -0
  289. package/dist/server/environment.d.ts.map +1 -0
  290. package/dist/server/environment.js +90 -0
  291. package/dist/server/environment.js.map +1 -0
  292. package/dist/server/managed-child-subscription.d.ts +11 -0
  293. package/dist/server/managed-child-subscription.d.ts.map +1 -0
  294. package/dist/server/managed-child-subscription.js +44 -0
  295. package/dist/server/managed-child-subscription.js.map +1 -0
  296. package/dist/server/managed-server-application.d.ts +324 -0
  297. package/dist/server/managed-server-application.d.ts.map +1 -0
  298. package/dist/server/managed-server-application.js +937 -0
  299. package/dist/server/managed-server-application.js.map +1 -0
  300. package/dist/server/node-coordinator.d.ts +191 -0
  301. package/dist/server/node-coordinator.d.ts.map +1 -0
  302. package/dist/server/node-coordinator.js +552 -0
  303. package/dist/server/node-coordinator.js.map +1 -0
  304. package/dist/server/process-server-coordinator.d.ts +15 -0
  305. package/dist/server/process-server-coordinator.d.ts.map +1 -0
  306. package/dist/server/process-server-coordinator.js +112 -0
  307. package/dist/server/process-server-coordinator.js.map +1 -0
  308. package/dist/server/reserved-spine-rpc-paths.json +8 -0
  309. package/dist/server/retryable-close.d.ts +28 -0
  310. package/dist/server/retryable-close.d.ts.map +1 -0
  311. package/dist/server/retryable-close.js +129 -0
  312. package/dist/server/retryable-close.js.map +1 -0
  313. package/dist/server/server-environment.d.ts +180 -0
  314. package/dist/server/server-environment.d.ts.map +1 -0
  315. package/dist/server/server-environment.js +418 -0
  316. package/dist/server/server-environment.js.map +1 -0
  317. package/dist/server/server-log.d.ts +18 -0
  318. package/dist/server/server-log.d.ts.map +1 -0
  319. package/dist/server/server-log.js +96 -0
  320. package/dist/server/server-log.js.map +1 -0
  321. package/dist/server/server.d.ts +389 -0
  322. package/dist/server/server.d.ts.map +1 -0
  323. package/dist/server/server.js +752 -0
  324. package/dist/server/server.js.map +1 -0
  325. package/dist/services/spine-services.d.ts +88 -0
  326. package/dist/services/spine-services.d.ts.map +1 -0
  327. package/dist/services/spine-services.js +1586 -0
  328. package/dist/services/spine-services.js.map +1 -0
  329. package/dist/stand/stand.d.ts +294 -0
  330. package/dist/stand/stand.d.ts.map +1 -0
  331. package/dist/stand/stand.js +680 -0
  332. package/dist/stand/stand.js.map +1 -0
  333. package/dist/stand/subscription-observer.d.ts +53 -0
  334. package/dist/stand/subscription-observer.d.ts.map +1 -0
  335. package/dist/stand/subscription-observer.js +444 -0
  336. package/dist/stand/subscription-observer.js.map +1 -0
  337. package/dist/stand/subscription-records.d.ts +12 -0
  338. package/dist/stand/subscription-records.d.ts.map +1 -0
  339. package/dist/stand/subscription-records.js +99 -0
  340. package/dist/stand/subscription-records.js.map +1 -0
  341. package/dist/stand/subscription-registry.d.ts +285 -0
  342. package/dist/stand/subscription-registry.d.ts.map +1 -0
  343. package/dist/stand/subscription-registry.js +403 -0
  344. package/dist/stand/subscription-registry.js.map +1 -0
  345. package/dist/stand/subscription-runtime.d.ts +102 -0
  346. package/dist/stand/subscription-runtime.d.ts.map +1 -0
  347. package/dist/stand/subscription-runtime.js +380 -0
  348. package/dist/stand/subscription-runtime.js.map +1 -0
  349. package/dist/testing/index.d.ts +14 -0
  350. package/dist/testing/index.d.ts.map +1 -0
  351. package/dist/testing/index.js +34 -0
  352. package/dist/testing/index.js.map +1 -0
  353. package/dist/tsconfig.tsbuildinfo +1 -0
  354. package/package.json +65 -0
@@ -0,0 +1,937 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ import { fork } from "node:child_process";
15
+ import { randomUUID } from "node:crypto";
16
+ import { fileURLToPath } from "node:url";
17
+ import { LogLayer, StructuredTransport } from "loglayer";
18
+ import { runningServerAccess } from "./server.js";
19
+ import { emitServerWarning } from "./server-log.js";
20
+ import { NodeCoordinator } from "./node-coordinator.js";
21
+ import { environmentDeliveryWorkerAccess } from "./environment-delivery-worker.js";
22
+ import { managedChildSubscriptionAccess } from "./managed-child-subscription.js";
23
+ import { InMemorySubscriptionRegistry } from "../stand/subscription-registry.js";
24
+ const childMarker = "SPINE_MANAGED_SERVER_CHILD";
25
+ const slotMarker = "SPINE_MANAGED_SERVER_SLOT";
26
+ const incarnationMarker = "SPINE_MANAGED_SERVER_INCARNATION";
27
+ const initialRestartDelayMs = 250;
28
+ const maximumRestartDelayMs = 30_000;
29
+ const healthyReadyMs = 60_000;
30
+ const closeGraceMs = 1_000;
31
+ const closeKillMs = 1_000;
32
+ const endpointMaximumBytes = 256;
33
+ const handleMembers = new WeakMap();
34
+ const managedTestRegistries = new WeakMap();
35
+ const lifecycleLogger = new LogLayer({
36
+ transport: new StructuredTransport({
37
+ logger: console,
38
+ level: "warn",
39
+ stringify: true,
40
+ messageField: "message",
41
+ dateField: "timestamp",
42
+ levelField: "severity",
43
+ levelFn: (level) => level.toUpperCase(),
44
+ }),
45
+ }).child();
46
+ /**
47
+ *
48
+ * Starts a managed parent and its complete-replica child processes.
49
+ */
50
+ export const ManagedServerApplication = Object.freeze({
51
+ async start(options) {
52
+ return ManagedServerValues.start(options, false);
53
+ },
54
+ async run(options) {
55
+ return ManagedServerValues.start(options, true);
56
+ },
57
+ });
58
+ const ManagedServerValues = Object.freeze({
59
+ async start(options, manageProcessSignals) {
60
+ if (!Number.isSafeInteger(options.processCount) || options.processCount < 1) {
61
+ throw new Error("Managed server processCount must be a positive safe integer.");
62
+ }
63
+ ManagedServerCoordinatorValues.coordinatorPort(options.port);
64
+ if (process.env[childMarker] === "true")
65
+ return ManagedServerValues.child(options);
66
+ return ManagedServerValues.parent(options, manageProcessSignals);
67
+ },
68
+ async child(options) {
69
+ const server = await options.createServer({ host: "127.0.0.1", port: 0 });
70
+ try {
71
+ ManagedServerValues.requireVolatileRegistries(server);
72
+ await options.synchronize?.();
73
+ }
74
+ catch (error) {
75
+ try {
76
+ await server.close();
77
+ }
78
+ catch (cleanup) {
79
+ throw new AggregateError([error, cleanup], "Managed child setup and server cleanup failed.");
80
+ }
81
+ throw error;
82
+ }
83
+ // Node may emit an IPC EPIPE after a parent has already disappeared. The
84
+ // disconnect handler below owns shutdown; this listener prevents that
85
+ // transport detail from becoming an unhandled child-process exception.
86
+ const containIpcError = () => undefined;
87
+ process.on("error", containIpcError);
88
+ managedChildSubscriptionAccess.install((subscriptionId) => {
89
+ const notification = ManagedServerValues.send({
90
+ type: "subscription-installed",
91
+ slot: process.env[slotMarker],
92
+ incarnation: process.env[incarnationMarker],
93
+ subscriptionId,
94
+ });
95
+ // spine-log-boundary: server.managed_replica_subscription_ipc
96
+ void notification.catch(() => undefined);
97
+ });
98
+ let activated = false;
99
+ let releaseActivation;
100
+ const activation = new Promise((resolve) => {
101
+ releaseActivation = resolve;
102
+ });
103
+ let closing;
104
+ const close = () => {
105
+ if (closing !== undefined)
106
+ return closing;
107
+ const draining = ManagedServerValues.send({
108
+ type: "draining",
109
+ slot: process.env[slotMarker],
110
+ incarnation: process.env[incarnationMarker],
111
+ });
112
+ // spine-log-boundary: server.managed_replica_drain_ipc
113
+ const attempt = draining
114
+ .catch(() => undefined)
115
+ .then(() => {
116
+ environmentDeliveryWorkerAccess.cancelManagedChild();
117
+ managedChildSubscriptionAccess.clear();
118
+ releaseActivation();
119
+ return runningServerAccess.drainDelivery(server);
120
+ })
121
+ .then(() => {
122
+ return server.close();
123
+ })
124
+ .then(() => {
125
+ const closed = ManagedServerValues.send({
126
+ type: "closed",
127
+ slot: process.env[slotMarker],
128
+ incarnation: process.env[incarnationMarker],
129
+ });
130
+ // spine-log-boundary: server.managed_replica_closed_ipc
131
+ return closed.catch(() => undefined);
132
+ }, (error) => {
133
+ const failed = ManagedServerValues.send({
134
+ type: "close-failed",
135
+ slot: process.env[slotMarker],
136
+ incarnation: process.env[incarnationMarker],
137
+ });
138
+ // spine-log-boundary: server.managed_replica_close_failed_ipc
139
+ void failed.catch(() => undefined);
140
+ if (closing === attempt)
141
+ closing = undefined;
142
+ throw error;
143
+ })
144
+ .then(() => {
145
+ process.off("disconnect", onDisconnect);
146
+ if (process.connected && typeof process.disconnect === "function")
147
+ process.disconnect();
148
+ process.off("error", containIpcError);
149
+ process.off("message", onMessage);
150
+ });
151
+ closing = attempt;
152
+ return attempt;
153
+ };
154
+ const onMessage = (message) => {
155
+ if (message.type === "activate" && !activated) {
156
+ activated = true;
157
+ environmentDeliveryWorkerAccess.activateManagedChild();
158
+ releaseActivation();
159
+ }
160
+ else if (message.type === "close") {
161
+ ManagedServerValues.closeFromEvent(close);
162
+ }
163
+ };
164
+ const onDisconnect = () => {
165
+ ManagedServerValues.closeFromEvent(close);
166
+ };
167
+ process.on("message", onMessage);
168
+ process.once("disconnect", onDisconnect);
169
+ try {
170
+ await ManagedServerValues.send({
171
+ type: "synchronizing",
172
+ slot: process.env[slotMarker],
173
+ incarnation: process.env[incarnationMarker],
174
+ endpoint: server.baseUrl,
175
+ });
176
+ }
177
+ catch (error) {
178
+ await close();
179
+ throw error;
180
+ }
181
+ await activation;
182
+ await ManagedServerValues.send({
183
+ type: "ready",
184
+ slot: process.env[slotMarker],
185
+ incarnation: process.env[incarnationMarker],
186
+ endpoint: server.baseUrl,
187
+ });
188
+ return {
189
+ ready: true,
190
+ close,
191
+ };
192
+ },
193
+ parent(options, manageProcessSignals) {
194
+ return new ManagedServerCoordinator({ ...options, manageProcessSignals }).start();
195
+ },
196
+ closeFromEvent(close) {
197
+ // spine-log-boundary: server.managed_replica_child_close
198
+ void close().catch(() => {
199
+ emitServerWarning(lifecycleLogger, "Managed child close failed.", {
200
+ operation: "managed_replica.child_close",
201
+ reasonCode: "close_failed",
202
+ });
203
+ });
204
+ },
205
+ requireVolatileRegistries(server) {
206
+ const registries = runningServerAccess.subscriptionRegistries(server) ?? managedTestRegistries.get(server);
207
+ if (registries === undefined ||
208
+ registries.some((registry) => !(registry instanceof InMemorySubscriptionRegistry)))
209
+ throw new Error("Managed application replicas require an in-memory Stand subscription registry.");
210
+ },
211
+ send(message) {
212
+ return new Promise((resolve, reject) => {
213
+ if (process.send === undefined || !process.connected) {
214
+ reject(new Error("Managed child has no parent IPC channel."));
215
+ return;
216
+ }
217
+ try {
218
+ process.send(message, (error) => {
219
+ if (error === null)
220
+ resolve();
221
+ else
222
+ reject(error);
223
+ });
224
+ }
225
+ catch (error) {
226
+ reject(error instanceof Error ? error : new Error("Managed child READY IPC failed."));
227
+ }
228
+ });
229
+ },
230
+ });
231
+ /**
232
+ *
233
+ * Coordinates the parent-only lifecycle of managed application replicas.
234
+ *
235
+ * @internal
236
+ */
237
+ export class ManagedServerCoordinator {
238
+ #slots;
239
+ #restart;
240
+ #options;
241
+ #dependencies;
242
+ #closing = false;
243
+ #starts = 0;
244
+ #ready;
245
+ #resolveReady;
246
+ #close;
247
+ #retired = new Set();
248
+ #retireTerminations = new Map();
249
+ #memberListeners = new Set();
250
+ #onSignal;
251
+ #nodeCoordinator;
252
+ /**
253
+ *
254
+ * Creates a coordinator for one configured replica cohort.
255
+ *
256
+ * @param options Configures complete child application replicas.
257
+ * @param dependencies Supplies private process and clock dependencies.
258
+ */
259
+ constructor(options, dependencies = ManagedServerCoordinatorValues.dependencies) {
260
+ this.#options = options;
261
+ this.#dependencies = dependencies;
262
+ this.#restart = ManagedServerCoordinatorValues.restart(options);
263
+ this.#slots = Array.from({ length: options.processCount }, (_, slot) => ({
264
+ slot,
265
+ failures: 0,
266
+ initialReady: false,
267
+ starting: false,
268
+ replacementTimer: undefined,
269
+ replica: undefined,
270
+ }));
271
+ }
272
+ /**
273
+ *
274
+ * Starts every initial replica and waits until all report READY.
275
+ *
276
+ * @returns The handle for the started managed cohort.
277
+ */
278
+ async start() {
279
+ this.#ready = new Promise((resolve) => {
280
+ this.#resolveReady = resolve;
281
+ });
282
+ if (this.#options.manageProcessSignals !== false)
283
+ this.#installSignalHandlers();
284
+ for (const slot of this.#slots)
285
+ this.#start(slot);
286
+ await this.#ready;
287
+ try {
288
+ const openCoordinator = this.#dependencies.openCoordinator;
289
+ if (openCoordinator !== undefined)
290
+ this.#nodeCoordinator = await openCoordinator({
291
+ members: this,
292
+ ...(this.#options.host === undefined ? {} : { host: this.#options.host }),
293
+ ...(this.#options.port === undefined ? {} : { port: this.#options.port }),
294
+ });
295
+ }
296
+ catch (error) {
297
+ try {
298
+ await this.close();
299
+ }
300
+ catch (rollback) {
301
+ throw new AggregateError([error, rollback], "Managed replica Coordinator start and rollback failed.");
302
+ }
303
+ throw error;
304
+ }
305
+ const isReady = () => this.#slots.some((slot) => slot.replica?.readyAt !== undefined);
306
+ const handle = {
307
+ get ready() {
308
+ return isReady();
309
+ },
310
+ close: () => this.close(),
311
+ };
312
+ handleMembers.set(handle, this);
313
+ return handle;
314
+ }
315
+ #start(slot) {
316
+ if (this.#closing ||
317
+ slot.starting ||
318
+ slot.replica !== undefined ||
319
+ this.#starts >= this.#restart.concurrentStarts)
320
+ return;
321
+ slot.starting = true;
322
+ this.#starts++;
323
+ const incarnation = randomUUID();
324
+ let child;
325
+ try {
326
+ child = this.#dependencies.spawn(this.#options.moduleUrl, slot.slot, incarnation);
327
+ }
328
+ catch {
329
+ slot.starting = false;
330
+ this.#starts--;
331
+ // spine-log-boundary: server.managed_replica_start
332
+ emitServerWarning(lifecycleLogger, "Managed replica start failed.", {
333
+ operation: "managed_replica.start",
334
+ reasonCode: "start_failed",
335
+ slot: String(slot.slot),
336
+ incarnation,
337
+ attempt: String(slot.failures + 1),
338
+ });
339
+ this.#scheduleReplacement(slot);
340
+ this.#drainStarts();
341
+ return;
342
+ }
343
+ const replica = {
344
+ slot: slot.slot,
345
+ incarnation,
346
+ child,
347
+ endpoint: undefined,
348
+ readyAt: undefined,
349
+ synchronizing: false,
350
+ draining: false,
351
+ subscriptionWaiters: new Map(),
352
+ closeAttempt: undefined,
353
+ expectedExit: false,
354
+ terminal: false,
355
+ };
356
+ slot.replica = replica;
357
+ child.on("message", (message) => {
358
+ this.#onMessage(slot, replica, message);
359
+ });
360
+ child.once("exit", () => {
361
+ this.#retired.delete(replica);
362
+ this.#retireTerminations.delete(replica);
363
+ this.#onExit(slot, replica);
364
+ });
365
+ child.once("error", () => {
366
+ this.#onError(slot, replica);
367
+ });
368
+ }
369
+ #onMessage(slot, replica, message) {
370
+ const closeResult = ManagedServerCoordinatorValues.closeResultMessage(message, slot.slot, replica.incarnation);
371
+ if (closeResult !== undefined) {
372
+ if (slot.replica !== replica || !replica.draining || replica.closeAttempt === undefined)
373
+ return;
374
+ if (closeResult === "closed")
375
+ replica.closeAttempt.outcome.resolve(undefined);
376
+ else
377
+ replica.closeAttempt.outcome.reject(new Error("Managed child server close failed."));
378
+ return;
379
+ }
380
+ if (ManagedServerCoordinatorValues.subscriptionInstalledMessage(message, slot.slot, replica.incarnation)) {
381
+ if (slot.replica !== replica)
382
+ return;
383
+ this.#settleSubscriptionWaiter(replica, message.subscriptionId);
384
+ return;
385
+ }
386
+ if (ManagedServerCoordinatorValues.synchronizingMessage(message, slot.slot, replica.incarnation)) {
387
+ if (slot.replica !== replica || replica.endpoint !== undefined)
388
+ return;
389
+ replica.endpoint = message.endpoint;
390
+ replica.synchronizing = true;
391
+ this.#notifyReadyMembers();
392
+ if (this.#nodeCoordinator === undefined)
393
+ replica.child.send({ type: "activate" }, () => undefined);
394
+ return;
395
+ }
396
+ if (ManagedServerCoordinatorValues.drainingMessage(message, slot.slot, replica.incarnation)) {
397
+ if (slot.replica !== replica)
398
+ return;
399
+ replica.closeAttempt?.acknowledged.resolve(undefined);
400
+ if (replica.draining)
401
+ return;
402
+ replica.draining = true;
403
+ replica.expectedExit = true;
404
+ this.#notifyReadyMembers();
405
+ return;
406
+ }
407
+ if (!ManagedServerCoordinatorValues.readyMessage(message, slot.slot, replica.incarnation))
408
+ return;
409
+ if (slot.replica !== replica || replica.readyAt !== undefined || this.#closing)
410
+ return;
411
+ slot.starting = false;
412
+ this.#starts--;
413
+ replica.synchronizing = false;
414
+ replica.endpoint = message.endpoint;
415
+ replica.readyAt = this.#dependencies.clock.now();
416
+ slot.initialReady = true;
417
+ if (this.#slots.every((candidate) => candidate.initialReady))
418
+ this.#resolveReady?.();
419
+ this.#notifyReadyMembers();
420
+ this.#drainStarts();
421
+ }
422
+ #onExit(slot, replica) {
423
+ if (slot.replica !== replica || replica.terminal)
424
+ return;
425
+ replica.terminal = true;
426
+ this.#clearSubscriptionWaiters(replica, new Error("Managed child exited before subscription installed."));
427
+ slot.replica = undefined;
428
+ this.#notifyReadyMembers();
429
+ if (slot.starting) {
430
+ slot.starting = false;
431
+ this.#starts--;
432
+ }
433
+ if (this.#closing || replica.expectedExit)
434
+ return;
435
+ if (replica.readyAt !== undefined &&
436
+ this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs) {
437
+ slot.failures = 0;
438
+ }
439
+ // spine-log-boundary: server.managed_replica_exit
440
+ emitServerWarning(lifecycleLogger, "Managed replica exited unexpectedly.", {
441
+ operation: "managed_replica.exit",
442
+ reasonCode: "unexpected_exit",
443
+ slot: String(slot.slot),
444
+ incarnation: replica.incarnation,
445
+ attempt: String(slot.failures + 1),
446
+ });
447
+ this.#scheduleReplacement(slot);
448
+ this.#drainStarts();
449
+ }
450
+ #onError(slot, replica) {
451
+ if (slot.replica !== replica || replica.terminal)
452
+ return;
453
+ // A ChildProcess error can precede exit while the OS process still lives.
454
+ // Retire the incarnation once, then actively terminate that known child.
455
+ this.#onExit(slot, replica);
456
+ this.#terminateUnexpected(replica);
457
+ }
458
+ #scheduleReplacement(slot) {
459
+ slot.failures++;
460
+ const delay = Math.min(this.#restart.initialDelayMs * 2 ** Math.max(0, slot.failures - 1), this.#restart.maximumDelayMs);
461
+ slot.replacementTimer = this.#dependencies.clock.setTimeout(() => {
462
+ slot.replacementTimer = undefined;
463
+ this.#start(slot);
464
+ }, delay);
465
+ // spine-log-boundary: server.managed_replica_retry
466
+ emitServerWarning(lifecycleLogger, "Managed replica replacement scheduled.", {
467
+ operation: "managed_replica.replace",
468
+ reasonCode: "unexpected_exit",
469
+ slot: String(slot.slot),
470
+ attempt: String(slot.failures),
471
+ delay: String(delay),
472
+ });
473
+ }
474
+ #drainStarts() {
475
+ for (const slot of this.#slots) {
476
+ if (this.#starts >= this.#restart.concurrentStarts)
477
+ return;
478
+ if (slot.replica === undefined && slot.replacementTimer === undefined)
479
+ this.#start(slot);
480
+ }
481
+ }
482
+ /**
483
+ *
484
+ * Stops managed children and prevents later replacement starts.
485
+ *
486
+ * @returns Completion after child and retired-child cleanup succeeds.
487
+ */
488
+ close() {
489
+ const closing = this.#close;
490
+ if (closing !== undefined)
491
+ return closing;
492
+ const close = (async () => {
493
+ this.#closing = true;
494
+ this.#removeSignalHandlers();
495
+ for (const slot of this.#slots) {
496
+ if (slot.replacementTimer !== undefined)
497
+ this.#dependencies.clock.clearTimeout(slot.replacementTimer);
498
+ slot.replacementTimer = undefined;
499
+ }
500
+ // `beginDrain()` makes the unary snapshot empty synchronously. Start every
501
+ // child quiescence attempt without waiting for its reconciliation promise:
502
+ // child close IPC must be issued in this turn, while the Coordinator keeps
503
+ // subscription relays alive until every child has settled.
504
+ const draining = this.#nodeCoordinator?.beginDrain();
505
+ for (const slot of this.#slots)
506
+ if (slot.replica !== undefined)
507
+ this.#clearSubscriptionWaiters(slot.replica, new Error("Managed application is closing before subscription installed."));
508
+ const childCloses = [
509
+ ...this.#slots.map((slot) => this.#closeReplica(slot.replica)),
510
+ ...[...this.#retired].map((replica) => this.#closeRetired(replica)),
511
+ ];
512
+ const results = await Promise.allSettled([draining, ...childCloses]);
513
+ const failures = [];
514
+ for (const result of results)
515
+ if (result.status === "rejected")
516
+ failures.push(result.reason);
517
+ if (failures.length === 1)
518
+ throw failures[0];
519
+ if (failures.length > 1)
520
+ throw new AggregateError(failures, "Managed child quiescence failed during drain.");
521
+ await this.#nodeCoordinator?.close();
522
+ })();
523
+ this.#close = close;
524
+ void close.then(undefined, () => {
525
+ if (this.#close === close)
526
+ this.#close = undefined;
527
+ });
528
+ return close;
529
+ }
530
+ #closeReplica(replica) {
531
+ return ManagedServerCoordinatorValues.close(replica, this.#dependencies.clock);
532
+ }
533
+ async #closeRetired(replica) {
534
+ replica.expectedExit = true;
535
+ await this.#terminateRetired(replica);
536
+ this.#retired.delete(replica);
537
+ }
538
+ #terminateUnexpected(replica) {
539
+ this.#retired.add(replica);
540
+ void this.#terminateRetired(replica);
541
+ }
542
+ #terminateRetired(replica) {
543
+ const prior = this.#retireTerminations.get(replica);
544
+ if (prior !== undefined)
545
+ return prior;
546
+ const termination = ManagedServerCoordinatorValues.terminate(replica.child, this.#dependencies.clock);
547
+ this.#retireTerminations.set(replica, termination);
548
+ void termination.then(() => {
549
+ this.#retireTerminations.delete(replica);
550
+ this.#retired.delete(replica);
551
+ }, () => {
552
+ this.#retireTerminations.delete(replica);
553
+ // spine-log-boundary: server.managed_replica_termination
554
+ emitServerWarning(lifecycleLogger, "Managed replica termination was bounded.", {
555
+ operation: "managed_replica.terminate",
556
+ reasonCode: "unresponsive",
557
+ slot: String(replica.slot),
558
+ incarnation: replica.incarnation,
559
+ });
560
+ });
561
+ return termination;
562
+ }
563
+ /**
564
+ *
565
+ * Returns ready child facts to the next Coordinator slice.
566
+ *
567
+ * @returns The ready child topology facts.
568
+ * @internal
569
+ */
570
+ readyMembers() {
571
+ return this.#members(false);
572
+ }
573
+ /**
574
+ * Returns READY and synchronizing members whose relays remain eligible.
575
+ *
576
+ * @returns Private relay member facts.
577
+ */
578
+ relayMembers() {
579
+ return this.#members(true);
580
+ }
581
+ /**
582
+ * Records an exact native subscription whose activation started for a member.
583
+ *
584
+ * @param member Supplies the member that owns the child subscription.
585
+ * @param subscription Supplies the activating native subscription.
586
+ */
587
+ onChildSubscriptionActivated(member, subscription) {
588
+ const replica = this.#replica(member);
589
+ const id = subscription.id?.value;
590
+ if (replica === undefined || id === undefined || replica.subscriptionWaiters.has(id))
591
+ return;
592
+ replica.subscriptionWaiters.set(id, ManagedServerCoordinatorValues.deferred());
593
+ }
594
+ /**
595
+ * Clears a pending child-installation wait after cancellation.
596
+ *
597
+ * @param member Supplies the member that owns the cancelled subscription.
598
+ * @param subscription Supplies the cancelled native subscription.
599
+ */
600
+ onChildSubscriptionCancelled(member, subscription) {
601
+ const replica = this.#replica(member);
602
+ const id = subscription.id?.value;
603
+ if (replica !== undefined && id !== undefined)
604
+ this.#settleSubscriptionWaiter(replica, id);
605
+ }
606
+ /**
607
+ * Activates synchronizing children after their retained relay children install.
608
+ *
609
+ * @returns Completion after each current child receives private activation.
610
+ */
611
+ async onRelaySynchronized() {
612
+ const synchronizing = this.#slots
613
+ .map((slot) => slot.replica)
614
+ .filter((replica) => replica !== undefined && replica.synchronizing && replica.readyAt === undefined);
615
+ await Promise.all(synchronizing.flatMap((replica) => [...replica.subscriptionWaiters.values()].map((waiter) => waiter.promise)));
616
+ for (const replica of synchronizing)
617
+ if (!replica.terminal && replica.synchronizing && replica.readyAt === undefined)
618
+ replica.child.send({ type: "activate" }, () => undefined);
619
+ }
620
+ #replica(member) {
621
+ const replica = this.#slots[member.slot]?.replica;
622
+ return replica?.incarnation === member.incarnation ? replica : undefined;
623
+ }
624
+ #clearSubscriptionWaiters(replica, error) {
625
+ for (const waiter of replica.subscriptionWaiters.values())
626
+ waiter.reject(error);
627
+ replica.subscriptionWaiters.clear();
628
+ }
629
+ #settleSubscriptionWaiter(replica, id) {
630
+ const waiter = replica.subscriptionWaiters.get(id);
631
+ if (waiter === undefined)
632
+ return;
633
+ replica.subscriptionWaiters.delete(id);
634
+ waiter.resolve(undefined);
635
+ }
636
+ #members(includeDraining) {
637
+ return this.#slots.flatMap((slot) => {
638
+ const replica = slot.replica;
639
+ if ((replica?.readyAt === undefined && !(includeDraining && replica?.synchronizing)) ||
640
+ (!includeDraining && replica.draining) ||
641
+ replica.endpoint === undefined ||
642
+ replica.child.pid === undefined)
643
+ return [];
644
+ return [
645
+ {
646
+ slot: slot.slot,
647
+ incarnation: replica.incarnation,
648
+ pid: replica.child.pid,
649
+ endpoint: replica.endpoint,
650
+ },
651
+ ];
652
+ });
653
+ }
654
+ /**
655
+ * Subscribes one private Coordinator to READY membership changes.
656
+ *
657
+ * @param onChange Runs after an admitted READY member or removed member changes.
658
+ * @returns Stops later private notifications.
659
+ * @internal
660
+ */
661
+ onReadyMembersChange(onChange) {
662
+ this.#memberListeners.add(onChange);
663
+ return () => this.#memberListeners.delete(onChange);
664
+ }
665
+ /**
666
+ * Subscribes one private Coordinator to relay-membership changes.
667
+ *
668
+ * @param onChange Runs after a relay member is admitted or removed.
669
+ * @returns Stops later relay-membership callbacks.
670
+ */
671
+ onRelayMembersChange(onChange) {
672
+ return this.onReadyMembersChange(onChange);
673
+ }
674
+ /**
675
+ * Returns the private public-listener endpoint for coordinator acceptance.
676
+ *
677
+ * @returns The listener endpoint after managed startup, when open.
678
+ * @internal
679
+ */
680
+ coordinatorEndpoint() {
681
+ return this.#nodeCoordinator?.baseUrl;
682
+ }
683
+ #notifyReadyMembers() {
684
+ for (const listener of this.#memberListeners)
685
+ listener();
686
+ }
687
+ #installSignalHandlers() {
688
+ if (this.#onSignal !== undefined)
689
+ return;
690
+ this.#onSignal = () => {
691
+ // spine-log-boundary: server.managed_replica_signal
692
+ void this.close()
693
+ .then(() => {
694
+ if (process.connected && typeof process.disconnect === "function")
695
+ process.disconnect();
696
+ })
697
+ .catch(() => {
698
+ emitServerWarning(lifecycleLogger, "Managed replica signal close failed.", {
699
+ operation: "managed_replica.signal",
700
+ reasonCode: "close_failed",
701
+ });
702
+ });
703
+ };
704
+ process.on("SIGINT", this.#onSignal);
705
+ process.on("SIGTERM", this.#onSignal);
706
+ }
707
+ #removeSignalHandlers() {
708
+ if (this.#onSignal === undefined)
709
+ return;
710
+ process.off("SIGINT", this.#onSignal);
711
+ process.off("SIGTERM", this.#onSignal);
712
+ this.#onSignal = undefined;
713
+ }
714
+ }
715
+ /**
716
+ *
717
+ * Exposes private Coordinator topology facts to the next internal slice.
718
+ *
719
+ * @internal
720
+ */
721
+ export const managedServerCoordinatorAccess = Object.freeze({
722
+ readyMembers(coordinator) {
723
+ return coordinator.readyMembers();
724
+ },
725
+ coordinatorEndpoint(coordinator) {
726
+ return coordinator.coordinatorEndpoint();
727
+ },
728
+ });
729
+ /**
730
+ *
731
+ * Exposes private managed-child facts to Coordinator forwarding tests.
732
+ *
733
+ * @internal
734
+ */
735
+ export const managedServerApplicationAccess = Object.freeze({
736
+ readyMembers(handle) {
737
+ return handleMembers.get(handle)?.readyMembers() ?? [];
738
+ },
739
+ coordinatorEndpoint(handle) {
740
+ return handleMembers.get(handle)?.coordinatorEndpoint();
741
+ },
742
+ installRegistriesForTest(server, registries) {
743
+ managedTestRegistries.set(server, registries);
744
+ },
745
+ });
746
+ const ManagedServerCoordinatorValues = Object.freeze({
747
+ deferred() {
748
+ let resolve;
749
+ let reject;
750
+ const promise = new Promise((next, fail) => {
751
+ resolve = next;
752
+ reject = fail;
753
+ });
754
+ return { promise, resolve, reject };
755
+ },
756
+ coordinatorPort(value) {
757
+ if (!Number.isSafeInteger(value) || value < 1 || value > 65_535)
758
+ throw new Error("Managed server Coordinator port must be a safe integer between 1 and 65535.");
759
+ return value;
760
+ },
761
+ dependencies: {
762
+ clock: {
763
+ now: () => Date.now(),
764
+ setTimeout: (onTimeout, delay) => setTimeout(onTimeout, delay),
765
+ clearTimeout: (timer) => {
766
+ clearTimeout(timer);
767
+ },
768
+ },
769
+ spawn: (moduleUrl, slot, incarnation) => fork(fileURLToPath(moduleUrl), [], {
770
+ env: {
771
+ ...process.env,
772
+ [childMarker]: "true",
773
+ [slotMarker]: String(slot),
774
+ [incarnationMarker]: incarnation,
775
+ },
776
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
777
+ }),
778
+ openCoordinator: (options) => NodeCoordinator.open(options),
779
+ },
780
+ restart(options) {
781
+ const restart = {
782
+ initialDelayMs: options.restart?.initialDelayMs ?? initialRestartDelayMs,
783
+ maximumDelayMs: options.restart?.maximumDelayMs ?? maximumRestartDelayMs,
784
+ healthyReadyMs: options.restart?.healthyReadyMs ?? healthyReadyMs,
785
+ concurrentStarts: options.restart?.concurrentStarts ?? Math.min(4, options.processCount),
786
+ };
787
+ for (const [name, value] of Object.entries(restart)) {
788
+ if (!Number.isSafeInteger(value) || value < 1)
789
+ throw new Error(`Managed server restart ${name} must be a positive safe integer.`);
790
+ }
791
+ if (restart.maximumDelayMs < restart.initialDelayMs)
792
+ throw new Error("Managed server restart maximumDelayMs must not be below initialDelayMs.");
793
+ if (restart.concurrentStarts > options.processCount)
794
+ throw new Error("Managed server restart concurrentStarts must not exceed processCount.");
795
+ return restart;
796
+ },
797
+ readyMessage(message, slot, incarnation) {
798
+ if (typeof message !== "object" || message === null)
799
+ return false;
800
+ const candidate = message;
801
+ return (candidate.type === "ready" &&
802
+ candidate.slot === String(slot) &&
803
+ candidate.incarnation === incarnation &&
804
+ typeof candidate.endpoint === "string" &&
805
+ canonicalLoopbackEndpoint(candidate.endpoint) !== undefined);
806
+ },
807
+ synchronizingMessage(message, slot, incarnation) {
808
+ if (typeof message !== "object" || message === null)
809
+ return false;
810
+ const candidate = message;
811
+ return (candidate.type === "synchronizing" &&
812
+ candidate.slot === String(slot) &&
813
+ candidate.incarnation === incarnation &&
814
+ typeof candidate.endpoint === "string" &&
815
+ canonicalLoopbackEndpoint(candidate.endpoint) !== undefined);
816
+ },
817
+ subscriptionInstalledMessage(message, slot, incarnation) {
818
+ if (typeof message !== "object" || message === null)
819
+ return false;
820
+ const candidate = message;
821
+ return (candidate.type === "subscription-installed" &&
822
+ candidate.slot === String(slot) &&
823
+ candidate.incarnation === incarnation &&
824
+ typeof candidate.subscriptionId === "string" &&
825
+ candidate.subscriptionId.length > 0);
826
+ },
827
+ drainingMessage(message, slot, incarnation) {
828
+ if (typeof message !== "object" || message === null)
829
+ return false;
830
+ const candidate = message;
831
+ return (candidate.type === "draining" &&
832
+ candidate.slot === String(slot) &&
833
+ candidate.incarnation === incarnation);
834
+ },
835
+ closeResultMessage(message, slot, incarnation) {
836
+ if (typeof message !== "object" || message === null)
837
+ return undefined;
838
+ const candidate = message;
839
+ if ((candidate.type !== "closed" && candidate.type !== "close-failed") ||
840
+ candidate.slot !== String(slot) ||
841
+ candidate.incarnation !== incarnation)
842
+ return undefined;
843
+ return candidate.type;
844
+ },
845
+ async close(replica, clock) {
846
+ if (replica === undefined)
847
+ return;
848
+ replica.expectedExit = true;
849
+ const { child } = replica;
850
+ if (child.exitCode !== null || child.signalCode !== null)
851
+ return;
852
+ const exited = new Promise((resolve) => child.once("exit", () => {
853
+ resolve();
854
+ }));
855
+ const closeAttempt = {
856
+ acknowledged: ManagedServerCoordinatorValues.deferred(),
857
+ outcome: ManagedServerCoordinatorValues.deferred(),
858
+ };
859
+ replica.closeAttempt = closeAttempt;
860
+ try {
861
+ if (child.connected)
862
+ child.send({ type: "close" }, () => undefined);
863
+ const observation = Promise.race([
864
+ closeAttempt.acknowledged.promise.then(() => "acknowledged"),
865
+ exited.then(() => "exited"),
866
+ ]);
867
+ if (await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock)) {
868
+ if ((await observation) === "exited")
869
+ return;
870
+ await Promise.race([
871
+ closeAttempt.outcome.promise,
872
+ exited.then(() => {
873
+ throw new Error("Managed child exited before graceful close completed.");
874
+ }),
875
+ ]);
876
+ await ManagedServerCoordinatorValues.terminate(child, clock, exited);
877
+ return;
878
+ }
879
+ await ManagedServerCoordinatorValues.terminate(child, clock, exited, false);
880
+ }
881
+ finally {
882
+ if (replica.closeAttempt === closeAttempt)
883
+ replica.closeAttempt = undefined;
884
+ }
885
+ },
886
+ async terminate(child, clock, knownExit, allowGrace = true) {
887
+ if (child.exitCode !== null || child.signalCode !== null)
888
+ return;
889
+ const exited = knownExit ??
890
+ new Promise((resolve) => child.once("exit", () => {
891
+ resolve();
892
+ }));
893
+ if (allowGrace && (await ManagedServerCoordinatorValues.within(exited, closeGraceMs, clock)))
894
+ return;
895
+ child.kill("SIGTERM");
896
+ if (await ManagedServerCoordinatorValues.within(exited, closeKillMs, clock))
897
+ return;
898
+ child.kill("SIGKILL");
899
+ if (await ManagedServerCoordinatorValues.within(exited, closeKillMs, clock))
900
+ return;
901
+ throw new Error("Managed child did not exit after SIGKILL.");
902
+ },
903
+ within(promise, delay, clock) {
904
+ return new Promise((resolve) => {
905
+ const timer = clock.setTimeout(() => {
906
+ resolve(false);
907
+ }, delay);
908
+ void promise.then(() => {
909
+ clock.clearTimeout(timer);
910
+ resolve(true);
911
+ });
912
+ });
913
+ },
914
+ });
915
+ function canonicalLoopbackEndpoint(value) {
916
+ if (Buffer.byteLength(value, "utf8") > endpointMaximumBytes)
917
+ return undefined;
918
+ try {
919
+ const endpoint = new URL(value);
920
+ if (endpoint.protocol !== "http:" ||
921
+ endpoint.hostname !== "127.0.0.1" ||
922
+ endpoint.port === "" ||
923
+ endpoint.username !== "" ||
924
+ endpoint.password !== "" ||
925
+ endpoint.pathname !== "/" ||
926
+ endpoint.search !== "" ||
927
+ endpoint.hash !== "" ||
928
+ endpoint.origin !== value)
929
+ return undefined;
930
+ const port = Number(endpoint.port);
931
+ return Number.isSafeInteger(port) && port >= 1 && port <= 65_535 ? value : undefined;
932
+ }
933
+ catch {
934
+ return undefined;
935
+ }
936
+ }
937
+ //# sourceMappingURL=managed-server-application.js.map