@drawbridge/drawbridge-utils 0.0.161 → 0.0.163
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.
- package/dist/connections/index.cjs +99 -8
- package/dist/connections/index.d.cts +98 -4
- package/dist/connections/index.d.ts +98 -4
- package/dist/connections/index.js +99 -8
- package/dist/html.cjs +5 -2
- package/dist/html.d.cts +20 -1
- package/dist/html.d.ts +20 -1
- package/dist/html.js +3 -1
- package/dist/index.cjs +34 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +34 -1
- package/dist/notification.cjs +4 -8
- package/dist/notification.d.cts +23 -16
- package/dist/notification.d.ts +23 -16
- package/dist/notification.js +4 -8
- package/dist/providers.cjs +131 -17
- package/dist/providers.d.cts +104 -12
- package/dist/providers.d.ts +104 -12
- package/dist/providers.js +127 -16
- package/package.json +1 -1
package/dist/providers.d.ts
CHANGED
|
@@ -128,8 +128,9 @@ const mask = ( value ) => {
|
|
|
128
128
|
// safe. Per-process state cannot be stale for anyone but the process holding it,
|
|
129
129
|
// and it expires on its own.
|
|
130
130
|
//
|
|
131
|
-
// slug -> { at, value }, where value is
|
|
132
|
-
//
|
|
131
|
+
// slug -> { at, value }, where value is { enabled, settings } — the decrypted
|
|
132
|
+
// settings and whether the row is switched on. Exported only so tests can age an
|
|
133
|
+
// entry without sleeping; nothing else should read it.
|
|
133
134
|
const providerMemo = new Map();
|
|
134
135
|
|
|
135
136
|
// SIXTY SECONDS, chosen for the human rather than for the load. The read is one
|
|
@@ -143,16 +144,35 @@ const MEMO_TTL_MS = 60 * 1000;
|
|
|
143
144
|
// For tests. A save clears its own slug; this clears everything.
|
|
144
145
|
const clearProviderMemo = () => providerMemo.clear();
|
|
145
146
|
|
|
146
|
-
//
|
|
147
|
-
// OAuth callback and every
|
|
147
|
+
// A VENDOR, as { enabled, settings } — the switch and the credentials, from one
|
|
148
|
+
// read. Memoized, because this is on the path of every OAuth callback and every
|
|
149
|
+
// send, so a guard costs a Map lookup rather than a document and a decrypt.
|
|
148
150
|
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
+
// THIS IS THE ROOT CHECK. There is no separate is-it-enabled function, because
|
|
152
|
+
// one question with two ways to ask it is two things to keep agreeing:
|
|
153
|
+
//
|
|
154
|
+
// const { enabled, settings } = await providerRow({ controller, slug : 'sendgrid' });
|
|
155
|
+
// if( ! enabled || ! settings.apiKey ) return;
|
|
156
|
+
//
|
|
157
|
+
// The switch is answered here and the caller names the credentials it needs.
|
|
158
|
+
// Which credentials matter is the FEATURE's question, not the vendor's, and
|
|
159
|
+
// folding the two together answers it wrong in both directions: `isLive` judges
|
|
160
|
+
// the fields a vendor marks `required`, so a send needing SendGrid's optional
|
|
161
|
+
// eventKey would pass a combined check and still fail, while one needing nothing
|
|
162
|
+
// but eventKey would be refused because accountSender happens to be blank. A
|
|
163
|
+
// vendor has one switch and many features.
|
|
164
|
+
//
|
|
165
|
+
// An unconfigured vendor answers enabled with no settings rather than throwing:
|
|
166
|
+
// the admin list has to render the row that lets someone fix it.
|
|
167
|
+
//
|
|
168
|
+
// ABSENT MEANS ON. Every row that predates the switch has no `enabled` field,
|
|
169
|
+
// and reading that as off would take every vendor down at once — so the flag is
|
|
170
|
+
// only ever believed when it says false.
|
|
151
171
|
//
|
|
152
172
|
// The memoized object is handed to every reader inside the window rather than
|
|
153
173
|
// copied per read — no caller mutates its credentials, and defending against one
|
|
154
174
|
// that does not exist would cost a clone on every send.
|
|
155
|
-
const
|
|
175
|
+
const providerRow = async ({ controller, slug }) => {
|
|
156
176
|
|
|
157
177
|
const memoized = providerMemo.get( slug );
|
|
158
178
|
|
|
@@ -163,7 +183,10 @@ const providerSettings = async ({ controller, slug }) => {
|
|
|
163
183
|
query : { slug }
|
|
164
184
|
});
|
|
165
185
|
|
|
166
|
-
const value =
|
|
186
|
+
const value = {
|
|
187
|
+
enabled : row?.enabled !== false,
|
|
188
|
+
settings : row?.settings ? decrypt( row.settings ) : {}
|
|
189
|
+
};
|
|
167
190
|
|
|
168
191
|
providerMemo.set( slug, { at : Date.now(), value });
|
|
169
192
|
|
|
@@ -171,6 +194,39 @@ const providerSettings = async ({ controller, slug }) => {
|
|
|
171
194
|
|
|
172
195
|
};
|
|
173
196
|
|
|
197
|
+
// A vendor's credentials, or nothing at all if the vendor is switched off.
|
|
198
|
+
//
|
|
199
|
+
// SWITCHED OFF READS AS UNCONFIGURED, and that is the whole design. Every
|
|
200
|
+
// consumer already handles a vendor with no credentials — availableConnections
|
|
201
|
+
// drops it from the catalogue, isLive reports not live, a send has nothing to
|
|
202
|
+
// send with — so turning one off needs no second code path anywhere. The keys
|
|
203
|
+
// stay on the row, which is the point: this is the switch you reach for instead
|
|
204
|
+
// of deleting a credential you will have to find again.
|
|
205
|
+
//
|
|
206
|
+
// `includeDisabled` is for the ADMIN SCREEN alone. That page exists to show what
|
|
207
|
+
// is stored and to switch it back on, and it cannot do either if the credentials
|
|
208
|
+
// disappear from its own view the moment they are turned off.
|
|
209
|
+
const providerSettings = async ({ controller, includeDisabled = false, slug }) => {
|
|
210
|
+
|
|
211
|
+
const row = await providerRow({ controller, slug });
|
|
212
|
+
|
|
213
|
+
return ( row.enabled || includeDisabled ) ? row.settings : {};
|
|
214
|
+
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// IS THIS VENDOR SWITCHED ON? The primitive both halves of the gate are built
|
|
218
|
+
// from, named for what it takes: a VENDOR slug (sendgrid, twilio, klaviyo), not
|
|
219
|
+
// a connection slug. The `provider` collection is one row per vendor, so this is
|
|
220
|
+
// that row's switch and nothing else.
|
|
221
|
+
//
|
|
222
|
+
// Its parameter is `vendor` rather than `slug` on purpose. vendorsEnabled below
|
|
223
|
+
// takes a CONNECTION slug and composes this across everything that connection
|
|
224
|
+
// spends, and the two are a letter apart — the argument name is what makes which
|
|
225
|
+
// one you are calling obvious at the call site.
|
|
226
|
+
const vendorEnabled = async ({ controller, vendor }) => (
|
|
227
|
+
( await providerRow({ controller, slug : vendor }) ).enabled
|
|
228
|
+
);
|
|
229
|
+
|
|
174
230
|
// EVERYTHING A CONNECTION SPENDS, merged across the vendors its manifest
|
|
175
231
|
// declares. `drawbridge` reads SendGrid, Twilio and HubSpot as one object, the
|
|
176
232
|
// way it did when they sat on one row — the row split, the readers did not.
|
|
@@ -194,6 +250,30 @@ const vendorSettings = async ({ controller, slug }) => {
|
|
|
194
250
|
|
|
195
251
|
};
|
|
196
252
|
|
|
253
|
+
// EVERY VENDOR A CONNECTION SPENDS, SWITCHED ON.
|
|
254
|
+
//
|
|
255
|
+
// The connection-level answer to the provider-level question, and the two are
|
|
256
|
+
// not one-to-one in either direction: `drawbridge` spends SendGrid, Twilio and
|
|
257
|
+
// HubSpot, so switching any one of them off makes that connection unusable,
|
|
258
|
+
// while `webhook` spends no vendor at all and can never be switched off.
|
|
259
|
+
//
|
|
260
|
+
// Same walk as vendorSettings, which is deliberate — a connection that reads
|
|
261
|
+
// three rows as one settings object has to answer the switch across the same
|
|
262
|
+
// three, or the two would disagree about which vendors a connection depends on.
|
|
263
|
+
const vendorsEnabled = async ({ controller, slug }) => {
|
|
264
|
+
|
|
265
|
+
const declared = Object.hasOwn( connections, slug ) ? Object.keys( connections[ slug ].provider?.vendors || {} ) : [];
|
|
266
|
+
|
|
267
|
+
for( const vendor of declared ){
|
|
268
|
+
|
|
269
|
+
if( ! await vendorEnabled({ controller, vendor }) ) return false;
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return true;
|
|
274
|
+
|
|
275
|
+
};
|
|
276
|
+
|
|
197
277
|
// Write a vendor's credentials.
|
|
198
278
|
//
|
|
199
279
|
// ONLY THE DECLARED FIELDS. The body arrives over the wire, so anything in it
|
|
@@ -203,7 +283,11 @@ const vendorSettings = async ({ controller, slug }) => {
|
|
|
203
283
|
// BLANK MEANS KEEP. A redacted field is never returned by the GET, so an
|
|
204
284
|
// unchanged form posts it back empty; treating that as a clear would wipe a
|
|
205
285
|
// credential every time someone edited the field next to it.
|
|
206
|
-
|
|
286
|
+
// SWITCHING OFF IS A SAVE, not its own route. The toggle sits in the same drawer
|
|
287
|
+
// as the credentials and travels with them, so an admin who turns a vendor off
|
|
288
|
+
// and corrects a key does it in one gesture. Omitted leaves the row's current
|
|
289
|
+
// answer alone, the same way a blank credential does.
|
|
290
|
+
const saveProviderSettings = async ({ authenticated, clear, controller, enabled, settings, slug }) => {
|
|
207
291
|
|
|
208
292
|
const fields = providerFields( slug );
|
|
209
293
|
|
|
@@ -263,6 +347,7 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
|
|
|
263
347
|
collection : 'provider',
|
|
264
348
|
data : {
|
|
265
349
|
$set : {
|
|
350
|
+
...( enabled !== undefined && { enabled : Boolean( enabled ) }),
|
|
266
351
|
settings : encrypt( merged )
|
|
267
352
|
}
|
|
268
353
|
},
|
|
@@ -309,7 +394,14 @@ const providerEnvNames = () => new Set(
|
|
|
309
394
|
//
|
|
310
395
|
// A field with no value is OMITTED rather than set empty, so `requires` sees
|
|
311
396
|
// the same absence it would for an unset variable.
|
|
312
|
-
|
|
397
|
+
//
|
|
398
|
+
// `includeDisabled` is for VERIFYING, not spending. Switching a vendor off means
|
|
399
|
+
// we stop sending through it; it does not mean a message signed with its secret
|
|
400
|
+
// stopped being genuine. Delivery events for mail already sent still arrive, and
|
|
401
|
+
// a STOP reply is an obligation whether or not we are currently sending — so
|
|
402
|
+
// the inbound routes read the secret with the switch ignored, and everything
|
|
403
|
+
// that decides what a merchant can connect or a step can run reads it honoured.
|
|
404
|
+
const providerCredentials = async ({ controller, includeDisabled = false }) => {
|
|
313
405
|
|
|
314
406
|
const credentials = {};
|
|
315
407
|
|
|
@@ -326,7 +418,7 @@ const providerCredentials = async ({ controller }) => {
|
|
|
326
418
|
// which is exactly where someone goes to fix it.
|
|
327
419
|
try {
|
|
328
420
|
|
|
329
|
-
const settings = await providerSettings({ controller, slug });
|
|
421
|
+
const settings = await providerSettings({ controller, includeDisabled, slug });
|
|
330
422
|
|
|
331
423
|
for( const field of providerFields( slug ) ){
|
|
332
424
|
|
|
@@ -348,4 +440,4 @@ const providerCredentials = async ({ controller }) => {
|
|
|
348
440
|
|
|
349
441
|
};
|
|
350
442
|
|
|
351
|
-
export { clearProviderMemo, isLive, mask, providerCredentials, providerEnvNames, providerFields, providerMemo, providerSettings, providerSlugs, saveProviderSettings, vendorSettings, vendors };
|
|
443
|
+
export { clearProviderMemo, isLive, mask, providerCredentials, providerEnvNames, providerFields, providerMemo, providerRow, providerSettings, providerSlugs, saveProviderSettings, vendorEnabled, vendorSettings, vendors, vendorsEnabled };
|
package/dist/providers.js
CHANGED
|
@@ -494,10 +494,15 @@ var attentive_default2 = {
|
|
|
494
494
|
"Attentive accepts these updates and applies them in the background, so a contact appears in your segment shortly after the sync rather than the instant it runs."
|
|
495
495
|
],
|
|
496
496
|
excerpt: "Sync your Drawbridge contacts into an Attentive segment.",
|
|
497
|
+
// NAMES THE SEGMENT STEP, because `status` below gates on it: a grant with
|
|
498
|
+
// no segment chosen sits at Pending, and a guide that stops at the consent
|
|
499
|
+
// leaves a merchant looking at a connection they think is broken. Same
|
|
500
|
+
// omission Klaviyo's guide already carries a note about.
|
|
497
501
|
guide: [
|
|
498
502
|
"Press Connect. Drawbridge sends you to Attentive to approve access.",
|
|
499
503
|
"Sign in to Attentive if you are not already, and authorize the permissions listed.",
|
|
500
|
-
"You
|
|
504
|
+
"You come back here to choose which Attentive segment your contacts should sync into.",
|
|
505
|
+
"The connection shows Pending until you choose a segment, then Active."
|
|
501
506
|
]
|
|
502
507
|
},
|
|
503
508
|
// A contact destination, like Klaviyo and Mailchimp — a merchant could
|
|
@@ -760,6 +765,9 @@ var attentive_default2 = {
|
|
|
760
765
|
// merchant would recognise in a builder label.
|
|
761
766
|
key: "Sync contact to Attentive",
|
|
762
767
|
queue: "connection",
|
|
768
|
+
// Attentive applies the update in the background and returns no id we
|
|
769
|
+
// could hand on, so there is nothing for a later step to interpolate.
|
|
770
|
+
returns: [],
|
|
763
771
|
// Nothing for a merchant to configure on the step itself — the segment
|
|
764
772
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
765
773
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -1987,13 +1995,18 @@ var drawbridge_default2 = {
|
|
|
1987
1995
|
request: request2,
|
|
1988
1996
|
response: { count, notified: recipients.length },
|
|
1989
1997
|
writes: recipients.map((member) => {
|
|
1990
|
-
var _a2, _b2;
|
|
1998
|
+
var _a2, _b2, _c2;
|
|
1991
1999
|
return queueNotification({
|
|
1992
2000
|
audience: "member",
|
|
1993
2001
|
message: interpolate((_a2 = step.settings) == null ? void 0 : _a2.message, values),
|
|
1994
2002
|
organization: workflow.organization,
|
|
1995
2003
|
send: { type: "email", email: member.email },
|
|
1996
|
-
|
|
2004
|
+
// The editor's plain rendering, interpolated the same way so
|
|
2005
|
+
// {{count}} resolves in the text part too. Spread rather than
|
|
2006
|
+
// written as null: a step saved before `text` existed has none,
|
|
2007
|
+
// and the send path strips the markup itself for those.
|
|
2008
|
+
...((_b2 = step.settings) == null ? void 0 : _b2.text) && { text: interpolate(step.settings.text, values) },
|
|
2009
|
+
title: interpolate((_c2 = step.settings) == null ? void 0 : _c2.subject, values),
|
|
1997
2010
|
workflow: workflow.id
|
|
1998
2011
|
});
|
|
1999
2012
|
})
|
|
@@ -2062,7 +2075,7 @@ var drawbridge_default2 = {
|
|
|
2062
2075
|
// unsubscribe token and the CAN-SPAM footer. The step's job is to say who
|
|
2063
2076
|
// and what, correctly, and to refuse early when it must not send at all.
|
|
2064
2077
|
send: async ({ context, step, workflow }, { canSend, read } = {}) => {
|
|
2065
|
-
var _a, _b;
|
|
2078
|
+
var _a, _b, _c;
|
|
2066
2079
|
const to = context == null ? void 0 : context.email;
|
|
2067
2080
|
if (!to) throw new Error("No email address on context (context.email is required)");
|
|
2068
2081
|
const request2 = { to };
|
|
@@ -2099,7 +2112,11 @@ var drawbridge_default2 = {
|
|
|
2099
2112
|
message: interpolate((_a = step.settings) == null ? void 0 : _a.message, context),
|
|
2100
2113
|
organization: workflow.organization,
|
|
2101
2114
|
send: { type: "email", email: to },
|
|
2102
|
-
|
|
2115
|
+
// The editor's plain rendering, so the text/plain part of this
|
|
2116
|
+
// email is what the merchant wrote rather than a regex's guess at
|
|
2117
|
+
// it. See the digest above for why it is spread, not nulled.
|
|
2118
|
+
...((_b = step.settings) == null ? void 0 : _b.text) && { text: interpolate(step.settings.text, context) },
|
|
2119
|
+
title: interpolate((_c = step.settings) == null ? void 0 : _c.subject, context),
|
|
2103
2120
|
workflow: workflow.id
|
|
2104
2121
|
})
|
|
2105
2122
|
]
|
|
@@ -2519,6 +2536,11 @@ var drawbridge_default2 = {
|
|
|
2519
2536
|
hook: "email.digest",
|
|
2520
2537
|
key: "Email \u2014 Digest",
|
|
2521
2538
|
queue: "notification",
|
|
2539
|
+
// Nothing for a later step to interpolate. Declared empty rather than
|
|
2540
|
+
// omitted, so "this step leaves nothing behind" and "nobody thought
|
|
2541
|
+
// about it" stay different statements — the second is what emptied the
|
|
2542
|
+
// builder's Variables menu for every step in the product.
|
|
2543
|
+
returns: [],
|
|
2522
2544
|
settings: {
|
|
2523
2545
|
// The organization OWNER is always a recipient, resolved by the
|
|
2524
2546
|
// hook, so this is additional recipients rather than the list. It
|
|
@@ -2527,7 +2549,22 @@ var drawbridge_default2 = {
|
|
|
2527
2549
|
// pick and could never save the step.
|
|
2528
2550
|
members: { of: "string", type: "array" },
|
|
2529
2551
|
message: { max: 2e3, required: true, type: "string" },
|
|
2530
|
-
|
|
2552
|
+
// THE LAST SPAM CHECK, stored beside the copy it measured. Shape is
|
|
2553
|
+
// pinned no further than the pair that proves the match, which is the
|
|
2554
|
+
// only part the api reads; the rest is SpamAssassin's own output and
|
|
2555
|
+
// is stored as it arrives. Same declaration the campaign's draw
|
|
2556
|
+
// notification validates against.
|
|
2557
|
+
//
|
|
2558
|
+
// Undeclared, this was silently dropped at submit — the settings
|
|
2559
|
+
// validator is noUnknown().strict(), so the dashboard's step form
|
|
2560
|
+
// mapped the key away rather than 400 — and every open of a workflow
|
|
2561
|
+
// step re-ran a check against a free service to be told what the last
|
|
2562
|
+
// open had already been told.
|
|
2563
|
+
score: { shape: { message: { type: "string" }, subject: { type: "string" } }, type: "object" },
|
|
2564
|
+
subject: { max: 150, required: true, type: "string" },
|
|
2565
|
+
// The editor's plain rendering of `message`, written beside it rather
|
|
2566
|
+
// than reconstructed by running a regex over the markup at send time.
|
|
2567
|
+
text: { max: 2e3, type: "string" }
|
|
2531
2568
|
},
|
|
2532
2569
|
triggers: ["schedule.day", "schedule.week", "schedule.month"],
|
|
2533
2570
|
usage: { actions: 0 }
|
|
@@ -2548,6 +2585,7 @@ var drawbridge_default2 = {
|
|
|
2548
2585
|
hook: "email.notify",
|
|
2549
2586
|
key: "Email \u2014 Notification",
|
|
2550
2587
|
queue: "notification",
|
|
2588
|
+
returns: [],
|
|
2551
2589
|
settings: {
|
|
2552
2590
|
members: { of: "string", type: "array" },
|
|
2553
2591
|
message: { max: 2e3, type: "string" },
|
|
@@ -2564,9 +2602,14 @@ var drawbridge_default2 = {
|
|
|
2564
2602
|
hook: "email.send",
|
|
2565
2603
|
key: "Email \u2014 Send email",
|
|
2566
2604
|
queue: "notification",
|
|
2605
|
+
returns: [],
|
|
2567
2606
|
settings: {
|
|
2568
2607
|
message: { max: 2e3, required: true, type: "string" },
|
|
2569
|
-
|
|
2608
|
+
// See email.digest above — the last spam check, and the editor's
|
|
2609
|
+
// plain rendering of the message.
|
|
2610
|
+
score: { shape: { message: { type: "string" }, subject: { type: "string" } }, type: "object" },
|
|
2611
|
+
subject: { max: 150, required: true, type: "string" },
|
|
2612
|
+
text: { max: 2e3, type: "string" }
|
|
2570
2613
|
},
|
|
2571
2614
|
triggers: ["lead.insert"],
|
|
2572
2615
|
// ONE SOURCE FOR THE PRICE. lib/pricing.js is the index of every
|
|
@@ -3165,6 +3208,13 @@ var klaviyo_default2 = {
|
|
|
3165
3208
|
// than a label that could be any of their Klaviyo accounts.
|
|
3166
3209
|
key: "Sync contact to " + (((_a = data2 == null ? void 0 : data2.settings) == null ? void 0 : _a.account) || "Klaviyo"),
|
|
3167
3210
|
queue: "connection",
|
|
3211
|
+
// The key the hook puts in `context`, which the runner merges into the
|
|
3212
|
+
// run so a later step can interpolate {{klaviyoProfileId}}. Declared
|
|
3213
|
+
// beside the hook that writes it — see the note on Shopify's
|
|
3214
|
+
// commerce.code, which is where this came back from.
|
|
3215
|
+
returns: [
|
|
3216
|
+
{ key: "klaviyoProfileId", label: "Klaviyo Profile ID" }
|
|
3217
|
+
],
|
|
3168
3218
|
// Nothing for a merchant to configure on the step itself — the list
|
|
3169
3219
|
// is chosen once on the connection. Declared empty rather than
|
|
3170
3220
|
// omitted, so "this step takes no settings" and "nobody thought about
|
|
@@ -3280,10 +3330,15 @@ var mailchimp_default2 = {
|
|
|
3280
3330
|
"Someone who unsubscribed inside Mailchimp keeps that choice: a resync only sets the status of a subscriber Mailchimp has never seen before."
|
|
3281
3331
|
],
|
|
3282
3332
|
excerpt: "Sync your Drawbridge contacts into a Mailchimp audience.",
|
|
3333
|
+
// NAMES THE AUDIENCE STEP, because `status` below gates on it: a grant with
|
|
3334
|
+
// no audience chosen sits at Pending, and a guide that stops at the consent
|
|
3335
|
+
// leaves a merchant looking at a connection they think is broken.
|
|
3283
3336
|
guide: [
|
|
3284
3337
|
"Press Connect. Drawbridge sends you to Mailchimp to approve access.",
|
|
3285
3338
|
"Sign in to Mailchimp if you are not already, and choose the account to connect.",
|
|
3286
|
-
"You come back here to pick the audience your contacts should sync into."
|
|
3339
|
+
"You come back here to pick the audience your contacts should sync into.",
|
|
3340
|
+
"The connection shows Pending until you pick an audience, then Active.",
|
|
3341
|
+
"You can remove Drawbridge at any time from the Authorized Apps page in your Mailchimp account."
|
|
3287
3342
|
]
|
|
3288
3343
|
},
|
|
3289
3344
|
// Mailchimp and SendGrid shared a group while they were SENDERS, where an org
|
|
@@ -3525,6 +3580,11 @@ var mailchimp_default2 = {
|
|
|
3525
3580
|
// than showing a string like a1b2c3d4e5.
|
|
3526
3581
|
key: "Sync contact to Mailchimp",
|
|
3527
3582
|
queue: "connection",
|
|
3583
|
+
// The key the hook puts in `context`, for a later step to interpolate
|
|
3584
|
+
// as {{mailchimpMemberId}}. Same reasoning as Klaviyo's.
|
|
3585
|
+
returns: [
|
|
3586
|
+
{ key: "mailchimpMemberId", label: "Mailchimp Member ID" }
|
|
3587
|
+
],
|
|
3528
3588
|
// Nothing for a merchant to configure on the step itself — the audience
|
|
3529
3589
|
// is chosen once on the connection. Declared empty rather than omitted,
|
|
3530
3590
|
// so "this step takes no settings" and "nobody thought about settings"
|
|
@@ -4199,8 +4259,8 @@ var shopify_default2 = {
|
|
|
4199
4259
|
}
|
|
4200
4260
|
}
|
|
4201
4261
|
const billable = (org == null ? void 0 : org.billingProvider) === "shopify" && fee > 0 && !backfill;
|
|
4202
|
-
const
|
|
4203
|
-
const { orderEventHandle } = (
|
|
4262
|
+
const providerRow2 = billable ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
|
|
4263
|
+
const { orderEventHandle } = (providerRow2 == null ? void 0 : providerRow2.settings) ? decrypt(providerRow2.settings) : {};
|
|
4204
4264
|
const handle = typeof orderEventHandle === "string" && slugify(orderEventHandle) || ORDER_EVENT_HANDLE;
|
|
4205
4265
|
if (billable && !((_f = connection2 == null ? void 0 : connection2.source) == null ? void 0 : _f.id)) {
|
|
4206
4266
|
(_g = logger2 == null ? void 0 : logger2.error) == null ? void 0 : _g.call(logger2, new Error(
|
|
@@ -4805,6 +4865,24 @@ var shopify_default2 = {
|
|
|
4805
4865
|
hook: "commerce.code",
|
|
4806
4866
|
key: "Issue a discount code",
|
|
4807
4867
|
queue: "connection",
|
|
4868
|
+
// WHAT THIS STEP LEAVES BEHIND FOR THE ONES AFTER IT, and the only
|
|
4869
|
+
// reason the builder can offer a Variables menu.
|
|
4870
|
+
//
|
|
4871
|
+
// These are the exact keys the hook puts in `context` — the runner
|
|
4872
|
+
// merges that into the run's context, and a later step interpolates
|
|
4873
|
+
// {{shopifyDiscountCode}} out of it. Declared beside the hook that
|
|
4874
|
+
// writes them so the two cannot drift; there is nowhere else that
|
|
4875
|
+
// knows both.
|
|
4876
|
+
//
|
|
4877
|
+
// They existed as a hand-written list in the api's workflow catalog
|
|
4878
|
+
// until it was derived from these manifests, and the derivation
|
|
4879
|
+
// hardcoded `returns : []` for every step. Nothing has offered a
|
|
4880
|
+
// variable since — an email step after a discount step had no way to
|
|
4881
|
+
// name the code it was supposed to send.
|
|
4882
|
+
returns: [
|
|
4883
|
+
{ key: "shopifyDiscountCode", label: "Shopify Discount Code" },
|
|
4884
|
+
{ key: "shopifyDiscountId", label: "Shopify Discount ID" }
|
|
4885
|
+
],
|
|
4808
4886
|
settings: {
|
|
4809
4887
|
discount: {
|
|
4810
4888
|
required: true,
|
|
@@ -4823,6 +4901,12 @@ var shopify_default2 = {
|
|
|
4823
4901
|
hook: "commerce.customer",
|
|
4824
4902
|
key: "Create customer",
|
|
4825
4903
|
queue: "connection",
|
|
4904
|
+
// Written on every path the hook can take — created, reused from an
|
|
4905
|
+
// earlier step, or reused from the lead — so a later step can rely on
|
|
4906
|
+
// it being there whenever this one succeeded.
|
|
4907
|
+
returns: [
|
|
4908
|
+
{ key: "shopifyCustomerId", label: "Shopify Customer ID" }
|
|
4909
|
+
],
|
|
4826
4910
|
settings: {},
|
|
4827
4911
|
triggers: ["lead.insert"],
|
|
4828
4912
|
usage: { actions: 1 }
|
|
@@ -5220,6 +5304,13 @@ var webhook_default = {
|
|
|
5220
5304
|
hook: "webhook.send",
|
|
5221
5305
|
key: "Send webhook",
|
|
5222
5306
|
queue: "webhook",
|
|
5307
|
+
// The receiver's response is buffered, not merged into the run — a
|
|
5308
|
+
// receiver owes us a status, not a document — so there is nothing here
|
|
5309
|
+
// for a later step to interpolate. Declared empty rather than omitted:
|
|
5310
|
+
// "leaves nothing behind" and "nobody thought about it" are different
|
|
5311
|
+
// statements, and the second one is what emptied the builder's
|
|
5312
|
+
// Variables menu for every step in the product.
|
|
5313
|
+
returns: [],
|
|
5223
5314
|
settings: {
|
|
5224
5315
|
url: { format: "url", required: true, type: "string" }
|
|
5225
5316
|
},
|
|
@@ -5553,17 +5644,25 @@ var mask = (value) => {
|
|
|
5553
5644
|
var providerMemo = /* @__PURE__ */ new Map();
|
|
5554
5645
|
var MEMO_TTL_MS = 60 * 1e3;
|
|
5555
5646
|
var clearProviderMemo = () => providerMemo.clear();
|
|
5556
|
-
var
|
|
5647
|
+
var providerRow = async ({ controller, slug: slug2 }) => {
|
|
5557
5648
|
const memoized = providerMemo.get(slug2);
|
|
5558
5649
|
if (memoized && Date.now() - memoized.at < MEMO_TTL_MS) return memoized.value;
|
|
5559
5650
|
const row = await controller.get({
|
|
5560
5651
|
collection: "provider",
|
|
5561
5652
|
query: { slug: slug2 }
|
|
5562
5653
|
});
|
|
5563
|
-
const value =
|
|
5654
|
+
const value = {
|
|
5655
|
+
enabled: (row == null ? void 0 : row.enabled) !== false,
|
|
5656
|
+
settings: (row == null ? void 0 : row.settings) ? decrypt(row.settings) : {}
|
|
5657
|
+
};
|
|
5564
5658
|
providerMemo.set(slug2, { at: Date.now(), value });
|
|
5565
5659
|
return value;
|
|
5566
5660
|
};
|
|
5661
|
+
var providerSettings = async ({ controller, includeDisabled = false, slug: slug2 }) => {
|
|
5662
|
+
const row = await providerRow({ controller, slug: slug2 });
|
|
5663
|
+
return row.enabled || includeDisabled ? row.settings : {};
|
|
5664
|
+
};
|
|
5665
|
+
var vendorEnabled = async ({ controller, vendor }) => (await providerRow({ controller, slug: vendor })).enabled;
|
|
5567
5666
|
var vendorSettings = async ({ controller, slug: slug2 }) => {
|
|
5568
5667
|
var _a;
|
|
5569
5668
|
const declared = Object.hasOwn(connections, slug2) ? Object.keys(((_a = connections[slug2].provider) == null ? void 0 : _a.vendors) || {}) : [];
|
|
@@ -5573,7 +5672,15 @@ var vendorSettings = async ({ controller, slug: slug2 }) => {
|
|
|
5573
5672
|
}
|
|
5574
5673
|
return merged;
|
|
5575
5674
|
};
|
|
5576
|
-
var
|
|
5675
|
+
var vendorsEnabled = async ({ controller, slug: slug2 }) => {
|
|
5676
|
+
var _a;
|
|
5677
|
+
const declared = Object.hasOwn(connections, slug2) ? Object.keys(((_a = connections[slug2].provider) == null ? void 0 : _a.vendors) || {}) : [];
|
|
5678
|
+
for (const vendor of declared) {
|
|
5679
|
+
if (!await vendorEnabled({ controller, vendor })) return false;
|
|
5680
|
+
}
|
|
5681
|
+
return true;
|
|
5682
|
+
};
|
|
5683
|
+
var saveProviderSettings = async ({ authenticated, clear, controller, enabled, settings, slug: slug2 }) => {
|
|
5577
5684
|
const fields2 = providerFields(slug2);
|
|
5578
5685
|
if (!fields2.length) return null;
|
|
5579
5686
|
const existing = await controller.get({
|
|
@@ -5595,6 +5702,7 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
|
|
|
5595
5702
|
collection: "provider",
|
|
5596
5703
|
data: {
|
|
5597
5704
|
$set: {
|
|
5705
|
+
...enabled !== void 0 && { enabled: Boolean(enabled) },
|
|
5598
5706
|
settings: encrypt(merged)
|
|
5599
5707
|
}
|
|
5600
5708
|
},
|
|
@@ -5609,11 +5717,11 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
|
|
|
5609
5717
|
var providerEnvNames = () => new Set(
|
|
5610
5718
|
providerSlugs().flatMap((slug2) => providerFields(slug2)).map((field2) => field2.credential).filter(Boolean)
|
|
5611
5719
|
);
|
|
5612
|
-
var providerCredentials = async ({ controller }) => {
|
|
5720
|
+
var providerCredentials = async ({ controller, includeDisabled = false }) => {
|
|
5613
5721
|
const credentials2 = {};
|
|
5614
5722
|
for (const slug2 of providerSlugs()) {
|
|
5615
5723
|
try {
|
|
5616
|
-
const settings = await providerSettings({ controller, slug: slug2 });
|
|
5724
|
+
const settings = await providerSettings({ controller, includeDisabled, slug: slug2 });
|
|
5617
5725
|
for (const field2 of providerFields(slug2)) {
|
|
5618
5726
|
const value = settings == null ? void 0 : settings[field2.key];
|
|
5619
5727
|
if (field2.credential && value) credentials2[field2.credential] = value;
|
|
@@ -5632,9 +5740,12 @@ export {
|
|
|
5632
5740
|
providerEnvNames,
|
|
5633
5741
|
providerFields,
|
|
5634
5742
|
providerMemo,
|
|
5743
|
+
providerRow,
|
|
5635
5744
|
providerSettings,
|
|
5636
5745
|
providerSlugs,
|
|
5637
5746
|
saveProviderSettings,
|
|
5747
|
+
vendorEnabled,
|
|
5638
5748
|
vendorSettings,
|
|
5639
|
-
vendors
|
|
5749
|
+
vendors,
|
|
5750
|
+
vendorsEnabled
|
|
5640
5751
|
};
|
package/package.json
CHANGED