@zapier/zapier-sdk 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +818 -70
  3. package/dist/api/client.d.ts.map +1 -1
  4. package/dist/api/client.js +10 -1
  5. package/dist/api/polling.d.ts +7 -0
  6. package/dist/api/polling.d.ts.map +1 -1
  7. package/dist/api/polling.js +29 -4
  8. package/dist/api/types.d.ts +7 -0
  9. package/dist/api/types.d.ts.map +1 -1
  10. package/dist/experimental.cjs +10389 -0
  11. package/dist/experimental.d.mts +2292 -0
  12. package/dist/experimental.d.ts +2308 -0
  13. package/dist/experimental.d.ts.map +1 -0
  14. package/dist/experimental.js +155 -0
  15. package/dist/experimental.mjs +10207 -0
  16. package/dist/index-C2vsvjgN.d.mts +9539 -0
  17. package/dist/index-C2vsvjgN.d.ts +9539 -0
  18. package/dist/index.cjs +813 -602
  19. package/dist/index.d.mts +6 -8562
  20. package/dist/index.d.ts +7 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -1
  23. package/dist/index.mjs +801 -602
  24. package/dist/plugins/apps/index.d.ts +1 -1
  25. package/dist/plugins/deprecated/inputFields.d.ts +468 -0
  26. package/dist/plugins/deprecated/inputFields.d.ts.map +1 -0
  27. package/dist/plugins/deprecated/inputFields.js +73 -0
  28. package/dist/plugins/fetch/index.d.ts +1 -1
  29. package/dist/plugins/fetch/schemas.d.ts +1 -1
  30. package/dist/plugins/{getInputFieldsSchema → getActionInputFieldsSchema}/index.d.ts +4 -4
  31. package/dist/plugins/getActionInputFieldsSchema/index.d.ts.map +1 -0
  32. package/dist/plugins/{getInputFieldsSchema → getActionInputFieldsSchema}/index.js +4 -4
  33. package/dist/plugins/{getInputFieldsSchema → getActionInputFieldsSchema}/schemas.d.ts +8 -8
  34. package/dist/plugins/getActionInputFieldsSchema/schemas.d.ts.map +1 -0
  35. package/dist/plugins/{getInputFieldsSchema → getActionInputFieldsSchema}/schemas.js +13 -10
  36. package/dist/plugins/{listInputFieldChoices → listActionInputFieldChoices}/index.d.ts +4 -4
  37. package/dist/plugins/listActionInputFieldChoices/index.d.ts.map +1 -0
  38. package/dist/plugins/{listInputFieldChoices → listActionInputFieldChoices}/index.js +4 -4
  39. package/dist/plugins/{listInputFieldChoices → listActionInputFieldChoices}/schemas.d.ts +10 -10
  40. package/dist/plugins/listActionInputFieldChoices/schemas.d.ts.map +1 -0
  41. package/dist/plugins/{listInputFieldChoices → listActionInputFieldChoices}/schemas.js +13 -10
  42. package/dist/plugins/{listInputFields → listActionInputFields}/index.d.ts +4 -4
  43. package/dist/plugins/listActionInputFields/index.d.ts.map +1 -0
  44. package/dist/plugins/{listInputFields → listActionInputFields}/index.js +4 -4
  45. package/dist/plugins/{listInputFields → listActionInputFields}/schemas.d.ts +9 -9
  46. package/dist/plugins/listActionInputFields/schemas.d.ts.map +1 -0
  47. package/dist/plugins/{listInputFields → listActionInputFields}/schemas.js +7 -7
  48. package/dist/plugins/request/index.d.ts +2 -2
  49. package/dist/plugins/request/schemas.d.ts +2 -2
  50. package/dist/plugins/triggers/ackTriggerInboxMessages/index.d.ts +51 -0
  51. package/dist/plugins/triggers/ackTriggerInboxMessages/index.d.ts.map +1 -0
  52. package/dist/plugins/triggers/ackTriggerInboxMessages/index.js +35 -0
  53. package/dist/plugins/triggers/ackTriggerInboxMessages/schemas.d.ts +34 -0
  54. package/dist/plugins/triggers/ackTriggerInboxMessages/schemas.d.ts.map +1 -0
  55. package/dist/plugins/triggers/ackTriggerInboxMessages/schemas.js +17 -0
  56. package/dist/plugins/triggers/createTriggerInbox/index.d.ts +62 -0
  57. package/dist/plugins/triggers/createTriggerInbox/index.d.ts.map +1 -0
  58. package/dist/plugins/triggers/createTriggerInbox/index.js +54 -0
  59. package/dist/plugins/triggers/createTriggerInbox/schemas.d.ts +20 -0
  60. package/dist/plugins/triggers/createTriggerInbox/schemas.d.ts.map +1 -0
  61. package/dist/plugins/triggers/createTriggerInbox/schemas.js +20 -0
  62. package/dist/plugins/triggers/deleteTriggerInbox/index.d.ts +50 -0
  63. package/dist/plugins/triggers/deleteTriggerInbox/index.d.ts.map +1 -0
  64. package/dist/plugins/triggers/deleteTriggerInbox/index.js +27 -0
  65. package/dist/plugins/triggers/deleteTriggerInbox/schemas.d.ts +14 -0
  66. package/dist/plugins/triggers/deleteTriggerInbox/schemas.d.ts.map +1 -0
  67. package/dist/plugins/triggers/deleteTriggerInbox/schemas.js +9 -0
  68. package/dist/plugins/triggers/drainTriggerInbox/index.d.ts +213 -0
  69. package/dist/plugins/triggers/drainTriggerInbox/index.d.ts.map +1 -0
  70. package/dist/plugins/triggers/drainTriggerInbox/index.js +227 -0
  71. package/dist/plugins/triggers/drainTriggerInbox/pipeline.d.ts +56 -0
  72. package/dist/plugins/triggers/drainTriggerInbox/pipeline.d.ts.map +1 -0
  73. package/dist/plugins/triggers/drainTriggerInbox/pipeline.js +225 -0
  74. package/dist/plugins/triggers/drainTriggerInbox/schemas.d.ts +104 -0
  75. package/dist/plugins/triggers/drainTriggerInbox/schemas.d.ts.map +1 -0
  76. package/dist/plugins/triggers/drainTriggerInbox/schemas.js +102 -0
  77. package/dist/plugins/triggers/ensureTriggerInbox/index.d.ts +63 -0
  78. package/dist/plugins/triggers/ensureTriggerInbox/index.d.ts.map +1 -0
  79. package/dist/plugins/triggers/ensureTriggerInbox/index.js +77 -0
  80. package/dist/plugins/triggers/ensureTriggerInbox/schemas.d.ts +21 -0
  81. package/dist/plugins/triggers/ensureTriggerInbox/schemas.d.ts.map +1 -0
  82. package/dist/plugins/triggers/ensureTriggerInbox/schemas.js +21 -0
  83. package/dist/plugins/triggers/getTriggerInbox/index.d.ts +50 -0
  84. package/dist/plugins/triggers/getTriggerInbox/index.d.ts.map +1 -0
  85. package/dist/plugins/triggers/getTriggerInbox/index.js +26 -0
  86. package/dist/plugins/triggers/getTriggerInbox/schemas.d.ts +14 -0
  87. package/dist/plugins/triggers/getTriggerInbox/schemas.d.ts.map +1 -0
  88. package/dist/plugins/triggers/getTriggerInbox/schemas.js +9 -0
  89. package/dist/plugins/triggers/getTriggerInputFieldsSchema/index.d.ts +48 -0
  90. package/dist/plugins/triggers/getTriggerInputFieldsSchema/index.d.ts.map +1 -0
  91. package/dist/plugins/triggers/getTriggerInputFieldsSchema/index.js +23 -0
  92. package/dist/plugins/triggers/getTriggerInputFieldsSchema/schemas.d.ts +13 -0
  93. package/dist/plugins/triggers/getTriggerInputFieldsSchema/schemas.d.ts.map +1 -0
  94. package/dist/plugins/triggers/getTriggerInputFieldsSchema/schemas.js +10 -0
  95. package/dist/plugins/triggers/leaseTriggerInboxMessages/index.d.ts +58 -0
  96. package/dist/plugins/triggers/leaseTriggerInboxMessages/index.d.ts.map +1 -0
  97. package/dist/plugins/triggers/leaseTriggerInboxMessages/index.js +70 -0
  98. package/dist/plugins/triggers/leaseTriggerInboxMessages/schemas.d.ts +57 -0
  99. package/dist/plugins/triggers/leaseTriggerInboxMessages/schemas.d.ts.map +1 -0
  100. package/dist/plugins/triggers/leaseTriggerInboxMessages/schemas.js +25 -0
  101. package/dist/plugins/triggers/listTriggerInboxMessages/index.d.ts +51 -0
  102. package/dist/plugins/triggers/listTriggerInboxMessages/index.d.ts.map +1 -0
  103. package/dist/plugins/triggers/listTriggerInboxMessages/index.js +48 -0
  104. package/dist/plugins/triggers/listTriggerInboxMessages/schemas.d.ts +37 -0
  105. package/dist/plugins/triggers/listTriggerInboxMessages/schemas.d.ts.map +1 -0
  106. package/dist/plugins/triggers/listTriggerInboxMessages/schemas.js +27 -0
  107. package/dist/plugins/triggers/listTriggerInboxes/index.d.ts +56 -0
  108. package/dist/plugins/triggers/listTriggerInboxes/index.d.ts.map +1 -0
  109. package/dist/plugins/triggers/listTriggerInboxes/index.js +51 -0
  110. package/dist/plugins/triggers/listTriggerInboxes/schemas.d.ts +58 -0
  111. package/dist/plugins/triggers/listTriggerInboxes/schemas.d.ts.map +1 -0
  112. package/dist/plugins/triggers/listTriggerInboxes/schemas.js +35 -0
  113. package/dist/plugins/triggers/listTriggerInputFieldChoices/index.d.ts +77 -0
  114. package/dist/plugins/triggers/listTriggerInputFieldChoices/index.d.ts.map +1 -0
  115. package/dist/plugins/triggers/listTriggerInputFieldChoices/index.js +30 -0
  116. package/dist/plugins/triggers/listTriggerInputFieldChoices/schemas.d.ts +20 -0
  117. package/dist/plugins/triggers/listTriggerInputFieldChoices/schemas.d.ts.map +1 -0
  118. package/dist/plugins/triggers/listTriggerInputFieldChoices/schemas.js +28 -0
  119. package/dist/plugins/triggers/listTriggerInputFields/index.d.ts +101 -0
  120. package/dist/plugins/triggers/listTriggerInputFields/index.d.ts.map +1 -0
  121. package/dist/plugins/triggers/listTriggerInputFields/index.js +33 -0
  122. package/dist/plugins/triggers/listTriggerInputFields/schemas.d.ts +16 -0
  123. package/dist/plugins/triggers/listTriggerInputFields/schemas.d.ts.map +1 -0
  124. package/dist/plugins/triggers/listTriggerInputFields/schemas.js +21 -0
  125. package/dist/plugins/triggers/pauseTriggerInbox/index.d.ts +50 -0
  126. package/dist/plugins/triggers/pauseTriggerInbox/index.d.ts.map +1 -0
  127. package/dist/plugins/triggers/pauseTriggerInbox/index.js +26 -0
  128. package/dist/plugins/triggers/pauseTriggerInbox/schemas.d.ts +14 -0
  129. package/dist/plugins/triggers/pauseTriggerInbox/schemas.d.ts.map +1 -0
  130. package/dist/plugins/triggers/pauseTriggerInbox/schemas.js +9 -0
  131. package/dist/plugins/triggers/releaseTriggerInboxMessages/index.d.ts +51 -0
  132. package/dist/plugins/triggers/releaseTriggerInboxMessages/index.d.ts.map +1 -0
  133. package/dist/plugins/triggers/releaseTriggerInboxMessages/index.js +37 -0
  134. package/dist/plugins/triggers/releaseTriggerInboxMessages/schemas.d.ts +34 -0
  135. package/dist/plugins/triggers/releaseTriggerInboxMessages/schemas.d.ts.map +1 -0
  136. package/dist/plugins/triggers/releaseTriggerInboxMessages/schemas.js +17 -0
  137. package/dist/plugins/triggers/resumeTriggerInbox/index.d.ts +50 -0
  138. package/dist/plugins/triggers/resumeTriggerInbox/index.d.ts.map +1 -0
  139. package/dist/plugins/triggers/resumeTriggerInbox/index.js +26 -0
  140. package/dist/plugins/triggers/resumeTriggerInbox/schemas.d.ts +14 -0
  141. package/dist/plugins/triggers/resumeTriggerInbox/schemas.d.ts.map +1 -0
  142. package/dist/plugins/triggers/resumeTriggerInbox/schemas.js +9 -0
  143. package/dist/plugins/triggers/shared.d.ts +17 -0
  144. package/dist/plugins/triggers/shared.d.ts.map +1 -0
  145. package/dist/plugins/triggers/shared.js +16 -0
  146. package/dist/plugins/triggers/updateTriggerInbox/index.d.ts +51 -0
  147. package/dist/plugins/triggers/updateTriggerInbox/index.d.ts.map +1 -0
  148. package/dist/plugins/triggers/updateTriggerInbox/index.js +30 -0
  149. package/dist/plugins/triggers/updateTriggerInbox/schemas.d.ts +15 -0
  150. package/dist/plugins/triggers/updateTriggerInbox/schemas.d.ts.map +1 -0
  151. package/dist/plugins/triggers/updateTriggerInbox/schemas.js +15 -0
  152. package/dist/plugins/triggers/utils.d.ts +17 -0
  153. package/dist/plugins/triggers/utils.d.ts.map +1 -0
  154. package/dist/plugins/triggers/utils.js +28 -0
  155. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts +163 -0
  156. package/dist/plugins/triggers/watchTriggerInbox/index.d.ts.map +1 -0
  157. package/dist/plugins/triggers/watchTriggerInbox/index.js +111 -0
  158. package/dist/registry.d.ts.map +1 -1
  159. package/dist/registry.js +2 -0
  160. package/dist/resolvers/actionKey.d.ts +1 -0
  161. package/dist/resolvers/actionKey.d.ts.map +1 -1
  162. package/dist/resolvers/actionKey.js +1 -1
  163. package/dist/resolvers/index.d.ts +2 -2
  164. package/dist/resolvers/index.d.ts.map +1 -1
  165. package/dist/resolvers/index.js +2 -2
  166. package/dist/resolvers/triggerInbox.d.ts +4 -0
  167. package/dist/resolvers/triggerInbox.d.ts.map +1 -0
  168. package/dist/resolvers/triggerInbox.js +18 -0
  169. package/dist/schemas/TriggerInbox.d.ts +59 -0
  170. package/dist/schemas/TriggerInbox.d.ts.map +1 -0
  171. package/dist/schemas/TriggerInbox.js +81 -0
  172. package/dist/schemas/TriggerMessage.d.ts +48 -0
  173. package/dist/schemas/TriggerMessage.d.ts.map +1 -0
  174. package/dist/schemas/TriggerMessage.js +79 -0
  175. package/dist/sdk.d.ts +621 -17
  176. package/dist/sdk.d.ts.map +1 -1
  177. package/dist/sdk.js +26 -9
  178. package/dist/types/errors.d.ts +13 -0
  179. package/dist/types/errors.d.ts.map +1 -1
  180. package/dist/types/errors.js +16 -0
  181. package/dist/types/plugin.d.ts +12 -1
  182. package/dist/types/plugin.d.ts.map +1 -1
  183. package/dist/types/properties.d.ts +12 -0
  184. package/dist/types/properties.d.ts.map +1 -1
  185. package/dist/types/properties.js +25 -0
  186. package/dist/types/registry.d.ts +5 -0
  187. package/dist/types/registry.d.ts.map +1 -1
  188. package/dist/types/sdk.d.ts +2 -2
  189. package/dist/types/sdk.d.ts.map +1 -1
  190. package/dist/types/signals.d.ts +20 -0
  191. package/dist/types/signals.d.ts.map +1 -0
  192. package/dist/types/signals.js +21 -0
  193. package/dist/utils/abort-utils.d.ts +13 -0
  194. package/dist/utils/abort-utils.d.ts.map +1 -1
  195. package/dist/utils/abort-utils.js +15 -0
  196. package/dist/utils/retry-utils.d.ts +6 -2
  197. package/dist/utils/retry-utils.d.ts.map +1 -1
  198. package/dist/utils/retry-utils.js +22 -3
  199. package/dist/utils/schema-utils.d.ts +19 -1
  200. package/dist/utils/schema-utils.d.ts.map +1 -1
  201. package/package.json +12 -1
  202. package/dist/plugins/getInputFieldsSchema/index.d.ts.map +0 -1
  203. package/dist/plugins/getInputFieldsSchema/schemas.d.ts.map +0 -1
  204. package/dist/plugins/listInputFieldChoices/index.d.ts.map +0 -1
  205. package/dist/plugins/listInputFieldChoices/schemas.d.ts.map +0 -1
  206. package/dist/plugins/listInputFields/index.d.ts.map +0 -1
  207. package/dist/plugins/listInputFields/schemas.d.ts.map +0 -1
