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

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,183 @@
1
+ /**
2
+ * The one claim decision both trigger stores apply.
3
+ *
4
+ * A claim is a pure function of the rows it reads: the trigger declaration,
5
+ * the fire row at the occurrence being claimed, and the fire row the active
6
+ * reservation holds. Each store gathers that snapshot in its own storage,
7
+ * calls {@link decide}, and applies the writes it answers. Writing the fences,
8
+ * the expired-reservation reclaim, and the supersede predecessor substitution
9
+ * once is what keeps `SqlTriggerStore` and the published in-memory store from
10
+ * drifting apart.
11
+ *
12
+ * @since 1.0.0-rc.0
13
+ */
14
+ import type { Overlap as Policy } from "./Trigger.ts";
15
+ import { TriggerError } from "./TriggerError.ts";
16
+ import { type Claim, type ClaimFire, type Outcome } from "./TriggerStore.ts";
17
+ /**
18
+ * A fire row as the decision reads it. `outcome` is `null` for a claimed but
19
+ * unsettled occurrence, matching the nullable column.
20
+ *
21
+ * @category models
22
+ * @since 1.0.0-rc.0
23
+ */
24
+ export interface Fire {
25
+ readonly outcome: Outcome | null;
26
+ readonly runId?: string | undefined;
27
+ }
28
+ /**
29
+ * Everything the decision reads, gathered by the store before it runs.
30
+ *
31
+ * `existingFire` is absent when this claim is what created the row.
32
+ * `activeFire` is the row at `reservationOccurrence(activeRunId)`, read after
33
+ * that insert, and is needed only while `activeRunId` is a reservation.
34
+ *
35
+ * @category models
36
+ * @since 1.0.0-rc.0
37
+ */
38
+ export interface Snapshot {
39
+ readonly revision: number;
40
+ readonly enabled: boolean;
41
+ readonly overlap: Policy;
42
+ readonly activeRunId?: string | undefined;
43
+ readonly activeClaimedAt?: number | undefined;
44
+ readonly pending?: number | undefined;
45
+ readonly existingFire?: Fire | undefined;
46
+ readonly activeFire?: Fire | undefined;
47
+ }
48
+ /**
49
+ * One durable edit a claim owes, in the order the store must apply them.
50
+ *
51
+ * `SetOutcome` with `whileOpen` may only settle a fire that is still `null` or
52
+ * `buffered`; the SQL store spells that as a `WHERE` clause.
53
+ * `ReleaseReservation` may only land while `expected` still owns the trigger.
54
+ *
55
+ * @category models
56
+ * @since 1.0.0-rc.0
57
+ */
58
+ export type Write = {
59
+ readonly _tag: "SetOutcome";
60
+ readonly occurrence: number;
61
+ readonly outcome: Outcome;
62
+ readonly whileOpen: boolean;
63
+ } | {
64
+ readonly _tag: "SetRunId";
65
+ readonly occurrence: number;
66
+ readonly runId: string;
67
+ } | {
68
+ readonly _tag: "ReleaseReservation";
69
+ readonly expected: string;
70
+ readonly activeRunId?: string | undefined;
71
+ readonly pending?: number | undefined;
72
+ } | {
73
+ readonly _tag: "AdvanceCursor";
74
+ readonly occurrence: number;
75
+ } | {
76
+ readonly _tag: "SetPending";
77
+ readonly occurrence: number;
78
+ } | {
79
+ readonly _tag: "Reserve";
80
+ readonly reservationId: string;
81
+ readonly claimedAt: number;
82
+ };
83
+ /**
84
+ * What a claim decided: a refusal the store fails with, or a claim outcome and
85
+ * the writes that make it durable. An unclaimed occurrence answers
86
+ * `{ claimed: false }` with no writes.
87
+ *
88
+ * @category models
89
+ * @since 1.0.0-rc.0
90
+ */
91
+ export type Decision = {
92
+ readonly _tag: "Refused";
93
+ readonly error: TriggerError;
94
+ } | {
95
+ readonly _tag: "Decided";
96
+ readonly claim: Claim;
97
+ readonly writes: ReadonlyArray<Write>;
98
+ };
99
+ /**
100
+ * The claim request, the rows it reads, the reservation id the store minted
101
+ * for it, and the store clock reading it was claimed at.
102
+ *
103
+ * @category models
104
+ * @since 1.0.0-rc.0
105
+ */
106
+ export interface Input {
107
+ readonly fire: ClaimFire;
108
+ readonly snapshot: Snapshot;
109
+ readonly reservationId: string;
110
+ readonly claimedAt: number;
111
+ }
112
+ type AdmittedDecision = Extract<Decision, {
113
+ readonly _tag: "Decided";
114
+ }>;
115
+ /**
116
+ * What a reader finds behind a trigger's active owner: nothing, a live owner,
117
+ * or an expired reservation to release.
118
+ *
119
+ * `expected` is the reservation the release must still find in place.
120
+ * `recovered` is the launched predecessor the release hands back, and
121
+ * `pending` is the occurrence it re-arms; both are absent when the reservation
122
+ * covered no unsettled fire.
123
+ *
124
+ * @category models
125
+ * @since 1.0.0-rc.0
126
+ */
127
+ export type Lease = {
128
+ readonly _tag: "Idle";
129
+ } | {
130
+ readonly _tag: "Held";
131
+ readonly runId: string;
132
+ } | {
133
+ readonly _tag: "Expired";
134
+ readonly expected: string;
135
+ readonly recovered?: string | undefined;
136
+ readonly pending?: number | undefined;
137
+ };
138
+ /**
139
+ * Applies the reservation lease to a trigger's active owner.
140
+ *
141
+ * `unfinished` is the fire row at `reservationOccurrence(activeRunId)` while
142
+ * that row is still `null` or `buffered`, and is absent otherwise. A process
143
+ * that died after claiming an occurrence but before launching it left the
144
+ * reservation; the expired lease releases it and re-arms both ordinary and
145
+ * buffered work.
146
+ *
147
+ * @category decision
148
+ * @since 1.0.0-rc.0
149
+ */
150
+ export declare const lease: (input: {
151
+ readonly activeRunId?: string | undefined;
152
+ readonly activeClaimedAt?: number | undefined;
153
+ readonly pending?: number | undefined;
154
+ readonly now: number;
155
+ readonly unfinished?: Fire | undefined;
156
+ }) => Lease;
157
+ /**
158
+ * The refusals a claim owes before it reads any fire row, in the order the
159
+ * stores apply them. A store checks these first so a refused claim leaves the
160
+ * ledger untouched; {@link decide} re-applies them so a store that only calls
161
+ * it is still fenced.
162
+ *
163
+ * @category decision
164
+ * @since 1.0.0-rc.0
165
+ */
166
+ export declare const refuse: (snapshot: Pick<Snapshot, "enabled" | "revision">, fire: ClaimFire) => TriggerError | undefined;
167
+ /**
168
+ * Applies the claim protocol to a snapshot.
169
+ *
170
+ * @category decision
171
+ * @since 1.0.0-rc.0
172
+ */
173
+ export declare const decide: (input: Input) => Decision;
174
+ /**
175
+ * Decides over a snapshot already admitted by {@link refuse} in the same
176
+ * transaction. This avoids checking the same immutable revision twice.
177
+ * @category decision
178
+ * @since 1.0.0-rc.0
179
+ * @private
180
+ */
181
+ export declare const decideFenced: ({ claimedAt, fire, reservationId, snapshot }: Input) => AdmittedDecision;
182
+ export {};
183
+ //# sourceMappingURL=ClaimDecision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClaimDecision.d.ts","sourceRoot":"","sources":["../../src/ClaimDecision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var ClaimDecision_exports = {};
30
+ __export(ClaimDecision_exports, {
31
+ decide: () => decide,
32
+ decideFenced: () => decideFenced,
33
+ lease: () => lease,
34
+ refuse: () => refuse
35
+ });
36
+ module.exports = __toCommonJS(ClaimDecision_exports);
37
+ var Overlap = __toESM(require("./Overlap.js"), 1);
38
+ var import_TriggerError = require("./TriggerError.js");
39
+ var import_TriggerStore = require("./TriggerStore.js");
40
+ const decided = (claim, writes) => ({ _tag: "Decided", claim, writes });
41
+ const lease = (input) => {
42
+ const { activeClaimedAt, activeRunId, now, pending, unfinished } = input;
43
+ if (activeRunId === void 0) return { _tag: "Idle" };
44
+ if (!(0, import_TriggerStore.isReservation)(activeRunId) || activeClaimedAt !== void 0 && activeClaimedAt > now - import_TriggerStore.reservationLeaseMs) {
45
+ return { _tag: "Held", runId: activeRunId };
46
+ }
47
+ const occurrence = (0, import_TriggerStore.reservationOccurrence)(activeRunId);
48
+ if (occurrence === void 0 || unfinished === void 0) return { _tag: "Expired", expected: activeRunId };
49
+ const predecessor = unfinished.runId;
50
+ return {
51
+ _tag: "Expired",
52
+ expected: activeRunId,
53
+ pending: Overlap.pendingAfter({ running: false, pending, due: occurrence }),
54
+ ...predecessor !== void 0 && !(0, import_TriggerStore.isReservation)(predecessor) ? { recovered: predecessor } : {}
55
+ };
56
+ };
57
+ const refuse = (snapshot, fire) => {
58
+ if (snapshot.revision !== fire.expectedRevision) {
59
+ return new import_TriggerError.TriggerError({
60
+ code: "revision_mismatch",
61
+ message: `trigger ${fire.triggerId} is at revision ${snapshot.revision}, not the claimed ${fire.expectedRevision}`
62
+ });
63
+ }
64
+ if (!snapshot.enabled) {
65
+ return new import_TriggerError.TriggerError({ code: "trigger_disabled", message: `trigger ${fire.triggerId} is disabled` });
66
+ }
67
+ return void 0;
68
+ };
69
+ const decide = (input) => {
70
+ const refusal = refuse(input.snapshot, input.fire);
71
+ return refusal === void 0 ? decideFenced(input) : { _tag: "Refused", error: refusal };
72
+ };
73
+ const decideFenced = ({ claimedAt, fire, reservationId, snapshot }) => {
74
+ let activeRunId = snapshot.activeRunId;
75
+ let pending = snapshot.pending;
76
+ const expiredReservation = activeRunId !== void 0 && (0, import_TriggerStore.isReservation)(activeRunId) && (snapshot.activeClaimedAt === void 0 || snapshot.activeClaimedAt <= claimedAt - import_TriggerStore.reservationLeaseMs) ? activeRunId : void 0;
77
+ const existing = snapshot.existingFire;
78
+ if (existing !== void 0) {
79
+ const resumableBuffer = fire.resumeBuffered === true && existing.outcome === "buffered";
80
+ const resumableReservation = existing.outcome === null && (activeRunId === void 0 || (0, import_TriggerStore.reservationOccurrence)(activeRunId) === fire.occurrence && expiredReservation !== void 0);
81
+ const resumableSupersede = fire.resumeBuffered === true && existing.outcome === null && snapshot.overlap === "supersede" && activeRunId !== void 0 && existing.runId === activeRunId;
82
+ if (!resumableBuffer && !resumableReservation && !resumableSupersede) return decided({ claimed: false }, []);
83
+ }
84
+ const writes = [];
85
+ if (expiredReservation !== void 0) {
86
+ const expiredOccurrence = (0, import_TriggerStore.reservationOccurrence)(expiredReservation);
87
+ const expiredFire = expiredOccurrence === void 0 ? void 0 : snapshot.activeFire;
88
+ if (expiredOccurrence !== void 0 && expiredFire !== void 0 && (expiredFire.outcome === null || expiredFire.outcome === "buffered")) {
89
+ if (snapshot.overlap === "supersede") {
90
+ const predecessor = expiredFire.runId;
91
+ activeRunId = predecessor !== void 0 && !(0, import_TriggerStore.isReservation)(predecessor) ? predecessor : void 0;
92
+ if (expiredOccurrence !== fire.occurrence) {
93
+ writes.push({ _tag: "SetOutcome", occurrence: expiredOccurrence, outcome: "superseded", whileOpen: false });
94
+ }
95
+ } else {
96
+ activeRunId = void 0;
97
+ if (expiredOccurrence !== fire.occurrence) {
98
+ pending = Overlap.pendingAfter({ running: false, pending, due: expiredOccurrence });
99
+ }
100
+ }
101
+ } else {
102
+ activeRunId = void 0;
103
+ }
104
+ writes.push({ _tag: "ReleaseReservation", expected: expiredReservation, activeRunId, pending });
105
+ }
106
+ const state = { running: activeRunId !== void 0, pending, due: fire.occurrence };
107
+ const action = Overlap.decide(snapshot.overlap, state);
108
+ if (action === "skip" || action === "buffer") {
109
+ writes.push({
110
+ _tag: "SetOutcome",
111
+ occurrence: fire.occurrence,
112
+ outcome: action === "skip" ? "skipped" : "buffered",
113
+ whileOpen: false
114
+ });
115
+ writes.push({ _tag: "AdvanceCursor", occurrence: fire.occurrence });
116
+ if (action === "buffer") writes.push({ _tag: "SetPending", occurrence: Overlap.pendingAfter(state) });
117
+ return decided({ claimed: true, action }, writes);
118
+ }
119
+ let supersededRunId = activeRunId;
120
+ if (action === "supersede" && activeRunId !== void 0) {
121
+ if ((0, import_TriggerStore.isReservation)(activeRunId)) {
122
+ const activeOccurrence = (0, import_TriggerStore.reservationOccurrence)(activeRunId);
123
+ if (activeOccurrence !== void 0) {
124
+ const predecessor = snapshot.activeFire?.runId;
125
+ if (predecessor !== void 0 && !(0, import_TriggerStore.isReservation)(predecessor)) supersededRunId = predecessor;
126
+ writes.push({ _tag: "SetOutcome", occurrence: activeOccurrence, outcome: "superseded", whileOpen: true });
127
+ }
128
+ }
129
+ if (supersededRunId !== void 0 && !(0, import_TriggerStore.isReservation)(supersededRunId)) {
130
+ writes.push({ _tag: "SetRunId", occurrence: fire.occurrence, runId: supersededRunId });
131
+ }
132
+ }
133
+ writes.push({ _tag: "Reserve", reservationId, claimedAt });
134
+ return decided(
135
+ {
136
+ claimed: true,
137
+ action,
138
+ reservationId,
139
+ ...supersededRunId === void 0 ? {} : { activeRunId: supersededRunId }
140
+ },
141
+ writes
142
+ );
143
+ };
144
+ //# sourceMappingURL=ClaimDecision.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ClaimDecision.ts"],
4
+ "sourcesContent": ["/**\n * The one claim decision both trigger stores apply.\n *\n * A claim is a pure function of the rows it reads: the trigger declaration,\n * the fire row at the occurrence being claimed, and the fire row the active\n * reservation holds. Each store gathers that snapshot in its own storage,\n * calls {@link decide}, and applies the writes it answers. Writing the fences,\n * the expired-reservation reclaim, and the supersede predecessor substitution\n * once is what keeps `SqlTriggerStore` and the published in-memory store from\n * drifting apart.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Overlap from \"./Overlap.ts\"\nimport type { Overlap as Policy } from \"./Trigger.ts\"\nimport { TriggerError } from \"./TriggerError.ts\"\nimport {\n type Claim,\n type ClaimFire,\n isReservation,\n type Outcome,\n reservationLeaseMs,\n reservationOccurrence\n} from \"./TriggerStore.ts\"\n\n/**\n * A fire row as the decision reads it. `outcome` is `null` for a claimed but\n * unsettled occurrence, matching the nullable column.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Fire {\n readonly outcome: Outcome | null\n readonly runId?: string | undefined\n}\n\n/**\n * Everything the decision reads, gathered by the store before it runs.\n *\n * `existingFire` is absent when this claim is what created the row.\n * `activeFire` is the row at `reservationOccurrence(activeRunId)`, read after\n * that insert, and is needed only while `activeRunId` is a reservation.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Snapshot {\n readonly revision: number\n readonly enabled: boolean\n readonly overlap: Policy\n readonly activeRunId?: string | undefined\n readonly activeClaimedAt?: number | undefined\n readonly pending?: number | undefined\n readonly existingFire?: Fire | undefined\n readonly activeFire?: Fire | undefined\n}\n\n/**\n * One durable edit a claim owes, in the order the store must apply them.\n *\n * `SetOutcome` with `whileOpen` may only settle a fire that is still `null` or\n * `buffered`; the SQL store spells that as a `WHERE` clause.\n * `ReleaseReservation` may only land while `expected` still owns the trigger.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Write =\n | {\n readonly _tag: \"SetOutcome\"\n readonly occurrence: number\n readonly outcome: Outcome\n readonly whileOpen: boolean\n }\n | { readonly _tag: \"SetRunId\"; readonly occurrence: number; readonly runId: string }\n | {\n readonly _tag: \"ReleaseReservation\"\n readonly expected: string\n readonly activeRunId?: string | undefined\n readonly pending?: number | undefined\n }\n | { readonly _tag: \"AdvanceCursor\"; readonly occurrence: number }\n | { readonly _tag: \"SetPending\"; readonly occurrence: number }\n | { readonly _tag: \"Reserve\"; readonly reservationId: string; readonly claimedAt: number }\n\n/**\n * What a claim decided: a refusal the store fails with, or a claim outcome and\n * the writes that make it durable. An unclaimed occurrence answers\n * `{ claimed: false }` with no writes.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Decision =\n | { readonly _tag: \"Refused\"; readonly error: TriggerError }\n | { readonly _tag: \"Decided\"; readonly claim: Claim; readonly writes: ReadonlyArray<Write> }\n\n/**\n * The claim request, the rows it reads, the reservation id the store minted\n * for it, and the store clock reading it was claimed at.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Input {\n readonly fire: ClaimFire\n readonly snapshot: Snapshot\n readonly reservationId: string\n readonly claimedAt: number\n}\n\ntype AdmittedDecision = Extract<Decision, { readonly _tag: \"Decided\" }>\n\nconst decided = (claim: Claim, writes: ReadonlyArray<Write>): AdmittedDecision => ({ _tag: \"Decided\", claim, writes })\n\n/**\n * What a reader finds behind a trigger's active owner: nothing, a live owner,\n * or an expired reservation to release.\n *\n * `expected` is the reservation the release must still find in place.\n * `recovered` is the launched predecessor the release hands back, and\n * `pending` is the occurrence it re-arms; both are absent when the reservation\n * covered no unsettled fire.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type Lease =\n | { readonly _tag: \"Idle\" }\n | { readonly _tag: \"Held\"; readonly runId: string }\n | {\n readonly _tag: \"Expired\"\n readonly expected: string\n readonly recovered?: string | undefined\n readonly pending?: number | undefined\n }\n\n/**\n * Applies the reservation lease to a trigger's active owner.\n *\n * `unfinished` is the fire row at `reservationOccurrence(activeRunId)` while\n * that row is still `null` or `buffered`, and is absent otherwise. A process\n * that died after claiming an occurrence but before launching it left the\n * reservation; the expired lease releases it and re-arms both ordinary and\n * buffered work.\n *\n * @category decision\n * @since 1.0.0-rc.0\n */\nexport const lease = (input: {\n readonly activeRunId?: string | undefined\n readonly activeClaimedAt?: number | undefined\n readonly pending?: number | undefined\n readonly now: number\n readonly unfinished?: Fire | undefined\n}): Lease => {\n const { activeClaimedAt, activeRunId, now, pending, unfinished } = input\n if (activeRunId === undefined) return { _tag: \"Idle\" }\n if (!isReservation(activeRunId) || (activeClaimedAt !== undefined && activeClaimedAt > now - reservationLeaseMs)) {\n return { _tag: \"Held\", runId: activeRunId }\n }\n const occurrence = reservationOccurrence(activeRunId)\n if (occurrence === undefined || unfinished === undefined) return { _tag: \"Expired\", expected: activeRunId }\n const predecessor = unfinished.runId\n return {\n _tag: \"Expired\",\n expected: activeRunId,\n pending: Overlap.pendingAfter({ running: false, pending, due: occurrence }),\n ...(predecessor !== undefined && !isReservation(predecessor) ? { recovered: predecessor } : {})\n }\n}\n\n/**\n * The refusals a claim owes before it reads any fire row, in the order the\n * stores apply them. A store checks these first so a refused claim leaves the\n * ledger untouched; {@link decide} re-applies them so a store that only calls\n * it is still fenced.\n *\n * @category decision\n * @since 1.0.0-rc.0\n */\nexport const refuse = (\n snapshot: Pick<Snapshot, \"enabled\" | \"revision\">,\n fire: ClaimFire\n): TriggerError | undefined => {\n if (snapshot.revision !== fire.expectedRevision) {\n return new TriggerError({\n code: \"revision_mismatch\",\n message: `trigger ${fire.triggerId} is at revision ${snapshot.revision}, not the claimed ${fire.expectedRevision}`\n })\n }\n if (!snapshot.enabled) {\n return new TriggerError({ code: \"trigger_disabled\", message: `trigger ${fire.triggerId} is disabled` })\n }\n return undefined\n}\n\n/**\n * Applies the claim protocol to a snapshot.\n *\n * @category decision\n * @since 1.0.0-rc.0\n */\nexport const decide = (input: Input): Decision => {\n const refusal = refuse(input.snapshot, input.fire)\n return refusal === undefined ? decideFenced(input) : { _tag: \"Refused\", error: refusal }\n}\n\n/**\n * Decides over a snapshot already admitted by {@link refuse} in the same\n * transaction. This avoids checking the same immutable revision twice.\n * @category decision\n * @since 1.0.0-rc.0\n * @private\n */\nexport const decideFenced = ({ claimedAt, fire, reservationId, snapshot }: Input): AdmittedDecision => {\n let activeRunId = snapshot.activeRunId\n let pending = snapshot.pending\n // A reservation with no claim timestamp predates the lease column. Nothing\n // writes that shape now, so treating it as expired is the only way such a\n // row is ever reclaimed.\n const expiredReservation = activeRunId !== undefined && isReservation(activeRunId) &&\n (snapshot.activeClaimedAt === undefined || snapshot.activeClaimedAt <= claimedAt - reservationLeaseMs)\n ? activeRunId\n : undefined\n const existing = snapshot.existingFire\n if (existing !== undefined) {\n const resumableBuffer = fire.resumeBuffered === true && existing.outcome === \"buffered\"\n const resumableReservation = existing.outcome === null &&\n (activeRunId === undefined ||\n (reservationOccurrence(activeRunId) === fire.occurrence && expiredReservation !== undefined))\n const resumableSupersede = fire.resumeBuffered === true && existing.outcome === null &&\n snapshot.overlap === \"supersede\" && activeRunId !== undefined && existing.runId === activeRunId\n if (!resumableBuffer && !resumableReservation && !resumableSupersede) return decided({ claimed: false }, [])\n }\n const writes: Array<Write> = []\n if (expiredReservation !== undefined) {\n const expiredOccurrence = reservationOccurrence(expiredReservation)\n const expiredFire = expiredOccurrence === undefined ? undefined : snapshot.activeFire\n if (\n expiredOccurrence !== undefined && expiredFire !== undefined &&\n (expiredFire.outcome === null || expiredFire.outcome === \"buffered\")\n ) {\n if (snapshot.overlap === \"supersede\") {\n const predecessor = expiredFire.runId\n activeRunId = predecessor !== undefined && !isReservation(predecessor) ? predecessor : undefined\n if (expiredOccurrence !== fire.occurrence) {\n writes.push({ _tag: \"SetOutcome\", occurrence: expiredOccurrence, outcome: \"superseded\", whileOpen: false })\n }\n } else {\n activeRunId = undefined\n if (expiredOccurrence !== fire.occurrence) {\n pending = Overlap.pendingAfter({ running: false, pending, due: expiredOccurrence })\n }\n }\n } else {\n activeRunId = undefined\n }\n writes.push({ _tag: \"ReleaseReservation\", expected: expiredReservation, activeRunId, pending })\n }\n const state: Overlap.State = { running: activeRunId !== undefined, pending, due: fire.occurrence }\n const action = Overlap.decide(snapshot.overlap, state)\n // A skip or buffer is complete inside the claim transaction. A fire or\n // supersede is only reserved here; its cursor advances when `recordResult`\n // makes the launched run durable.\n if (action === \"skip\" || action === \"buffer\") {\n writes.push({\n _tag: \"SetOutcome\",\n occurrence: fire.occurrence,\n outcome: action === \"skip\" ? \"skipped\" : \"buffered\",\n whileOpen: false\n })\n writes.push({ _tag: \"AdvanceCursor\", occurrence: fire.occurrence })\n if (action === \"buffer\") writes.push({ _tag: \"SetPending\", occurrence: Overlap.pendingAfter(state) })\n return decided({ claimed: true, action }, writes)\n }\n let supersededRunId = activeRunId\n if (action === \"supersede\" && activeRunId !== undefined) {\n if (isReservation(activeRunId)) {\n const activeOccurrence = reservationOccurrence(activeRunId)\n if (activeOccurrence !== undefined) {\n const predecessor = snapshot.activeFire?.runId\n if (predecessor !== undefined && !isReservation(predecessor)) supersededRunId = predecessor\n writes.push({ _tag: \"SetOutcome\", occurrence: activeOccurrence, outcome: \"superseded\", whileOpen: true })\n }\n }\n if (supersededRunId !== undefined && !isReservation(supersededRunId)) {\n writes.push({ _tag: \"SetRunId\", occurrence: fire.occurrence, runId: supersededRunId })\n }\n }\n writes.push({ _tag: \"Reserve\", reservationId, claimedAt })\n return decided(\n {\n claimed: true,\n action,\n reservationId,\n ...(supersededRunId === undefined ? {} : { activeRunId: supersededRunId })\n },\n writes\n )\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,cAAyB;AAEzB,0BAA6B;AAC7B,0BAOO;AA2FP,MAAM,UAAU,CAAC,OAAc,YAAoD,EAAE,MAAM,WAAW,OAAO,OAAO;AAoC7G,MAAM,QAAQ,CAAC,UAMT;AACX,QAAM,EAAE,iBAAiB,aAAa,KAAK,SAAS,WAAW,IAAI;AACnE,MAAI,gBAAgB,OAAW,QAAO,EAAE,MAAM,OAAO;AACrD,MAAI,KAAC,mCAAc,WAAW,KAAM,oBAAoB,UAAa,kBAAkB,MAAM,wCAAqB;AAChH,WAAO,EAAE,MAAM,QAAQ,OAAO,YAAY;AAAA,EAC5C;AACA,QAAM,iBAAa,2CAAsB,WAAW;AACpD,MAAI,eAAe,UAAa,eAAe,OAAW,QAAO,EAAE,MAAM,WAAW,UAAU,YAAY;AAC1G,QAAM,cAAc,WAAW;AAC/B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,QAAQ,aAAa,EAAE,SAAS,OAAO,SAAS,KAAK,WAAW,CAAC;AAAA,IAC1E,GAAI,gBAAgB,UAAa,KAAC,mCAAc,WAAW,IAAI,EAAE,WAAW,YAAY,IAAI,CAAC;AAAA,EAC/F;AACF;AAWO,MAAM,SAAS,CACpB,UACA,SAC6B;AAC7B,MAAI,SAAS,aAAa,KAAK,kBAAkB;AAC/C,WAAO,IAAI,iCAAa;AAAA,MACtB,MAAM;AAAA,MACN,SAAS,WAAW,KAAK,SAAS,mBAAmB,SAAS,QAAQ,qBAAqB,KAAK,gBAAgB;AAAA,IAClH,CAAC;AAAA,EACH;AACA,MAAI,CAAC,SAAS,SAAS;AACrB,WAAO,IAAI,iCAAa,EAAE,MAAM,oBAAoB,SAAS,WAAW,KAAK,SAAS,eAAe,CAAC;AAAA,EACxG;AACA,SAAO;AACT;AAQO,MAAM,SAAS,CAAC,UAA2B;AAChD,QAAM,UAAU,OAAO,MAAM,UAAU,MAAM,IAAI;AACjD,SAAO,YAAY,SAAY,aAAa,KAAK,IAAI,EAAE,MAAM,WAAW,OAAO,QAAQ;AACzF;AASO,MAAM,eAAe,CAAC,EAAE,WAAW,MAAM,eAAe,SAAS,MAA+B;AACrG,MAAI,cAAc,SAAS;AAC3B,MAAI,UAAU,SAAS;AAIvB,QAAM,qBAAqB,gBAAgB,cAAa,mCAAc,WAAW,MAC5E,SAAS,oBAAoB,UAAa,SAAS,mBAAmB,YAAY,0CACnF,cACA;AACJ,QAAM,WAAW,SAAS;AAC1B,MAAI,aAAa,QAAW;AAC1B,UAAM,kBAAkB,KAAK,mBAAmB,QAAQ,SAAS,YAAY;AAC7E,UAAM,uBAAuB,SAAS,YAAY,SAC/C,gBAAgB,cACd,2CAAsB,WAAW,MAAM,KAAK,cAAc,uBAAuB;AACtF,UAAM,qBAAqB,KAAK,mBAAmB,QAAQ,SAAS,YAAY,QAC9E,SAAS,YAAY,eAAe,gBAAgB,UAAa,SAAS,UAAU;AACtF,QAAI,CAAC,mBAAmB,CAAC,wBAAwB,CAAC,mBAAoB,QAAO,QAAQ,EAAE,SAAS,MAAM,GAAG,CAAC,CAAC;AAAA,EAC7G;AACA,QAAM,SAAuB,CAAC;AAC9B,MAAI,uBAAuB,QAAW;AACpC,UAAM,wBAAoB,2CAAsB,kBAAkB;AAClE,UAAM,cAAc,sBAAsB,SAAY,SAAY,SAAS;AAC3E,QACE,sBAAsB,UAAa,gBAAgB,WAClD,YAAY,YAAY,QAAQ,YAAY,YAAY,aACzD;AACA,UAAI,SAAS,YAAY,aAAa;AACpC,cAAM,cAAc,YAAY;AAChC,sBAAc,gBAAgB,UAAa,KAAC,mCAAc,WAAW,IAAI,cAAc;AACvF,YAAI,sBAAsB,KAAK,YAAY;AACzC,iBAAO,KAAK,EAAE,MAAM,cAAc,YAAY,mBAAmB,SAAS,cAAc,WAAW,MAAM,CAAC;AAAA,QAC5G;AAAA,MACF,OAAO;AACL,sBAAc;AACd,YAAI,sBAAsB,KAAK,YAAY;AACzC,oBAAU,QAAQ,aAAa,EAAE,SAAS,OAAO,SAAS,KAAK,kBAAkB,CAAC;AAAA,QACpF;AAAA,MACF;AAAA,IACF,OAAO;AACL,oBAAc;AAAA,IAChB;AACA,WAAO,KAAK,EAAE,MAAM,sBAAsB,UAAU,oBAAoB,aAAa,QAAQ,CAAC;AAAA,EAChG;AACA,QAAM,QAAuB,EAAE,SAAS,gBAAgB,QAAW,SAAS,KAAK,KAAK,WAAW;AACjG,QAAM,SAAS,QAAQ,OAAO,SAAS,SAAS,KAAK;AAIrD,MAAI,WAAW,UAAU,WAAW,UAAU;AAC5C,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB,SAAS,WAAW,SAAS,YAAY;AAAA,MACzC,WAAW;AAAA,IACb,CAAC;AACD,WAAO,KAAK,EAAE,MAAM,iBAAiB,YAAY,KAAK,WAAW,CAAC;AAClE,QAAI,WAAW,SAAU,QAAO,KAAK,EAAE,MAAM,cAAc,YAAY,QAAQ,aAAa,KAAK,EAAE,CAAC;AACpG,WAAO,QAAQ,EAAE,SAAS,MAAM,OAAO,GAAG,MAAM;AAAA,EAClD;AACA,MAAI,kBAAkB;AACtB,MAAI,WAAW,eAAe,gBAAgB,QAAW;AACvD,YAAI,mCAAc,WAAW,GAAG;AAC9B,YAAM,uBAAmB,2CAAsB,WAAW;AAC1D,UAAI,qBAAqB,QAAW;AAClC,cAAM,cAAc,SAAS,YAAY;AACzC,YAAI,gBAAgB,UAAa,KAAC,mCAAc,WAAW,EAAG,mBAAkB;AAChF,eAAO,KAAK,EAAE,MAAM,cAAc,YAAY,kBAAkB,SAAS,cAAc,WAAW,KAAK,CAAC;AAAA,MAC1G;AAAA,IACF;AACA,QAAI,oBAAoB,UAAa,KAAC,mCAAc,eAAe,GAAG;AACpE,aAAO,KAAK,EAAE,MAAM,YAAY,YAAY,KAAK,YAAY,OAAO,gBAAgB,CAAC;AAAA,IACvF;AAAA,EACF;AACA,SAAO,KAAK,EAAE,MAAM,WAAW,eAAe,UAAU,CAAC;AACzD,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,GAAI,oBAAoB,SAAY,CAAC,IAAI,EAAE,aAAa,gBAAgB;AAAA,IAC1E;AAAA,IACA;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Typed wrappers around Effect Cron.
3
+ *
4
+ * Effect's field parser only range-checks each field, so an expression like
5
+ * `0 0 30 2 *` parses and then never matches a calendar date. Its occurrence
6
+ * search answers that by exhausting its bound and throwing, which inside
7
+ * `Effect.gen` is a defect rather than a typed failure: the fiber that owns the
8
+ * search dies instead of reporting. Every search here is wrapped so the
9
+ * exhausted bound arrives as `unsatisfiable_cron`, and {@link parse} probes for
10
+ * one occurrence so an unsatisfiable expression is refused where it is
11
+ * declared rather than at the tick that would have fired it.
12
+ *
13
+ * A satisfiable expression carries the opposite hazard: `* * * * *` across a
14
+ * year is 525,600 occurrences, and materializing them costs seconds and
15
+ * hundreds of megabytes. {@link occurrencesBetween} therefore always searches
16
+ * under a finite cap and reports when an unstated limit would exceed
17
+ * {@link maxOccurrences}.
18
+ *
19
+ * A timezone makes the expression a wall-clock schedule. A wall time that
20
+ * spring forward skips fires at the instant it would have had on the old
21
+ * offset, so a daily 02:30 fires at 03:30 daylight time. A wall time that fall
22
+ * back repeats fires once, at its first instant, unless the hour field is every
23
+ * hour, in which case both passes fire. Occurrences are whole seconds in UTC,
24
+ * whatever the host's zone.
25
+ *
26
+ * @since 0.1.0
27
+ */
28
+ import * as EffectCron from "effect/Cron";
29
+ import * as Effect from "effect/Effect";
30
+ import { TriggerError } from "./TriggerError.ts";
31
+ /**
32
+ * A parsed cron expression, kept beside the text it came from so the
33
+ * declaration can be round-tripped.
34
+ *
35
+ * @category models
36
+ * @since 0.1.0
37
+ */
38
+ export interface Cron {
39
+ readonly expression: string;
40
+ readonly timezone?: string | undefined;
41
+ readonly value: EffectCron.Cron;
42
+ }
43
+ /**
44
+ * The first occurrence strictly after `from`.
45
+ *
46
+ * @category getters
47
+ * @since 0.1.0
48
+ */
49
+ export declare const next: (cron: Cron, from: Date) => Effect.Effect<Date, TriggerError>;
50
+ /**
51
+ * Returns the latest occurrence at or before `at`.
52
+ *
53
+ * @category getters
54
+ * @since 0.1.0
55
+ */
56
+ export declare const previousAtOrBefore: (cron: Cron, at: Date) => Effect.Effect<Date, TriggerError>;
57
+ /**
58
+ * The greatest number of occurrences one search returns when its caller states
59
+ * no limit of its own.
60
+ *
61
+ * @category constants
62
+ * @since 0.1.0
63
+ */
64
+ export declare const maxOccurrences = 1000;
65
+ /**
66
+ * The occurrences in `(from, to]`, in order.
67
+ *
68
+ * A stated `limit` caps the result silently and must be a non-negative safe
69
+ * integer. With no stated limit, the search fails with
70
+ * `catch_up_bound_exceeded` when the interval holds more than
71
+ * {@link maxOccurrences}. A caller with its own bound can pass one more than
72
+ * that bound and compare the length, the way `CatchUp.occurrences` does.
73
+ *
74
+ * @category sequencing
75
+ * @since 0.1.0
76
+ */
77
+ export declare const occurrencesBetween: (cron: Cron, from: Date, to: Date, limit?: number | undefined) => Effect.Effect<ReadonlyArray<Date>, TriggerError>;
78
+ /**
79
+ * Parses a cron expression in an optional timezone, reporting a malformed one
80
+ * as `invalid_cron` and one the calendar never satisfies as
81
+ * `unsatisfiable_cron`.
82
+ *
83
+ * The satisfiability probe searches forward from the current instant, which is
84
+ * the same search every tick performs, so an expression that survives parsing
85
+ * is one the scheduler can keep answering.
86
+ *
87
+ * @category constructors
88
+ * @since 0.1.0
89
+ */
90
+ export declare const parse: (expression: string, timezone?: string) => Effect.Effect<Cron, TriggerError>;
91
+ //# sourceMappingURL=Cron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cron.d.ts","sourceRoot":"","sources":["../../src/Cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAA;CAChC;AAYD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,IAAI,EAAE,MAAM,IAAI,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CACwB,CAAA;AAEtG;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,IAAI,EAAE,IAAI,IAAI,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAKvF,CAAA;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,IAAI,EACV,MAAM,IAAI,EACV,IAAI,IAAI,EACR,QAAQ,MAAM,GAAG,SAAS,KACzB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,YAAY,CAoCjD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,GAAI,YAAY,MAAM,EAAE,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAgB1F,CAAA"}
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var Cron_exports = {};
30
+ __export(Cron_exports, {
31
+ maxOccurrences: () => maxOccurrences,
32
+ next: () => next,
33
+ occurrencesBetween: () => occurrencesBetween,
34
+ parse: () => parse,
35
+ previousAtOrBefore: () => previousAtOrBefore
36
+ });
37
+ module.exports = __toCommonJS(Cron_exports);
38
+ var Clock = __toESM(require("effect/Clock"), 1);
39
+ var EffectCron = __toESM(require("effect/Cron"), 1);
40
+ var Effect = __toESM(require("effect/Effect"), 1);
41
+ var Result = __toESM(require("effect/Result"), 1);
42
+ var WallClock = __toESM(require("./internal/WallClock.js"), 1);
43
+ var import_TriggerError = require("./TriggerError.js");
44
+ const exhausted = (cron, search, cause) => new import_TriggerError.TriggerError({
45
+ code: "unsatisfiable_cron",
46
+ message: `cron expression '${cron.expression}' has no ${search} occurrence`,
47
+ cause
48
+ });
49
+ const attempt = (cron, search, find) => Effect.try({ try: find, catch: (cause) => exhausted(cron, search, cause) });
50
+ const next = (cron, from) => attempt(cron, "next", () => new Date(WallClock.next(WallClock.compile(cron.value), from.getTime())));
51
+ const previousAtOrBefore = (cron, at) => attempt(
52
+ cron,
53
+ "previous",
54
+ () => new Date(WallClock.previousAtOrBefore(WallClock.compile(cron.value), at.getTime()))
55
+ );
56
+ const maxOccurrences = 1e3;
57
+ const occurrencesBetween = (cron, from, to, limit) => {
58
+ if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 0)) {
59
+ return Effect.fail(
60
+ new import_TriggerError.TriggerError({
61
+ code: "invalid_options",
62
+ message: `occurrence limit must be a non-negative safe integer, received ${limit}`,
63
+ path: "limit"
64
+ })
65
+ );
66
+ }
67
+ const searchLimit = limit ?? maxOccurrences + 1;
68
+ if (searchLimit === 0) return Effect.succeed([]);
69
+ const search = attempt(cron, "interval", () => {
70
+ const wall = WallClock.compile(cron.value);
71
+ const occurrences = [];
72
+ let cursor = from.getTime();
73
+ while (occurrences.length < searchLimit) {
74
+ cursor = WallClock.next(wall, cursor);
75
+ if (cursor > to.getTime()) break;
76
+ occurrences.push(new Date(cursor));
77
+ }
78
+ return occurrences;
79
+ });
80
+ if (limit !== void 0) return search;
81
+ return search.pipe(
82
+ Effect.flatMap(
83
+ (occurrences) => occurrences.length > maxOccurrences ? Effect.fail(
84
+ new import_TriggerError.TriggerError({
85
+ code: "catch_up_bound_exceeded",
86
+ message: `interval contains more than ${maxOccurrences} occurrences; maxOccurrences is ${maxOccurrences}`
87
+ })
88
+ ) : Effect.succeed(occurrences)
89
+ )
90
+ );
91
+ };
92
+ const parse = (expression, timezone) => Effect.gen(function* () {
93
+ const parsed = EffectCron.parse(expression, timezone);
94
+ if (Result.isFailure(parsed)) {
95
+ return yield* Effect.fail(
96
+ new import_TriggerError.TriggerError({ code: "invalid_cron", message: parsed.failure.message, cause: parsed.failure })
97
+ );
98
+ }
99
+ const cron = {
100
+ expression,
101
+ ...timezone === void 0 ? {} : { timezone },
102
+ value: parsed.success
103
+ };
104
+ const now = yield* Clock.currentTimeMillis;
105
+ yield* next(cron, new Date(now));
106
+ return cron;
107
+ });
108
+ //# sourceMappingURL=Cron.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Cron.ts"],
4
+ "sourcesContent": ["/**\n * Typed wrappers around Effect Cron.\n *\n * Effect's field parser only range-checks each field, so an expression like\n * `0 0 30 2 *` parses and then never matches a calendar date. Its occurrence\n * search answers that by exhausting its bound and throwing, which inside\n * `Effect.gen` is a defect rather than a typed failure: the fiber that owns the\n * search dies instead of reporting. Every search here is wrapped so the\n * exhausted bound arrives as `unsatisfiable_cron`, and {@link parse} probes for\n * one occurrence so an unsatisfiable expression is refused where it is\n * declared rather than at the tick that would have fired it.\n *\n * A satisfiable expression carries the opposite hazard: `* * * * *` across a\n * year is 525,600 occurrences, and materializing them costs seconds and\n * hundreds of megabytes. {@link occurrencesBetween} therefore always searches\n * under a finite cap and reports when an unstated limit would exceed\n * {@link maxOccurrences}.\n *\n * A timezone makes the expression a wall-clock schedule. A wall time that\n * spring forward skips fires at the instant it would have had on the old\n * offset, so a daily 02:30 fires at 03:30 daylight time. A wall time that fall\n * back repeats fires once, at its first instant, unless the hour field is every\n * hour, in which case both passes fire. Occurrences are whole seconds in UTC,\n * whatever the host's zone.\n *\n * @since 0.1.0\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as EffectCron from \"effect/Cron\"\nimport * as Effect from \"effect/Effect\"\nimport * as Result from \"effect/Result\"\nimport * as WallClock from \"./internal/WallClock.ts\"\nimport { TriggerError } from \"./TriggerError.ts\"\n\n/**\n * A parsed cron expression, kept beside the text it came from so the\n * declaration can be round-tripped.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Cron {\n readonly expression: string\n readonly timezone?: string | undefined\n readonly value: EffectCron.Cron\n}\n\nconst exhausted = (cron: Cron, search: string, cause: unknown): TriggerError =>\n new TriggerError({\n code: \"unsatisfiable_cron\",\n message: `cron expression '${cron.expression}' has no ${search} occurrence`,\n cause\n })\n\nconst attempt = <A>(cron: Cron, search: string, find: () => A): Effect.Effect<A, TriggerError> =>\n Effect.try({ try: find, catch: (cause) => exhausted(cron, search, cause) })\n\n/**\n * The first occurrence strictly after `from`.\n *\n * @category getters\n * @since 0.1.0\n */\nexport const next = (cron: Cron, from: Date): Effect.Effect<Date, TriggerError> =>\n attempt(cron, \"next\", () => new Date(WallClock.next(WallClock.compile(cron.value), from.getTime())))\n\n/**\n * Returns the latest occurrence at or before `at`.\n *\n * @category getters\n * @since 0.1.0\n */\nexport const previousAtOrBefore = (cron: Cron, at: Date): Effect.Effect<Date, TriggerError> =>\n attempt(\n cron,\n \"previous\",\n () => new Date(WallClock.previousAtOrBefore(WallClock.compile(cron.value), at.getTime()))\n )\n\n/**\n * The greatest number of occurrences one search returns when its caller states\n * no limit of its own.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const maxOccurrences = 1000\n\n/**\n * The occurrences in `(from, to]`, in order.\n *\n * A stated `limit` caps the result silently and must be a non-negative safe\n * integer. With no stated limit, the search fails with\n * `catch_up_bound_exceeded` when the interval holds more than\n * {@link maxOccurrences}. A caller with its own bound can pass one more than\n * that bound and compare the length, the way `CatchUp.occurrences` does.\n *\n * @category sequencing\n * @since 0.1.0\n */\nexport const occurrencesBetween = (\n cron: Cron,\n from: Date,\n to: Date,\n limit?: number | undefined\n): Effect.Effect<ReadonlyArray<Date>, TriggerError> => {\n if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 0)) {\n return Effect.fail(\n new TriggerError({\n code: \"invalid_options\",\n message: `occurrence limit must be a non-negative safe integer, received ${limit}`,\n path: \"limit\"\n })\n )\n }\n const searchLimit = limit ?? maxOccurrences + 1\n if (searchLimit === 0) return Effect.succeed([])\n const search = attempt(cron, \"interval\", () => {\n const wall = WallClock.compile(cron.value)\n const occurrences: Array<Date> = []\n let cursor = from.getTime()\n while (occurrences.length < searchLimit) {\n cursor = WallClock.next(wall, cursor)\n if (cursor > to.getTime()) break\n occurrences.push(new Date(cursor))\n }\n return occurrences\n })\n if (limit !== undefined) return search\n return search.pipe(\n Effect.flatMap((occurrences) =>\n occurrences.length > maxOccurrences\n ? Effect.fail(\n new TriggerError({\n code: \"catch_up_bound_exceeded\",\n message: `interval contains more than ${maxOccurrences} occurrences; maxOccurrences is ${maxOccurrences}`\n })\n )\n : Effect.succeed(occurrences)\n )\n )\n}\n\n/**\n * Parses a cron expression in an optional timezone, reporting a malformed one\n * as `invalid_cron` and one the calendar never satisfies as\n * `unsatisfiable_cron`.\n *\n * The satisfiability probe searches forward from the current instant, which is\n * the same search every tick performs, so an expression that survives parsing\n * is one the scheduler can keep answering.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const parse = (expression: string, timezone?: string): Effect.Effect<Cron, TriggerError> =>\n Effect.gen(function*() {\n const parsed = EffectCron.parse(expression, timezone)\n if (Result.isFailure(parsed)) {\n return yield* Effect.fail(\n new TriggerError({ code: \"invalid_cron\", message: parsed.failure.message, cause: parsed.failure })\n )\n }\n const cron: Cron = {\n expression,\n ...(timezone === undefined ? {} : { timezone }),\n value: parsed.success\n }\n const now = yield* Clock.currentTimeMillis\n yield* next(cron, new Date(now))\n return cron\n })\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,YAAuB;AACvB,iBAA4B;AAC5B,aAAwB;AACxB,aAAwB;AACxB,gBAA2B;AAC3B,0BAA6B;AAe7B,MAAM,YAAY,CAAC,MAAY,QAAgB,UAC7C,IAAI,iCAAa;AAAA,EACf,MAAM;AAAA,EACN,SAAS,oBAAoB,KAAK,UAAU,YAAY,MAAM;AAAA,EAC9D;AACF,CAAC;AAEH,MAAM,UAAU,CAAI,MAAY,QAAgB,SAC9C,OAAO,IAAI,EAAE,KAAK,MAAM,OAAO,CAAC,UAAU,UAAU,MAAM,QAAQ,KAAK,EAAE,CAAC;AAQrE,MAAM,OAAO,CAAC,MAAY,SAC/B,QAAQ,MAAM,QAAQ,MAAM,IAAI,KAAK,UAAU,KAAK,UAAU,QAAQ,KAAK,KAAK,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC;AAQ9F,MAAM,qBAAqB,CAAC,MAAY,OAC7C;AAAA,EACE;AAAA,EACA;AAAA,EACA,MAAM,IAAI,KAAK,UAAU,mBAAmB,UAAU,QAAQ,KAAK,KAAK,GAAG,GAAG,QAAQ,CAAC,CAAC;AAC1F;AASK,MAAM,iBAAiB;AAcvB,MAAM,qBAAqB,CAChC,MACA,MACA,IACA,UACqD;AACrD,MAAI,UAAU,WAAc,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,IAAI;AACtE,WAAO,OAAO;AAAA,MACZ,IAAI,iCAAa;AAAA,QACf,MAAM;AAAA,QACN,SAAS,kEAAkE,KAAK;AAAA,QAChF,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AACA,QAAM,cAAc,SAAS,iBAAiB;AAC9C,MAAI,gBAAgB,EAAG,QAAO,OAAO,QAAQ,CAAC,CAAC;AAC/C,QAAM,SAAS,QAAQ,MAAM,YAAY,MAAM;AAC7C,UAAM,OAAO,UAAU,QAAQ,KAAK,KAAK;AACzC,UAAM,cAA2B,CAAC;AAClC,QAAI,SAAS,KAAK,QAAQ;AAC1B,WAAO,YAAY,SAAS,aAAa;AACvC,eAAS,UAAU,KAAK,MAAM,MAAM;AACpC,UAAI,SAAS,GAAG,QAAQ,EAAG;AAC3B,kBAAY,KAAK,IAAI,KAAK,MAAM,CAAC;AAAA,IACnC;AACA,WAAO;AAAA,EACT,CAAC;AACD,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO;AAAA,IACZ,OAAO;AAAA,MAAQ,CAAC,gBACd,YAAY,SAAS,iBACjB,OAAO;AAAA,QACP,IAAI,iCAAa;AAAA,UACf,MAAM;AAAA,UACN,SAAS,+BAA+B,cAAc,mCAAmC,cAAc;AAAA,QACzG,CAAC;AAAA,MACH,IACE,OAAO,QAAQ,WAAW;AAAA,IAChC;AAAA,EACF;AACF;AAcO,MAAM,QAAQ,CAAC,YAAoB,aACxC,OAAO,IAAI,aAAY;AACrB,QAAM,SAAS,WAAW,MAAM,YAAY,QAAQ;AACpD,MAAI,OAAO,UAAU,MAAM,GAAG;AAC5B,WAAO,OAAO,OAAO;AAAA,MACnB,IAAI,iCAAa,EAAE,MAAM,gBAAgB,SAAS,OAAO,QAAQ,SAAS,OAAO,OAAO,QAAQ,CAAC;AAAA,IACnG;AAAA,EACF;AACA,QAAM,OAAa;AAAA,IACjB;AAAA,IACA,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C,OAAO,OAAO;AAAA,EAChB;AACA,QAAM,MAAM,OAAO,MAAM;AACzB,SAAO,KAAK,MAAM,IAAI,KAAK,GAAG,CAAC;AAC/B,SAAO;AACT,CAAC;",
6
+ "names": []
7
+ }