@ontrails/core 1.0.0-beta.15 → 1.0.0-beta.17

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +64 -19
  3. package/package.json +9 -8
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/context.ts +4 -0
  10. package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
  11. package/src/detours.ts +8 -0
  12. package/src/draft.ts +4 -0
  13. package/src/error-projection.ts +51 -0
  14. package/src/errors.ts +192 -39
  15. package/src/execute.ts +443 -44
  16. package/src/fire.ts +965 -91
  17. package/src/index.ts +285 -38
  18. package/src/layer-projection.ts +192 -0
  19. package/src/layer.ts +32 -1
  20. package/src/observe.ts +361 -0
  21. package/src/permits.ts +12 -0
  22. package/src/redaction/patterns.ts +6 -3
  23. package/src/resilience.ts +8 -8
  24. package/src/resource-config.ts +477 -44
  25. package/src/resource.ts +21 -1
  26. package/src/result.ts +18 -0
  27. package/src/run.ts +2 -2
  28. package/src/schedule-runtime.ts +689 -0
  29. package/src/schedule.ts +324 -0
  30. package/src/serialization.ts +90 -57
  31. package/src/signal-diagnostics.ts +633 -0
  32. package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
  33. package/src/signal.ts +37 -0
  34. package/src/store/accessor-protocol.ts +5 -5
  35. package/src/structured-examples.ts +245 -0
  36. package/src/surface-derivation.ts +91 -0
  37. package/src/surface-filter.ts +7 -13
  38. package/src/topo.ts +386 -6
  39. package/src/tracing.ts +345 -0
  40. package/src/trail.ts +168 -22
  41. package/src/trails/derive-trail.ts +1 -1
  42. package/src/trails/ingest.ts +7 -15
  43. package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
  44. package/src/transport-error-map.ts +146 -28
  45. package/src/types.ts +82 -28
  46. package/src/validate-established-topo.ts +6 -0
  47. package/src/validate-topo.ts +212 -5
  48. package/src/validation.ts +58 -1
  49. package/src/webhook.ts +276 -0
  50. package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
  51. package/.turbo/turbo-build.log +0 -1
  52. package/.turbo/turbo-lint.log +0 -3
  53. package/.turbo/turbo-typecheck.log +0 -1
  54. package/dist/blob-ref.d.ts +0 -20
  55. package/dist/blob-ref.d.ts.map +0 -1
  56. package/dist/blob-ref.js +0 -22
  57. package/dist/blob-ref.js.map +0 -1
  58. package/dist/branded.d.ts +0 -36
  59. package/dist/branded.d.ts.map +0 -1
  60. package/dist/branded.js +0 -89
  61. package/dist/branded.js.map +0 -1
  62. package/dist/collections.d.ts +0 -31
  63. package/dist/collections.d.ts.map +0 -1
  64. package/dist/collections.js +0 -60
  65. package/dist/collections.js.map +0 -1
  66. package/dist/context.d.ts +0 -22
  67. package/dist/context.d.ts.map +0 -1
  68. package/dist/context.js +0 -37
  69. package/dist/context.js.map +0 -1
  70. package/dist/contour.d.ts +0 -81
  71. package/dist/contour.d.ts.map +0 -1
  72. package/dist/contour.js +0 -176
  73. package/dist/contour.js.map +0 -1
  74. package/dist/cross-schema.d.ts +0 -18
  75. package/dist/cross-schema.d.ts.map +0 -1
  76. package/dist/cross-schema.js +0 -29
  77. package/dist/cross-schema.js.map +0 -1
  78. package/dist/derive.d.ts +0 -44
  79. package/dist/derive.d.ts.map +0 -1
  80. package/dist/derive.js +0 -149
  81. package/dist/derive.js.map +0 -1
  82. package/dist/draft.d.ts +0 -28
  83. package/dist/draft.d.ts.map +0 -1
  84. package/dist/draft.js +0 -157
  85. package/dist/draft.js.map +0 -1
  86. package/dist/errors.d.ts +0 -141
  87. package/dist/errors.d.ts.map +0 -1
  88. package/dist/errors.js +0 -185
  89. package/dist/errors.js.map +0 -1
  90. package/dist/execute.d.ts +0 -52
  91. package/dist/execute.d.ts.map +0 -1
  92. package/dist/execute.js +0 -511
  93. package/dist/execute.js.map +0 -1
  94. package/dist/fetch.d.ts +0 -15
  95. package/dist/fetch.d.ts.map +0 -1
  96. package/dist/fetch.js +0 -102
  97. package/dist/fetch.js.map +0 -1
  98. package/dist/fire.d.ts +0 -43
  99. package/dist/fire.d.ts.map +0 -1
  100. package/dist/fire.js +0 -185
  101. package/dist/fire.js.map +0 -1
  102. package/dist/guards.d.ts +0 -17
  103. package/dist/guards.d.ts.map +0 -1
  104. package/dist/guards.js +0 -25
  105. package/dist/guards.js.map +0 -1
  106. package/dist/index.d.ts +0 -59
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -61
  109. package/dist/index.js.map +0 -1
  110. package/dist/internal/cross-batch.d.ts +0 -44
  111. package/dist/internal/cross-batch.d.ts.map +0 -1
  112. package/dist/internal/cross-batch.js +0 -58
  113. package/dist/internal/cross-batch.js.map +0 -1
  114. package/dist/internal/fork-ctx.d.ts +0 -41
  115. package/dist/internal/fork-ctx.d.ts.map +0 -1
  116. package/dist/internal/fork-ctx.js +0 -47
  117. package/dist/internal/fork-ctx.js.map +0 -1
  118. package/dist/internal/signal-ref.d.ts +0 -15
  119. package/dist/internal/signal-ref.d.ts.map +0 -1
  120. package/dist/internal/signal-ref.js +0 -41
  121. package/dist/internal/signal-ref.js.map +0 -1
  122. package/dist/internal/topo-saves.d.ts +0 -53
  123. package/dist/internal/topo-saves.d.ts.map +0 -1
  124. package/dist/internal/topo-saves.js +0 -449
  125. package/dist/internal/topo-saves.js.map +0 -1
  126. package/dist/internal/topo-snapshots.d.ts +0 -57
  127. package/dist/internal/topo-snapshots.d.ts.map +0 -1
  128. package/dist/internal/topo-snapshots.js +0 -372
  129. package/dist/internal/topo-snapshots.js.map +0 -1
  130. package/dist/internal/topo-store-read.d.ts +0 -70
  131. package/dist/internal/topo-store-read.d.ts.map +0 -1
  132. package/dist/internal/topo-store-read.js +0 -221
  133. package/dist/internal/topo-store-read.js.map +0 -1
  134. package/dist/internal/topo-store.d.ts +0 -26
  135. package/dist/internal/topo-store.d.ts.map +0 -1
  136. package/dist/internal/topo-store.js +0 -621
  137. package/dist/internal/topo-store.js.map +0 -1
  138. package/dist/internal/tracing.d.ts +0 -98
  139. package/dist/internal/tracing.d.ts.map +0 -1
  140. package/dist/internal/tracing.js +0 -102
  141. package/dist/internal/tracing.js.map +0 -1
  142. package/dist/internal/trails-db.d.ts +0 -16
  143. package/dist/internal/trails-db.d.ts.map +0 -1
  144. package/dist/internal/trails-db.js +0 -118
  145. package/dist/internal/trails-db.js.map +0 -1
  146. package/dist/internal/zod-wrappers.d.ts +0 -40
  147. package/dist/internal/zod-wrappers.d.ts.map +0 -1
  148. package/dist/internal/zod-wrappers.js +0 -59
  149. package/dist/internal/zod-wrappers.js.map +0 -1
  150. package/dist/layer.d.ts +0 -23
  151. package/dist/layer.d.ts.map +0 -1
  152. package/dist/layer.js +0 -21
  153. package/dist/layer.js.map +0 -1
  154. package/dist/path-security.d.ts +0 -28
  155. package/dist/path-security.d.ts.map +0 -1
  156. package/dist/path-security.js +0 -63
  157. package/dist/path-security.js.map +0 -1
  158. package/dist/patterns/bulk.d.ts +0 -15
  159. package/dist/patterns/bulk.d.ts.map +0 -1
  160. package/dist/patterns/bulk.js +0 -14
  161. package/dist/patterns/bulk.js.map +0 -1
  162. package/dist/patterns/change.d.ts +0 -10
  163. package/dist/patterns/change.d.ts.map +0 -1
  164. package/dist/patterns/change.js +0 -10
  165. package/dist/patterns/change.js.map +0 -1
  166. package/dist/patterns/date-range.d.ts +0 -10
  167. package/dist/patterns/date-range.d.ts.map +0 -1
  168. package/dist/patterns/date-range.js +0 -10
  169. package/dist/patterns/date-range.js.map +0 -1
  170. package/dist/patterns/index.d.ts +0 -9
  171. package/dist/patterns/index.d.ts.map +0 -1
  172. package/dist/patterns/index.js +0 -9
  173. package/dist/patterns/index.js.map +0 -1
  174. package/dist/patterns/pagination.d.ts +0 -18
  175. package/dist/patterns/pagination.d.ts.map +0 -1
  176. package/dist/patterns/pagination.js +0 -18
  177. package/dist/patterns/pagination.js.map +0 -1
  178. package/dist/patterns/progress.d.ts +0 -11
  179. package/dist/patterns/progress.d.ts.map +0 -1
  180. package/dist/patterns/progress.js +0 -11
  181. package/dist/patterns/progress.js.map +0 -1
  182. package/dist/patterns/sorting.d.ts +0 -13
  183. package/dist/patterns/sorting.d.ts.map +0 -1
  184. package/dist/patterns/sorting.js +0 -10
  185. package/dist/patterns/sorting.js.map +0 -1
  186. package/dist/patterns/status.d.ts +0 -15
  187. package/dist/patterns/status.d.ts.map +0 -1
  188. package/dist/patterns/status.js +0 -9
  189. package/dist/patterns/status.js.map +0 -1
  190. package/dist/patterns/timestamps.d.ts +0 -10
  191. package/dist/patterns/timestamps.d.ts.map +0 -1
  192. package/dist/patterns/timestamps.js +0 -10
  193. package/dist/patterns/timestamps.js.map +0 -1
  194. package/dist/redaction/index.d.ts +0 -4
  195. package/dist/redaction/index.d.ts.map +0 -1
  196. package/dist/redaction/index.js +0 -3
  197. package/dist/redaction/index.js.map +0 -1
  198. package/dist/redaction/patterns.d.ts +0 -9
  199. package/dist/redaction/patterns.d.ts.map +0 -1
  200. package/dist/redaction/patterns.js +0 -39
  201. package/dist/redaction/patterns.js.map +0 -1
  202. package/dist/redaction/redactor.d.ts +0 -27
  203. package/dist/redaction/redactor.d.ts.map +0 -1
  204. package/dist/redaction/redactor.js +0 -89
  205. package/dist/redaction/redactor.js.map +0 -1
  206. package/dist/resilience.d.ts +0 -34
  207. package/dist/resilience.d.ts.map +0 -1
  208. package/dist/resilience.js +0 -164
  209. package/dist/resilience.js.map +0 -1
  210. package/dist/resource-config.d.ts +0 -22
  211. package/dist/resource-config.d.ts.map +0 -1
  212. package/dist/resource-config.js +0 -210
  213. package/dist/resource-config.js.map +0 -1
  214. package/dist/resource.d.ts +0 -74
  215. package/dist/resource.d.ts.map +0 -1
  216. package/dist/resource.js +0 -61
  217. package/dist/resource.js.map +0 -1
  218. package/dist/result.d.ts +0 -57
  219. package/dist/result.d.ts.map +0 -1
  220. package/dist/result.js +0 -156
  221. package/dist/result.js.map +0 -1
  222. package/dist/run.d.ts +0 -28
  223. package/dist/run.d.ts.map +0 -1
  224. package/dist/run.js +0 -35
  225. package/dist/run.js.map +0 -1
  226. package/dist/serialization.d.ts +0 -27
  227. package/dist/serialization.d.ts.map +0 -1
  228. package/dist/serialization.js +0 -154
  229. package/dist/serialization.js.map +0 -1
  230. package/dist/signal.d.ts +0 -33
  231. package/dist/signal.d.ts.map +0 -1
  232. package/dist/signal.js +0 -17
  233. package/dist/signal.js.map +0 -1
  234. package/dist/store/accessor-protocol.d.ts +0 -51
  235. package/dist/store/accessor-protocol.d.ts.map +0 -1
  236. package/dist/store/accessor-protocol.js +0 -2
  237. package/dist/store/accessor-protocol.js.map +0 -1
  238. package/dist/store/index.d.ts +0 -2
  239. package/dist/store/index.d.ts.map +0 -1
  240. package/dist/store/index.js +0 -2
  241. package/dist/store/index.js.map +0 -1
  242. package/dist/surface-filter.d.ts +0 -13
  243. package/dist/surface-filter.d.ts.map +0 -1
  244. package/dist/surface-filter.js +0 -76
  245. package/dist/surface-filter.js.map +0 -1
  246. package/dist/topo-store.d.ts +0 -62
  247. package/dist/topo-store.d.ts.map +0 -1
  248. package/dist/topo-store.js +0 -319
  249. package/dist/topo-store.js.map +0 -1
  250. package/dist/topo.d.ts +0 -39
  251. package/dist/topo.d.ts.map +0 -1
  252. package/dist/topo.js +0 -241
  253. package/dist/topo.js.map +0 -1
  254. package/dist/trail.d.ts +0 -163
  255. package/dist/trail.d.ts.map +0 -1
  256. package/dist/trail.js +0 -75
  257. package/dist/trail.js.map +0 -1
  258. package/dist/trails/derive-trail.d.ts +0 -66
  259. package/dist/trails/derive-trail.d.ts.map +0 -1
  260. package/dist/trails/derive-trail.js +0 -378
  261. package/dist/trails/derive-trail.js.map +0 -1
  262. package/dist/trails/index.d.ts +0 -5
  263. package/dist/trails/index.d.ts.map +0 -1
  264. package/dist/trails/index.js +0 -3
  265. package/dist/trails/index.js.map +0 -1
  266. package/dist/trails/ingest.d.ts +0 -24
  267. package/dist/trails/ingest.d.ts.map +0 -1
  268. package/dist/trails/ingest.js +0 -63
  269. package/dist/trails/ingest.js.map +0 -1
  270. package/dist/transport-error-map.d.ts +0 -106
  271. package/dist/transport-error-map.d.ts.map +0 -1
  272. package/dist/transport-error-map.js +0 -24
  273. package/dist/transport-error-map.js.map +0 -1
  274. package/dist/type-checks.test-d.d.ts +0 -71
  275. package/dist/type-checks.test-d.d.ts.map +0 -1
  276. package/dist/type-checks.test-d.js +0 -12
  277. package/dist/type-checks.test-d.js.map +0 -1
  278. package/dist/type-utils.d.ts +0 -33
  279. package/dist/type-utils.d.ts.map +0 -1
  280. package/dist/type-utils.js +0 -12
  281. package/dist/type-utils.js.map +0 -1
  282. package/dist/types.d.ts +0 -177
  283. package/dist/types.d.ts.map +0 -1
  284. package/dist/types.js +0 -3
  285. package/dist/types.js.map +0 -1
  286. package/dist/validate-established-topo.d.ts +0 -76
  287. package/dist/validate-established-topo.d.ts.map +0 -1
  288. package/dist/validate-established-topo.js +0 -43
  289. package/dist/validate-established-topo.js.map +0 -1
  290. package/dist/validate-topo.d.ts +0 -24
  291. package/dist/validate-topo.d.ts.map +0 -1
  292. package/dist/validate-topo.js +0 -193
  293. package/dist/validate-topo.js.map +0 -1
  294. package/dist/validation.d.ts +0 -27
  295. package/dist/validation.d.ts.map +0 -1
  296. package/dist/validation.js +0 -165
  297. package/dist/validation.js.map +0 -1
  298. package/dist/workspace.d.ts +0 -25
  299. package/dist/workspace.d.ts.map +0 -1
  300. package/dist/workspace.js +0 -57
  301. package/dist/workspace.js.map +0 -1
  302. package/src/__tests__/blob-ref.test.ts +0 -103
  303. package/src/__tests__/branded.test.ts +0 -148
  304. package/src/__tests__/collections.test.ts +0 -126
  305. package/src/__tests__/context.test.ts +0 -77
  306. package/src/__tests__/contour.test.ts +0 -263
  307. package/src/__tests__/derive-trail.test.ts +0 -668
  308. package/src/__tests__/derive.test.ts +0 -260
  309. package/src/__tests__/errors.test.ts +0 -427
  310. package/src/__tests__/execute.test.ts +0 -1563
  311. package/src/__tests__/fetch.test.ts +0 -217
  312. package/src/__tests__/fire.test.ts +0 -1056
  313. package/src/__tests__/fork-ctx.test.ts +0 -83
  314. package/src/__tests__/guards.test.ts +0 -102
  315. package/src/__tests__/ingest.test.ts +0 -191
  316. package/src/__tests__/layer.test.ts +0 -125
  317. package/src/__tests__/path-security.test.ts +0 -114
  318. package/src/__tests__/patterns.test.ts +0 -273
  319. package/src/__tests__/redaction.test.ts +0 -244
  320. package/src/__tests__/resilience.test.ts +0 -246
  321. package/src/__tests__/resource.test.ts +0 -204
  322. package/src/__tests__/result.test.ts +0 -155
  323. package/src/__tests__/run.test.ts +0 -181
  324. package/src/__tests__/serialization.test.ts +0 -401
  325. package/src/__tests__/service-config.test.ts +0 -224
  326. package/src/__tests__/signal.test.ts +0 -82
  327. package/src/__tests__/surface-filter.test.ts +0 -196
  328. package/src/__tests__/topo-store-read.test.ts +0 -256
  329. package/src/__tests__/topo-store.test.ts +0 -869
  330. package/src/__tests__/topo.test.ts +0 -573
  331. package/src/__tests__/trail-permit.test.ts +0 -60
  332. package/src/__tests__/trail.test.ts +0 -426
  333. package/src/__tests__/trails-db.test.ts +0 -198
  334. package/src/__tests__/transport-error-map.test.ts +0 -82
  335. package/src/__tests__/type-utils.test.ts +0 -118
  336. package/src/__tests__/validate-topo.test.ts +0 -611
  337. package/src/__tests__/validation.test.ts +0 -336
  338. package/src/__tests__/workspace.test.ts +0 -183
  339. package/src/__tests__/zod-wrappers.test.ts +0 -62
  340. package/src/internal/topo-snapshots.ts +0 -517
  341. package/src/internal/topo-store-read.ts +0 -481
  342. package/src/internal/topo-store.ts +0 -1192
  343. package/src/internal/tracing.ts +0 -187
  344. package/src/topo-store.ts +0 -510
  345. package/src/type-checks.test-d.ts +0 -106
  346. package/tsconfig.json +0 -9
  347. package/tsconfig.tests.json +0 -10
  348. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,689 @@