@@ -0,0 +1,227 @@
1
+ import { definePlugin } from "../../../utils/plugin-utils";
2
+ import { DrainTriggerInboxSchema, ZapierAbortDrainSignal, ZapierReleaseTriggerMessageSignal, } from "./schemas";
3
+ import { runBatchedDrainPipeline } from "./pipeline";
4
+ import { ZapierApiError, ZapierValidationError } from "../../../types/errors";
5
+ import { ZapierSignal } from "../../../types/signals";
6
+ import { triggerInboxResolver } from "../../../resolvers";
7
+ import { resolveTriggerInboxId } from "../utils";
8
+ import { triggersDefaults } from "../shared";
9
+ import { isAbortError } from "../../../utils/abort-utils";
10
+ export { ZapierAbortDrainSignal, ZapierReleaseTriggerMessageSignal, } from "./schemas";
11
+ /**
12
+ * Server returns 400 lease_expired when a UUIDv7 timestamp is in the
13
+ * past. The API atomically quarantines messages that have hit
14
+ * MESSAGE_LEASE_MAX before raising — work has been done server-side
15
+ * already, so failing the drain on a clock-skew race would be a poor
16
+ * UX.
17
+ */
18
+ function isLeaseExpiredError(err) {
19
+ if (!(err instanceof ZapierValidationError))
20
+ return false;
21
+ return err.errors?.some((e) => e.code === "lease_expired") ?? false;
22
+ }
23
+ /**
24
+ * One drain pass through `runBatchedDrainPipeline`. Used directly by
25
+ * `drainTriggerInbox` (single pass) and as the inner loop body of
26
+ * `watchTriggerInbox` (called repeatedly with poll backoff between
27
+ * empty passes).
28
+ */
29
+ export async function runDrainPass(options) {
30
+ const { sdk, inboxId, onMessage, concurrency, leaseLimit, leaseSeconds, maxMessages, releaseOnError, continueOnError, onError, signal, } = options;
31
+ let firstFetch = options.firstFetch;
32
+ let abortedFromCallback = false;
33
+ let firstHandlerError = undefined;
34
+ const outcomes = await runBatchedDrainPipeline({
35
+ concurrency,
36
+ maxItems: maxMessages,
37
+ leaseSize: leaseLimit,
38
+ fetchBatch: async (requestedSize) => {
39
+ if (signal?.aborted)
40
+ return "exhausted";
41
+ let lease;
42
+ try {
43
+ ({ data: lease } = await sdk.leaseTriggerInboxMessages({
44
+ inbox: inboxId,
45
+ leaseLimit: requestedSize,
46
+ leaseSeconds,
47
+ signal,
48
+ }));
49
+ }
50
+ catch (err) {
51
+ // Only swallow abort-shaped failures. A real auth / validation
52
+ // / transport error that races with cancellation must still
53
+ // reject — checking `signal?.aborted` alone would silently hide
54
+ // a 401 that landed in the same tick the user pressed Ctrl-C.
55
+ if (signal?.aborted && isAbortError(err))
56
+ return "exhausted";
57
+ throw err;
58
+ }
59
+ if (lease.results.length === 0) {
60
+ if (firstFetch &&
61
+ lease.inbox_attributes.status === "initialization_failure") {
62
+ throw new ZapierApiError(`Trigger inbox ${inboxId} is in initialization_failure state — inspect via getTriggerInbox.`);
63
+ }
64
+ firstFetch = false;
65
+ return "exhausted";
66
+ }
67
+ firstFetch = false;
68
+ if (!lease.lease_id)
69
+ return "exhausted";
70
+ return { id: lease.lease_id, items: lease.results };
71
+ },
72
+ processItem: async (message) => {
73
+ if (signal?.aborted) {
74
+ return {
75
+ value: message,
76
+ action: "release",
77
+ abort: true,
78
+ };
79
+ }
80
+ try {
81
+ await onMessage(message);
82
+ return { value: message, action: "ack" };
83
+ }
84
+ catch (err) {
85
+ let action = "ignore";
86
+ let abort = false;
87
+ if (err instanceof ZapierAbortDrainSignal) {
88
+ abort = true;
89
+ abortedFromCallback = true;
90
+ if (releaseOnError)
91
+ action = "release";
92
+ }
93
+ else if (err instanceof ZapierReleaseTriggerMessageSignal) {
94
+ action = "release";
95
+ }
96
+ else if (releaseOnError) {
97
+ action = "release";
98
+ }
99
+ // Observe (signals filtered — they're control-flow, not failures).
100
+ if (onError && !(err instanceof ZapierSignal)) {
101
+ try {
102
+ await onError(err, message);
103
+ }
104
+ catch {
105
+ // observer's own error is intentionally swallowed.
106
+ }
107
+ }
108
+ // Fail-fast: capture the first real error and tell the
109
+ // pipeline to abort. continueOnError keeps it running.
110
+ if (!continueOnError && !(err instanceof ZapierSignal)) {
111
+ if (firstHandlerError === undefined)
112
+ firstHandlerError = err;
113
+ abort = true;
114
+ }
115
+ return { value: message, action, abort };
116
+ }
117
+ },
118
+ ackItem: async (leaseId, messageId) => {
119
+ try {
120
+ await sdk.ackTriggerInboxMessages({
121
+ inbox: inboxId,
122
+ lease: leaseId,
123
+ messages: [messageId],
124
+ });
125
+ }
126
+ catch (err) {
127
+ if (!isLeaseExpiredError(err))
128
+ throw err;
129
+ }
130
+ },
131
+ releaseItems: async (leaseId, messageIds) => {
132
+ try {
133
+ await sdk.releaseTriggerInboxMessages({
134
+ inbox: inboxId,
135
+ lease: leaseId,
136
+ messages: messageIds,
137
+ });
138
+ }
139
+ catch (err) {
140
+ if (!isLeaseExpiredError(err))
141
+ throw err;
142
+ }
143
+ },
144
+ });
145
+ if (firstHandlerError !== undefined)
146
+ throw firstHandlerError;
147
+ return { abortedFromCallback, processed: outcomes.length };
148
+ }
149
+ /**
150
+ * Validate the per-message callback. `onMessage` is required — no
151
+ * identity default, since "lease and ack everything in this inbox"
152
+ * is a footgun on a casual call. JS callers that skip onMessage hit
153
+ * this guard at runtime; TS callers are rejected at the call site.
154
+ */
155
+ export function requireOnMessage(onMessage) {
156
+ if (typeof onMessage !== "function") {
157
+ throw new ZapierValidationError("onMessage is required.");
158
+ }
159
+ return onMessage;
160
+ }
161
+ /**
162
+ * Compute concurrency and lease size with symmetric defaulting:
163
+ *
164
+ * both unset -> concurrency=1, leaseLimit=1
165
+ * concurrency=N -> concurrency=N, leaseLimit=N
166
+ * leaseLimit=L -> concurrency=L, leaseLimit=L
167
+ * both set -> use both as given
168
+ *
169
+ * The symmetry kills the "set leaseLimit alone, get prefetch
170
+ * pathology" footgun: a leased message buffer is never larger than
171
+ * the worker pool that can immediately work it.
172
+ */
173
+ export function resolveConcurrencyAndLease(options) {
174
+ const concurrency = options.concurrency ?? options.leaseLimit ?? 1;
175
+ const leaseLimit = options.leaseLimit ?? concurrency;
176
+ return { concurrency, leaseLimit };
177
+ }
178
+ export const drainTriggerInboxPlugin = definePlugin((sdk) => {
179
+ async function drainTriggerInbox(options) {
180
+ const onMessage = requireOnMessage(options.onMessage);
181
+ const { concurrency, leaseLimit } = resolveConcurrencyAndLease(options);
182
+ const inboxId = await resolveTriggerInboxId({
183
+ api: sdk.context.api,
184
+ inbox: options.inbox,
185
+ });
186
+ await runDrainPass({
187
+ sdk,
188
+ inboxId,
189
+ onMessage,
190
+ concurrency,
191
+ leaseLimit,
192
+ leaseSeconds: options.leaseSeconds,
193
+ maxMessages: options.maxMessages,
194
+ releaseOnError: options.releaseOnError ?? false,
195
+ continueOnError: options.continueOnError ?? false,
196
+ onError: options.onError,
197
+ signal: options.signal,
198
+ firstFetch: true,
199
+ });
200
+ }
201
+ return {
202
+ drainTriggerInbox,
203
+ context: {
204
+ meta: {
205
+ drainTriggerInbox: {
206
+ ...triggersDefaults,
207
+ type: "create",
208
+ description: "Drain an existing trigger inbox: lease currently-available messages, process each via onMessage, return when the inbox is empty, maxMessages is reached, or the abort signal fires.",
209
+ itemType: "void",
210
+ // The doc generator's default for type="create" + itemType
211
+ // appends "Item" (e.g. "RecordItem"), which would render
212
+ // "voidItem" here. drainTriggerInbox returns Promise<void>,
213
+ // so we override.
214
+ returnType: "void",
215
+ inputSchema: DrainTriggerInboxSchema,
216
+ resolvers: {
217
+ inbox: triggerInboxResolver,
218
+ },
219
+ // Returns Promise<void>; the CLI ships a hand-written
220
+ // drain-trigger-inbox plugin that adds presentation flags
221
+ // (interactive, --json, etc.) around this command.
222
+ packages: ["sdk"],
223
+ },
224
+ },
225
+ },
226
+ };
227
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Generic worker-pool drain over a batched fetch source. The pipeline
3
+ * owns mechanism: concurrent workers, low-watermark refill,
4
+ * batch-completion detection, deferred-release accumulation, and
5
+ * end-of-drain cleanup. Plugin-specific policy (signal interpretation,
6
+ * error swallowing, locator resolution) lives in the caller's hooks.
7
+ */
8
+ export type ItemAction = "ack" | "release" | "ignore";
9
+ export interface ProcessItemResult<TValue> {
10
+ /** Pushed onto the drain's results array. */
11
+ value: TValue;
12
+ /** What the pipeline does with the item: ack, release, or ignore (lease timeout returns it). */
13
+ action: ItemAction;
14
+ /** Stop the drain after current in-flight items finish. */
15
+ abort?: boolean;
16
+ }
17
+ export interface BatchedDrainPipelineOptions<TItem extends {
18
+ id: string;
19
+ }, TValue> {
20
+ concurrency: number;
21
+ maxItems?: number;
22
+ leaseSize: number;
23
+ /**
24
+ * Fetch the next batch. `"exhausted"` signals no more items will arrive.
25
+ * Errors thrown after the initial fetch surface only after in-flight
26
+ * workers wind down.
27
+ */
28
+ fetchBatch(requestedSize: number): Promise<{
29
+ id: string;
30
+ items: TItem[];
31
+ } | "exhausted">;
32
+ /**
33
+ * Run the per-item callback. The returned `action` controls
34
+ * ack/release; `abort: true` short-circuits the drain after current
35
+ * in-flight items finish.
36
+ */
37
+ processItem(item: TItem): Promise<ProcessItemResult<TValue>>;
38
+ /**
39
+ * Called once per item the callback marked `"ack"`, immediately after
40
+ * processItem returns. Per-item rather than per-batch so a crash
41
+ * mid-drain can't roll back already-committed items. The hook is on
42
+ * the hot path; in drainTriggerInbox it issues one
43
+ * sdk.ackTriggerInboxMessages call per acked message.
44
+ */
45
+ ackItem(batchId: string, itemId: string): Promise<void>;
46
+ /**
47
+ * Called once at drain end per batch with items the callback marked
48
+ * `"release"`. Stays batched (vs ackItem's per-item) because an
49
+ * unreleased item just waits for the lease timeout — no work is lost.
50
+ */
51
+ releaseItems(batchId: string, itemIds: string[]): Promise<void>;
52
+ }
53
+ export declare function runBatchedDrainPipeline<TItem extends {
54
+ id: string;
55
+ }, TValue>(options: BatchedDrainPipelineOptions<TItem, TValue>): Promise<TValue[]>;
56
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/drainTriggerInbox/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtD,MAAM,WAAW,iBAAiB,CAAC,MAAM;IACvC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,gGAAgG;IAChG,MAAM,EAAE,UAAU,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B,CAC1C,KAAK,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC5B,MAAM;IAEN,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CACR,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,EAAE,CAAA;KAAE,GAAG,WAAW,CAAC,CAAC;IACzD;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AA4ED,wBAAsB,uBAAuB,CAC3C,KAAK,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC5B,MAAM,EACN,OAAO,EAAE,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAyKxE"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Generic worker-pool drain over a batched fetch source. The pipeline
3
+ * owns mechanism: concurrent workers, low-watermark refill,
4
+ * batch-completion detection, deferred-release accumulation, and
5
+ * end-of-drain cleanup. Plugin-specific policy (signal interpretation,
6
+ * error swallowing, locator resolution) lives in the caller's hooks.
7
+ */
8
+ // With per-item ack the tracker is purely a counter for refill math and
9
+ // the worker exit condition. It used to also accumulate fulfilled item
10
+ // ids for a per-batch ack call.
11
+ function createBatchTracker() {
12
+ const batches = new Map();
13
+ return {
14
+ addBatch(id, total) {
15
+ batches.set(id, { total, inFlight: 0, completed: 0 });
16
+ },
17
+ markInFlight(id) {
18
+ const e = batches.get(id);
19
+ if (e)
20
+ e.inFlight++;
21
+ },
22
+ /** Decrement inFlight and increment completed. Paired with markInFlight. */
23
+ markCompleted(id) {
24
+ const e = batches.get(id);
25
+ if (!e)
26
+ return;
27
+ e.inFlight--;
28
+ e.completed++;
29
+ },
30
+ /** Drop the batch once all items completed; tracker stays small over long drains. */
31
+ finalizeIfDone(id) {
32
+ const e = batches.get(id);
33
+ if (e && e.completed === e.total)
34
+ batches.delete(id);
35
+ },
36
+ inFlightTotal() {
37
+ let n = 0;
38
+ for (const e of batches.values())
39
+ n += e.inFlight;
40
+ return n;
41
+ },
42
+ /** Items leased but not yet completed (queued + in-flight). */
43
+ pipelineSize() {
44
+ let n = 0;
45
+ for (const e of batches.values())
46
+ n += e.total - e.completed;
47
+ return n;
48
+ },
49
+ };
50
+ }
51
+ /**
52
+ * One-shot waiter. Workers `wait()` when the queue is empty; producers
53
+ * `notifyAll()` to wake them. Each `notifyAll` swaps in a fresh waiter
54
+ * so already-woken workers don't re-fire on the next notify.
55
+ */
56
+ function createWaiter() {
57
+ const make = () => {
58
+ let signal;
59
+ const promise = new Promise((r) => {
60
+ signal = r;
61
+ });
62
+ return { promise, signal };
63
+ };
64
+ let current = make();
65
+ return {
66
+ wait: () => current.promise,
67
+ notifyAll: () => {
68
+ const prev = current;
69
+ current = make();
70
+ prev.signal();
71
+ },
72
+ };
73
+ }
74
+ function addToMap(m, key, value) {
75
+ const existing = m.get(key) ?? [];
76
+ m.set(key, [...existing, value]);
77
+ }
78
+ export async function runBatchedDrainPipeline(options) {
79
+ const { concurrency, maxItems, leaseSize, fetchBatch, processItem, ackItem, releaseItems, } = options;
80
+ const tracker = createBatchTracker();
81
+ const waiter = createWaiter();
82
+ const accumulated = [];
83
+ const pendingReleases = new Map();
84
+ const queue = [];
85
+ let totalProcessed = 0;
86
+ let aborted = false;
87
+ let exhausted = false;
88
+ let fetchInProgress = false;
89
+ let fetchError = undefined;
90
+ async function tryFetch() {
91
+ if (fetchInProgress || exhausted || aborted)
92
+ return;
93
+ const remainingCap = maxItems !== undefined
94
+ ? maxItems - totalProcessed - tracker.pipelineSize()
95
+ : Number.POSITIVE_INFINITY;
96
+ if (remainingCap <= 0)
97
+ return;
98
+ const requestedSize = Math.min(remainingCap, leaseSize);
99
+ fetchInProgress = true;
100
+ try {
101
+ const result = await fetchBatch(requestedSize);
102
+ if (result === "exhausted") {
103
+ exhausted = true;
104
+ return;
105
+ }
106
+ tracker.addBatch(result.id, result.items.length);
107
+ for (const item of result.items) {
108
+ queue.push({ item, batchId: result.id });
109
+ }
110
+ }
111
+ catch (err) {
112
+ fetchError = err;
113
+ exhausted = true;
114
+ }
115
+ finally {
116
+ fetchInProgress = false;
117
+ waiter.notifyAll();
118
+ }
119
+ }
120
+ async function processQueueEntry(entry) {
121
+ tracker.markInFlight(entry.batchId);
122
+ // markCompleted / finalizeIfDone / notifyAll all run in finally so a
123
+ // throw from processItem or ackItem still decrements inFlight and
124
+ // wakes peer workers. Skipping markCompleted on a processItem throw
125
+ // wedges sibling workers: they wake on notifyAll, check
126
+ // `inFlightTotal === 0` for exit, see the never-decremented count,
127
+ // and block again — permanently with concurrency > 1.
128
+ try {
129
+ const result = await processItem(entry.item);
130
+ accumulated.push(result.value);
131
+ totalProcessed++;
132
+ if (result.action === "release") {
133
+ addToMap(pendingReleases, entry.batchId, entry.item.id);
134
+ }
135
+ if (result.abort)
136
+ aborted = true;
137
+ if (result.action === "ack") {
138
+ await ackItem(entry.batchId, entry.item.id);
139
+ }
140
+ }
141
+ finally {
142
+ tracker.markCompleted(entry.batchId);
143
+ tracker.finalizeIfDone(entry.batchId);
144
+ waiter.notifyAll();
145
+ }
146
+ }
147
+ async function worker() {
148
+ while (true) {
149
+ if (aborted)
150
+ return;
151
+ if (maxItems !== undefined && totalProcessed >= maxItems)
152
+ return;
153
+ const entry = queue.shift();
154
+ if (entry !== undefined) {
155
+ // Low-watermark refill: keep the pipeline at ~concurrency so a
156
+ // freed worker has work queued. Fire-and-forget so the worker
157
+ // doesn't block on the lease HTTP.
158
+ if (tracker.pipelineSize() < concurrency &&
159
+ !fetchInProgress &&
160
+ !exhausted) {
161
+ void tryFetch();
162
+ }
163
+ await processQueueEntry(entry);
164
+ continue;
165
+ }
166
+ if (!exhausted && !fetchInProgress)
167
+ void tryFetch();
168
+ if (queue.length === 0 &&
169
+ !fetchInProgress &&
170
+ (exhausted || aborted) &&
171
+ tracker.inFlightTotal() === 0) {
172
+ return;
173
+ }
174
+ await waiter.wait();
175
+ }
176
+ }
177
+ // Initial fetch primes the queue. Errors here surface directly without
178
+ // ever spinning up workers.
179
+ await tryFetch();
180
+ if (fetchError !== undefined)
181
+ throw fetchError;
182
+ // Cleanup runs in finally so a thrown lease-boundary ack or a captured
183
+ // mid-drain fetchError still flushes deferred releases.
184
+ let cleanupError = undefined;
185
+ try {
186
+ if (!exhausted || queue.length > 0) {
187
+ // Catch-and-track on each worker so all settle before we surface
188
+ // the first error. With Promise.all, a failing worker rejects
189
+ // immediately and sibling workers continue running on the next
190
+ // tick, firing API calls after the caller has received the
191
+ // rejection.
192
+ let firstWorkerError = undefined;
193
+ const workers = Array.from({ length: concurrency }, () => worker().catch((err) => {
194
+ if (firstWorkerError === undefined)
195
+ firstWorkerError = err;
196
+ }));
197
+ await Promise.all(workers);
198
+ if (firstWorkerError !== undefined)
199
+ throw firstWorkerError;
200
+ }
201
+ if (fetchError !== undefined)
202
+ throw fetchError;
203
+ }
204
+ finally {
205
+ // Aborted-but-unstarted items are still leased to us but were never
206
+ // offered to the callback — release them. With per-item ack there's
207
+ // nothing else to flush at cleanup; successful items were acked
208
+ // immediately as they completed.
209
+ while (queue.length > 0) {
210
+ const entry = queue.shift();
211
+ addToMap(pendingReleases, entry.batchId, entry.item.id);
212
+ }
213
+ for (const [batchId, itemIds] of pendingReleases) {
214
+ try {
215
+ await releaseItems(batchId, itemIds);
216
+ }
217
+ catch (err) {
218
+ cleanupError ?? (cleanupError = err);
219
+ }
220
+ }
221
+ }
222
+ if (cleanupError !== undefined)
223
+ throw cleanupError;
224
+ return accumulated;
225
+ }
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ import type { LeasedTriggerMessageItem } from "../../../schemas/TriggerMessage";
3
+ import { ZapierSignal } from "../../../types/signals";
4
+ /**
5
+ * Signal an `onMessage` callback can throw to release the current
6
+ * message back to the inbox immediately (status returns to
7
+ * `available`) rather than leaving it leased until the lease
8
+ * timeout. The actual release call is deferred to the end of the
9
+ * drain so the same drain doesn't immediately re-lease it.
10
+ *
11
+ * Always triggers release regardless of `releaseOnError`.
12
+ */
13
+ export declare class ZapierReleaseTriggerMessageSignal extends ZapierSignal {
14
+ readonly name = "ZapierReleaseTriggerMessageSignal";
15
+ readonly code: "ZAPIER_RELEASE_TRIGGER_MESSAGE_SIGNAL";
16
+ }
17
+ /**
18
+ * Signal an `onMessage` callback can throw to stop the drain after
19
+ * the current batch — no further leases, no further callbacks. The
20
+ * triggering message gets the same release-or-leave treatment as any
21
+ * thrown error (per `releaseOnError`). Other in-flight messages in
22
+ * the same batch finish normally before the command returns.
23
+ * Already-leased-but-not-yet-started messages are released so
24
+ * they're available for re-lease.
25
+ */
26
+ export declare class ZapierAbortDrainSignal extends ZapierSignal {
27
+ readonly name = "ZapierAbortDrainSignal";
28
+ readonly code: "ZAPIER_ABORT_DRAIN_SIGNAL";
29
+ }
30
+ /**
31
+ * Per-message handler. Resolves to ack the message; rejects to
32
+ * release-or-leave per `releaseOnError`. Throw a
33
+ * `ZapierReleaseTriggerMessageSignal` for explicit release; throw a
34
+ * `ZapierAbortDrainSignal` to stop the drain after the current
35
+ * batch.
36
+ */
37
+ export type DrainTriggerInboxCallback = (message: LeasedTriggerMessageItem) => void | Promise<void>;
38
+ /**
39
+ * Per-message error observer for `continueOnError: true` mode.
40
+ * Called with the failure reason and the message; signals
41
+ * (`ZapierSignal` subclasses) are filtered out — they're
42
+ * control-flow throws, not failures to observe. Synchronous from
43
+ * the loop's perspective; throwing from `onError` is swallowed,
44
+ * since it's an observer not a flow controller.
45
+ */
46
+ export type DrainTriggerInboxErrorObserver = (error: unknown, message: LeasedTriggerMessageItem) => void | Promise<void>;
47
+ export declare const DrainTriggerInboxSchema: z.ZodObject<{
48
+ inbox: z.ZodString & {
49
+ _def: z.core.$ZodStringDef & import("../../..").PositionalMetadata;
50
+ };
51
+ onMessage: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
52
+ concurrency: z.ZodOptional<z.ZodNumber>;
53
+ leaseLimit: z.ZodOptional<z.ZodNumber>;
54
+ leaseSeconds: z.ZodOptional<z.ZodNumber>;
55
+ releaseOnError: z.ZodOptional<z.ZodBoolean>;
56
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
57
+ onError: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
58
+ signal: z.ZodOptional<z.ZodCustom<AbortSignal, AbortSignal>>;
59
+ maxMessages: z.ZodOptional<z.ZodNumber>;
60
+ }, z.core.$strip>;
61
+ export declare const WatchTriggerInboxSchema: z.ZodObject<{
62
+ inbox: z.ZodString & {
63
+ _def: z.core.$ZodStringDef & import("../../..").PositionalMetadata;
64
+ };
65
+ onMessage: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
66
+ concurrency: z.ZodOptional<z.ZodNumber>;
67
+ leaseLimit: z.ZodOptional<z.ZodNumber>;
68
+ leaseSeconds: z.ZodOptional<z.ZodNumber>;
69
+ releaseOnError: z.ZodOptional<z.ZodBoolean>;
70
+ continueOnError: z.ZodOptional<z.ZodBoolean>;
71
+ onError: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
72
+ signal: z.ZodOptional<z.ZodCustom<AbortSignal, AbortSignal>>;
73
+ maxDrainIntervalSeconds: z.ZodOptional<z.ZodNumber>;
74
+ }, z.core.$strip>;
75
+ /**
76
+ * Fields shared by both `drainTriggerInbox` and `watchTriggerInbox`.
77
+ * `onMessage` is required at the type level; runtime validation in
78
+ * the handler covers JS callers who bypass TS.
79
+ */
80
+ interface TriggerInboxCommandSharedFields {
81
+ inbox: string;
82
+ onMessage: DrainTriggerInboxCallback;
83
+ concurrency?: number;
84
+ leaseLimit?: number;
85
+ leaseSeconds?: number;
86
+ releaseOnError?: boolean;
87
+ continueOnError?: boolean;
88
+ onError?: DrainTriggerInboxErrorObserver;
89
+ signal?: AbortSignal;
90
+ }
91
+ /**
92
+ * Hand-typed options for `drainTriggerInbox`. The Zod schema above is
93
+ * for docs and registry; this type is what the SDK function accepts,
94
+ * with `onMessage` / `onError` typed as proper callbacks instead of
95
+ * opaque `z.function()`.
96
+ */
97
+ export type DrainTriggerInboxOptions = TriggerInboxCommandSharedFields & {
98
+ maxMessages?: number;
99
+ };
100
+ export type WatchTriggerInboxOptions = TriggerInboxCommandSharedFields & {
101
+ maxDrainIntervalSeconds?: number;
102
+ };
103
+ export type { LeasedTriggerMessageItem };
104
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../../src/plugins/triggers/drainTriggerInbox/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;;;;;;GAQG;AACH,qBAAa,iCAAkC,SAAQ,YAAY;IACjE,QAAQ,CAAC,IAAI,uCAAuC;IACpD,QAAQ,CAAC,IAAI,EAAG,uCAAuC,CAAU;CAClE;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,IAAI,EAAG,2BAA2B,CAAU;CACtD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,CACtC,OAAO,EAAE,wBAAwB,KAC9B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,MAAM,8BAA8B,GAAG,CAC3C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,wBAAwB,KAC9B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAoE1B,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;iBAWnC,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;iBAWnC,CAAC;AAEF;;;;GAIG;AACH,UAAU,+BAA+B;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,yBAAyB,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,8BAA8B,CAAC;IACzC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,GAAG;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,GAAG;IACvE,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,YAAY,EAAE,wBAAwB,EAAE,CAAC"}