@smthrs/triggers 0.0.0-stage → 1.0.0-rc.4

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 (226) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/LICENSE +21 -0
  3. package/README.md +109 -2
  4. package/dist/cjs/CatchUp.d.ts +25 -0
  5. package/dist/cjs/CatchUp.d.ts.map +1 -0
  6. package/dist/cjs/CatchUp.js +72 -0
  7. package/dist/cjs/CatchUp.js.map +7 -0
  8. package/dist/cjs/Channel.d.ts +108 -0
  9. package/dist/cjs/Channel.d.ts.map +1 -0
  10. package/dist/cjs/Channel.js +25 -0
  11. package/dist/cjs/Channel.js.map +7 -0
  12. package/dist/cjs/ClaimDecision.d.ts +183 -0
  13. package/dist/cjs/ClaimDecision.d.ts.map +1 -0
  14. package/dist/cjs/ClaimDecision.js +144 -0
  15. package/dist/cjs/ClaimDecision.js.map +7 -0
  16. package/dist/cjs/Cron.d.ts +91 -0
  17. package/dist/cjs/Cron.d.ts.map +1 -0
  18. package/dist/cjs/Cron.js +108 -0
  19. package/dist/cjs/Cron.js.map +7 -0
  20. package/dist/cjs/DispatchReader.d.ts +77 -0
  21. package/dist/cjs/DispatchReader.d.ts.map +1 -0
  22. package/dist/cjs/DispatchReader.js +121 -0
  23. package/dist/cjs/DispatchReader.js.map +7 -0
  24. package/dist/cjs/Overlap.d.ts +40 -0
  25. package/dist/cjs/Overlap.d.ts.map +1 -0
  26. package/dist/cjs/Overlap.js +37 -0
  27. package/dist/cjs/Overlap.js.map +7 -0
  28. package/dist/cjs/Schedule.d.ts +96 -0
  29. package/dist/cjs/Schedule.d.ts.map +1 -0
  30. package/dist/cjs/Schedule.js +61 -0
  31. package/dist/cjs/Schedule.js.map +7 -0
  32. package/dist/cjs/Scheduler.d.ts +248 -0
  33. package/dist/cjs/Scheduler.d.ts.map +1 -0
  34. package/dist/cjs/Scheduler.js +647 -0
  35. package/dist/cjs/Scheduler.js.map +7 -0
  36. package/dist/cjs/SqlTriggerStore.d.ts +29 -0
  37. package/dist/cjs/SqlTriggerStore.d.ts.map +1 -0
  38. package/dist/cjs/SqlTriggerStore.js +483 -0
  39. package/dist/cjs/SqlTriggerStore.js.map +7 -0
  40. package/dist/cjs/Trigger.d.ts +81 -0
  41. package/dist/cjs/Trigger.d.ts.map +1 -0
  42. package/dist/cjs/Trigger.js +54 -0
  43. package/dist/cjs/Trigger.js.map +7 -0
  44. package/dist/cjs/TriggerError.d.ts +58 -0
  45. package/dist/cjs/TriggerError.d.ts.map +1 -0
  46. package/dist/cjs/TriggerError.js +83 -0
  47. package/dist/cjs/TriggerError.js.map +7 -0
  48. package/dist/cjs/TriggerStore.d.ts +411 -0
  49. package/dist/cjs/TriggerStore.d.ts.map +1 -0
  50. package/dist/cjs/TriggerStore.js +127 -0
  51. package/dist/cjs/TriggerStore.js.map +7 -0
  52. package/dist/cjs/Webhook.d.ts +118 -0
  53. package/dist/cjs/Webhook.d.ts.map +1 -0
  54. package/dist/cjs/Webhook.js +153 -0
  55. package/dist/cjs/Webhook.js.map +7 -0
  56. package/dist/cjs/index.d.ts +30 -0
  57. package/dist/cjs/index.d.ts.map +1 -0
  58. package/dist/cjs/index.js +57 -0
  59. package/dist/cjs/index.js.map +7 -0
  60. package/dist/cjs/internal/ActiveRuns.d.ts +51 -0
  61. package/dist/cjs/internal/ActiveRuns.d.ts.map +1 -0
  62. package/dist/cjs/internal/ActiveRuns.js +56 -0
  63. package/dist/cjs/internal/ActiveRuns.js.map +7 -0
  64. package/dist/cjs/internal/DueOccurrences.d.ts +32 -0
  65. package/dist/cjs/internal/DueOccurrences.d.ts.map +1 -0
  66. package/dist/cjs/internal/DueOccurrences.js +80 -0
  67. package/dist/cjs/internal/DueOccurrences.js.map +7 -0
  68. package/dist/cjs/internal/WallClock.d.ts +59 -0
  69. package/dist/cjs/internal/WallClock.d.ts.map +1 -0
  70. package/dist/cjs/internal/WallClock.js +132 -0
  71. package/dist/cjs/internal/WallClock.js.map +7 -0
  72. package/dist/cjs/migrations/0001_triggers.d.ts +15 -0
  73. package/dist/cjs/migrations/0001_triggers.d.ts.map +1 -0
  74. package/dist/cjs/migrations/0001_triggers.js +64 -0
  75. package/dist/cjs/migrations/0001_triggers.js.map +7 -0
  76. package/dist/cjs/migrations/0002_reservation_lease.d.ts +15 -0
  77. package/dist/cjs/migrations/0002_reservation_lease.d.ts.map +1 -0
  78. package/dist/cjs/migrations/0002_reservation_lease.js +41 -0
  79. package/dist/cjs/migrations/0002_reservation_lease.js.map +7 -0
  80. package/dist/cjs/migrations/0003_heartbeat.d.ts +16 -0
  81. package/dist/cjs/migrations/0003_heartbeat.d.ts.map +1 -0
  82. package/dist/cjs/migrations/0003_heartbeat.js +44 -0
  83. package/dist/cjs/migrations/0003_heartbeat.js.map +7 -0
  84. package/dist/cjs/migrations/0004_fire_run_index.d.ts +17 -0
  85. package/dist/cjs/migrations/0004_fire_run_index.d.ts.map +1 -0
  86. package/dist/cjs/migrations/0004_fire_run_index.js +40 -0
  87. package/dist/cjs/migrations/0004_fire_run_index.js.map +7 -0
  88. package/dist/cjs/migrations/index.d.ts +33 -0
  89. package/dist/cjs/migrations/index.d.ts.map +1 -0
  90. package/dist/cjs/migrations/index.js +50 -0
  91. package/dist/cjs/migrations/index.js.map +7 -0
  92. package/dist/cjs/package.json +1 -0
  93. package/dist/cjs/test/TestTriggers.d.ts +12 -0
  94. package/dist/cjs/test/TestTriggers.d.ts.map +1 -0
  95. package/dist/cjs/test/TestTriggers.js +421 -0
  96. package/dist/cjs/test/TestTriggers.js.map +7 -0
  97. package/dist/esm/CatchUp.d.ts +25 -0
  98. package/dist/esm/CatchUp.d.ts.map +1 -0
  99. package/dist/esm/CatchUp.js +52 -0
  100. package/dist/esm/CatchUp.js.map +1 -0
  101. package/dist/esm/Channel.d.ts +108 -0
  102. package/dist/esm/Channel.d.ts.map +1 -0
  103. package/dist/esm/Channel.js +19 -0
  104. package/dist/esm/Channel.js.map +1 -0
  105. package/dist/esm/ClaimDecision.d.ts +183 -0
  106. package/dist/esm/ClaimDecision.d.ts.map +1 -0
  107. package/dist/esm/ClaimDecision.js +172 -0
  108. package/dist/esm/ClaimDecision.js.map +1 -0
  109. package/dist/esm/Cron.d.ts +91 -0
  110. package/dist/esm/Cron.d.ts.map +1 -0
  111. package/dist/esm/Cron.js +132 -0
  112. package/dist/esm/Cron.js.map +1 -0
  113. package/dist/esm/DispatchReader.d.ts +77 -0
  114. package/dist/esm/DispatchReader.d.ts.map +1 -0
  115. package/dist/esm/DispatchReader.js +152 -0
  116. package/dist/esm/DispatchReader.js.map +1 -0
  117. package/dist/esm/Overlap.d.ts +40 -0
  118. package/dist/esm/Overlap.d.ts.map +1 -0
  119. package/dist/esm/Overlap.js +31 -0
  120. package/dist/esm/Overlap.js.map +1 -0
  121. package/dist/esm/Schedule.d.ts +96 -0
  122. package/dist/esm/Schedule.d.ts.map +1 -0
  123. package/dist/esm/Schedule.js +73 -0
  124. package/dist/esm/Schedule.js.map +1 -0
  125. package/dist/esm/Scheduler.d.ts +248 -0
  126. package/dist/esm/Scheduler.d.ts.map +1 -0
  127. package/dist/esm/Scheduler.js +734 -0
  128. package/dist/esm/Scheduler.js.map +1 -0
  129. package/dist/esm/SqlTriggerStore.d.ts +29 -0
  130. package/dist/esm/SqlTriggerStore.d.ts.map +1 -0
  131. package/dist/esm/SqlTriggerStore.js +483 -0
  132. package/dist/esm/SqlTriggerStore.js.map +1 -0
  133. package/dist/esm/Trigger.d.ts +81 -0
  134. package/dist/esm/Trigger.d.ts.map +1 -0
  135. package/dist/esm/Trigger.js +56 -0
  136. package/dist/esm/Trigger.js.map +1 -0
  137. package/dist/esm/TriggerError.d.ts +58 -0
  138. package/dist/esm/TriggerError.d.ts.map +1 -0
  139. package/dist/esm/TriggerError.js +110 -0
  140. package/dist/esm/TriggerError.js.map +1 -0
  141. package/dist/esm/TriggerStore.d.ts +411 -0
  142. package/dist/esm/TriggerStore.d.ts.map +1 -0
  143. package/dist/esm/TriggerStore.js +209 -0
  144. package/dist/esm/TriggerStore.js.map +1 -0
  145. package/dist/esm/Webhook.d.ts +118 -0
  146. package/dist/esm/Webhook.d.ts.map +1 -0
  147. package/dist/esm/Webhook.js +186 -0
  148. package/dist/esm/Webhook.js.map +1 -0
  149. package/dist/esm/index.d.ts +30 -0
  150. package/dist/esm/index.d.ts.map +1 -0
  151. package/dist/esm/index.js +30 -0
  152. package/dist/esm/index.js.map +1 -0
  153. package/dist/esm/internal/ActiveRuns.d.ts +51 -0
  154. package/dist/esm/internal/ActiveRuns.d.ts.map +1 -0
  155. package/dist/esm/internal/ActiveRuns.js +34 -0
  156. package/dist/esm/internal/ActiveRuns.js.map +1 -0
  157. package/dist/esm/internal/DueOccurrences.d.ts +32 -0
  158. package/dist/esm/internal/DueOccurrences.d.ts.map +1 -0
  159. package/dist/esm/internal/DueOccurrences.js +55 -0
  160. package/dist/esm/internal/DueOccurrences.js.map +1 -0
  161. package/dist/esm/internal/WallClock.d.ts +59 -0
  162. package/dist/esm/internal/WallClock.d.ts.map +1 -0
  163. package/dist/esm/internal/WallClock.js +155 -0
  164. package/dist/esm/internal/WallClock.js.map +1 -0
  165. package/dist/esm/migrations/0001_triggers.d.ts +15 -0
  166. package/dist/esm/migrations/0001_triggers.d.ts.map +1 -0
  167. package/dist/esm/migrations/0001_triggers.js +42 -0
  168. package/dist/esm/migrations/0001_triggers.js.map +1 -0
  169. package/dist/esm/migrations/0002_reservation_lease.d.ts +15 -0
  170. package/dist/esm/migrations/0002_reservation_lease.d.ts.map +1 -0
  171. package/dist/esm/migrations/0002_reservation_lease.js +19 -0
  172. package/dist/esm/migrations/0002_reservation_lease.js.map +1 -0
  173. package/dist/esm/migrations/0003_heartbeat.d.ts +16 -0
  174. package/dist/esm/migrations/0003_heartbeat.d.ts.map +1 -0
  175. package/dist/esm/migrations/0003_heartbeat.js +23 -0
  176. package/dist/esm/migrations/0003_heartbeat.js.map +1 -0
  177. package/dist/esm/migrations/0004_fire_run_index.d.ts +17 -0
  178. package/dist/esm/migrations/0004_fire_run_index.d.ts.map +1 -0
  179. package/dist/esm/migrations/0004_fire_run_index.js +20 -0
  180. package/dist/esm/migrations/0004_fire_run_index.js.map +1 -0
  181. package/dist/esm/migrations/index.d.ts +33 -0
  182. package/dist/esm/migrations/index.d.ts.map +1 -0
  183. package/dist/esm/migrations/index.js +37 -0
  184. package/dist/esm/migrations/index.js.map +1 -0
  185. package/dist/esm/test/TestTriggers.d.ts +12 -0
  186. package/dist/esm/test/TestTriggers.d.ts.map +1 -0
  187. package/dist/esm/test/TestTriggers.js +439 -0
  188. package/dist/esm/test/TestTriggers.js.map +1 -0
  189. package/docs/README.md +140 -0
  190. package/docs/api.md +1186 -0
  191. package/docs/concepts/channels.md +103 -0
  192. package/docs/concepts/claim-protocol.md +229 -0
  193. package/docs/concepts/policies.md +124 -0
  194. package/docs/concepts/schedules.md +134 -0
  195. package/docs/guides/choose-a-policy.md +113 -0
  196. package/docs/guides/declare-a-trigger.md +136 -0
  197. package/docs/guides/ingest-a-webhook.md +164 -0
  198. package/docs/guides/run-the-scheduler.md +173 -0
  199. package/docs/guides/testing.md +157 -0
  200. package/docs/installation.md +81 -0
  201. package/docs/quickstart.md +163 -0
  202. package/docs/troubleshooting.md +281 -0
  203. package/package.json +241 -3
  204. package/src/CatchUp.ts +73 -0
  205. package/src/Channel.ts +121 -0
  206. package/src/ClaimDecision.ts +303 -0
  207. package/src/Cron.ts +173 -0
  208. package/src/DispatchReader.ts +179 -0
  209. package/src/Overlap.ts +53 -0
  210. package/src/Schedule.ts +112 -0
  211. package/src/Scheduler.ts +1154 -0
  212. package/src/SqlTriggerStore.ts +638 -0
  213. package/src/Trigger.ts +84 -0
  214. package/src/TriggerError.ts +129 -0
  215. package/src/TriggerStore.ts +514 -0
  216. package/src/Webhook.ts +270 -0
  217. package/src/index.ts +30 -0
  218. package/src/internal/ActiveRuns.ts +83 -0
  219. package/src/internal/DueOccurrences.ts +101 -0
  220. package/src/internal/WallClock.ts +172 -0
  221. package/src/migrations/0001_triggers.ts +43 -0
  222. package/src/migrations/0002_reservation_lease.ts +20 -0
  223. package/src/migrations/0003_heartbeat.ts +24 -0
  224. package/src/migrations/0004_fire_run_index.ts +21 -0
  225. package/src/migrations/index.ts +40 -0
  226. package/src/test/TestTriggers.ts +563 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Which occurrences one scheduler tick owes a trigger.