1
+ import type { ActivationEntry } from './activation-source.js';
2
+ import { activationSourceKey } from './activation-source-projection.js';
3
+ import {
4
+ buildActivationProvenanceTraceAttrs,
5
+ withActivationProvenance,
6
+ } from './activation-provenance.js';
7
+ import type { ActivationProvenance } from './activation-provenance.js';
8
+ import { getActivationWherePredicate } from './activation-source.js';
9
+ import { createTrailContext } from './context.js';
10
+ import type { ExecuteTrailOptions } from './execute.js';
11
+ import { ConflictError, InternalError } from './errors.js';
12
+ import {
13
+ getTraceSink,
14
+ TRACE_CONTEXT_KEY,
15
+ traceContextFromRecord,
16
+ writeActivationTraceRecord,
17
+ } from './tracing.js';
18
+ import type { TraceContext } from './tracing.js';
19
+ import { Result } from './result.js';
20
+ import { drainResources } from './resource-config.js';
21
+ import type { ResourceDrainReport } from './resource-config.js';
22
+ import { run } from './run.js';
23
+ import type { Topo } from './topo.js';
24
+ import type { AnyTrail } from './trail.js';
25
+ import type { Logger, TrailContext, TrailContextInit } from './types.js';
26
+ import { validateTopo } from './validate-topo.js';
27
+
28
+ type ScheduleInputDefault = Record<string, never>;
29
+
30
+ export type ScheduleRuntimeState = 'idle' | 'running' | 'stopped' | 'stopping';
31
+
32
+ export type ScheduleCronHandler = () => Promise<void> | void;
33
+
34
+ export interface ScheduleCronJob {
35
+ readonly cron?: string | undefined;
36
+ ref?: (() => unknown) | undefined;
37
+ stop(): unknown;
38
+ unref?: (() => unknown) | undefined;
39
+ }
40
+
41
+ export type ScheduleCronFactory = (
42
+ cron: string,
43
+ handler: ScheduleCronHandler
44
+ ) => ScheduleCronJob;
45
+
46
+ export type ScheduleRuntimeLogger = Pick<Logger, 'debug' | 'error' | 'warn'>;
47
+
48
+ export type ScheduleRuntimeSkipReason =
49
+ | 'stopped'
50
+ | 'where_error'
51
+ | 'where_false';
52
+
53
+ export type ScheduleRuntimeRunStatus = 'err' | 'ok' | 'skipped';
54
+
55
+ export interface ScheduleRuntimeRunRecord {
56
+ readonly activation?: ActivationProvenance | undefined;
57
+ readonly error?: Error | undefined;
58
+ readonly result?: Result<unknown, Error> | undefined;
59
+ readonly skipReason?: ScheduleRuntimeSkipReason | undefined;
60
+ readonly sourceId: string;
61
+ readonly status: ScheduleRuntimeRunStatus;
62
+ readonly trailId: string;
63
+ }
64
+
65
+ export interface ScheduleRuntimeOptions extends Pick<
66
+ ExecuteTrailOptions,
67
+ | 'abortSignal'
68
+ | 'configValues'
69
+ | 'createContext'
70
+ | 'ctx'
71
+ | 'layers'
72
+ | 'resources'
73
+ > {
74
+ readonly cron?: ScheduleCronFactory | undefined;
75
+ readonly logger?: ScheduleRuntimeLogger | undefined;
76
+ readonly onRun?:
77
+ | ((record: ScheduleRuntimeRunRecord) => Promise<void> | void)
78
+ | undefined;
79
+ readonly unref?: boolean | undefined;
80
+ }
81
+
82
+ export interface ScheduleRuntimeRegistrationReport {
83
+ readonly cron: string;
84
+ readonly sourceId: string;
85
+ readonly timezone?: string | undefined;
86
+ readonly trailId: string;
87
+ }
88
+
89
+ export type ScheduleRuntimeWarningCode = 'schedule_timezone_metadata_only';
90
+
91
+ export interface ScheduleRuntimeWarning {
92
+ readonly code: ScheduleRuntimeWarningCode;
93
+ readonly message: string;
94
+ readonly sourceId: string;
95
+ readonly timezone: string;
96
+ readonly trailId: string;
97
+ }
98
+
99
+ export interface ScheduleRuntimeStartReport {
100
+ readonly registered: readonly ScheduleRuntimeRegistrationReport[];
101
+ readonly warnings: readonly ScheduleRuntimeWarning[];
102
+ }
103
+
104
+ export interface ScheduleRuntimeStopReport {
105
+ readonly resources: ResourceDrainReport;
106
+ readonly settledRuns: number;
107
+ readonly stopped: readonly ScheduleRuntimeRegistrationReport[];
108
+ }
109
+
110
+ export interface ScheduleRuntime {
111
+ readonly state: () => ScheduleRuntimeState;
112
+ readonly start: () => Promise<Result<ScheduleRuntimeStartReport, Error>>;
113
+ readonly stop: () => Promise<Result<ScheduleRuntimeStopReport, Error>>;
114
+ }
115
+
116
+ interface BunCronGlobal {
117
+ readonly Bun?: {
118
+ readonly cron?: ScheduleCronFactory | undefined;
119
+ };
120
+ }
121
+
122
+ interface ScheduleActivationRegistration {
123
+ readonly activation: ActivationEntry;
124
+ readonly cron: string;
125
+ readonly input: unknown;
126
+ readonly sourceId: string;
127
+ readonly timezone?: string | undefined;
128
+ readonly trail: AnyTrail;
129
+ }
130
+
131
+ interface RunningScheduleRegistration extends ScheduleActivationRegistration {
132
+ readonly job: ScheduleCronJob;
133
+ }
134
+
135
+ const EMPTY_INPUT = Object.freeze({}) as ScheduleInputDefault;
136
+
137
+ const defaultCronFactory: ScheduleCronFactory = (cron, handler) => {
138
+ const bunCron = (globalThis as unknown as BunCronGlobal).Bun?.cron;
139
+ if (typeof bunCron !== 'function') {
140
+ throw new InternalError(
141
+ 'Bun.cron is not available in this runtime; provide a cron factory to createScheduleRuntime()'
142
+ );
143
+ }
144
+ return bunCron(cron, handler);
145
+ };
146
+
147
+ const errorFromUnknown = (error: unknown): Error =>
148
+ error instanceof Error ? error : new Error(String(error));
149
+
150
+ const errorRecord = (error: Error): Record<string, unknown> => ({
151
+ message: error.message,
152
+ name: error.name,
153
+ ...(error.cause instanceof Error
154
+ ? { cause: { message: error.cause.message, name: error.cause.name } }
155
+ : {}),
156
+ });
157
+
158
+ const registrationReport = (
159
+ registration: ScheduleActivationRegistration
160
+ ): ScheduleRuntimeRegistrationReport => ({
161
+ cron: registration.cron,
162
+ sourceId: registration.sourceId,
163
+ ...(registration.timezone === undefined
164
+ ? {}
165
+ : { timezone: registration.timezone }),
166
+ trailId: registration.trail.id,
167
+ });
168
+
169
+ const timezoneMetadataWarning = (
170
+ registration: ScheduleActivationRegistration
171
+ ): ScheduleRuntimeWarning | undefined =>
172
+ registration.timezone === undefined || registration.timezone === 'UTC'
173
+ ? undefined
174
+ : {
175
+ code: 'schedule_timezone_metadata_only',
176
+ message:
177
+ 'The built-in Bun cron runtime records schedule timezone metadata but does not apply timezone-aware scheduling.',
178
+ sourceId: registration.sourceId,
179
+ timezone: registration.timezone,
180
+ trailId: registration.trail.id,
181
+ };
182
+
183
+ const collectStartWarnings = (
184
+ registrations: readonly ScheduleActivationRegistration[]
185
+ ): ScheduleRuntimeWarning[] =>
186
+ registrations.flatMap((registration) => {
187
+ const warning = timezoneMetadataWarning(registration);
188
+ return warning === undefined ? [] : [warning];
189
+ });
190
+
191
+ const logStartWarnings = (
192
+ warnings: readonly ScheduleRuntimeWarning[],
193
+ logger: ScheduleRuntimeLogger | undefined
194
+ ): void => {
195
+ for (const warning of warnings) {
196
+ logger?.warn(warning.message, {
197
+ code: warning.code,
198
+ sourceId: warning.sourceId,
199
+ timezone: warning.timezone,
200
+ trailId: warning.trailId,
201
+ });
202
+ }
203
+ };
204
+
205
+ const sourceInput = (activation: ActivationEntry): unknown =>
206
+ Object.hasOwn(activation.source, 'input')
207
+ ? activation.source.input
208
+ : EMPTY_INPUT;
209
+
210
+ const collectScheduleActivations = (
211
+ graph: Topo
212
+ ): ScheduleActivationRegistration[] => {
213
+ const registrations = new Map<string, ScheduleActivationRegistration>();
214
+ for (const trail of graph.list()) {
215
+ for (const activation of trail.activationSources) {
216
+ if (activation.source.kind !== 'schedule') {
217
+ continue;
218
+ }
219
+ const key = `${trail.id}\0${activationSourceKey(activation.source)}`;
220
+ const previous = registrations.get(key);
221
+ if (
222
+ previous !== undefined &&
223
+ (previous.activation.where !== undefined ||
224
+ activation.where === undefined)
225
+ ) {
226
+ continue;
227
+ }
228
+ registrations.set(key, {
229
+ activation,
230
+ cron:
231
+ typeof activation.source.cron === 'string'
232
+ ? activation.source.cron
233
+ : '',
234
+ input: sourceInput(activation),
235
+ sourceId: activation.source.id,
236
+ ...(activation.source.timezone === undefined
237
+ ? {}
238
+ : { timezone: activation.source.timezone }),
239
+ trail,
240
+ });
241
+ }
242
+ }
243
+
244
+ return [...registrations.values()];
245
+ };
246
+
247
+ const scheduleActivationProvenance = (
248
+ registration: ScheduleActivationRegistration
249
+ ): ActivationProvenance => {
250
+ const fireId = Bun.randomUUIDv7();
251
+ return {
252
+ fireId,
253
+ rootFireId: fireId,
254
+ source: {
255
+ cron: registration.cron,
256
+ id: registration.sourceId,
257
+ kind: 'schedule',
258
+ ...(registration.activation.source.meta === undefined
259
+ ? {}
260
+ : { meta: registration.activation.source.meta }),
261
+ ...(registration.timezone === undefined
262
+ ? {}
263
+ : { timezone: registration.timezone }),
264
+ },
265
+ };
266
+ };
267
+
268
+ const scheduleActivationTraceAttrs = (
269
+ registration: ScheduleActivationRegistration,
270
+ activation: ActivationProvenance
271
+ ): Readonly<Record<string, unknown>> => ({
272
+ ...buildActivationProvenanceTraceAttrs(activation),
273
+ 'trails.activation.target_trail.id': registration.trail.id,
274
+ });
275
+
276
+ const recordScheduleActivationTrace = async (
277
+ registration: ScheduleActivationRegistration,
278
+ activation: ActivationProvenance,
279
+ graph: Topo
280
+ ): Promise<TraceContext | undefined> => {
281
+ const record = await writeActivationTraceRecord(
282
+ 'activation.scheduled',
283
+ scheduleActivationTraceAttrs(registration, activation),
284
+ 'ok',
285
+ undefined,
286
+ undefined,
287
+ graph.observe?.trace ?? getTraceSink()
288
+ );
289
+ return record === undefined ? undefined : traceContextFromRecord(record);
290
+ };
291
+
292
+ const emitRunRecord = async (
293
+ options: ScheduleRuntimeOptions,
294
+ record: ScheduleRuntimeRunRecord
295
+ ): Promise<void> => {
296
+ try {
297
+ await options.onRun?.(record);
298
+ } catch (error) {
299
+ options.logger?.warn('Schedule runtime onRun callback failed', {
300
+ error: errorFromUnknown(error).message,
301
+ sourceId: record.sourceId,
302
+ trailId: record.trailId,
303
+ });
304
+ }
305
+ };
306
+
307
+ const runContextOption = (
308
+ options: ScheduleRuntimeOptions,
309
+ activation: ActivationProvenance | undefined,
310
+ traceContext?: TraceContext | undefined
311
+ ): Pick<ExecuteTrailOptions, 'ctx'> | Record<string, never> => {
312
+ if (activation === undefined && traceContext === undefined) {
313
+ return options.ctx === undefined ? {} : { ctx: options.ctx };
314
+ }
315
+ const withActivation =
316
+ activation === undefined
317
+ ? (options.ctx ?? {})
318
+ : withActivationProvenance(options.ctx ?? {}, activation);
319
+ const ctx =
320
+ traceContext === undefined
321
+ ? withActivation
322
+ : {
323
+ ...withActivation,
324
+ extensions: {
325
+ ...withActivation.extensions,
326
+ [TRACE_CONTEXT_KEY]: traceContext,
327
+ },
328
+ };
329
+ return { ctx };
330
+ };
331
+
332
+ const runOptions = (
333
+ options: ScheduleRuntimeOptions,
334
+ activation?: ActivationProvenance | undefined,
335
+ traceContext?: TraceContext | undefined
336
+ ): Pick<
337
+ ExecuteTrailOptions,
338
+ | 'abortSignal'
339
+ | 'configValues'
340
+ | 'createContext'
341
+ | 'ctx'
342
+ | 'layers'
343
+ | 'resources'
344
+ > => ({
345
+ ...(options.abortSignal === undefined
346
+ ? {}
347
+ : { abortSignal: options.abortSignal }),
348
+ ...(options.configValues === undefined
349
+ ? {}
350
+ : { configValues: options.configValues }),
351
+ ...(options.createContext === undefined
352
+ ? {}
353
+ : { createContext: options.createContext }),
354
+ ...runContextOption(options, activation, traceContext),
355
+ ...(options.layers === undefined ? {} : { layers: options.layers }),
356
+ ...(options.resources === undefined ? {} : { resources: options.resources }),
357
+ });
358
+
359
+ const shouldRunActivation = async (
360
+ registration: ScheduleActivationRegistration,
361
+ options: ScheduleRuntimeOptions,
362
+ activation: ActivationProvenance
363
+ ): Promise<boolean> => {
364
+ const predicate = getActivationWherePredicate(registration.activation.where);
365
+ if (predicate === undefined) {
366
+ return true;
367
+ }
368
+
369
+ try {
370
+ const matched = await predicate(registration.input);
371
+ if (!matched) {
372
+ await emitRunRecord(options, {
373
+ activation,
374
+ skipReason: 'where_false',
375
+ sourceId: registration.sourceId,
376
+ status: 'skipped',
377
+ trailId: registration.trail.id,
378
+ });
379
+ }
380
+ return matched;
381
+ } catch (error) {
382
+ const cause = errorFromUnknown(error);
383
+ options.logger?.warn('Schedule activation predicate failed', {
384
+ error: cause.message,
385
+ sourceId: registration.sourceId,
386
+ trailId: registration.trail.id,
387
+ });
388
+ await emitRunRecord(options, {
389
+ activation,
390
+ error: cause,
391
+ skipReason: 'where_error',
392
+ sourceId: registration.sourceId,
393
+ status: 'skipped',
394
+ trailId: registration.trail.id,
395
+ });
396
+ return false;
397
+ }
398
+ };
399
+
400
+ const executeScheduleActivation = async (
401
+ graph: Topo,
402
+ registration: ScheduleActivationRegistration,
403
+ options: ScheduleRuntimeOptions,
404
+ activation: ActivationProvenance
405
+ ): Promise<void> => {
406
+ const shouldRun = await shouldRunActivation(
407
+ registration,
408
+ options,
409
+ activation
410
+ );
411
+ if (!shouldRun) {
412
+ return;
413
+ }
414
+
415
+ const traceContext = await recordScheduleActivationTrace(
416
+ registration,
417
+ activation,
418
+ graph
419
+ );
420
+
421
+ try {
422
+ const result = await run(
423
+ graph,
424
+ registration.trail.id,
425
+ registration.input,
426
+ runOptions(options, activation, traceContext)
427
+ );
428
+ if (result.isErr()) {
429
+ options.logger?.warn('Scheduled trail failed', {
430
+ error: result.error.message,
431
+ sourceId: registration.sourceId,
432
+ trailId: registration.trail.id,
433
+ });
434
+ await emitRunRecord(options, {
435
+ activation,
436
+ result,
437
+ sourceId: registration.sourceId,
438
+ status: 'err',
439
+ trailId: registration.trail.id,
440
+ });
441
+ return;
442
+ }
443
+ await emitRunRecord(options, {
444
+ activation,
445
+ result,
446
+ sourceId: registration.sourceId,
447
+ status: 'ok',
448
+ trailId: registration.trail.id,
449
+ });
450
+ } catch (error) {
451
+ const cause = errorFromUnknown(error);
452
+ options.logger?.error('Scheduled trail rejected unexpectedly', {
453
+ error: cause.message,
454
+ sourceId: registration.sourceId,
455
+ trailId: registration.trail.id,
456
+ });
457
+ await emitRunRecord(options, {
458
+ activation,
459
+ error: cause,
460
+ sourceId: registration.sourceId,
461
+ status: 'err',
462
+ trailId: registration.trail.id,
463
+ });
464
+ }
465
+ };
466
+
467
+ const mergeContextOverrides = (
468
+ base: TrailContextInit,
469
+ options: ScheduleRuntimeOptions
470
+ ): TrailContextInit => {
471
+ const withCtx =
472
+ options.ctx === undefined
473
+ ? base
474
+ : {
475
+ ...base,
476
+ ...options.ctx,
477
+ extensions: { ...base.extensions, ...options.ctx.extensions },
478
+ };
479
+
480
+ return options.abortSignal === undefined
481
+ ? withCtx
482
+ : { ...withCtx, abortSignal: options.abortSignal };
483
+ };
484
+
485
+ const createDrainContext = async (
486
+ options: ScheduleRuntimeOptions
487
+ ): Promise<TrailContext> => {
488
+ const seed =
489
+ options.createContext === undefined
490
+ ? undefined
491
+ : await options.createContext();
492
+ const base = createTrailContext(seed);
493
+ return createTrailContext(mergeContextOverrides(base, options));
494
+ };
495
+
496
+ const stopCronJobs = (
497
+ registrations: readonly RunningScheduleRegistration[]
498
+ ): Error[] => {
499
+ const failures: Error[] = [];
500
+ for (const registration of registrations) {
501
+ try {
502
+ registration.job.stop();
503
+ } catch (error) {
504
+ failures.push(errorFromUnknown(error));
505
+ }
506
+ }
507
+ return failures;
508
+ };
509
+
510
+ const startFailure = (
511
+ registration: ScheduleActivationRegistration,
512
+ error: unknown
513
+ ): InternalError => {
514
+ const cause = errorFromUnknown(error);
515
+ return new InternalError(
516
+ `Schedule source "${registration.sourceId}" failed to register for trail "${registration.trail.id}": ${cause.message}`,
517
+ {
518
+ cause,
519
+ context: { ...registrationReport(registration) },
520
+ }
521
+ );
522
+ };
523
+
524
+ const stopFailure = (
525
+ failures: readonly Error[],
526
+ report: Omit<ScheduleRuntimeStopReport, 'resources'>,
527
+ drainError?: Error
528
+ ): InternalError => {
529
+ const primary = failures[0] ?? drainError;
530
+ return new InternalError('Schedule runtime stop failed', {
531
+ ...(primary === undefined ? {} : { cause: primary }),
532
+ context: {
533
+ failures: [
534
+ ...failures.map(errorRecord),
535
+ ...(drainError === undefined ? [] : [errorRecord(drainError)]),
536
+ ],
537
+ settledRuns: report.settledRuns,
538
+ stopped: report.stopped,
539
+ },
540
+ });
541
+ };
542
+
543
+ export const createScheduleRuntime = (
544
+ graph: Topo,
545
+ options: ScheduleRuntimeOptions = {}
546
+ ): ScheduleRuntime => {
547
+ const cron = options.cron ?? defaultCronFactory;
548
+ const inFlight = new Set<Promise<void>>();
549
+ let state: ScheduleRuntimeState = 'idle';
550
+ let running: RunningScheduleRegistration[] = [];
551
+
552
+ const runAndTrack = (
553
+ registration: ScheduleActivationRegistration
554
+ ): Promise<void> => {
555
+ const activation = scheduleActivationProvenance(registration);
556
+ if (state !== 'running') {
557
+ return emitRunRecord(options, {
558
+ activation,
559
+ skipReason: 'stopped',
560
+ sourceId: registration.sourceId,
561
+ status: 'skipped',
562
+ trailId: registration.trail.id,
563
+ });
564
+ }
565
+
566
+ const execution = executeScheduleActivation(
567
+ graph,
568
+ registration,
569
+ options,
570
+ activation
571
+ );
572
+ inFlight.add(execution);
573
+ void (async () => {
574
+ try {
575
+ await execution;
576
+ } finally {
577
+ inFlight.delete(execution);
578
+ }
579
+ })();
580
+ return execution;
581
+ };
582
+
583
+ const start = async (): Promise<
584
+ Result<ScheduleRuntimeStartReport, Error>
585
+ > => {
586
+ if (state !== 'idle') {
587
+ return Result.err(
588
+ new ConflictError(`Schedule runtime cannot start from state "${state}"`)
589
+ );
590
+ }
591
+
592
+ const validated = validateTopo(graph);
593
+ if (validated.isErr()) {
594
+ return Result.err(validated.error);
595
+ }
596
+
597
+ const registrations = collectScheduleActivations(graph);
598
+ const started: RunningScheduleRegistration[] = [];
599
+ state = 'running';
600
+
601
+ for (const registration of registrations) {
602
+ try {
603
+ const job = cron(registration.cron, () => runAndTrack(registration));
604
+ if (options.unref === true) {
605
+ job.unref?.();
606
+ }
607
+ started.push({ ...registration, job });
608
+ } catch (error) {
609
+ state = 'stopping';
610
+ stopCronJobs(started);
611
+ state = 'idle';
612
+ running = [];
613
+ return Result.err(startFailure(registration, error));
614
+ }
615
+ }
616
+
617
+ running = started;
618
+ const warnings = collectStartWarnings(running);
619
+ logStartWarnings(warnings, options.logger);
620
+ return Result.ok({
621
+ registered: running.map(registrationReport),
622
+ warnings,
623
+ });
624
+ };
625
+
626
+ const stop = async (): Promise<Result<ScheduleRuntimeStopReport, Error>> => {
627
+ if (state === 'idle' || state === 'stopped') {
628
+ return Result.ok({
629
+ resources: { disposed: [], evicted: [] },
630
+ settledRuns: 0,
631
+ stopped: [],
632
+ });
633
+ }
634
+ if (state === 'stopping') {
635
+ return Result.err(
636
+ new ConflictError('Schedule runtime stop is already in progress')
637
+ );
638
+ }
639
+
640
+ state = 'stopping';
641
+ const stopping = running;
642
+ const stopped = stopping.map(registrationReport);
643
+ const stopFailures = stopCronJobs(stopping);
644
+ const pending = [...inFlight];
645
+ const settled = await Promise.allSettled(pending);
646
+ const rejectedRuns = settled
647
+ .filter((entry) => entry.status === 'rejected')
648
+ .map((entry) => errorFromUnknown(entry.reason));
649
+
650
+ const partialReport = { settledRuns: pending.length, stopped };
651
+ const failures = [...stopFailures, ...rejectedRuns];
652
+ const drained = await (async () => {
653
+ try {
654
+ const drainCtx = await createDrainContext(options);
655
+ return await drainResources(
656
+ graph.listResources(),
657
+ drainCtx,
658
+ options.configValues
659
+ );
660
+ } catch (error) {
661
+ return Result.err(errorFromUnknown(error));
662
+ }
663
+ })();
664
+
665
+ running = [];
666
+ state = 'stopped';
667
+
668
+ if (drained.isErr() || failures.length > 0) {
669
+ return Result.err(
670
+ stopFailure(
671
+ failures,
672
+ partialReport,
673
+ drained.isErr() ? drained.error : undefined
674
+ )
675
+ );
676
+ }
677
+
678
+ return Result.ok({
679
+ resources: drained.value,
680
+ ...partialReport,
681
+ });
682
+ };
683
+
684
+ return Object.freeze({
685
+ start,
686
+ state: () => state,
687
+ stop,
688
+ });
689
+ };