@adonis-agora/durable 0.49.1 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/CHANGELOG.md +279 -0
  2. package/README.md +53 -22
  3. package/dist/assets/spa/assets/index-DupjrK7N.js +16 -0
  4. package/dist/assets/spa/assets/index-DupjrK7N.js.map +1 -0
  5. package/dist/assets/spa/index.html +1 -1
  6. package/dist/commands/durable_worker.d.ts.map +1 -1
  7. package/dist/commands/durable_worker.js +9 -8
  8. package/dist/commands/durable_worker.js.map +1 -1
  9. package/dist/commands/work.d.ts.map +1 -1
  10. package/dist/commands/work.js +3 -1
  11. package/dist/commands/work.js.map +1 -1
  12. package/dist/configure.d.ts.map +1 -1
  13. package/dist/configure.js +1 -0
  14. package/dist/configure.js.map +1 -1
  15. package/dist/providers/dashboard_provider.d.ts +2 -0
  16. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  17. package/dist/providers/dashboard_provider.js +50 -6
  18. package/dist/providers/dashboard_provider.js.map +1 -1
  19. package/dist/providers/durable_provider.d.ts.map +1 -1
  20. package/dist/providers/durable_provider.js +146 -35
  21. package/dist/providers/durable_provider.js.map +1 -1
  22. package/dist/src/admission-redis/redis-admission-backend.d.ts.map +1 -1
  23. package/dist/src/admission-redis/redis-admission-backend.js +3 -1
  24. package/dist/src/admission-redis/redis-admission-backend.js.map +1 -1
  25. package/dist/src/ambient-step.d.ts +24 -0
  26. package/dist/src/ambient-step.d.ts.map +1 -0
  27. package/dist/src/ambient-step.js +42 -0
  28. package/dist/src/ambient-step.js.map +1 -0
  29. package/dist/src/base-workflow.d.ts +10 -1
  30. package/dist/src/base-workflow.d.ts.map +1 -1
  31. package/dist/src/base-workflow.js.map +1 -1
  32. package/dist/src/codec-state-store.d.ts +28 -2
  33. package/dist/src/codec-state-store.d.ts.map +1 -1
  34. package/dist/src/codec-state-store.js +61 -3
  35. package/dist/src/codec-state-store.js.map +1 -1
  36. package/dist/src/commands/worker.d.ts +8 -3
  37. package/dist/src/commands/worker.d.ts.map +1 -1
  38. package/dist/src/commands/worker.js +35 -8
  39. package/dist/src/commands/worker.js.map +1 -1
  40. package/dist/src/config_types.d.ts +6 -0
  41. package/dist/src/config_types.d.ts.map +1 -1
  42. package/dist/src/control-plane-redis/redis-control-plane.d.ts.map +1 -1
  43. package/dist/src/control-plane-redis/redis-control-plane.js +22 -6
  44. package/dist/src/control-plane-redis/redis-control-plane.js.map +1 -1
  45. package/dist/src/control-planes/factory.d.ts +6 -0
  46. package/dist/src/control-planes/factory.d.ts.map +1 -1
  47. package/dist/src/control-planes/factory.js +1 -0
  48. package/dist/src/control-planes/factory.js.map +1 -1
  49. package/dist/src/dashboard/gateway-adapter.d.ts +15 -5
  50. package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -1
  51. package/dist/src/dashboard/gateway-adapter.js +71 -7
  52. package/dist/src/dashboard/gateway-adapter.js.map +1 -1
  53. package/dist/src/dashboard/handlers.d.ts +53 -1
  54. package/dist/src/dashboard/handlers.d.ts.map +1 -1
  55. package/dist/src/dashboard/handlers.js +200 -20
  56. package/dist/src/dashboard/handlers.js.map +1 -1
  57. package/dist/src/dashboard/index.d.ts +2 -2
  58. package/dist/src/dashboard/index.d.ts.map +1 -1
  59. package/dist/src/dashboard/index.js +2 -2
  60. package/dist/src/dashboard/index.js.map +1 -1
  61. package/dist/src/dashboard/openapi.d.ts.map +1 -1
  62. package/dist/src/dashboard/openapi.js +71 -1
  63. package/dist/src/dashboard/openapi.js.map +1 -1
  64. package/dist/src/define_config.d.ts +122 -8
  65. package/dist/src/define_config.d.ts.map +1 -1
  66. package/dist/src/define_config.js.map +1 -1
  67. package/dist/src/diagnostics-bridge.d.ts +16 -3
  68. package/dist/src/diagnostics-bridge.d.ts.map +1 -1
  69. package/dist/src/diagnostics-bridge.js +9 -3
  70. package/dist/src/diagnostics-bridge.js.map +1 -1
  71. package/dist/src/discovery-roots.d.ts +92 -0
  72. package/dist/src/discovery-roots.d.ts.map +1 -0
  73. package/dist/src/discovery-roots.js +150 -0
  74. package/dist/src/discovery-roots.js.map +1 -0
  75. package/dist/src/engine.d.ts +428 -21
  76. package/dist/src/engine.d.ts.map +1 -1
  77. package/dist/src/engine.js +1566 -164
  78. package/dist/src/engine.js.map +1 -1
  79. package/dist/src/entities.d.ts +36 -1
  80. package/dist/src/entities.d.ts.map +1 -1
  81. package/dist/src/entities.js +65 -4
  82. package/dist/src/entities.js.map +1 -1
  83. package/dist/src/errors.d.ts +46 -0
  84. package/dist/src/errors.d.ts.map +1 -1
  85. package/dist/src/errors.js +48 -0
  86. package/dist/src/errors.js.map +1 -1
  87. package/dist/src/event-trigger-bridge.d.ts +8 -0
  88. package/dist/src/event-trigger-bridge.d.ts.map +1 -1
  89. package/dist/src/event-trigger-bridge.js +24 -3
  90. package/dist/src/event-trigger-bridge.js.map +1 -1
  91. package/dist/src/handshake/announced.d.ts +27 -0
  92. package/dist/src/handshake/announced.d.ts.map +1 -1
  93. package/dist/src/handshake/announced.js +62 -0
  94. package/dist/src/handshake/announced.js.map +1 -1
  95. package/dist/src/handshake/directory.d.ts +92 -0
  96. package/dist/src/handshake/directory.d.ts.map +1 -0
  97. package/dist/src/handshake/directory.js +139 -0
  98. package/dist/src/handshake/directory.js.map +1 -0
  99. package/dist/src/hooks/barrel-sources.d.ts +28 -0
  100. package/dist/src/hooks/barrel-sources.d.ts.map +1 -0
  101. package/dist/src/hooks/barrel-sources.js +92 -0
  102. package/dist/src/hooks/barrel-sources.js.map +1 -0
  103. package/dist/src/hooks/steps.d.ts +12 -3
  104. package/dist/src/hooks/steps.d.ts.map +1 -1
  105. package/dist/src/hooks/steps.js +7 -11
  106. package/dist/src/hooks/steps.js.map +1 -1
  107. package/dist/src/hooks/workflows.d.ts +12 -3
  108. package/dist/src/hooks/workflows.d.ts.map +1 -1
  109. package/dist/src/hooks/workflows.js +7 -13
  110. package/dist/src/hooks/workflows.js.map +1 -1
  111. package/dist/src/index.d.ts +12 -3
  112. package/dist/src/index.d.ts.map +1 -1
  113. package/dist/src/index.js +12 -3
  114. package/dist/src/index.js.map +1 -1
  115. package/dist/src/interfaces.d.ts +309 -21
  116. package/dist/src/interfaces.d.ts.map +1 -1
  117. package/dist/src/interfaces.js +10 -0
  118. package/dist/src/interfaces.js.map +1 -1
  119. package/dist/src/metrics.d.ts.map +1 -1
  120. package/dist/src/metrics.js +5 -0
  121. package/dist/src/metrics.js.map +1 -1
  122. package/dist/src/namespaces.d.ts +17 -0
  123. package/dist/src/namespaces.d.ts.map +1 -0
  124. package/dist/src/namespaces.js +24 -0
  125. package/dist/src/namespaces.js.map +1 -0
  126. package/dist/src/on-error.d.ts +4 -1
  127. package/dist/src/on-error.d.ts.map +1 -1
  128. package/dist/src/on-error.js +15 -2
  129. package/dist/src/on-error.js.map +1 -1
  130. package/dist/src/otel/durable-metrics.d.ts.map +1 -1
  131. package/dist/src/otel/durable-metrics.js +5 -0
  132. package/dist/src/otel/durable-metrics.js.map +1 -1
  133. package/dist/src/otel/durable-otel.d.ts.map +1 -1
  134. package/dist/src/otel/durable-otel.js +5 -0
  135. package/dist/src/otel/durable-otel.js.map +1 -1
  136. package/dist/src/pollers.d.ts +10 -1
  137. package/dist/src/pollers.d.ts.map +1 -1
  138. package/dist/src/pollers.js +15 -5
  139. package/dist/src/pollers.js.map +1 -1
  140. package/dist/src/protocol.d.ts +12 -0
  141. package/dist/src/protocol.d.ts.map +1 -1
  142. package/dist/src/protocol.js +27 -1
  143. package/dist/src/protocol.js.map +1 -1
  144. package/dist/src/remote-event.d.ts +6 -0
  145. package/dist/src/remote-event.d.ts.map +1 -0
  146. package/dist/src/remote-event.js +19 -0
  147. package/dist/src/remote-event.js.map +1 -0
  148. package/dist/src/remote-workflow-executor.d.ts +34 -12
  149. package/dist/src/remote-workflow-executor.d.ts.map +1 -1
  150. package/dist/src/remote-workflow-executor.js +42 -40
  151. package/dist/src/remote-workflow-executor.js.map +1 -1
  152. package/dist/src/run-detail-summary.d.ts +90 -3
  153. package/dist/src/run-detail-summary.d.ts.map +1 -1
  154. package/dist/src/run-detail-summary.js +360 -2
  155. package/dist/src/run-detail-summary.js.map +1 -1
  156. package/dist/src/run-facets.d.ts +48 -0
  157. package/dist/src/run-facets.d.ts.map +1 -0
  158. package/dist/src/run-facets.js +100 -0
  159. package/dist/src/run-facets.js.map +1 -0
  160. package/dist/src/run-gateway/interface.d.ts +27 -1
  161. package/dist/src/run-gateway/interface.d.ts.map +1 -1
  162. package/dist/src/run-gateway/proxy-run-gateway.d.ts +32 -4
  163. package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -1
  164. package/dist/src/run-gateway/proxy-run-gateway.js +101 -5
  165. package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -1
  166. package/dist/src/run-gateway/run-request-responder.d.ts +18 -5
  167. package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -1
  168. package/dist/src/run-gateway/run-request-responder.js +155 -32
  169. package/dist/src/run-gateway/run-request-responder.js.map +1 -1
  170. package/dist/src/run-gateway/store-run-gateway.d.ts +23 -1
  171. package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -1
  172. package/dist/src/run-gateway/store-run-gateway.js +38 -0
  173. package/dist/src/run-gateway/store-run-gateway.js.map +1 -1
  174. package/dist/src/scheduler.d.ts +44 -3
  175. package/dist/src/scheduler.d.ts.map +1 -1
  176. package/dist/src/scheduler.js +36 -1
  177. package/dist/src/scheduler.js.map +1 -1
  178. package/dist/src/search-attributes-schema.d.ts +44 -0
  179. package/dist/src/search-attributes-schema.d.ts.map +1 -0
  180. package/dist/src/search-attributes-schema.js +51 -0
  181. package/dist/src/search-attributes-schema.js.map +1 -0
  182. package/dist/src/singleton-admission.d.ts +75 -0
  183. package/dist/src/singleton-admission.d.ts.map +1 -0
  184. package/dist/src/singleton-admission.js +156 -0
  185. package/dist/src/singleton-admission.js.map +1 -0
  186. package/dist/src/singleton-gate.d.ts +23 -8
  187. package/dist/src/singleton-gate.d.ts.map +1 -1
  188. package/dist/src/singleton-gate.js +59 -12
  189. package/dist/src/singleton-gate.js.map +1 -1
  190. package/dist/src/standard-schema.d.ts +86 -0
  191. package/dist/src/standard-schema.d.ts.map +1 -0
  192. package/dist/src/standard-schema.js +33 -0
  193. package/dist/src/standard-schema.js.map +1 -0
  194. package/dist/src/step-discovery.d.ts +21 -11
  195. package/dist/src/step-discovery.d.ts.map +1 -1
  196. package/dist/src/step-discovery.js +86 -45
  197. package/dist/src/step-discovery.js.map +1 -1
  198. package/dist/src/step-ref.d.ts +38 -12
  199. package/dist/src/step-ref.d.ts.map +1 -1
  200. package/dist/src/step-ref.js +5 -13
  201. package/dist/src/step-ref.js.map +1 -1
  202. package/dist/src/stores/factory.d.ts +6 -0
  203. package/dist/src/stores/factory.d.ts.map +1 -1
  204. package/dist/src/stores/factory.js +4 -1
  205. package/dist/src/stores/factory.js.map +1 -1
  206. package/dist/src/stores/lucid-helpers.d.ts +2 -0
  207. package/dist/src/stores/lucid-helpers.d.ts.map +1 -1
  208. package/dist/src/stores/lucid-helpers.js +7 -0
  209. package/dist/src/stores/lucid-helpers.js.map +1 -1
  210. package/dist/src/stores/lucid-mappers.d.ts +25 -0
  211. package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
  212. package/dist/src/stores/lucid-mappers.js +81 -18
  213. package/dist/src/stores/lucid-mappers.js.map +1 -1
  214. package/dist/src/stores/lucid-schema.d.ts +59 -1
  215. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  216. package/dist/src/stores/lucid-schema.js +271 -25
  217. package/dist/src/stores/lucid-schema.js.map +1 -1
  218. package/dist/src/stores/lucid.d.ts +61 -2
  219. package/dist/src/stores/lucid.d.ts.map +1 -1
  220. package/dist/src/stores/lucid.js +346 -41
  221. package/dist/src/stores/lucid.js.map +1 -1
  222. package/dist/src/telescope/dashboard.d.ts.map +1 -1
  223. package/dist/src/telescope/dashboard.js +30 -1
  224. package/dist/src/telescope/dashboard.js.map +1 -1
  225. package/dist/src/telescope/data-providers.d.ts +19 -1
  226. package/dist/src/telescope/data-providers.d.ts.map +1 -1
  227. package/dist/src/telescope/data-providers.js +119 -3
  228. package/dist/src/telescope/data-providers.js.map +1 -1
  229. package/dist/src/telescope/extension.d.ts.map +1 -1
  230. package/dist/src/telescope/extension.js +2 -1
  231. package/dist/src/telescope/extension.js.map +1 -1
  232. package/dist/src/telescope/index.d.ts +1 -1
  233. package/dist/src/telescope/index.d.ts.map +1 -1
  234. package/dist/src/telescope/index.js +1 -1
  235. package/dist/src/telescope/index.js.map +1 -1
  236. package/dist/src/testing/in-memory-state-store.d.ts +44 -15
  237. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  238. package/dist/src/testing/in-memory-state-store.js +204 -41
  239. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  240. package/dist/src/testing-kit/checkpoint-aggregation-contract.d.ts +16 -0
  241. package/dist/src/testing-kit/checkpoint-aggregation-contract.d.ts.map +1 -0
  242. package/dist/src/testing-kit/checkpoint-aggregation-contract.js +228 -0
  243. package/dist/src/testing-kit/checkpoint-aggregation-contract.js.map +1 -0
  244. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  245. package/dist/src/testing-kit/state-store-conformance.js +355 -3
  246. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  247. package/dist/src/transport-pool.d.ts +15 -1
  248. package/dist/src/transport-pool.d.ts.map +1 -1
  249. package/dist/src/transport-pool.js +50 -4
  250. package/dist/src/transport-pool.js.map +1 -1
  251. package/dist/src/transports/adaptive-concurrency.d.ts +168 -0
  252. package/dist/src/transports/adaptive-concurrency.d.ts.map +1 -0
  253. package/dist/src/transports/adaptive-concurrency.js +353 -0
  254. package/dist/src/transports/adaptive-concurrency.js.map +1 -0
  255. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts +1 -165
  256. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts.map +1 -1
  257. package/dist/src/transports/bullmq/adaptive-concurrency.js +3 -351
  258. package/dist/src/transports/bullmq/adaptive-concurrency.js.map +1 -1
  259. package/dist/src/transports/bullmq/bullmq-transport.d.ts +49 -1
  260. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  261. package/dist/src/transports/bullmq/bullmq-transport.js +330 -38
  262. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  263. package/dist/src/transports/bullmq/deps.d.ts +18 -2
  264. package/dist/src/transports/bullmq/deps.d.ts.map +1 -1
  265. package/dist/src/transports/bullmq/deps.js +44 -2
  266. package/dist/src/transports/bullmq/deps.js.map +1 -1
  267. package/dist/src/transports/db-helpers.d.ts +39 -0
  268. package/dist/src/transports/db-helpers.d.ts.map +1 -1
  269. package/dist/src/transports/db-helpers.js +73 -0
  270. package/dist/src/transports/db-helpers.js.map +1 -1
  271. package/dist/src/transports/db-remote.d.ts +53 -0
  272. package/dist/src/transports/db-remote.d.ts.map +1 -0
  273. package/dist/src/transports/db-remote.js +147 -0
  274. package/dist/src/transports/db-remote.js.map +1 -0
  275. package/dist/src/transports/db-schema.d.ts +44 -1
  276. package/dist/src/transports/db-schema.d.ts.map +1 -1
  277. package/dist/src/transports/db-schema.js +227 -88
  278. package/dist/src/transports/db-schema.js.map +1 -1
  279. package/dist/src/transports/db.d.ts +189 -6
  280. package/dist/src/transports/db.d.ts.map +1 -1
  281. package/dist/src/transports/db.js +1291 -137
  282. package/dist/src/transports/db.js.map +1 -1
  283. package/dist/src/transports/event-emitter.d.ts +8 -0
  284. package/dist/src/transports/event-emitter.d.ts.map +1 -1
  285. package/dist/src/transports/event-emitter.js +36 -3
  286. package/dist/src/transports/event-emitter.js.map +1 -1
  287. package/dist/src/transports/factory.d.ts +110 -5
  288. package/dist/src/transports/factory.d.ts.map +1 -1
  289. package/dist/src/transports/factory.js +56 -9
  290. package/dist/src/transports/factory.js.map +1 -1
  291. package/dist/src/transports/queue-mock-adapter.d.ts +7 -2
  292. package/dist/src/transports/queue-mock-adapter.d.ts.map +1 -1
  293. package/dist/src/transports/queue-mock-adapter.js +18 -2
  294. package/dist/src/transports/queue-mock-adapter.js.map +1 -1
  295. package/dist/src/transports/queue-remote.d.ts +146 -0
  296. package/dist/src/transports/queue-remote.d.ts.map +1 -0
  297. package/dist/src/transports/queue-remote.js +398 -0
  298. package/dist/src/transports/queue-remote.js.map +1 -0
  299. package/dist/src/transports/queue.d.ts +159 -6
  300. package/dist/src/transports/queue.d.ts.map +1 -1
  301. package/dist/src/transports/queue.js +747 -37
  302. package/dist/src/transports/queue.js.map +1 -1
  303. package/dist/src/transports/task-failure.d.ts +31 -0
  304. package/dist/src/transports/task-failure.d.ts.map +1 -0
  305. package/dist/src/transports/task-failure.js +50 -0
  306. package/dist/src/transports/task-failure.js.map +1 -0
  307. package/dist/src/workflow-als.d.ts +1 -1
  308. package/dist/src/workflow-als.d.ts.map +1 -1
  309. package/dist/src/workflow-ctx.d.ts +10 -0
  310. package/dist/src/workflow-ctx.d.ts.map +1 -1
  311. package/dist/src/workflow-ctx.js +97 -15
  312. package/dist/src/workflow-ctx.js.map +1 -1
  313. package/dist/src/workflow-discovery.d.ts +32 -41
  314. package/dist/src/workflow-discovery.d.ts.map +1 -1
  315. package/dist/src/workflow-discovery.js +65 -88
  316. package/dist/src/workflow-discovery.js.map +1 -1
  317. package/dist/src/workflow-ref.d.ts +26 -0
  318. package/dist/src/workflow-ref.d.ts.map +1 -1
  319. package/dist/src/workflow-ref.js.map +1 -1
  320. package/dist/stubs/config/durable.stub +6 -0
  321. package/dist/stubs/config/durable_dashboard.stub +7 -3
  322. package/dist/stubs/database/migrations/create_durable_tables.stub +2 -1
  323. package/dist/stubs/database/migrations/create_durable_transport_messages.stub +43 -0
  324. package/dist/stubs/database/migrations/create_durable_transport_tables.stub +12 -9
  325. package/dist/stubs/database/migrations/create_durable_transport_worker_keys.stub +1 -1
  326. package/dist/stubs/make/workflow/main.stub +1 -1
  327. package/package.json +6 -4
  328. package/skills/durable-cluster/SKILL.md +80 -11
  329. package/skills/durable-determinism/SKILL.md +11 -5
  330. package/skills/durable-reliability/SKILL.md +77 -37
  331. package/skills/durable-setup/SKILL.md +51 -22
  332. package/skills/durable-transports-stores/SKILL.md +128 -18
  333. package/skills/durable-workflows/SKILL.md +83 -18
  334. package/dist/assets/spa/assets/index-DdB9zxuX.js +0 -16
  335. package/dist/assets/spa/assets/index-DdB9zxuX.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ import { TERMINAL_RUN_STATUSES } from './interfaces.js';