3
+ *
4
+ * @since 1.0.0-rc.0
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as CatchUp from "../CatchUp.js";
8
+ import * as Cron from "../Cron.js";
9
+ // A bound the declaration cannot honour is a statement about how much history
10
+ // to replay, not a reason to stop scheduling: the backlog beyond the bound is
11
+ // abandoned, loudly, and the current occurrence still fires.
12
+ const withinBound = (triggerId, owed) => Effect.catch(owed, (error) => error.code === "catch_up_bound_exceeded"
13
+ ? Effect.as(Effect.annotateLogs(Effect.logWarning("A trigger abandoned catch-up work beyond its bound", error), { triggerId }), [])
14
+ : Effect.fail(error));
15
+ /**
16
+ * Computes what a trigger owes at `now`, given the watermark this process has
17
+ * already dispatched through, or `undefined` on first sight of the trigger.
18
+ *
19
+ * The answer depends on nothing but its arguments. The watermark is the
20
+ * caller's to keep: it only moves forward, and only past occurrences the
21
+ * caller finished dispatching.
22
+ *
23
+ * @private
24
+ * @since 1.0.0-rc.0
25
+ */
26
+ export const compute = (trigger, now, observed) => Effect.gen(function* () {
27
+ const cron = yield* Cron.parse(trigger.cron, trigger.timezone);
28
+ const current = (yield* Cron.previousAtOrBefore(cron, new Date(now))).getTime();
29
+ if (observed === undefined) {
30
+ // First sight of this trigger in this process. A trigger that has never
31
+ // fired starts from here rather than from whatever occurrence last
32
+ // passed: registering a weekly trigger on a Sunday evening owes nothing
33
+ // for the Monday six days gone, which is what `catchUp` says.
34
+ if (trigger.lastFiredAt === undefined)
35
+ return { occurrences: [], watermark: current };
36
+ if (current <= trigger.lastFiredAt)
37
+ return { occurrences: [], watermark: current };
38
+ const owed = yield* withinBound(trigger.id, CatchUp.occurrences(trigger.catchUp, trigger.maxCatchUp, new Date(trigger.lastFiredAt), new Date(now), cron));
39
+ // The current boundary is owed whatever the policy says about the
40
+ // backlog, exactly as on every later poll. Under `none`, or once the
41
+ // bound abandoned the backlog, a restart used to skip it.
42
+ const occurrences = owed.map((occurrence) => occurrence.getTime());
43
+ if (occurrences.at(-1) !== current)
44
+ occurrences.push(current);
45
+ return { occurrences, watermark: current };
46
+ }
47
+ if (current <= observed)
48
+ return { occurrences: [], watermark: observed };
49
+ const backlog = yield* withinBound(trigger.id, CatchUp.occurrences(trigger.catchUp, trigger.maxCatchUp, new Date(observed), new Date(current - 1), cron));
50
+ return {
51
+ occurrences: [...backlog.map((occurrence) => occurrence.getTime()), current],
52
+ watermark: current
53
+ };
54
+ });
55
+ //# sourceMappingURL=DueOccurrences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DueOccurrences.js","sourceRoot":"","sources":["../../../src/internal/DueOccurrences.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which occurrences one scheduler tick owes a trigger.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport * as CatchUp from \"../CatchUp.ts\"\nimport * as Cron from \"../Cron.ts\"\nimport type { TriggerError } from \"../TriggerError.ts\"\nimport type { Registered } from \"../TriggerStore.ts\"\n\n/**\n * The occurrences to claim, oldest first, and the watermark that stands once\n * every one of them has been dispatched.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface Due {\n readonly occurrences: ReadonlyArray<number>\n readonly watermark: number\n}\n\n// A bound the declaration cannot honour is a statement about how much history\n// to replay, not a reason to stop scheduling: the backlog beyond the bound is\n// abandoned, loudly, and the current occurrence still fires.\nconst withinBound = (\n triggerId: string,\n owed: Effect.Effect<ReadonlyArray<Date>, TriggerError>\n): Effect.Effect<ReadonlyArray<Date>, TriggerError> =>\n Effect.catch(owed, (error) =>\n error.code === \"catch_up_bound_exceeded\"\n ? Effect.as(\n Effect.annotateLogs(\n Effect.logWarning(\"A trigger abandoned catch-up work beyond its bound\", error),\n { triggerId }\n ),\n [] as ReadonlyArray<Date>\n )\n : Effect.fail(error))\n\n/**\n * Computes what a trigger owes at `now`, given the watermark this process has\n * already dispatched through, or `undefined` on first sight of the trigger.\n *\n * The answer depends on nothing but its arguments. The watermark is the\n * caller's to keep: it only moves forward, and only past occurrences the\n * caller finished dispatching.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const compute = (\n trigger: Registered,\n now: number,\n observed: number | undefined\n): Effect.Effect<Due, TriggerError> =>\n Effect.gen(function*() {\n const cron = yield* Cron.parse(trigger.cron, trigger.timezone)\n const current = (yield* Cron.previousAtOrBefore(cron, new Date(now))).getTime()\n if (observed === undefined) {\n // First sight of this trigger in this process. A trigger that has never\n // fired starts from here rather than from whatever occurrence last\n // passed: registering a weekly trigger on a Sunday evening owes nothing\n // for the Monday six days gone, which is what `catchUp` says.\n if (trigger.lastFiredAt === undefined) return { occurrences: [], watermark: current }\n if (current <= trigger.lastFiredAt) return { occurrences: [], watermark: current }\n const owed = yield* withinBound(\n trigger.id,\n CatchUp.occurrences(\n trigger.catchUp,\n trigger.maxCatchUp,\n new Date(trigger.lastFiredAt),\n new Date(now),\n cron\n )\n )\n // The current boundary is owed whatever the policy says about the\n // backlog, exactly as on every later poll. Under `none`, or once the\n // bound abandoned the backlog, a restart used to skip it.\n const occurrences = owed.map((occurrence) => occurrence.getTime())\n if (occurrences.at(-1) !== current) occurrences.push(current)\n return { occurrences, watermark: current }\n }\n if (current <= observed) return { occurrences: [], watermark: observed }\n const backlog = yield* withinBound(\n trigger.id,\n CatchUp.occurrences(\n trigger.catchUp,\n trigger.maxCatchUp,\n new Date(observed),\n new Date(current - 1),\n cron\n )\n )\n return {\n occurrences: [...backlog.map((occurrence) => occurrence.getTime()), current],\n watermark: current\n }\n })\n"]}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Wall-clock cron occurrences in a time zone, daylight saving included.
3
+ *
4
+ * A cron expression names wall-clock times. Searching those times with a UTC
5
+ * cron is exact, so the zone only matters when a wall time is turned into an
6
+ * instant:
7
+ *
8
+ * - A wall time that exists once is that instant.
9
+ * - A wall time inside a spring-forward gap fires at the instant it would have
10
+ * had on the old offset: a daily 02:30 fires at 03:30 daylight time.
11
+ * - A wall time repeated by fall back fires once, at its first instant. An
12
+ * expression whose hour field is every hour fires in both passes instead, so
13
+ * an hourly or minutely schedule keeps running through the repeated hour.
14
+ *
15
+ * Instants are whole seconds in UTC, so millisecond zeroing never reads the
16
+ * host's zone.
17
+ *
18
+ * @since 1.0.0-rc.1
19
+ */
20
+ import * as EffectCron from "effect/Cron";
21
+ /** The zone's wall clock minus UTC at an instant, in milliseconds. */
22
+ type Offset = (instant: number) => number;
23
+ /**
24
+ * A cron compiled for wall-clock search.
25
+ *
26
+ * @private
27
+ * @since 1.0.0-rc.1
28
+ */
29
+ export interface WallCron {
30
+ readonly wall: EffectCron.Cron;
31
+ readonly offset: Offset;
32
+ readonly everyHour: boolean;
33
+ }
34
+ /**
35
+ * Compiles an Effect cron once: its fields as a UTC cron over wall-clock
36
+ * times, and its zone's offset.
37
+ *
38
+ * @private
39
+ * @since 1.0.0-rc.1
40
+ */
41
+ export declare const compile: (cron: EffectCron.Cron) => WallCron;
42
+ /**
43
+ * The first occurrence strictly after `from`. Throws when the wall-clock search
44
+ * is exhausted.
45
+ *
46
+ * @private
47
+ * @since 1.0.0-rc.1
48
+ */
49
+ export declare const next: (cron: WallCron, from: number) => number;
50
+ /**
51
+ * The latest occurrence at or before `at`, in whole seconds. Throws when the
52
+ * wall-clock search is exhausted.
53
+ *
54
+ * @private
55
+ * @since 1.0.0-rc.1
56
+ */
57
+ export declare const previousAtOrBefore: (cron: WallCron, at: number) => number;
58
+ export {};
59
+ //# sourceMappingURL=WallClock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WallClock.d.ts","sourceRoot":"","sources":["../../../src/internal/WallClock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AAOzC,sEAAsE;AACtE,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;AA0CzC;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAID;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,UAAU,CAAC,IAAI,KAAG,QAsB/C,CAAA;AAqBD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,QAAQ,EAAE,MAAM,MAAM,KAAG,MAUnD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,KAAG,MAW/D,CAAA"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Wall-clock cron occurrences in a time zone, daylight saving included.
3
+ *
4
+ * A cron expression names wall-clock times. Searching those times with a UTC
5
+ * cron is exact, so the zone only matters when a wall time is turned into an
6
+ * instant:
7
+ *
8
+ * - A wall time that exists once is that instant.
9
+ * - A wall time inside a spring-forward gap fires at the instant it would have
10
+ * had on the old offset: a daily 02:30 fires at 03:30 daylight time.
11
+ * - A wall time repeated by fall back fires once, at its first instant. An
12
+ * expression whose hour field is every hour fires in both passes instead, so
13
+ * an hourly or minutely schedule keeps running through the repeated hour.
14
+ *
15
+ * Instants are whole seconds in UTC, so millisecond zeroing never reads the
16
+ * host's zone.
17
+ *
18
+ * @since 1.0.0-rc.1
19
+ */
20
+ import * as EffectCron from "effect/Cron";
21
+ import * as DateTime from "effect/DateTime";
22
+ import * as Option from "effect/Option";
23
+ const day = 86_400_000;
24
+ const second = 1_000;
25
+ const formatters = new Map();
26
+ const formatter = (timeZone) => {
27
+ let format = formatters.get(timeZone);
28
+ if (format === undefined) {
29
+ format = new Intl.DateTimeFormat("en-US", {
30
+ timeZone,
31
+ year: "numeric",
32
+ month: "numeric",
33
+ day: "numeric",
34
+ hour: "numeric",
35
+ minute: "numeric",
36
+ second: "numeric",
37
+ hourCycle: "h23"
38
+ });
39
+ formatters.set(timeZone, format);
40
+ }
41
+ return format;
42
+ };
43
+ const named = (timeZone) => (instant) => {
44
+ const whole = Math.floor(instant / second) * second;
45
+ const fields = { year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 0 };
46
+ for (const part of formatter(timeZone).formatToParts(whole)) {
47
+ if (part.type in fields)
48
+ fields[part.type] = Number(part.value);
49
+ }
50
+ const wall = Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second);
51
+ return wall - whole;
52
+ };
53
+ // Read on every call, so a host whose `TZ` changes is followed.
54
+ const host = (instant) => -new Date(instant).getTimezoneOffset() * 60_000;
55
+ const offsetOf = (zone) => {
56
+ if (Option.isNone(zone))
57
+ return host;
58
+ const value = zone.value;
59
+ if (DateTime.isTimeZoneOffset(value))
60
+ return () => value.offset;
61
+ return named(value.id);
62
+ };
63
+ const compiled = new WeakMap();
64
+ /**
65
+ * Compiles an Effect cron once: its fields as a UTC cron over wall-clock
66
+ * times, and its zone's offset.
67
+ *
68
+ * @private
69
+ * @since 1.0.0-rc.1
70
+ */
71
+ export const compile = (cron) => {
72
+ let wallCron = compiled.get(cron);
73
+ if (wallCron === undefined) {
74
+ wallCron = {
75
+ wall: EffectCron.make({
76
+ seconds: cron.seconds,
77
+ minutes: cron.minutes,
78
+ hours: cron.hours,
79
+ days: cron.days,
80
+ months: cron.months,
81
+ weekdays: cron.weekdays,
82
+ // Whether day of month and weekday must both match. Effect keeps the
83
+ // flag off its public type, but `make` takes it and every cron has it.
84
+ and: cron.and,
85
+ tz: DateTime.zoneMakeNamedUnsafe("UTC")
86
+ }),
87
+ offset: offsetOf(cron.tz),
88
+ everyHour: cron.hours.size === 0 || cron.hours.size === 24
89
+ };
90
+ compiled.set(cron, wallCron);
91
+ }
92
+ return wallCron;
93
+ };
94
+ /** The offsets in force from a day before an instant to a day after it. */
95
+ const near = (cron, instant) => [
96
+ cron.offset(instant - day),
97
+ cron.offset(instant),
98
+ cron.offset(instant + day)
99
+ ];
100
+ /** The instants one matching wall time fires at, earliest first. */
101
+ const resolve = (cron, wall) => {
102
+ const before = cron.offset(wall - day);
103
+ const after = cron.offset(wall + day);
104
+ const candidates = before === after ? [before] : [before, after];
105
+ const valid = candidates.map((offset) => wall - offset).filter((instant) => cron.offset(instant) === wall - instant);
106
+ // No offset reads this wall time back: it is inside a gap.
107
+ if (valid.length === 0)
108
+ return [wall - before];
109
+ valid.sort((left, right) => left - right);
110
+ return cron.everyHour ? valid : valid.slice(0, 1);
111
+ };
112
+ /**
113
+ * The first occurrence strictly after `from`. Throws when the wall-clock search
114
+ * is exhausted.
115
+ *
116
+ * @private
117
+ * @since 1.0.0-rc.1
118
+ */
119
+ export const next = (cron, from) => {
120
+ // Every wall time that can fire after `from` lies after `from` plus the
121
+ // smallest nearby offset.
122
+ let wall = from + Math.min(...near(cron, from));
123
+ let best = Number.POSITIVE_INFINITY;
124
+ for (;;) {
125
+ wall = EffectCron.next(cron.wall, wall).getTime();
126
+ if (wall - Math.max(...near(cron, wall)) >= best)
127
+ return best;
128
+ for (const instant of resolve(cron, wall))
129
+ if (instant > from && instant < best)
130
+ best = instant;
131
+ }
132
+ };
133
+ /**
134
+ * The latest occurrence at or before `at`, in whole seconds. Throws when the
135
+ * wall-clock search is exhausted.
136
+ *
137
+ * @private
138
+ * @since 1.0.0-rc.1
139
+ */
140
+ export const previousAtOrBefore = (cron, at) => {
141
+ const until = Math.floor(at / second) * second;
142
+ // Every wall time that can fire at or before `until` lies at or before
143
+ // `until` plus the largest nearby offset.
144
+ let wall = until + Math.max(...near(cron, until)) + second;
145
+ let best = Number.NEGATIVE_INFINITY;
146
+ for (;;) {
147
+ wall = EffectCron.prev(cron.wall, wall).getTime();
148
+ if (wall - Math.min(...near(cron, wall)) <= best)
149
+ return best;
150
+ for (const instant of resolve(cron, wall))
151
+ if (instant <= until && instant > best)
152
+ best = instant;
153
+ }
154
+ };
155
+ //# sourceMappingURL=WallClock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WallClock.js","sourceRoot":"","sources":["../../../src/internal/WallClock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,MAAM,GAAG,GAAG,UAAU,CAAA;AACtB,MAAM,MAAM,GAAG,KAAK,CAAA;AAKpB,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+B,CAAA;AAEzD,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAuB,EAAE;IAC1D,IAAI,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACrC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YACxC,QAAQ;YACR,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,KAAK;SACjB,CAAC,CAAA;QACF,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,KAAK,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,MAAM,CAAA;IACnD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IAC3E,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,IAA2B,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxF,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3G,OAAO,IAAI,GAAG,KAAK,CAAA;AACrB,CAAC,CAAA;AAED,gEAAgE;AAChE,MAAM,IAAI,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,iBAAiB,EAAE,GAAG,MAAM,CAAA;AAEjF,MAAM,QAAQ,GAAG,CAAC,IAAsC,EAAU,EAAE;IAClE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACxB,IAAI,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAA;IAC/D,OAAO,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AACxB,CAAC,CAAA;AAcD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA6B,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAqB,EAAY,EAAE;IACzD,IAAI,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,GAAG;YACT,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC;gBACpB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,qEAAqE;gBACrE,uEAAuE;gBACvE,GAAG,EAAG,IAAoD,CAAC,GAAG;gBAC9D,EAAE,EAAE,QAAQ,CAAC,mBAAmB,CAAC,KAAK,CAAC;aACxC,CAAC;YACF,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,EAAE;SAC3D,CAAA;QACD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,2EAA2E;AAC3E,MAAM,IAAI,GAAG,CAAC,IAAc,EAAE,OAAe,EAAyB,EAAE,CAAC;IACvE,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IACpB,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;CAC3B,CAAA;AAED,oEAAoE;AACpE,MAAM,OAAO,GAAG,CAAC,IAAc,EAAE,IAAY,EAAyB,EAAE;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;IACrC,MAAM,UAAU,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,CAAA;IACpH,2DAA2D;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAAA;IAC9C,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAA;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACnD,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAc,EAAE,IAAY,EAAU,EAAE;IAC3D,wEAAwE;IACxE,0BAA0B;IAC1B,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAC/C,IAAI,IAAI,GAAG,MAAM,CAAC,iBAAiB,CAAA;IACnC,SAAS,CAAC;QACR,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAA;QACjD,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QAC7D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,IAAI,OAAO,GAAG,IAAI,IAAI,OAAO,GAAG,IAAI;gBAAE,IAAI,GAAG,OAAO,CAAA;IACjG,CAAC;AACH,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAc,EAAE,EAAU,EAAU,EAAE;IACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,MAAM,CAAA;IAC9C,uEAAuE;IACvE,0CAA0C;IAC1C,IAAI,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,GAAG,MAAM,CAAA;IAC1D,IAAI,IAAI,GAAG,MAAM,CAAC,iBAAiB,CAAA;IACnC,SAAS,CAAC;QACR,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAA;QACjD,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QAC7D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,GAAG,IAAI;gBAAE,IAAI,GAAG,OAAO,CAAA;IACnG,CAAC;AACH,CAAC,CAAA","sourcesContent":["/**\n * Wall-clock cron occurrences in a time zone, daylight saving included.\n *\n * A cron expression names wall-clock times. Searching those times with a UTC\n * cron is exact, so the zone only matters when a wall time is turned into an\n * instant:\n *\n * - A wall time that exists once is that instant.\n * - A wall time inside a spring-forward gap fires at the instant it would have\n * had on the old offset: a daily 02:30 fires at 03:30 daylight time.\n * - A wall time repeated by fall back fires once, at its first instant. An\n * expression whose hour field is every hour fires in both passes instead, so\n * an hourly or minutely schedule keeps running through the repeated hour.\n *\n * Instants are whole seconds in UTC, so millisecond zeroing never reads the\n * host's zone.\n *\n * @since 1.0.0-rc.1\n */\n\nimport * as EffectCron from \"effect/Cron\"\nimport * as DateTime from \"effect/DateTime\"\nimport * as Option from \"effect/Option\"\n\nconst day = 86_400_000\nconst second = 1_000\n\n/** The zone's wall clock minus UTC at an instant, in milliseconds. */\ntype Offset = (instant: number) => number\n\nconst formatters = new Map<string, Intl.DateTimeFormat>()\n\nconst formatter = (timeZone: string): Intl.DateTimeFormat => {\n let format = formatters.get(timeZone)\n if (format === undefined) {\n format = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n year: \"numeric\",\n month: \"numeric\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"numeric\",\n second: \"numeric\",\n hourCycle: \"h23\"\n })\n formatters.set(timeZone, format)\n }\n return format\n}\n\nconst named = (timeZone: string): Offset => (instant) => {\n const whole = Math.floor(instant / second) * second\n const fields = { year: 0, month: 1, day: 1, hour: 0, minute: 0, second: 0 }\n for (const part of formatter(timeZone).formatToParts(whole)) {\n if (part.type in fields) fields[part.type as keyof typeof fields] = Number(part.value)\n }\n const wall = Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour, fields.minute, fields.second)\n return wall - whole\n}\n\n// Read on every call, so a host whose `TZ` changes is followed.\nconst host: Offset = (instant) => -new Date(instant).getTimezoneOffset() * 60_000\n\nconst offsetOf = (zone: Option.Option<DateTime.TimeZone>): Offset => {\n if (Option.isNone(zone)) return host\n const value = zone.value\n if (DateTime.isTimeZoneOffset(value)) return () => value.offset\n return named(value.id)\n}\n\n/**\n * A cron compiled for wall-clock search.\n *\n * @private\n * @since 1.0.0-rc.1\n */\nexport interface WallCron {\n readonly wall: EffectCron.Cron\n readonly offset: Offset\n readonly everyHour: boolean\n}\n\nconst compiled = new WeakMap<EffectCron.Cron, WallCron>()\n\n/**\n * Compiles an Effect cron once: its fields as a UTC cron over wall-clock\n * times, and its zone's offset.\n *\n * @private\n * @since 1.0.0-rc.1\n */\nexport const compile = (cron: EffectCron.Cron): WallCron => {\n let wallCron = compiled.get(cron)\n if (wallCron === undefined) {\n wallCron = {\n wall: EffectCron.make({\n seconds: cron.seconds,\n minutes: cron.minutes,\n hours: cron.hours,\n days: cron.days,\n months: cron.months,\n weekdays: cron.weekdays,\n // Whether day of month and weekday must both match. Effect keeps the\n // flag off its public type, but `make` takes it and every cron has it.\n and: (cron as EffectCron.Cron & { readonly and: boolean }).and,\n tz: DateTime.zoneMakeNamedUnsafe(\"UTC\")\n }),\n offset: offsetOf(cron.tz),\n everyHour: cron.hours.size === 0 || cron.hours.size === 24\n }\n compiled.set(cron, wallCron)\n }\n return wallCron\n}\n\n/** The offsets in force from a day before an instant to a day after it. */\nconst near = (cron: WallCron, instant: number): ReadonlyArray<number> => [\n cron.offset(instant - day),\n cron.offset(instant),\n cron.offset(instant + day)\n]\n\n/** The instants one matching wall time fires at, earliest first. */\nconst resolve = (cron: WallCron, wall: number): ReadonlyArray<number> => {\n const before = cron.offset(wall - day)\n const after = cron.offset(wall + day)\n const candidates = before === after ? [before] : [before, after]\n const valid = candidates.map((offset) => wall - offset).filter((instant) => cron.offset(instant) === wall - instant)\n // No offset reads this wall time back: it is inside a gap.\n if (valid.length === 0) return [wall - before]\n valid.sort((left, right) => left - right)\n return cron.everyHour ? valid : valid.slice(0, 1)\n}\n\n/**\n * The first occurrence strictly after `from`. Throws when the wall-clock search\n * is exhausted.\n *\n * @private\n * @since 1.0.0-rc.1\n */\nexport const next = (cron: WallCron, from: number): number => {\n // Every wall time that can fire after `from` lies after `from` plus the\n // smallest nearby offset.\n let wall = from + Math.min(...near(cron, from))\n let best = Number.POSITIVE_INFINITY\n for (;;) {\n wall = EffectCron.next(cron.wall, wall).getTime()\n if (wall - Math.max(...near(cron, wall)) >= best) return best\n for (const instant of resolve(cron, wall)) if (instant > from && instant < best) best = instant\n }\n}\n\n/**\n * The latest occurrence at or before `at`, in whole seconds. Throws when the\n * wall-clock search is exhausted.\n *\n * @private\n * @since 1.0.0-rc.1\n */\nexport const previousAtOrBefore = (cron: WallCron, at: number): number => {\n const until = Math.floor(at / second) * second\n // Every wall time that can fire at or before `until` lies at or before\n // `until` plus the largest nearby offset.\n let wall = until + Math.max(...near(cron, until)) + second\n let best = Number.NEGATIVE_INFINITY\n for (;;) {\n wall = EffectCron.prev(cron.wall, wall).getTime()\n if (wall - Math.min(...near(cron, wall)) <= best) return best\n for (const instant of resolve(cron, wall)) if (instant <= until && instant > best) best = instant\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /** @since 0.1.0 */
2
+ import * as Effect from "effect/Effect";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+ /**
5
+ * Creates the `flows_triggers` and `flows_trigger_fires` tables.
6
+ *
7
+ * A named export rather than `export default`: the CommonJS build reads a
8
+ * default import of a sibling module as the whole exports object, so the
9
+ * migrator received `{ default }` instead of this Effect.
10
+ *
11
+ * @category migrations
12
+ * @since 0.1.0
13
+ */
14
+ export declare const triggers: Effect.Effect<void, unknown, SqlClient.SqlClient>;
15
+ //# sourceMappingURL=0001_triggers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0001_triggers.d.ts","sourceRoot":"","sources":["../../../src/migrations/0001_triggers.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAGnB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CA0BrE,CAAA"}
@@ -0,0 +1,42 @@
1
+ /** @since 0.1.0 */
2
+ import * as Dialect from "@smthrs/database/Dialect";
3
+ import * as Effect from "effect/Effect";
4
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
5
+ /**
6
+ * Creates the `flows_triggers` and `flows_trigger_fires` tables.
7
+ *
8
+ * A named export rather than `export default`: the CommonJS build reads a
9
+ * default import of a sibling module as the whole exports object, so the
10
+ * migrator received `{ default }` instead of this Effect.
11
+ *
12
+ * @category migrations
13
+ * @since 0.1.0
14
+ */
15
+ export const triggers = Effect.gen(function* () {
16
+ const sql = yield* SqlClient.SqlClient;
17
+ yield* sql `CREATE TABLE IF NOT EXISTS flows_triggers (
18
+ trigger_id TEXT PRIMARY KEY,
19
+ flow_id TEXT NOT NULL,
20
+ input_json TEXT NOT NULL,
21
+ cron TEXT NOT NULL,
22
+ timezone TEXT,
23
+ overlap TEXT NOT NULL CHECK (overlap IN ('skip', 'buffer-one', 'supersede')),
24
+ catch_up TEXT NOT NULL CHECK (catch_up IN ('none', 'one', 'all')),
25
+ max_catch_up ${Dialect.integer(sql)} NOT NULL CHECK (max_catch_up >= 0),
26
+ enabled ${Dialect.integer(sql)} NOT NULL CHECK (enabled IN (0, 1)),
27
+ revision ${Dialect.integer(sql)} NOT NULL,
28
+ last_fired_at_ms ${Dialect.integer(sql)},
29
+ pending_at_ms ${Dialect.integer(sql)},
30
+ active_run_id TEXT
31
+ )`;
32
+ yield* sql `CREATE TABLE IF NOT EXISTS flows_trigger_fires (
33
+ trigger_id TEXT NOT NULL,
34
+ occurrence_at_ms ${Dialect.integer(sql)} NOT NULL,
35
+ outcome TEXT,
36
+ run_id TEXT,
37
+ error TEXT,
38
+ PRIMARY KEY (trigger_id, occurrence_at_ms),
39
+ FOREIGN KEY (trigger_id) REFERENCES flows_triggers (trigger_id)
40
+ )`;
41
+ });
42
+ //# sourceMappingURL=0001_triggers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0001_triggers.js","sourceRoot":"","sources":["../../../src/migrations/0001_triggers.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAEnB,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAsD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC7F,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IACtC,KAAK,CAAC,CAAC,GAAG,CAAA;;;;;;;;mBAQO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;cACzB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;eACnB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;uBACZ,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;oBACvB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;;IAEpC,CAAA;IACF,KAAK,CAAC,CAAC,GAAG,CAAA;;uBAEW,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;;;;;;IAMvC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/** @since 0.1.0 */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Effect from \"effect/Effect\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\n\n/**\n * Creates the `flows_triggers` and `flows_trigger_fires` tables.\n *\n * A named export rather than `export default`: the CommonJS build reads a\n * default import of a sibling module as the whole exports object, so the\n * migrator received `{ default }` instead of this Effect.\n *\n * @category migrations\n * @since 0.1.0\n */\nexport const triggers: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {\n const sql = yield* SqlClient.SqlClient\n yield* sql`CREATE TABLE IF NOT EXISTS flows_triggers (\n trigger_id TEXT PRIMARY KEY,\n flow_id TEXT NOT NULL,\n input_json TEXT NOT NULL,\n cron TEXT NOT NULL,\n timezone TEXT,\n overlap TEXT NOT NULL CHECK (overlap IN ('skip', 'buffer-one', 'supersede')),\n catch_up TEXT NOT NULL CHECK (catch_up IN ('none', 'one', 'all')),\n max_catch_up ${Dialect.integer(sql)} NOT NULL CHECK (max_catch_up >= 0),\n enabled ${Dialect.integer(sql)} NOT NULL CHECK (enabled IN (0, 1)),\n revision ${Dialect.integer(sql)} NOT NULL,\n last_fired_at_ms ${Dialect.integer(sql)},\n pending_at_ms ${Dialect.integer(sql)},\n active_run_id TEXT\n )`\n yield* sql`CREATE TABLE IF NOT EXISTS flows_trigger_fires (\n trigger_id TEXT NOT NULL,\n occurrence_at_ms ${Dialect.integer(sql)} NOT NULL,\n outcome TEXT,\n run_id TEXT,\n error TEXT,\n PRIMARY KEY (trigger_id, occurrence_at_ms),\n FOREIGN KEY (trigger_id) REFERENCES flows_triggers (trigger_id)\n )`\n})\n"]}
@@ -0,0 +1,15 @@
1
+ /** @since 0.1.0 */
2
+ import * as Effect from "effect/Effect";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+ /**
5
+ * Adds `active_claimed_at_ms`, the lease start of a launch reservation.
6
+ *
7
+ * A named export rather than `export default`: the CommonJS build reads a
8
+ * default import of a sibling module as the whole exports object, so the
9
+ * migrator received `{ default }` instead of this Effect.
10
+ *
11
+ * @category migrations
12
+ * @since 0.1.0
13
+ */
14
+ export declare const reservationLease: Effect.Effect<void, unknown, SqlClient.SqlClient>;
15
+ //# sourceMappingURL=0002_reservation_lease.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0002_reservation_lease.d.ts","sourceRoot":"","sources":["../../../src/migrations/0002_reservation_lease.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAGnB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAG7E,CAAA"}
@@ -0,0 +1,19 @@
1
+ /** @since 0.1.0 */
2
+ import * as Dialect from "@smthrs/database/Dialect";
3
+ import * as Effect from "effect/Effect";
4
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
5
+ /**
6
+ * Adds `active_claimed_at_ms`, the lease start of a launch reservation.
7
+ *
8
+ * A named export rather than `export default`: the CommonJS build reads a
9
+ * default import of a sibling module as the whole exports object, so the
10
+ * migrator received `{ default }` instead of this Effect.
11
+ *
12
+ * @category migrations
13
+ * @since 0.1.0
14
+ */
15
+ export const reservationLease = Effect.gen(function* () {
16
+ const sql = yield* SqlClient.SqlClient;
17
+ yield* sql `ALTER TABLE flows_triggers ADD COLUMN active_claimed_at_ms ${Dialect.integer(sql)}`;
18
+ });
19
+ //# sourceMappingURL=0002_reservation_lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0002_reservation_lease.js","sourceRoot":"","sources":["../../../src/migrations/0002_reservation_lease.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAEnB,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAsD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrG,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IACtC,KAAK,CAAC,CAAC,GAAG,CAAA,8DAA8D,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAA;AAChG,CAAC,CAAC,CAAA","sourcesContent":["/** @since 0.1.0 */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Effect from \"effect/Effect\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\n\n/**\n * Adds `active_claimed_at_ms`, the lease start of a launch reservation.\n *\n * A named export rather than `export default`: the CommonJS build reads a\n * default import of a sibling module as the whole exports object, so the\n * migrator received `{ default }` instead of this Effect.\n *\n * @category migrations\n * @since 0.1.0\n */\nexport const reservationLease: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {\n const sql = yield* SqlClient.SqlClient\n yield* sql`ALTER TABLE flows_triggers ADD COLUMN active_claimed_at_ms ${Dialect.integer(sql)}`\n})\n"]}
@@ -0,0 +1,16 @@
1
+ /** @since 1.0.0-rc.0 */
2
+ import * as Effect from "effect/Effect";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+ /**
5
+ * Creates `flows_scheduler_heartbeat`, one row per scheduler host holding the
6
+ * last poll it recorded, so a listing can say whether anything is listening.
7
+ *
8
+ * A named export rather than `export default`: the CommonJS build reads a
9
+ * default import of a sibling module as the whole exports object, so the
10
+ * migrator received `{ default }` instead of this Effect.
11
+ *
12
+ * @category migrations
13
+ * @since 1.0.0-rc.0
14
+ */
15
+ export declare const schedulerHeartbeat: Effect.Effect<void, unknown, SqlClient.SqlClient>;
16
+ //# sourceMappingURL=0003_heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0003_heartbeat.d.ts","sourceRoot":"","sources":["../../../src/migrations/0003_heartbeat.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAGxB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAM/E,CAAA"}
@@ -0,0 +1,23 @@
1
+ /** @since 1.0.0-rc.0 */
2
+ import * as Dialect from "@smthrs/database/Dialect";
3
+ import * as Effect from "effect/Effect";
4
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
5
+ /**
6
+ * Creates `flows_scheduler_heartbeat`, one row per scheduler host holding the
7
+ * last poll it recorded, so a listing can say whether anything is listening.
8
+ *
9
+ * A named export rather than `export default`: the CommonJS build reads a
10
+ * default import of a sibling module as the whole exports object, so the
11
+ * migrator received `{ default }` instead of this Effect.
12
+ *
13
+ * @category migrations
14
+ * @since 1.0.0-rc.0
15
+ */
16
+ export const schedulerHeartbeat = Effect.gen(function* () {
17
+ const sql = yield* SqlClient.SqlClient;
18
+ yield* sql `CREATE TABLE IF NOT EXISTS flows_scheduler_heartbeat (
19
+ host TEXT PRIMARY KEY,
20
+ ticked_at_ms ${Dialect.integer(sql)} NOT NULL
21
+ )`;
22
+ });
23
+ //# sourceMappingURL=0003_heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0003_heartbeat.js","sourceRoot":"","sources":["../../../src/migrations/0003_heartbeat.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAA;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACvG,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IACtC,KAAK,CAAC,CAAC,GAAG,CAAA;;mBAEO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;IACnC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/** @since 1.0.0-rc.0 */\n\nimport * as Dialect from \"@smthrs/database/Dialect\"\nimport * as Effect from \"effect/Effect\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\n\n/**\n * Creates `flows_scheduler_heartbeat`, one row per scheduler host holding the\n * last poll it recorded, so a listing can say whether anything is listening.\n *\n * A named export rather than `export default`: the CommonJS build reads a\n * default import of a sibling module as the whole exports object, so the\n * migrator received `{ default }` instead of this Effect.\n *\n * @category migrations\n * @since 1.0.0-rc.0\n */\nexport const schedulerHeartbeat: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {\n const sql = yield* SqlClient.SqlClient\n yield* sql`CREATE TABLE IF NOT EXISTS flows_scheduler_heartbeat (\n host TEXT PRIMARY KEY,\n ticked_at_ms ${Dialect.integer(sql)} NOT NULL\n )`\n})\n"]}
@@ -0,0 +1,17 @@
1
+ /** @since 1.0.0-rc.1 */
2
+ import * as Effect from "effect/Effect";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+ /**
5
+ * Indexes `flows_trigger_fires` by `run_id`, so a fires listing filtered to
6
+ * one run and the prune's active-run check read an index instead of the
7
+ * whole ledger.
8
+ *
9
+ * A named export rather than `export default`: the CommonJS build reads a
10
+ * default import of a sibling module as the whole exports object, so the
11
+ * migrator received `{ default }` instead of this Effect.
12
+ *
13
+ * @category migrations
14
+ * @since 1.0.0-rc.1
15
+ */
16
+ export declare const fireRunIndex: Effect.Effect<void, unknown, SqlClient.SqlClient>;
17
+ //# sourceMappingURL=0004_fire_run_index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0004_fire_run_index.d.ts","sourceRoot":"","sources":["../../../src/migrations/0004_fire_run_index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAGzE,CAAA"}
@@ -0,0 +1,20 @@
1
+ /** @since 1.0.0-rc.1 */
2
+ import * as Effect from "effect/Effect";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+ /**
5
+ * Indexes `flows_trigger_fires` by `run_id`, so a fires listing filtered to
6
+ * one run and the prune's active-run check read an index instead of the
7
+ * whole ledger.
8
+ *
9
+ * A named export rather than `export default`: the CommonJS build reads a
10
+ * default import of a sibling module as the whole exports object, so the
11
+ * migrator received `{ default }` instead of this Effect.
12
+ *
13
+ * @category migrations
14
+ * @since 1.0.0-rc.1
15
+ */
16
+ export const fireRunIndex = Effect.gen(function* () {
17
+ const sql = yield* SqlClient.SqlClient;
18
+ yield* sql `CREATE INDEX IF NOT EXISTS flows_trigger_fires_run_id ON flows_trigger_fires (run_id)`;
19
+ });
20
+ //# sourceMappingURL=0004_fire_run_index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0004_fire_run_index.js","sourceRoot":"","sources":["../../../src/migrations/0004_fire_run_index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,SAAS,MAAM,+BAA+B,CAAA;AAE1D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,YAAY,GAAsD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACjG,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAA;IACtC,KAAK,CAAC,CAAC,GAAG,CAAA,uFAAuF,CAAA;AACnG,CAAC,CAAC,CAAA","sourcesContent":["/** @since 1.0.0-rc.1 */\n\nimport * as Effect from \"effect/Effect\"\nimport * as SqlClient from \"effect/unstable/sql/SqlClient\"\n\n/**\n * Indexes `flows_trigger_fires` by `run_id`, so a fires listing filtered to\n * one run and the prune's active-run check read an index instead of the\n * whole ledger.\n *\n * A named export rather than `export default`: the CommonJS build reads a\n * default import of a sibling module as the whole exports object, so the\n * migrator received `{ default }` instead of this Effect.\n *\n * @category migrations\n * @since 1.0.0-rc.1\n */\nexport const fireRunIndex: Effect.Effect<void, unknown, SqlClient.SqlClient> = Effect.gen(function*() {\n const sql = yield* SqlClient.SqlClient\n yield* sql`CREATE INDEX IF NOT EXISTS flows_trigger_fires_run_id ON flows_trigger_fires (run_id)`\n})\n"]}
@@ -0,0 +1,33 @@
1
+ /** @since 0.1.0 */
2
+ import * as Layer from "effect/Layer";
3
+ import * as Migrator from "effect/unstable/sql/Migrator";
4
+ /**
5
+ * The migration record {@link run} applies, keyed by migration file name.
6
+ *
7
+ * Exported so a test and the release smoke can assert that every entry is the
8
+ * migration Effect itself in both the ESM and the CommonJS build.
9
+ *
10
+ * @category migrations
11
+ * @since 1.0.0-rc.0
12
+ */
13
+ export declare const migrations: {
14
+ "0001_triggers": import("effect/Effect").Effect<void, unknown, import("effect/unstable/sql/SqlClient").SqlClient>;
15
+ "0002_reservation_lease": import("effect/Effect").Effect<void, unknown, import("effect/unstable/sql/SqlClient").SqlClient>;
16
+ "0003_heartbeat": import("effect/Effect").Effect<void, unknown, import("effect/unstable/sql/SqlClient").SqlClient>;
17
+ "0004_fire_run_index": import("effect/Effect").Effect<void, unknown, import("effect/unstable/sql/SqlClient").SqlClient>;
18
+ };
19
+ /**
20
+ * Applies the triggers schema migrations.
21
+ *
22
+ * @category migrations
23
+ * @since 0.1.0
24
+ */
25
+ export declare const run: import("effect/Effect").Effect<readonly (readonly [id: number, name: string])[], import("effect/unstable/sql/SqlError").SqlError | Migrator.MigrationError, import("effect/unstable/sql/SqlClient").SqlClient>;
26
+ /**
27
+ * Runs {@link run} once as a layer.
28
+ *
29
+ * @category layers
30
+ * @since 0.1.0
31
+ */
32
+ export declare const layer: Layer.Layer<never, import("effect/unstable/sql/SqlError").SqlError | Migrator.MigrationError, import("effect/unstable/sql/SqlClient").SqlClient>;
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAEnB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,8BAA8B,CAAA;AAMxD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU;;;;;CAKtB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,GAAG,gNAAoG,CAAA;AAEpH;;;;;GAKG;AACH,eAAO,MAAM,KAAK,kJAA2B,CAAA"}
@@ -0,0 +1,37 @@
1
+ /** @since 0.1.0 */
2
+ import * as Layer from "effect/Layer";
3
+ import * as Migrator from "effect/unstable/sql/Migrator";
4
+ import { triggers } from "./0001_triggers.js";
5
+ import { reservationLease } from "./0002_reservation_lease.js";
6
+ import { schedulerHeartbeat } from "./0003_heartbeat.js";
7
+ import { fireRunIndex } from "./0004_fire_run_index.js";
8
+ /**
9
+ * The migration record {@link run} applies, keyed by migration file name.
10
+ *
11
+ * Exported so a test and the release smoke can assert that every entry is the
12
+ * migration Effect itself in both the ESM and the CommonJS build.
13
+ *
14
+ * @category migrations
15
+ * @since 1.0.0-rc.0
16
+ */
17
+ export const migrations = {
18
+ "0001_triggers": triggers,
19
+ "0002_reservation_lease": reservationLease,
20
+ "0003_heartbeat": schedulerHeartbeat,
21
+ "0004_fire_run_index": fireRunIndex
22
+ };
23
+ /**
24
+ * Applies the triggers schema migrations.
25
+ *
26
+ * @category migrations
27
+ * @since 0.1.0
28
+ */
29
+ export const run = Migrator.make({})({ loader: Migrator.fromRecord(migrations), table: "flows_trigger_migrations" });
30
+ /**
31
+ * Runs {@link run} once as a layer.
32
+ *
33
+ * @category layers
34
+ * @since 0.1.0
35
+ */
36
+ export const layer = Layer.effectDiscard(run);
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAEnB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,8BAA8B,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEvD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,eAAe,EAAE,QAAQ;IACzB,wBAAwB,EAAE,gBAAgB;IAC1C,gBAAgB,EAAE,kBAAkB;IACpC,qBAAqB,EAAE,YAAY;CACpC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAA;AAEpH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA","sourcesContent":["/** @since 0.1.0 */\n\nimport * as Layer from \"effect/Layer\"\nimport * as Migrator from \"effect/unstable/sql/Migrator\"\nimport { triggers } from \"./0001_triggers.ts\"\nimport { reservationLease } from \"./0002_reservation_lease.ts\"\nimport { schedulerHeartbeat } from \"./0003_heartbeat.ts\"\nimport { fireRunIndex } from \"./0004_fire_run_index.ts\"\n\n/**\n * The migration record {@link run} applies, keyed by migration file name.\n *\n * Exported so a test and the release smoke can assert that every entry is the\n * migration Effect itself in both the ESM and the CommonJS build.\n *\n * @category migrations\n * @since 1.0.0-rc.0\n */\nexport const migrations = {\n \"0001_triggers\": triggers,\n \"0002_reservation_lease\": reservationLease,\n \"0003_heartbeat\": schedulerHeartbeat,\n \"0004_fire_run_index\": fireRunIndex\n}\n\n/**\n * Applies the triggers schema migrations.\n *\n * @category migrations\n * @since 0.1.0\n */\nexport const run = Migrator.make({})({ loader: Migrator.fromRecord(migrations), table: \"flows_trigger_migrations\" })\n\n/**\n * Runs {@link run} once as a layer.\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = Layer.effectDiscard(run)\n"]}
@@ -0,0 +1,12 @@
1
+ /** @since 0.1.0 */
2
+ import * as Layer from "effect/Layer";
3
+ import { TriggerStore } from "../TriggerStore.ts";
4
+ /**
5
+ * Provides an in-memory {@link TriggerStore} for tests: real claim and
6
+ * overlap semantics, no database.
7
+ *
8
+ * @category layers
9
+ * @since 0.1.0
10
+ */
11
+ export declare const layer: Layer.Layer<TriggerStore>;
12
+ //# sourceMappingURL=TestTriggers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTriggers.d.ts","sourceRoot":"","sources":["../../../src/test/TestTriggers.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAInB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAUrC,OAAO,EAmBL,YAAY,EACb,MAAM,oBAAoB,CAAA;AAsL3B;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY,CAmVzC,CAAA"}