@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,118 @@
1
+ /**
2
+ * Verified webhook ingestion through the authoritative Control boundary.
3
+ *
4
+ * @see packages/smithers/agent/triggers/docs/api.md
5
+ * @since 0.1.0
6
+ */
7
+ import * as ControlChannels from "@smthrs/control/Channels";
8
+ import type { ControlError } from "@smthrs/control/ControlError";
9
+ import type { Receipt, RunSummary } from "@smthrs/control/ControlSchema";
10
+ import type { CredentialRef } from "@smthrs/control/Credential";
11
+ import * as Effect from "effect/Effect";
12
+ import type * as Redacted from "effect/Redacted";
13
+ import type * as Channel from "./Channel.ts";
14
+ import { TriggerError } from "./TriggerError.ts";
15
+ /**
16
+ * Compares a supplied byte string against the expected one without returning
17
+ * early on a mismatch.
18
+ *
19
+ * The loop runs exactly `expected.length` times, so its iteration count is
20
+ * fixed by the secret side of the comparison and never by the caller's. Reading
21
+ * the longer of the two instead let a caller lengthen its input until the work
22
+ * stopped growing, which reports the expected signature's length. The length
23
+ * difference is folded into the result, so inputs of unequal length always
24
+ * disagree.
25
+ *
26
+ * @category verification
27
+ * @since 0.1.0
28
+ */
29
+ export declare const constantTimeEqual: (expected: Uint8Array, supplied: Uint8Array) => boolean;
30
+ /**
31
+ * Configuration for a raw-byte signature verifier.
32
+ *
33
+ * `expected` receives a private copy of the request bytes and the redacted
34
+ * credential reference the channel was declared with, and answers with the
35
+ * signature bytes the request must carry in `header`. It returns an Effect so
36
+ * the secret is resolved through the host's resolver per request rather than
37
+ * captured in a closure at declaration time, and so a resolution or HMAC
38
+ * failure arrives as a typed `verification_failed` instead of a defect that
39
+ * kills the fiber.
40
+ *
41
+ * @category models
42
+ * @since 0.1.0
43
+ */
44
+ export interface SignatureConfig {
45
+ readonly header: string;
46
+ readonly expected: (body: Uint8Array, credential: Redacted.Redacted<CredentialRef>) => Effect.Effect<Uint8Array, TriggerError>;
47
+ }
48
+ /**
49
+ * Builds a verifier that compares a signature header in constant time.
50
+ *
51
+ * An absent or empty header is refused before `expected` runs, and an
52
+ * `expected` that answers with zero bytes is refused after it. Neither is a
53
+ * comparison the constant-time equality can decide: it agrees on two empty
54
+ * byte strings, so a request carrying no signature at all would have
55
+ * authenticated against a secret that resolved to the empty string. A
56
+ * zero-length expected signature is a misconfigured credential, never a valid
57
+ * one, so the door stays closed instead of opening for everyone.
58
+ *
59
+ * Every refusal carries the same message, `webhook signature in <header> did
60
+ * not verify`, whatever the reason. A failure raised by `expected` is kept in
61
+ * `cause` only: its message names the credential reference or the resolver
62
+ * that could not produce a secret, which is a host detail, and the refusal
63
+ * that answers a bad signature answers a broken resolver the same way.
64
+ *
65
+ * @category constructors
66
+ * @since 0.1.0
67
+ */
68
+ export declare const makeSignatureVerifier: (config: SignatureConfig) => Channel.Verify;
69
+ /**
70
+ * Webhook declaration configuration.
71
+ *
72
+ * `credential` is required. It used to default to a reference named after the
73
+ * channel, so a webhook declared without one verified against whatever
74
+ * credential happened to share its name instead of being refused, and two
75
+ * declarations differing only in credential verified identically.
76
+ *
77
+ * @category models
78
+ * @since 0.1.0
79
+ */
80
+ export interface Config<Payload, Outbound = never> extends Channel.Config<Payload, RunSummary, Outbound> {
81
+ readonly credential: Redacted.Redacted<CredentialRef>;
82
+ }
83
+ /**
84
+ * A webhook door that can only ingest through `Channels` and `Control`.
85
+ *
86
+ * @category services
87
+ * @since 0.1.0
88
+ */
89
+ export interface Webhook {
90
+ readonly name: string;
91
+ readonly register: Effect.Effect<void, never, ControlChannels.Channels>;
92
+ readonly ingest: (raw: Channel.RawInbound) => Effect.Effect<Receipt, ControlError | TriggerError, ControlChannels.Channels>;
93
+ }
94
+ /**
95
+ * Builds a webhook door whose only dispatch path is the Control channel
96
+ * coordinator.
97
+ *
98
+ * Verification occurs inside `Channels.ingest` before the adapter's JSON or
99
+ * schema decoder and before any Control operation.
100
+ *
101
+ * `ingest` does not register: a channel is registered once, deliberately,
102
+ * through {@link Webhook.register}, so traffic to an unregistered channel is
103
+ * reported as unavailable rather than silently self-registering the door it
104
+ * arrived at.
105
+ *
106
+ * `ingest` also copies `body`, `headers`, and `idempotencyKey` before anything
107
+ * reads them. Verification, delivery fingerprinting, and decoding then all see
108
+ * one private snapshot, so a verifier that edits the bytes it was handed, or a
109
+ * caller that mutates its own object between building this Effect and running
110
+ * it, cannot authenticate one payload and have another decoded. A
111
+ * `SharedArrayBuffer`-backed view is copied out of shared memory by the same
112
+ * step.
113
+ *
114
+ * @category constructors
115
+ * @since 0.1.0
116
+ */
117
+ export declare const make: <Payload, Outbound = never>(config: Config<Payload, Outbound>) => Webhook;
118
+ //# sourceMappingURL=Webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Webhook.d.ts","sourceRoot":"","sources":["../../src/Webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,eAAe,MAAM,0BAA0B,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE/D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAEhD,OAAO,KAAK,KAAK,OAAO,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,UAAU,EAAE,UAAU,UAAU,KAAG,OAM9E,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,CACjB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,KACzC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;CAC7C;AAcD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,eAAe,KAAG,OAAO,CAAC,MA2BvE,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,KAAK,CAAE,SAAQ,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IACtG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;CACtD;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACvE,QAAQ,CAAC,MAAM,EAAE,CACf,GAAG,EAAE,OAAO,CAAC,UAAU,KACpB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;CACnF;AA6DD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,GAAG,KAAK,EAC5C,QAAQ,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,KAChC,OAgCF,CAAA"}
@@ -0,0 +1,153 @@
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 Webhook_exports = {};
30
+ __export(Webhook_exports, {
31
+ constantTimeEqual: () => constantTimeEqual,
32
+ make: () => make,
33
+ makeSignatureVerifier: () => makeSignatureVerifier
34
+ });
35
+ module.exports = __toCommonJS(Webhook_exports);
36
+ var ControlChannels = __toESM(require("@smthrs/control/Channels"), 1);
37
+ var import_ControlError = require("@smthrs/control/ControlError");
38
+ var ControlWebhook = __toESM(require("@smthrs/control/WebhookChannel"), 1);
39
+ var Effect = __toESM(require("effect/Effect"), 1);
40
+ var Schema = __toESM(require("effect/Schema"), 1);
41
+ var import_TriggerError = require("./TriggerError.js");
42
+ const constantTimeEqual = (expected, supplied) => {
43
+ let difference = expected.length ^ supplied.length;
44
+ for (const [index, byte] of expected.entries()) {
45
+ difference |= byte ^ (supplied[index] ?? 0);
46
+ }
47
+ return difference === 0;
48
+ };
49
+ const ownedCopy = (body) => Uint8Array.from(body);
50
+ const makeSignatureVerifier = (config) => (raw, credential) => {
51
+ const refuse = (cause) => Effect.fail(
52
+ new import_TriggerError.TriggerError({
53
+ code: "verification_failed",
54
+ message: `webhook signature in ${config.header} did not verify`,
55
+ ...cause === void 0 ? {} : { cause }
56
+ })
57
+ );
58
+ return Effect.suspend(() => {
59
+ const supplied = raw.headers[config.header.toLowerCase()] ?? raw.headers[config.header];
60
+ if (supplied === void 0 || supplied.length === 0) {
61
+ return refuse();
62
+ }
63
+ const actual = new TextEncoder().encode(supplied);
64
+ return config.expected(ownedCopy(raw.body), credential).pipe(
65
+ Effect.matchEffect({
66
+ onFailure: (error) => refuse(error),
67
+ onSuccess: (expected) => expected.length > 0 && constantTimeEqual(expected, actual) ? Effect.void : refuse()
68
+ })
69
+ );
70
+ });
71
+ };
72
+ const invalidInput = (issue) => new import_ControlError.InvalidInput({ issue });
73
+ const toControlChannel = (config) => {
74
+ const channel = ControlWebhook.make({
75
+ name: config.name,
76
+ schema: config.schema,
77
+ credential: config.credential,
78
+ // The message is fixed here rather than forwarded from the verifier: a
79
+ // custom `verify` may say why it refused, and that reason travels toward
80
+ // the unauthenticated sender on the same error that answers a bad
81
+ // signature. `Unauthorized` carries no cause, so nothing else survives
82
+ // the crossing.
83
+ verify: (raw, credential) => config.verify(raw, credential).pipe(
84
+ Effect.mapError(() => new import_ControlError.Unauthorized({ message: `webhook ${config.name} did not verify the request` }))
85
+ ),
86
+ map: (payload) => {
87
+ const inbound = config.inbound(payload);
88
+ if ("start" in inbound) {
89
+ return Effect.succeed({
90
+ _tag: "Start",
91
+ flowId: inbound.start.flowId,
92
+ input: inbound.start.input
93
+ });
94
+ }
95
+ return Schema.decodeUnknownEffect(Schema.Json)(inbound.signal.value).pipe(
96
+ Effect.map(
97
+ (value) => ({
98
+ _tag: "Signal",
99
+ runId: inbound.signal.runId,
100
+ signal: {
101
+ name: inbound.signal.stepId,
102
+ payload: value
103
+ }
104
+ })
105
+ ),
106
+ Effect.mapError((error) => invalidInput(String(error)))
107
+ );
108
+ },
109
+ project: (run) => {
110
+ if (config.outbound === void 0) {
111
+ return {
112
+ cursor: String(run.updatedAt),
113
+ operation: "noop",
114
+ message: null
115
+ };
116
+ }
117
+ return {
118
+ cursor: String(run.updatedAt),
119
+ operation: "post",
120
+ message: config.outbound(run)
121
+ };
122
+ }
123
+ });
124
+ return channel;
125
+ };
126
+ const make = (config) => {
127
+ const channel = toControlChannel(config);
128
+ const register = Effect.flatMap(ControlChannels.Channels, (channels) => channels.register(channel));
129
+ return {
130
+ name: config.name,
131
+ register,
132
+ ingest: (raw) => {
133
+ const snapshot = {
134
+ body: ownedCopy(raw.body),
135
+ headers: { ...raw.headers },
136
+ idempotencyKey: raw.idempotencyKey
137
+ };
138
+ return Effect.gen(function* () {
139
+ const channels = yield* ControlChannels.Channels;
140
+ return yield* channels.ingest({ channel: config.name, raw: snapshot });
141
+ }).pipe(
142
+ Effect.mapError(
143
+ (error) => error instanceof import_ControlError.Unauthorized ? new import_TriggerError.TriggerError({
144
+ code: "verification_failed",
145
+ message: `webhook ${config.name} did not verify the request`,
146
+ cause: error
147
+ }) : error
148
+ )
149
+ );
150
+ }
151
+ };
152
+ };
153
+ //# sourceMappingURL=Webhook.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Webhook.ts"],
4
+ "sourcesContent": ["/**\n * Verified webhook ingestion through the authoritative Control boundary.\n *\n * @see packages/smithers/agent/triggers/docs/api.md\n * @since 0.1.0\n */\n\nimport * as ControlChannels from \"@smthrs/control/Channels\"\nimport type { ControlError } from \"@smthrs/control/ControlError\"\nimport { InvalidInput, Unauthorized } from \"@smthrs/control/ControlError\"\nimport type { Receipt, RunSummary } from \"@smthrs/control/ControlSchema\"\nimport type { CredentialRef } from \"@smthrs/control/Credential\"\nimport * as ControlWebhook from \"@smthrs/control/WebhookChannel\"\nimport * as Effect from \"effect/Effect\"\nimport type * as Redacted from \"effect/Redacted\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Channel from \"./Channel.ts\"\nimport { TriggerError } from \"./TriggerError.ts\"\n\n/**\n * Compares a supplied byte string against the expected one without returning\n * early on a mismatch.\n *\n * The loop runs exactly `expected.length` times, so its iteration count is\n * fixed by the secret side of the comparison and never by the caller's. Reading\n * the longer of the two instead let a caller lengthen its input until the work\n * stopped growing, which reports the expected signature's length. The length\n * difference is folded into the result, so inputs of unequal length always\n * disagree.\n *\n * @category verification\n * @since 0.1.0\n */\nexport const constantTimeEqual = (expected: Uint8Array, supplied: Uint8Array): boolean => {\n let difference = expected.length ^ supplied.length\n for (const [index, byte] of expected.entries()) {\n difference |= byte ^ (supplied[index] ?? 0)\n }\n return difference === 0\n}\n\n/**\n * Configuration for a raw-byte signature verifier.\n *\n * `expected` receives a private copy of the request bytes and the redacted\n * credential reference the channel was declared with, and answers with the\n * signature bytes the request must carry in `header`. It returns an Effect so\n * the secret is resolved through the host's resolver per request rather than\n * captured in a closure at declaration time, and so a resolution or HMAC\n * failure arrives as a typed `verification_failed` instead of a defect that\n * kills the fiber.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface SignatureConfig {\n readonly header: string\n readonly expected: (\n body: Uint8Array,\n credential: Redacted.Redacted<CredentialRef>\n ) => Effect.Effect<Uint8Array, TriggerError>\n}\n\n/**\n * Copies request bytes into memory nobody else holds.\n *\n * `body.slice()` is not enough: a Node `Buffer` is a `Uint8Array` whose\n * `slice` is an alias of `subarray` and answers with a view over the same\n * memory, so a caller that reused its `Buffer` after handing it over changed\n * what got verified, and a verifier that edited the bytes it was handed edited\n * the caller's. `Uint8Array.from` always allocates, and it copies only the\n * viewed range of an offset view and only a snapshot of a `SharedArrayBuffer`.\n */\nconst ownedCopy = (body: Uint8Array): Uint8Array => Uint8Array.from(body)\n\n/**\n * Builds a verifier that compares a signature header in constant time.\n *\n * An absent or empty header is refused before `expected` runs, and an\n * `expected` that answers with zero bytes is refused after it. Neither is a\n * comparison the constant-time equality can decide: it agrees on two empty\n * byte strings, so a request carrying no signature at all would have\n * authenticated against a secret that resolved to the empty string. A\n * zero-length expected signature is a misconfigured credential, never a valid\n * one, so the door stays closed instead of opening for everyone.\n *\n * Every refusal carries the same message, `webhook signature in <header> did\n * not verify`, whatever the reason. A failure raised by `expected` is kept in\n * `cause` only: its message names the credential reference or the resolver\n * that could not produce a secret, which is a host detail, and the refusal\n * that answers a bad signature answers a broken resolver the same way.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const makeSignatureVerifier = (config: SignatureConfig): Channel.Verify => (raw, credential) => {\n const refuse = (cause?: TriggerError) =>\n Effect.fail(\n new TriggerError({\n code: \"verification_failed\",\n message: `webhook signature in ${config.header} did not verify`,\n ...(cause === undefined ? {} : { cause })\n })\n )\n return Effect.suspend(() => {\n const supplied = raw.headers[config.header.toLowerCase()] ?? raw.headers[config.header]\n if (supplied === undefined || supplied.length === 0) {\n return refuse()\n }\n const actual = new TextEncoder().encode(supplied)\n // The verifier gets its own copy: nothing it does to these bytes can reach\n // the buffer that is about to be fingerprinted and decoded.\n return config.expected(ownedCopy(raw.body), credential).pipe(\n Effect.matchEffect({\n onFailure: (error) => refuse(error),\n onSuccess: (expected) =>\n expected.length > 0 && constantTimeEqual(expected, actual)\n ? Effect.void\n : refuse()\n })\n )\n })\n}\n\n/**\n * Webhook declaration configuration.\n *\n * `credential` is required. It used to default to a reference named after the\n * channel, so a webhook declared without one verified against whatever\n * credential happened to share its name instead of being refused, and two\n * declarations differing only in credential verified identically.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Config<Payload, Outbound = never> extends Channel.Config<Payload, RunSummary, Outbound> {\n readonly credential: Redacted.Redacted<CredentialRef>\n}\n\n/**\n * A webhook door that can only ingest through `Channels` and `Control`.\n *\n * @category services\n * @since 0.1.0\n */\nexport interface Webhook {\n readonly name: string\n readonly register: Effect.Effect<void, never, ControlChannels.Channels>\n readonly ingest: (\n raw: Channel.RawInbound\n ) => Effect.Effect<Receipt, ControlError | TriggerError, ControlChannels.Channels>\n}\n\nconst invalidInput = (issue: string): InvalidInput => new InvalidInput({ issue })\n\nconst toControlChannel = <Payload, Outbound>(\n config: Config<Payload, Outbound>\n): ControlChannels.Channel<Payload> => {\n const channel = ControlWebhook.make({\n name: config.name,\n schema: config.schema,\n credential: config.credential,\n // The message is fixed here rather than forwarded from the verifier: a\n // custom `verify` may say why it refused, and that reason travels toward\n // the unauthenticated sender on the same error that answers a bad\n // signature. `Unauthorized` carries no cause, so nothing else survives\n // the crossing.\n verify: (raw, credential) =>\n config.verify(raw, credential).pipe(\n Effect.mapError(() => new Unauthorized({ message: `webhook ${config.name} did not verify the request` }))\n ),\n map: (payload) => {\n const inbound = config.inbound(payload)\n if (\"start\" in inbound) {\n return Effect.succeed({\n _tag: \"Start\" as const,\n flowId: inbound.start.flowId,\n input: inbound.start.input\n })\n }\n return Schema.decodeUnknownEffect(Schema.Json)(inbound.signal.value).pipe(\n Effect.map(\n (value) => ({\n _tag: \"Signal\" as const,\n runId: inbound.signal.runId,\n signal: {\n name: inbound.signal.stepId,\n payload: value\n }\n })\n ),\n Effect.mapError((error) => invalidInput(String(error)))\n )\n },\n project: (run) => {\n if (config.outbound === undefined) {\n return {\n cursor: String(run.updatedAt),\n operation: \"noop\" as const,\n message: null\n }\n }\n return {\n cursor: String(run.updatedAt),\n operation: \"post\" as const,\n message: config.outbound(run)\n }\n }\n })\n return channel\n}\n\n/**\n * Builds a webhook door whose only dispatch path is the Control channel\n * coordinator.\n *\n * Verification occurs inside `Channels.ingest` before the adapter's JSON or\n * schema decoder and before any Control operation.\n *\n * `ingest` does not register: a channel is registered once, deliberately,\n * through {@link Webhook.register}, so traffic to an unregistered channel is\n * reported as unavailable rather than silently self-registering the door it\n * arrived at.\n *\n * `ingest` also copies `body`, `headers`, and `idempotencyKey` before anything\n * reads them. Verification, delivery fingerprinting, and decoding then all see\n * one private snapshot, so a verifier that edits the bytes it was handed, or a\n * caller that mutates its own object between building this Effect and running\n * it, cannot authenticate one payload and have another decoded. A\n * `SharedArrayBuffer`-backed view is copied out of shared memory by the same\n * step.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <Payload, Outbound = never>(\n config: Config<Payload, Outbound>\n): Webhook => {\n const channel = toControlChannel(config)\n const register = Effect.flatMap(ControlChannels.Channels, (channels) => channels.register(channel))\n return {\n name: config.name,\n register,\n ingest: (raw) => {\n // Copied here rather than inside the Effect: the copy has to happen when\n // the caller hands the request over, not when the returned Effect\n // eventually runs, or a caller that reuses its own buffer in between\n // changes what gets authenticated.\n const snapshot: Channel.RawInbound = {\n body: ownedCopy(raw.body),\n headers: { ...raw.headers },\n idempotencyKey: raw.idempotencyKey\n }\n return Effect.gen(function*() {\n const channels = yield* ControlChannels.Channels\n return yield* channels.ingest({ channel: config.name, raw: snapshot })\n }).pipe(\n Effect.mapError((error) =>\n error instanceof Unauthorized\n ? new TriggerError({\n code: \"verification_failed\",\n message: `webhook ${config.name} did not verify the request`,\n cause: error\n })\n : error\n )\n )\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `/triggers` — durable schedules and verified inbound channels.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ /** @category errors @since 0.1.0 */
7
+ export * as TriggerError from "./TriggerError.ts";
8
+ /** @category schedules @since 0.1.0 */
9
+ export * as Cron from "./Cron.ts";
10
+ /** @category declarations @since 0.1.0 */
11
+ export * as Trigger from "./Trigger.ts";
12
+ /** @category declarations @since 0.1.0 */
13
+ export * as Schedule from "./Schedule.ts";
14
+ /** @category policies @since 0.1.0 */
15
+ export * as Overlap from "./Overlap.ts";
16
+ /** @category policies @since 0.1.0 */
17
+ export * as CatchUp from "./CatchUp.ts";
18
+ /** @category services @since 0.1.0 */
19
+ export * as TriggerStore from "./TriggerStore.ts";
20
+ /** @category services @since 0.1.0 */
21
+ export * as SqlTriggerStore from "./SqlTriggerStore.ts";
22
+ /** @category scheduling @since 0.1.0 */
23
+ export * as Scheduler from "./Scheduler.ts";
24
+ /** @category services @since 1.0.0-rc.0 */
25
+ export * as DispatchReader from "./DispatchReader.ts";
26
+ /** @category channels @since 0.1.0 */
27
+ export * as Channel from "./Channel.ts";
28
+ /** @category webhooks @since 0.1.0 */
29
+ export * as Webhook from "./Webhook.ts";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oCAAoC;AACpC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AACjD,uCAAuC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,0CAA0C;AAC1C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,0CAA0C;AAC1C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,sCAAsC;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,sCAAsC;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,sCAAsC;AACtC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AACjD,sCAAsC;AACtC,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AACvD,wCAAwC;AACxC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,2CAA2C;AAC3C,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD,sCAAsC;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,sCAAsC;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
@@ -0,0 +1,57 @@
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 index_exports = {};
30
+ __export(index_exports, {
31
+ CatchUp: () => CatchUp,
32
+ Channel: () => Channel,
33
+ Cron: () => Cron,
34
+ DispatchReader: () => DispatchReader,
35
+ Overlap: () => Overlap,
36
+ Schedule: () => Schedule,
37
+ Scheduler: () => Scheduler,
38
+ SqlTriggerStore: () => SqlTriggerStore,
39
+ Trigger: () => Trigger,
40
+ TriggerError: () => TriggerError,
41
+ TriggerStore: () => TriggerStore,
42
+ Webhook: () => Webhook
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+ var TriggerError = __toESM(require("./TriggerError.js"), 1);
46
+ var Cron = __toESM(require("./Cron.js"), 1);
47
+ var Trigger = __toESM(require("./Trigger.js"), 1);
48
+ var Schedule = __toESM(require("./Schedule.js"), 1);
49
+ var Overlap = __toESM(require("./Overlap.js"), 1);
50
+ var CatchUp = __toESM(require("./CatchUp.js"), 1);
51
+ var TriggerStore = __toESM(require("./TriggerStore.js"), 1);
52
+ var SqlTriggerStore = __toESM(require("./SqlTriggerStore.js"), 1);
53
+ var Scheduler = __toESM(require("./Scheduler.js"), 1);
54
+ var DispatchReader = __toESM(require("./DispatchReader.js"), 1);
55
+ var Channel = __toESM(require("./Channel.js"), 1);
56
+ var Webhook = __toESM(require("./Webhook.js"), 1);
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": ["/**\n * `/triggers` \u2014 durable schedules and verified inbound channels.\n *\n * @since 0.1.0\n */\n\n/** @category errors @since 0.1.0 */\nexport * as TriggerError from \"./TriggerError.ts\"\n/** @category schedules @since 0.1.0 */\nexport * as Cron from \"./Cron.ts\"\n/** @category declarations @since 0.1.0 */\nexport * as Trigger from \"./Trigger.ts\"\n/** @category declarations @since 0.1.0 */\nexport * as Schedule from \"./Schedule.ts\"\n/** @category policies @since 0.1.0 */\nexport * as Overlap from \"./Overlap.ts\"\n/** @category policies @since 0.1.0 */\nexport * as CatchUp from \"./CatchUp.ts\"\n/** @category services @since 0.1.0 */\nexport * as TriggerStore from \"./TriggerStore.ts\"\n/** @category services @since 0.1.0 */\nexport * as SqlTriggerStore from \"./SqlTriggerStore.ts\"\n/** @category scheduling @since 0.1.0 */\nexport * as Scheduler from \"./Scheduler.ts\"\n/** @category services @since 1.0.0-rc.0 */\nexport * as DispatchReader from \"./DispatchReader.ts\"\n/** @category channels @since 0.1.0 */\nexport * as Channel from \"./Channel.ts\"\n/** @category webhooks @since 0.1.0 */\nexport * as Webhook from \"./Webhook.ts\"\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,mBAA8B;AAE9B,WAAsB;AAEtB,cAAyB;AAEzB,eAA0B;AAE1B,cAAyB;AAEzB,cAAyB;AAEzB,mBAA8B;AAE9B,sBAAiC;AAEjC,gBAA2B;AAE3B,qBAAgC;AAEhC,cAAyB;AAEzB,cAAyB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What one scheduler process knows about the run each trigger owns.
3
+ *
4
+ * @since 1.0.0-rc.0
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import type * as Fiber from "effect/Fiber";
8
+ /**
9
+ * What this process knows about the run one trigger currently owns: which
10
+ * occurrence claimed it, the reservation or run id the store holds for it, and
11
+ * the monitor fiber when this process is the one watching it.
12
+ *
13
+ * `runId` is always known. A claim that hands out work names the reservation it
14
+ * wrote, and a recovered entry is read back from the store, so an entry with
15
+ * nothing to release cannot be constructed.
16
+ *
17
+ * @private
18
+ * @since 1.0.0-rc.0
19
+ */
20
+ export interface Active {
21
+ readonly occurrence: number;
22
+ readonly runId: string;
23
+ readonly fiber?: Fiber.Fiber<void> | undefined;
24
+ }
25
+ /**
26
+ * The entries of one scheduler, keyed by trigger id.
27
+ *
28
+ * `take` is the only unfenced write: an occurrence that claimed the trigger,
29
+ * or a run recovered from the store, replaces whatever entry was there. Every
30
+ * later write is fenced on the occurrence that took the entry, so one guard
31
+ * states the rule once: a launch that has been superseded, or whose run
32
+ * already settled, no longer owns the entry and must not write to it. Spelling
33
+ * the fence out at each site is how the three copies of it drifted apart.
34
+ *
35
+ * @private
36
+ * @since 1.0.0-rc.0
37
+ */
38
+ export interface ActiveRuns {
39
+ readonly get: (triggerId: string) => Effect.Effect<Active | undefined>;
40
+ readonly take: (triggerId: string, entry: Active) => Effect.Effect<void>;
41
+ readonly update: (triggerId: string, occurrence: number, change: (entry: Active) => Active) => Effect.Effect<void>;
42
+ readonly remove: (triggerId: string, occurrence: number) => Effect.Effect<void>;
43
+ }
44
+ /**
45
+ * Allocates an empty set of entries.
46
+ *
47
+ * @private
48
+ * @since 1.0.0-rc.0
49
+ */
50
+ export declare const make: Effect.Effect<ActiveRuns>;
51
+ //# sourceMappingURL=ActiveRuns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActiveRuns.d.ts","sourceRoot":"","sources":["../../../src/internal/ActiveRuns.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAG1C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,CAAA;CAC/C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACtE,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxE,QAAQ,CAAC,MAAM,EAAE,CACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,KAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAChF;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAwB1C,CAAA"}
@@ -0,0 +1,56 @@
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 ActiveRuns_exports = {};
30
+ __export(ActiveRuns_exports, {
31
+ make: () => make
32
+ });
33
+ module.exports = __toCommonJS(ActiveRuns_exports);
34
+ var Effect = __toESM(require("effect/Effect"), 1);
35
+ var Ref = __toESM(require("effect/Ref"), 1);
36
+ const make = Effect.map(
37
+ Ref.make(/* @__PURE__ */ new Map()),
38
+ (entries) => {
39
+ const fenced = (triggerId, occurrence, change) => Ref.update(entries, (current) => {
40
+ const entry = current.get(triggerId);
41
+ if (entry?.occurrence !== occurrence) return current;
42
+ const next = new Map(current);
43
+ const updated = change(entry);
44
+ if (updated === void 0) next.delete(triggerId);
45
+ else next.set(triggerId, updated);
46
+ return next;
47
+ });
48
+ return {
49
+ get: (triggerId) => Effect.map(Ref.get(entries), (current) => current.get(triggerId)),
50
+ take: (triggerId, entry) => Ref.update(entries, (current) => new Map(current).set(triggerId, entry)),
51
+ update: fenced,
52
+ remove: (triggerId, occurrence) => fenced(triggerId, occurrence, () => void 0)
53
+ };
54
+ }
55
+ );
56
+ //# sourceMappingURL=ActiveRuns.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/internal/ActiveRuns.ts"],
4
+ "sourcesContent": ["/**\n * What one scheduler process knows about the run each trigger owns.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport type * as Fiber from \"effect/Fiber\"\nimport * as Ref from \"effect/Ref\"\n\n/**\n * What this process knows about the run one trigger currently owns: which\n * occurrence claimed it, the reservation or run id the store holds for it, and\n * the monitor fiber when this process is the one watching it.\n *\n * `runId` is always known. A claim that hands out work names the reservation it\n * wrote, and a recovered entry is read back from the store, so an entry with\n * nothing to release cannot be constructed.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface Active {\n readonly occurrence: number\n readonly runId: string\n readonly fiber?: Fiber.Fiber<void> | undefined\n}\n\n/**\n * The entries of one scheduler, keyed by trigger id.\n *\n * `take` is the only unfenced write: an occurrence that claimed the trigger,\n * or a run recovered from the store, replaces whatever entry was there. Every\n * later write is fenced on the occurrence that took the entry, so one guard\n * states the rule once: a launch that has been superseded, or whose run\n * already settled, no longer owns the entry and must not write to it. Spelling\n * the fence out at each site is how the three copies of it drifted apart.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport interface ActiveRuns {\n readonly get: (triggerId: string) => Effect.Effect<Active | undefined>\n readonly take: (triggerId: string, entry: Active) => Effect.Effect<void>\n readonly update: (\n triggerId: string,\n occurrence: number,\n change: (entry: Active) => Active\n ) => Effect.Effect<void>\n readonly remove: (triggerId: string, occurrence: number) => Effect.Effect<void>\n}\n\n/**\n * Allocates an empty set of entries.\n *\n * @private\n * @since 1.0.0-rc.0\n */\nexport const make: Effect.Effect<ActiveRuns> = Effect.map(\n Ref.make<ReadonlyMap<string, Active>>(new Map()),\n (entries) => {\n const fenced = (\n triggerId: string,\n occurrence: number,\n change: (entry: Active) => Active | undefined\n ): Effect.Effect<void> =>\n Ref.update(entries, (current) => {\n const entry = current.get(triggerId)\n if (entry?.occurrence !== occurrence) return current\n const next = new Map(current)\n const updated = change(entry)\n if (updated === undefined) next.delete(triggerId)\n else next.set(triggerId, updated)\n return next\n })\n return {\n get: (triggerId) => Effect.map(Ref.get(entries), (current) => current.get(triggerId)),\n take: (triggerId, entry) => Ref.update(entries, (current) => new Map(current).set(triggerId, entry)),\n update: fenced,\n remove: (triggerId, occurrence) => fenced(triggerId, occurrence, () => undefined)\n }\n }\n)\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,aAAwB;AAExB,UAAqB;AAkDd,MAAM,OAAkC,OAAO;AAAA,EACpD,IAAI,KAAkC,oBAAI,IAAI,CAAC;AAAA,EAC/C,CAAC,YAAY;AACX,UAAM,SAAS,CACb,WACA,YACA,WAEA,IAAI,OAAO,SAAS,CAAC,YAAY;AAC/B,YAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,UAAI,OAAO,eAAe,WAAY,QAAO;AAC7C,YAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,YAAM,UAAU,OAAO,KAAK;AAC5B,UAAI,YAAY,OAAW,MAAK,OAAO,SAAS;AAAA,UAC3C,MAAK,IAAI,WAAW,OAAO;AAChC,aAAO;AAAA,IACT,CAAC;AACH,WAAO;AAAA,MACL,KAAK,CAAC,cAAc,OAAO,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,YAAY,QAAQ,IAAI,SAAS,CAAC;AAAA,MACpF,MAAM,CAAC,WAAW,UAAU,IAAI,OAAO,SAAS,CAAC,YAAY,IAAI,IAAI,OAAO,EAAE,IAAI,WAAW,KAAK,CAAC;AAAA,MACnG,QAAQ;AAAA,MACR,QAAQ,CAAC,WAAW,eAAe,OAAO,WAAW,YAAY,MAAM,MAAS;AAAA,IAClF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,32 @@
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 type { TriggerError } from "../TriggerError.ts";
8
+ import type { Registered } from "../TriggerStore.ts";
9
+ /**
10
+ * The occurrences to claim, oldest first, and the watermark that stands once
11
+ * every one of them has been dispatched.
12
+ *
13
+ * @private
14
+ * @since 1.0.0-rc.0
15
+ */
16
+ export interface Due {
17
+ readonly occurrences: ReadonlyArray<number>;
18
+ readonly watermark: number;
19
+ }
20
+ /**
21
+ * Computes what a trigger owes at `now`, given the watermark this process has
22
+ * already dispatched through, or `undefined` on first sight of the trigger.
23
+ *
24
+ * The answer depends on nothing but its arguments. The watermark is the
25
+ * caller's to keep: it only moves forward, and only past occurrences the
26
+ * caller finished dispatching.
27
+ *
28
+ * @private
29
+ * @since 1.0.0-rc.0
30
+ */
31
+ export declare const compute: (trigger: Registered, now: number, observed: number | undefined) => Effect.Effect<Due, TriggerError>;
32
+ //# sourceMappingURL=DueOccurrences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DueOccurrences.d.ts","sourceRoot":"","sources":["../../../src/internal/DueOccurrences.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAoBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,GAClB,SAAS,UAAU,EACnB,KAAK,MAAM,EACX,UAAU,MAAM,GAAG,SAAS,KAC3B,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,CA2C9B,CAAA"}