2
+ /**
3
+ * Reserved engine tag marking a run that holds a durable singleton slot. Written only by a store's
4
+ * {@link StateStore.tryAdmitSingleton} (inside its atomic claim), stripped from every fresh run's
5
+ * tags, and cleared when the run reaches a terminal status. The slot is independent of the execution
6
+ * lease: a holder that sleeps, waits on a signal, is parked `blocked` or has its lease expire keeps it.
7
+ */
8
+ export const SINGLETON_ADMITTED_TAG = 'durable:singleton:admitted';
9
+ /**
10
+ * Reserved engine tag marking a not-yet-admitted singleton run whose admission is decided by the tag
11
+ * scheme: it holds no slot unless it also carries {@link SINGLETON_ADMITTED_TAG}. Stamped on the run's
12
+ * first execution (in the same write that sets it `running`, before it competes) and on every denial;
13
+ * removed on admission, terminal settlement and requeue. An untagged executed run WITHOUT it was
14
+ * executed (admitted) by a replica older than 0.51 — see {@link legacySingletonHolders}.
15
+ */
16
+ export const SINGLETON_TRACKED_TAG = 'durable:singleton:tracked';
17
+ /** Tags only the engine and the store may write; stripped from a fresh run's user-supplied tags. */
18
+ export const SINGLETON_RESERVED_TAGS = [
19
+ SINGLETON_ADMITTED_TAG,
20
+ SINGLETON_TRACKED_TAG,
21
+ ];
22
+ /** The statuses a singleton run occupies its key in — as a holder or as a FIFO waiter. */
23
+ export const SINGLETON_ACTIVE_STATUSES = [
24
+ 'pending',
25
+ 'running',
26
+ 'suspended',
27
+ 'blocked',
28
+ ];
29
+ /** The same `(createdAt, id)` total order every instance uses for the FIFO waiter queue. */
30
+ function byCreation(a, b) {
31
+ return a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id);
32
+ }
33
+ /** The statuses the pre-0.51 scan admission ranked a key's runs in (it ignored `pending`/`blocked`). */
34
+ const LEGACY_SCAN_STATUSES = ['running', 'suspended'];
35
+ /** `tags` without the engine's singleton markers (admitted and tracked). */
36
+ export function withoutSingletonMarkers(tags) {
37
+ return tags?.filter((tag) => !SINGLETON_RESERVED_TAGS.includes(tag));
38
+ }
39
+ /** `tags` carrying {@link SINGLETON_TRACKED_TAG} (returned unchanged when admitted or already tracked). */
40
+ export function withSingletonTracked(tags) {
41
+ const current = tags ?? [];
42
+ if (current.includes(SINGLETON_ADMITTED_TAG) || current.includes(SINGLETON_TRACKED_TAG)) {
43
+ return current;
44
+ }
45
+ return [...current, SINGLETON_TRACKED_TAG];
46
+ }
47
+ /**
48
+ * The runs of `active`, other than `runId`, that may hold a slot WITHOUT carrying
49
+ * {@link SINGLETON_ADMITTED_TAG}: holders admitted by a replica older than 0.51, during a rolling
50
+ * upgrade that mixes old and new replicas.
51
+ *
52
+ * Old replicas admit by a scan: a run may execute while it ranks among the `limit` oldest
53
+ * `running`/`suspended` runs of the key by `(createdAt, id)`, and they write no tag. Among those
54
+ * statuses an untagged run counts as a legacy holder when either:
55
+ *
56
+ * - it lacks {@link SINGLETON_TRACKED_TAG}. A new replica stamps that marker on a run's first
57
+ * execution and on every denial, so an untagged executed run without it was executed — admitted —
58
+ * by an old replica, wherever it ranks (an old replica can admit a run past an older `pending` one
59
+ * it never saw). A denial alone can't be told from a holder (both park the run `suspended` on a
60
+ * retry `wakeAt`, exactly like a holder sleeping in `ctx.sleep`), which is why the marker exists.
61
+ * Waiters an old replica parked count too until a new replica re-checks them (one retry, ~1s):
62
+ * that only delays, never over-admits.
63
+ * - it sits inside the old scan's own window (the `limit` oldest of those runs, every tagged holder
64
+ * keeping its seat in it). An old replica can admit a run a new replica had marked without clearing
65
+ * the marker; the window is what it admitted it on. With no old replica left this never changes a `limit: 1` decision (a run inside the window
66
+ * is the head of the FIFO, admitted next anyway); with a larger limit it can only delay one.
67
+ */
68
+ export function legacySingletonHolders(active, runId, limit) {
69
+ const scanned = active
70
+ .filter((run) => LEGACY_SCAN_STATUSES.includes(run.status))
71
+ .sort(byCreation);
72
+ // The old scan's window, with every tagged holder given its seat in it whatever its status: a
73
+ // holder parked `blocked` is invisible to the old scan, and without its seat the window would fill
74
+ // with the tracked waiters behind it, each counting the next as a holder.
75
+ const window = new Set(active
76
+ .filter((run) => LEGACY_SCAN_STATUSES.includes(run.status) || run.tags?.includes(SINGLETON_ADMITTED_TAG))
77
+ .sort(byCreation)
78
+ .slice(0, limit)
79
+ .map((run) => run.id));
80
+ return scanned.filter((run) => run.id !== runId &&
81
+ !run.tags?.includes(SINGLETON_ADMITTED_TAG) &&
82
+ (!run.tags?.includes(SINGLETON_TRACKED_TAG) || window.has(run.id)));
83
+ }
84
+ /**
85
+ * The admission decision, given every active run of `workflow` sharing `tag` (a superset is fine: the
86
+ * filter is re-applied here). Returns the candidate's new tags when it is (or already was) admitted,
87
+ * `null` when it must wait. Call only inside a store's atomic critical section — between a read that
88
+ * serialized competing claims and the write of the returned tags.
89
+ *
90
+ * Holders (runs carrying {@link SINGLETON_ADMITTED_TAG}) always keep their slot; only the slots they
91
+ * leave free go to the oldest unadmitted waiters, so a later or same-millisecond start can never
92
+ * displace a holder. Holders a pre-atomic (0.50 and older) replica admitted without the tag are
93
+ * counted too (see {@link legacySingletonHolders}), so a fleet mid rolling upgrade never admits past
94
+ * `limit`. Admission replaces the candidate's {@link SINGLETON_TRACKED_TAG} with the admitted tag.
95
+ */
96
+ export function singletonAdmissionTags(runs, runId, tag, workflow, limit) {
97
+ if (!Number.isInteger(limit) || limit < 1) {
98
+ throw new Error('singleton limit must be a positive integer');
99
+ }
100
+ const active = runs.filter((run) => run.workflow === workflow &&
101
+ SINGLETON_ACTIVE_STATUSES.includes(run.status) &&
102
+ run.tags?.includes(tag));
103
+ const candidate = active.find((run) => run.id === runId);
104
+ if (!candidate)
105
+ return null;
106
+ if (candidate.tags?.includes(SINGLETON_ADMITTED_TAG))
107
+ return candidate.tags ?? [];
108
+ const legacy = new Set(legacySingletonHolders(active, runId, limit).map((run) => run.id));
109
+ const holders = active.filter((run) => run.tags?.includes(SINGLETON_ADMITTED_TAG)).length + legacy.size;
110
+ const free = limit - holders;
111
+ if (free <= 0)
112
+ return null;
113
+ const waiters = active
114
+ .filter((run) => !run.tags?.includes(SINGLETON_ADMITTED_TAG) && !legacy.has(run.id))
115
+ .sort(byCreation);
116
+ if (!waiters.slice(0, free).some((run) => run.id === runId))
117
+ return null;
118
+ return [
119
+ ...(candidate.tags ?? []).filter((t) => t !== SINGLETON_TRACKED_TAG),
120
+ SINGLETON_ADMITTED_TAG,
121
+ ];
122
+ }
123
+ /**
124
+ * The patch that parks a denied candidate on its retry timer, or `undefined` when nothing may be
125
+ * written: the run is gone, or it already left the active set (a concurrent `cancel` settled it) —
126
+ * a denial must never overwrite that and resurrect the run. Apply it under the same locks as the read.
127
+ */
128
+ export function singletonAdmissionDenialPatch(runs, runId, tag, workflow, retryWakeAt) {
129
+ if (retryWakeAt === undefined)
130
+ return undefined;
131
+ const candidate = runs.find((run) => run.id === runId &&
132
+ run.workflow === workflow &&
133
+ SINGLETON_ACTIVE_STATUSES.includes(run.status) &&
134
+ run.tags?.includes(tag));
135
+ if (!candidate)
136
+ return undefined;
137
+ // Marked as a waiter the tag scheme tracks, so it is never mistaken for a legacy holder.
138
+ return {
139
+ status: 'suspended',
140
+ wakeAt: retryWakeAt,
141
+ tags: withSingletonTracked(candidate.tags),
142
+ updatedAt: new Date(),
143
+ };
144
+ }
145
+ /** Whether a run patch settles the run terminally (and so must release its singleton slot). */
146
+ export function settlesTerminally(patch) {
147
+ return (patch.status !== undefined &&
148
+ TERMINAL_RUN_STATUSES.includes(patch.status));
149
+ }
150
+ /** `tags` without the singleton markers (admitted and tracked), or `undefined` when nothing changes. */
151
+ export function withoutAdmission(tags) {
152
+ if (!tags?.some((t) => SINGLETON_RESERVED_TAGS.includes(t)))
153
+ return undefined;
154
+ return withoutSingletonMarkers(tags);
155
+ }
156
+ //# sourceMappingURL=singleton-admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton-admission.js","sourceRoot":"","sources":["../../src/singleton-admission.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { SingletonConfig } from './engine.js';
2
2
  import type { StateStore, WorkflowRun } from './interfaces.js';
3
3
  export interface SingletonGateDeps {
4
- store: Pick<StateStore, 'listRuns' | 'updateRun'>;
4
+ store: Pick<StateStore, 'listRuns' | 'updateRun' | 'updateRunIf' | 'tryAdmitSingleton'>;
5
5
  clock: () => number;
6
6
  /** Hand a gated run to the run dispatcher (fire-and-forget, like the engine's other dispatches). */
7
7
  dispatch: (runId: string) => void;
@@ -9,13 +9,15 @@ export interface SingletonGateDeps {
9
9
  configFor: (run: WorkflowRun) => SingletonConfig | undefined;
10
10
  }
11
11
  /**
12
- * Per-key serialization for singleton workflows — start-time back-pressure, FIFO race-free
13
- * admission, and notify-on-release wakeups. Extracted from {@link WorkflowEngine} so the whole
12
+ * Per-key serialization for singleton workflows — start-time back-pressure, durable atomic FIFO
13
+ * admission (claimed by the store's {@link StateStore.tryAdmitSingleton}), and notify-on-release wakeups. Extracted from {@link WorkflowEngine} so the whole
14
14
  * singleton feature lives in one place instead of being smeared across `start`, `execute`, both run
15
15
  * loops, and `cancel`.
16
16
  */
17
17
  export declare class SingletonGate {
18
18
  private readonly deps;
19
+ /** The non-atomic-store warning is printed once per engine, not once per admission. */
20
+ private warnedNonAtomic;
19
21
  constructor(deps: SingletonGateDeps);
20
22
  /** The tag a singleton run carries, so the gate can find others sharing its key. */
21
23
  tag(cfg: SingletonConfig, input: unknown): string;
@@ -23,16 +25,29 @@ export declare class SingletonGate {
23
25
  retryWakeAt(): number;
24
26
  /**
25
27
  * Reject a start that would grow the same-key backlog past `limit + maxQueueDepth` (counting
26
- * `pending`/`running`/`suspended` runs sharing the key in one scan). No-op when no `maxQueueDepth`
27
- * is configured.
28
+ * `pending`/`running`/`suspended`/`blocked` runs sharing the key in one scan). No-op when no
29
+ * `maxQueueDepth` is configured.
28
30
  */
29
31
  assertCapacity(workflow: string, cfg: SingletonConfig, input: unknown): Promise<void>;
30
32
  /**
31
- * Whether `run` may run now under its key: it's among the `limit` oldest in-flight (running or
32
- * suspended) runs sharing the key, by `(createdAt, id)` order. A consistent store gives every
33
- * instance the same ordering, so admission is race-free + FIFO.
33
+ * Claim (or confirm) a slot for `run` under its key, atomically in the store: runs already holding
34
+ * a slot keep it, and free slots go to the oldest waiters by `(createdAt, id)`. A denied run is
35
+ * parked `suspended` on a jittered retry timer by the store, in the same critical section — never
36
+ * over a run a concurrent cancel already settled. Holding a slot is durable (a reserved tag), so a
37
+ * holder that sleeps, waits, is recovered on another instance or loses its lease keeps it until it
38
+ * settles terminally.
34
39
  */
35
40
  admit(run: WorkflowRun, cfg: SingletonConfig): Promise<boolean>;
41
+ /**
42
+ * The pre-atomic admission, kept for a custom store without {@link StateStore.tryAdmitSingleton}
43
+ * so an upgrade doesn't break it: `run` is admitted while it is among the `limit` oldest
44
+ * `running`/`suspended` runs sharing its key by `(createdAt, id)`. It is a read followed by a
45
+ * decision, not a claim, so replicas can briefly over-admit and a same-millisecond start can
46
+ * displace a holder — hence the one-time warning. A denied run is parked on the retry timer here,
47
+ * conditionally, so a concurrent cancel is not overwritten.
48
+ */
49
+ private admitByScan;
50
+ private warnNonAtomic;
36
51
  /**
37
52
  * Notify-on-release: a singleton run settled (freeing a slot), so dispatch the oldest gated
38
53
  * (`suspended` + same tag) waiters now instead of waiting for their ~1s retry timer. Each re-checks
@@ -1 +1 @@
1
- {"version":3,"file":"singleton-gate.d.ts","sourceRoot":"","sources":["../../src/singleton-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAU/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC,CAAC;IAClD,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,oGAAoG;IACpG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,oFAAoF;IACpF,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,eAAe,GAAG,SAAS,CAAC;CAC9D;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,iBAAiB,EAAI;IAExD,oFAAoF;IACpF,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;IAED,gGAAgG;IAChG,WAAW,IAAI,MAAM,CAMpB;IAED;;;;OAIG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1F;IAED;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAYpE;IAED;;;;;OAKG;IACG,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBlD;CACF"}
1
+ {"version":3,"file":"singleton-gate.d.ts","sourceRoot":"","sources":["../../src/singleton-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAe/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,mBAAmB,CAAC,CAAC;IACxF,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,oGAAoG;IACpG,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,oFAAoF;IACpF,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,eAAe,GAAG,SAAS,CAAC;CAC9D;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IAIZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,uFAAuF;IACvF,OAAO,CAAC,eAAe,CAAS;IAEhC,YAA6B,IAAI,EAAE,iBAAiB,EAAI;IAExD,oFAAoF;IACpF,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;IAED,gGAAgG;IAChG,WAAW,IAAI,MAAM,CAMpB;IAED;;;;OAIG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1F;IAED;;;;;;;OAOG;IACG,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBpE;IAED;;;;;;;OAOG;YACW,WAAW;IAmBzB,OAAO,CAAC,aAAa;IAarB;;;;;OAKG;IACG,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBlD;CACF"}
@@ -1,4 +1,5 @@
1
1
  import { SingletonQueueFullError } from './errors.js';
2
+ import { SINGLETON_ACTIVE_STATUSES, SINGLETON_ADMITTED_TAG, SINGLETON_TRACKED_TAG, } from './singleton-admission.js';
2
3
  const SINGLETON_RETRY_MS = 1000;
3
4
  /**
4
5
  * Max jitter (ms, each direction) added to {@link SINGLETON_RETRY_MS} so a queue of N gated runs
@@ -7,13 +8,15 @@ const SINGLETON_RETRY_MS = 1000;
7
8
  */
8
9
  const SINGLETON_RETRY_JITTER_MS = 250;
9
10
  /**
10
- * Per-key serialization for singleton workflows — start-time back-pressure, FIFO race-free
11
- * admission, and notify-on-release wakeups. Extracted from {@link WorkflowEngine} so the whole
11
+ * Per-key serialization for singleton workflows — start-time back-pressure, durable atomic FIFO
12
+ * admission (claimed by the store's {@link StateStore.tryAdmitSingleton}), and notify-on-release wakeups. Extracted from {@link WorkflowEngine} so the whole
12
13
  * singleton feature lives in one place instead of being smeared across `start`, `execute`, both run
13
14
  * loops, and `cancel`.
14
15
  */
15
16
  export class SingletonGate {
16
17
  deps;
18
+ /** The non-atomic-store warning is printed once per engine, not once per admission. */
19
+ warnedNonAtomic = false;
17
20
  constructor(deps) {
18
21
  this.deps = deps;
19
22
  }
@@ -29,8 +32,8 @@ export class SingletonGate {
29
32
  }
30
33
  /**
31
34
  * Reject a start that would grow the same-key backlog past `limit + maxQueueDepth` (counting
32
- * `pending`/`running`/`suspended` runs sharing the key in one scan). No-op when no `maxQueueDepth`
33
- * is configured.
35
+ * `pending`/`running`/`suspended`/`blocked` runs sharing the key in one scan). No-op when no
36
+ * `maxQueueDepth` is configured.
34
37
  */
35
38
  async assertCapacity(workflow, cfg, input) {
36
39
  if (cfg.maxQueueDepth == null)
@@ -39,27 +42,68 @@ export class SingletonGate {
39
42
  const queued = await this.deps.store.listRuns({
40
43
  tag: this.tag(cfg, input),
41
44
  workflow,
42
- statuses: ['pending', 'running', 'suspended'],
45
+ statuses: SINGLETON_ACTIVE_STATUSES,
43
46
  });
44
47
  if (queued.length >= cap) {
45
48
  throw new SingletonQueueFullError(workflow, cfg.key(input), cfg.maxQueueDepth);
46
49
  }
47
50
  }
48
51
  /**
49
- * Whether `run` may run now under its key: it's among the `limit` oldest in-flight (running or
50
- * suspended) runs sharing the key, by `(createdAt, id)` order. A consistent store gives every
51
- * instance the same ordering, so admission is race-free + FIFO.
52
+ * Claim (or confirm) a slot for `run` under its key, atomically in the store: runs already holding
53
+ * a slot keep it, and free slots go to the oldest waiters by `(createdAt, id)`. A denied run is
54
+ * parked `suspended` on a jittered retry timer by the store, in the same critical section — never
55
+ * over a run a concurrent cancel already settled. Holding a slot is durable (a reserved tag), so a
56
+ * holder that sleeps, waits, is recovered on another instance or loses its lease keeps it until it
57
+ * settles terminally.
52
58
  */
53
59
  async admit(run, cfg) {
54
- // ONE scan for both in-flight statuses; the total `(createdAt, id)` sort makes admission order
55
- // independent of the store's row order, preserving the FIFO + race-free-across-instances contract.
60
+ const store = this.deps.store;
61
+ if (!store.tryAdmitSingleton)
62
+ return this.admitByScan(run, cfg);
63
+ const admitted = await store.tryAdmitSingleton(run.id, this.tag(cfg, run.input), run.workflow, cfg.limit ?? 1, this.retryWakeAt());
64
+ if (admitted && !run.tags?.includes(SINGLETON_ADMITTED_TAG)) {
65
+ run.tags = [
66
+ ...(run.tags ?? []).filter((t) => t !== SINGLETON_TRACKED_TAG),
67
+ SINGLETON_ADMITTED_TAG,
68
+ ];
69
+ }
70
+ return admitted;
71
+ }
72
+ /**
73
+ * The pre-atomic admission, kept for a custom store without {@link StateStore.tryAdmitSingleton}
74
+ * so an upgrade doesn't break it: `run` is admitted while it is among the `limit` oldest
75
+ * `running`/`suspended` runs sharing its key by `(createdAt, id)`. It is a read followed by a
76
+ * decision, not a claim, so replicas can briefly over-admit and a same-millisecond start can
77
+ * displace a holder — hence the one-time warning. A denied run is parked on the retry timer here,
78
+ * conditionally, so a concurrent cancel is not overwritten.
79
+ */
80
+ async admitByScan(run, cfg) {
81
+ this.warnNonAtomic();
56
82
  const inflight = (await this.deps.store.listRuns({
57
83
  tag: this.tag(cfg, run.input),
58
84
  workflow: run.workflow,
59
85
  statuses: ['running', 'suspended'],
60
86
  })).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id));
61
87
  const idx = inflight.findIndex((r) => r.id === run.id);
62
- return idx >= 0 && idx < (cfg.limit ?? 1);
88
+ if (idx >= 0 && idx < (cfg.limit ?? 1))
89
+ return true;
90
+ await this.deps.store.updateRunIf(run.id, SINGLETON_ACTIVE_STATUSES, {
91
+ status: 'suspended',
92
+ wakeAt: this.retryWakeAt(),
93
+ updatedAt: new Date(),
94
+ });
95
+ return false;
96
+ }
97
+ warnNonAtomic() {
98
+ if (this.warnedNonAtomic)
99
+ return;
100
+ this.warnedNonAtomic = true;
101
+ console.warn('[adonis-durable] singleton admission is NOT atomic on this state store: it does not implement ' +
102
+ 'StateStore.tryAdmitSingleton, so the engine falls back to a scan of running/suspended runs. ' +
103
+ 'Replicas can briefly admit more than `limit` runs per key, and a same-millisecond start can ' +
104
+ 'displace a holder. Implement tryAdmitSingleton (see the exported singletonAdmissionTags / ' +
105
+ 'singletonAdmissionDenialPatch helpers and the "Store support" section of the singleton docs) ' +
106
+ 'to make admission atomic.');
63
107
  }
64
108
  /**
65
109
  * Notify-on-release: a singleton run settled (freeing a slot), so dispatch the oldest gated
@@ -74,7 +118,10 @@ export class SingletonGate {
74
118
  const tag = settled.tags?.find((t) => t.startsWith('singleton:'));
75
119
  if (!tag)
76
120
  return;
77
- const gated = (await this.deps.store.listRuns({ tag, workflow: settled.workflow, statuses: ['suspended'] })).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id));
121
+ const gated = (await this.deps.store.listRuns({ tag, workflow: settled.workflow, statuses: ['suspended'] }))
122
+ // A suspended HOLDER (sleeping, waiting on a signal) is not waiting for a slot — leave it be.
123
+ .filter((r) => !r.tags?.includes(SINGLETON_ADMITTED_TAG))
124
+ .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id));
78
125
  for (const next of gated.slice(0, cfg.limit ?? 1)) {
79
126
  // Make the run due NOW rather than clearing its retry timer. The dispatch below goes through
80
127
  // the configured runDispatcher, which may legitimately be a NO-OP (a poll-only deployment
@@ -1 +1 @@
1
- {"version":3,"file":"singleton-gate.js","sourceRoot":"","sources":["../../src/singleton-gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"singleton-gate.js","sourceRoot":"","sources":["../../src/singleton-gate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAElC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC;;;;GAIG;AACH,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAWtC;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAIK,IAAI;IAHjC,uFAAuF;IAC/E,eAAe,GAAG,KAAK,CAAC;IAEhC,YAA6B,IAAuB;oBAAvB,IAAI;IAAsB,CAAC;IAExD,oFAAoF;IACpF,GAAG,CAAC,GAAoB,EAAE,KAAc;QACtC,OAAO,aAAa,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;IACvC,CAAC;IAED,gGAAgG;IAChG,WAAW;QACT,OAAO,CACL,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACjB,kBAAkB;YAClB,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,yBAAyB,CAAC,CAChE,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,QAAgB,EAAE,GAAoB,EAAE,KAAc;QACzE,IAAI,GAAG,CAAC,aAAa,IAAI,IAAI;YAAE,OAAO;QACtC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC5C,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC;YACzB,QAAQ;YACR,QAAQ,EAAE,yBAAyB;SACpC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACzB,MAAM,IAAI,uBAAuB,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,CAAC,GAAgB,EAAE,GAAoB;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,iBAAiB;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,iBAAiB,CAC5C,GAAG,CAAC,EAAE,EACN,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EACxB,GAAG,CAAC,QAAQ,EACZ,GAAG,CAAC,KAAK,IAAI,CAAC,EACd,IAAI,CAAC,WAAW,EAAE,CACnB,CAAC;QACF,IAAI,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,IAAI,GAAG;gBACT,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,qBAAqB,CAAC;gBAC9D,sBAAsB;aACvB,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,WAAW,CAAC,GAAgB,EAAE,GAAoB;QAC9D,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,CACf,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC7B,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC;YAC7B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,QAAQ,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC;SACnC,CAAC,CACH,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5F,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;QACvD,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpD,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,yBAAyB,EAAE;YACnE,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE;YAC1B,SAAS,EAAE,IAAI,IAAI,EAAE;SACtB,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,aAAa;QACnB,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO;QACjC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,IAAI,CACV,gGAAgG;YAC9F,8FAA8F;YAC9F,8FAA8F;YAC9F,4FAA4F;YAC5F,+FAA+F;YAC/F,2BAA2B,CAC9B,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CAAC,OAAoB;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,KAAK,GAAG,CACZ,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAC7F;YACC,8FAA8F;aAC7F,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,sBAAsB,CAAC,CAAC;aACxD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7F,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;YAClD,6FAA6F;YAC7F,0FAA0F;YAC1F,2FAA2F;YAC3F,uFAAuF;YACvF,4FAA4F;YAC5F,4FAA4F;YAC5F,2BAA2B;YAC3B,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK;iBAClB,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;iBACxE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,86 @@
1
+ import { type SchemaValidationCode } from './errors.js';
2
+ /**
3
+ * Vendored subset of the Standard Schema spec (https://standardschema.dev) — the `~standard`
4
+ * property protocol implemented by zod (3.24+ / 4), valibot, arktype, effect schema and others.
5
+ * Vendored (rather than taken as a dependency) as the spec recommends, so the package stays free of
6
+ * any schema library at runtime: the interface is types only, erased by the build. Source:
7
+ * https://github.com/standard-schema/standard-schema (MIT) — trimmed to the `StandardSchemaV1` shape
8
+ * this package consumes, identical to the copy in the NestJS sibling (`@dudousxd/nestjs-durable-core`).
9
+ *
10
+ * Used by `@Step({ input, output })` / `defineStep`, `register(..., { inputSchema })` /
11
+ * `static workflow = { inputSchema }` and the typed `searchAttributes` schema, so each accepts
12
+ * whichever schema library the app already uses.
13
+ */
14
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
15
+ /** The Standard Schema properties. */
16
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>;
17
+ }
18
+ export declare namespace StandardSchemaV1 {
19
+ /** The Standard Schema properties interface. */
20
+ interface Props<Input = unknown, Output = Input> {
21
+ /** The version number of the standard. */
22
+ readonly version: 1;
23
+ /** The vendor name of the schema library. */
24
+ readonly vendor: string;
25
+ /** Validates unknown input values. */
26
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
27
+ /** Inferred types associated with the schema. */
28
+ readonly types?: Types<Input, Output> | undefined;
29
+ }
30
+ /** The result interface of the validate function. */
31
+ type Result<Output> = SuccessResult<Output> | FailureResult;
32
+ /** The result interface if validation succeeds. */
33
+ interface SuccessResult<Output> {
34
+ /** The typed output value. */
35
+ readonly value: Output;
36
+ /** A falsy value for `issues` indicates success. */
37
+ readonly issues?: undefined;
38
+ }
39
+ /** The result interface if validation fails. */
40
+ interface FailureResult {
41
+ /** The issues of failed validation. */
42
+ readonly issues: ReadonlyArray<Issue>;
43
+ }
44
+ /** The issue interface of the failure output. */
45
+ interface Issue {
46
+ /** The error message of the issue. */
47
+ readonly message: string;
48
+ /** The path of the issue, if any. */
49
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
50
+ }
51
+ /** The path segment interface of the issue. */
52
+ interface PathSegment {
53
+ /** The key representing a path segment. */
54
+ readonly key: PropertyKey;
55
+ }
56
+ /** The Standard Schema types interface. */
57
+ interface Types<Input = unknown, Output = Input> {
58
+ /** The input type of the schema. */
59
+ readonly input: Input;
60
+ /** The output type of the schema. */
61
+ readonly output: Output;
62
+ }
63
+ /** Infers the input type of a Standard Schema. */
64
+ type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
65
+ /** Infers the output type of a Standard Schema. */
66
+ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
67
+ }
68
+ /**
69
+ * Validate `value` against any Standard Schema and return the schema's OUTPUT value (parsed /
70
+ * coerced / defaulted — e.g. zod's `.transform`, valibot's `pipe`, arktype morphs). Both a sync and
71
+ * an async (`Promise`-returning) `~standard.validate` are supported. On failure throws a
72
+ * {@link SchemaValidationError} — a non-retryable `FatalError` carrying the issues in `details`, so a
73
+ * bad payload fails fast instead of being retried forever.
74
+ *
75
+ * `label` names what was validated in the error message, e.g. `step "billing:charge" input`. `code`
76
+ * is the error's code: `invalid_input` / `invalid_output` for an input or output schema, and
77
+ * `schema_validation` (the default) otherwise.
78
+ */
79
+ export declare function validateStandard<S extends StandardSchemaV1>(schema: S, value: unknown, label: string, code?: SchemaValidationCode): Promise<StandardSchemaV1.InferOutput<S>>;
80
+ /**
81
+ * Synchronous {@link validateStandard}, for call sites that must not await a validator (the typed
82
+ * search-attributes write, where an async refinement could make replay nondeterministic). A schema
83
+ * whose `validate` returns a `Promise` throws a clear error instead of being silently awaited.
84
+ */
85
+ export declare function validateStandardSync<S extends StandardSchemaV1>(schema: S, value: unknown, label: string, code?: SchemaValidationCode): StandardSchemaV1.InferOutput<S>;
86
+ //# sourceMappingURL=standard-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard-schema.d.ts","sourceRoot":"","sources":["../../src/standard-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { SchemaValidationError } from './errors.js';
2
+ /**
3
+ * Validate `value` against any Standard Schema and return the schema's OUTPUT value (parsed /
4
+ * coerced / defaulted — e.g. zod's `.transform`, valibot's `pipe`, arktype morphs). Both a sync and
5
+ * an async (`Promise`-returning) `~standard.validate` are supported. On failure throws a
6
+ * {@link SchemaValidationError} — a non-retryable `FatalError` carrying the issues in `details`, so a
7
+ * bad payload fails fast instead of being retried forever.
8
+ *
9
+ * `label` names what was validated in the error message, e.g. `step "billing:charge" input`. `code`
10
+ * is the error's code: `invalid_input` / `invalid_output` for an input or output schema, and
11
+ * `schema_validation` (the default) otherwise.
12
+ */
13
+ export async function validateStandard(schema, value, label, code) {
14
+ const result = await schema['~standard'].validate(value);
15
+ if (result.issues)
16
+ throw new SchemaValidationError(label, result.issues, code);
17
+ return result.value;
18
+ }
19
+ /**
20
+ * Synchronous {@link validateStandard}, for call sites that must not await a validator (the typed
21
+ * search-attributes write, where an async refinement could make replay nondeterministic). A schema
22
+ * whose `validate` returns a `Promise` throws a clear error instead of being silently awaited.
23
+ */
24
+ export function validateStandardSync(schema, value, label, code) {
25
+ const result = schema['~standard'].validate(value);
26
+ if (result instanceof Promise) {
27
+ throw new TypeError(`${label}: schema validation must be synchronous (the schema's \`~standard.validate\` returned a Promise) — drop any async refinement from it.`);
28
+ }
29
+ if (result.issues)
30
+ throw new SchemaValidationError(label, result.issues, code);
31
+ return result.value;
32
+ }
33
+ //# sourceMappingURL=standard-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard-schema.js","sourceRoot":"","sources":["../../src/standard-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAkF/E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAS,EACT,KAAc,EACd,KAAa,EACb,IAA2B;IAE3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,KAAwC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAS,EACT,KAAc,EACd,KAAa,EACb,IAA2B;IAE3B,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,SAAS,CACjB,GAAG,KAAK,uIAAuI,CAChJ,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,KAAwC,CAAC;AACzD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { type BarrelTree, type RootedBarrel } from './discovery-roots.js';
1
2
  import type { StepLogger } from './interfaces.js';
2
3
  import type { StepHandler } from './protocol.js';
3
4
  import { type DurableStepMeta } from './step-ref.js';
@@ -13,7 +14,7 @@ export interface DiscoveredStep {
13
14
  }
14
15
  /**
15
16
  * Register a single discovered step on `server` under its stamped name, wrapping it with any
16
- * declared zod validation. The low-level `transport.handle(name, fn)` stays the escape hatch — this
17
+ * declared schema validation. The low-level `transport.handle(name, fn)` stays the escape hatch — this
17
18
  * is the convention the `app/steps` discovery builds on.
18
19
  */
19
20
  export declare function registerStep(server: StepServer, step: DiscoveredStep): void;
@@ -28,16 +29,25 @@ export declare function collectSteps(exported: unknown): DiscoveredStep[];
28
29
  * onto `server`. De-duplicated by routing name (last wins). Returns the registered metadata. */
29
30
  export declare function registerSteps(server: StepServer, exports: Iterable<unknown>): DurableStepMeta[];
30
31
  /**
31
- * Scan `dir` RECURSIVELY for modules and register every exported `@Step` class / `defineStep`
32
- * handler on `server` — the `app/steps` convention, mirroring `app/workflows` discovery. Only the
33
- * extension present in `dir` is imported (see {@link pickModuleExt}). Missing directory → no-op
34
- * (the convention is opt-in). Returns the registered metadata.
32
+ * Scan `dir` — one directory or several — RECURSIVELY and register every exported `@Step` class /
33
+ * `defineStep` handler on `server`: the `app/steps` convention, mirroring `app/workflows` discovery.
34
+ * Only the extension present in each directory is imported (see `pickModuleExt`). Missing
35
+ * directory → nothing from it (the convention is opt-in). With several directories the same step
36
+ * name defined by different handlers in two of them throws a {@link DuplicateDiscoveryError}.
37
+ * Returns the registered metadata.
38
+ *
39
+ * `server` is anything with `handle(name, fn)`: a transport, the store-less `WorkerRuntime`, or the
40
+ * `WorkflowEngine` itself (it forwards to its primary transport) — the latter is how app code
41
+ * serves an extra directory from a preload: `await registerStepsFromDir(engine, dir)`.
35
42
  */
36
- export declare function registerStepsFromDir(server: StepServer, dir: string): Promise<DurableStepMeta[]>;
43
+ export declare function registerStepsFromDir(server: StepServer, dir: string | readonly string[]): Promise<DurableStepMeta[]>;
37
44
  /** The build-time barrel shape the Assembler `init` hook generates for `app/steps` (key → lazy
38
- * module import), mirroring {@link import('./workflow-discovery.js').WorkflowsBarrel}. */
39
- export type StepsBarrel = Record<string, () => Promise<Record<string, unknown>>>;
40
- /** Register every `@Step`/`defineStep` reachable from a generated {@link StepsBarrel} onto `server`
41
- * — the build-time equivalent of {@link registerStepsFromDir} with no runtime `readdir`. */
42
- export declare function registerStepsFromBarrel(server: StepServer, barrel: StepsBarrel): Promise<DurableStepMeta[]>;
45
+ * module import, nested per sub-directory), mirroring
46
+ * {@link import('./workflow-discovery.js').WorkflowsBarrel}. A multi-root hook writes a
47
+ * {@link RootedBarrel} array instead. */
48
+ export type StepsBarrel = BarrelTree;
49
+ /** Register every `@Step`/`defineStep` reachable from a generated {@link StepsBarrel} (or a
50
+ * multi-root {@link RootedBarrel} array) onto `server` — the build-time equivalent of
51
+ * {@link registerStepsFromDir} with no runtime `readdir`, same cross-root duplicate rule. */
52
+ export declare function registerStepsFromBarrel(server: StepServer, barrel: StepsBarrel | readonly RootedBarrel[]): Promise<DurableStepMeta[]>;
43
53
  //# sourceMappingURL=step-discovery.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"step-discovery.d.ts","sourceRoot":"","sources":["../../src/step-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,KAAK,eAAe,EAAc,MAAM,eAAe,CAAC;AAGjE;uFACuF;AACvF,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;CAC7C;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,cAAc,EAAE,CA8BhE;AAED;iGACiG;AACjG,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,CAY/F;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,EAAE,CAAC,CAsB5B;AAED;2FAC2F;AAC3F,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAEjF;6FAC6F;AAC7F,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,EAAE,CAAC,CAY5B"}
1
+ {"version":3,"file":"step-discovery.d.ts","sourceRoot":"","sources":["../../src/step-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAIf,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,KAAK,eAAe,EAAc,MAAM,eAAe,CAAC;AAEjE;uFACuF;AACvF,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;CAC7C;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AAoBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,cAAc,EAAE,CA8BhE;AAED;iGACiG;AACjG,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,CAY/F;AAmED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAC9B,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAED;;;0CAG0C;AAC1C,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAErC;;8FAE8F;AAC9F,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,WAAW,GAAG,SAAS,YAAY,EAAE,GAC5C,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B"}