@drawbridge/drawbridge-utils 0.0.177 → 0.0.178

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/pricing.cjs CHANGED
@@ -792,6 +792,16 @@ var TRIGGERS = Object.freeze({
792
792
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
793
793
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
794
794
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
795
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
796
+ // four workflows they fire were hand-written in drawbridge-sync's register job
797
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
798
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
799
+ // is neither an event Drawbridge raised nor a schedule it keeps.
800
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
801
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
802
+ // The token audit, dispatched programmatically by the register job when a
803
+ // store's grant is exchanged or rotated.
804
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
795
805
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
796
806
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
797
807
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -844,7 +854,7 @@ var OUTCOMES = Object.freeze({
844
854
  unimplemented: "unimplemented",
845
855
  unsupported: "unsupported"
846
856
  });
847
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
857
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
848
858
  var PLATFORM_CREDENTIALS = Object.freeze([
849
859
  // Seals a merchant's stored connection settings. The webhook connection is
850
860
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -5810,7 +5820,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5810
5820
  return fetchAppToken({ clientId, clientSecret, fetcher });
5811
5821
  };
5812
5822
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5813
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
5823
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5814
5824
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5815
5825
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5816
5826
  }
@@ -5824,8 +5834,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5824
5834
  const body = {
5825
5835
  attributes: {
5826
5836
  value: Number(value),
5827
- ...reference && {
5828
- reference: String(reference).slice(0, 128)
5837
+ ...reference2 && {
5838
+ reference: String(reference2).slice(0, 128)
5829
5839
  }
5830
5840
  },
5831
5841
  event_handle: eventHandle,
@@ -7986,6 +7996,12 @@ var shopify_default2 = {
7986
7996
  orderId: String(orderId),
7987
7997
  rate,
7988
7998
  shopId: connection2.source.id,
7999
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
8000
+ // to assume. queue/usage.js named the slug itself and
8001
+ // defaults to it for anything already queued when this
8002
+ // shipped — an app-store vendor billing through its own meter
8003
+ // enqueues the same job with its own slug.
8004
+ slug: connection2.slug,
7989
8005
  // The App Events API returns no event id, so one is generated
7990
8006
  // here — the event handle plus the order id — and sent as the
7991
8007
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -9209,6 +9225,13 @@ var shopify_default2 = {
9209
9225
  key: "Shopify Order Tracking",
9210
9226
  queue: "connection",
9211
9227
  system: true,
9228
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
9229
+ // the one on every stored workflow, because these documents exist on
9230
+ // prod with thousands of runs behind them and provisioning is
9231
+ // idempotent on (connection, system, title) — a different trigger
9232
+ // here would not be picked up by the existing rows anyway, and a
9233
+ // different TITLE would provision a second workflow for every store.
9234
+ trigger: { event: "shopify.order.create", type: "webhook" },
9212
9235
  type: "step.commerce.order.record"
9213
9236
  })
9214
9237
  },
@@ -9219,6 +9242,15 @@ var shopify_default2 = {
9219
9242
  key: "Shopify Product Sync",
9220
9243
  queue: "connection",
9221
9244
  system: true,
9245
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
9246
+ // latter, and it is the right long-term answer — a Drawbridge product
9247
+ // row appearing should pull the vendor's copy, rather than
9248
+ // stream/product.js branching on the slug and enqueueing a
9249
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
9250
+ // stored on prod against the webhook, and changing a live workflow's
9251
+ // trigger is a data migration. So the declaration matches what exists
9252
+ // and `product.insert` stays a separate, migration-gated change.
9253
+ trigger: { event: "shopify.product.update", type: "webhook" },
9222
9254
  type: "step.commerce.product.sync"
9223
9255
  })
9224
9256
  }
@@ -9240,26 +9272,37 @@ var shopify_default2 = {
9240
9272
  type: "step.connection.health.check"
9241
9273
  })
9242
9274
  },
9243
- // Audit-only. The "Shopify Token Activity" system workflow lists these
9244
- // for descriptive grouping, but its audit step docs are written manually
9245
- // at OAuth time — the workflow is never dispatched. Routing is declared
9246
- // defensively so that if it ever IS dispatched, the job lands on a real
9247
- // queue and the handler lookup misses cleanly instead of throwing
9248
- // "Unknown step type".
9275
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
9276
+ // the single genuinely new thing in it.
9277
+ //
9278
+ // Every other system step is its own workflow, titled by its own `key`.
9279
+ // These two are "Shopify Token Activity", one row in the merchant's list
9280
+ // with both steps in it — so they declare `workflow`, which names the
9281
+ // title, the trigger and the ORDER, and the provisioner groups on it.
9282
+ // Without that key the pair could only be provisioned by the hand-written
9283
+ // spec in drawbridge-sync's register job, which is exactly why that spec
9284
+ // outlived every other one.
9285
+ //
9286
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
9287
+ // the workflow is never dispatched; `queue` is declared defensively so
9288
+ // that if it ever IS, the job lands on a real queue and the handler lookup
9289
+ // misses cleanly rather than throwing "Unknown step type".
9249
9290
  token: {
9250
9291
  exchange: () => ({
9251
9292
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
9252
9293
  key: "Shopify Token Exchange",
9253
9294
  queue: "connection",
9254
9295
  system: true,
9255
- type: "step.connection.token.exchange"
9296
+ type: "step.connection.token.exchange",
9297
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
9256
9298
  }),
9257
9299
  refresh: () => ({
9258
9300
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
9259
9301
  key: "Shopify Token Refresh",
9260
9302
  queue: "connection",
9261
9303
  system: true,
9262
- type: "step.connection.token.refresh"
9304
+ type: "step.connection.token.refresh",
9305
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
9263
9306
  })
9264
9307
  }
9265
9308
  }
@@ -9715,7 +9758,7 @@ var webhook_default = {
9715
9758
  // The connect prompt only where connecting is not already the card's whole
9716
9759
  // story: a disconnected or errored document renders a Connect action itself,
9717
9760
  // and a task repeating it talks over the button.
9718
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
9761
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9719
9762
  {
9720
9763
  message: 'Compute HMAC-SHA256( secret, t + "." + raw body ) and constant-time compare it against a v1 in the X-Drawbridge-Signature header; reject timestamps older than five minutes.',
9721
9764
  title: "Requests must be verified",
@@ -9802,6 +9845,77 @@ var attentive_default3 = {
9802
9845
  }
9803
9846
  };
9804
9847
 
9848
+ // lib/connections/icons/brightdata.js
9849
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9850
+ <rect width="500" height="500" fill="#0F58FF"/>
9851
+ <circle cx="250" cy="250" r="52" fill="white"/>
9852
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
9853
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
9854
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
9855
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
9856
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
9857
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
9858
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
9859
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
9860
+ </svg>`;
9861
+
9862
+ // lib/connections/vendors/brightdata.js
9863
+ var brightdata_default2 = {
9864
+ fields: [
9865
+ {
9866
+ credential: "BRIGHTDATA_BROWSER_URI",
9867
+ input: "password",
9868
+ key: "browserUri",
9869
+ // A wss:// endpoint with the account's user and password in its
9870
+ // authority, which is why it is a password field rather than a url one:
9871
+ // the whole string is the credential.
9872
+ label: "Scraping Browser endpoint",
9873
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
9874
+ redact: true,
9875
+ required: true
9876
+ },
9877
+ {
9878
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
9879
+ input: "password",
9880
+ key: "unlockerApiKey",
9881
+ label: "Web Unlocker API key",
9882
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
9883
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
9884
+ // takes the unlimited key deliberately, because there is no way to rotate
9885
+ // this programmatically and a date nothing can act on is a date that only
9886
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
9887
+ // taken instead, the field to add is a stored date and a warning ahead of
9888
+ // it — not a rotation, which the vendor cannot support.
9889
+ message: "Bright Data account settings \u2192 API keys. Taken with an unlimited expiry: their API offers no way to rotate a key, so a dated one can only ever break scraping silently.",
9890
+ redact: true,
9891
+ required: true
9892
+ },
9893
+ {
9894
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
9895
+ input: "text",
9896
+ key: "unlockerZone",
9897
+ label: "Web Unlocker zone",
9898
+ // Not secret — a zone is a label on the account, and it is shown rather
9899
+ // than redacted so an operator can confirm which one is in use without
9900
+ // clearing the field to read it.
9901
+ message: "The zone name the Unlocker requests run against.",
9902
+ required: true
9903
+ }
9904
+ ],
9905
+ icon: brightdata_default,
9906
+ name: "Bright Data",
9907
+ slug: "brightdata",
9908
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
9909
+ urls: {
9910
+ api: "https://docs.brightdata.com/api-reference/authentication",
9911
+ dashboard: "https://brightdata.com/cp/setting/users",
9912
+ // Bright Data has no scope model — a key carries the account's own access —
9913
+ // so there is nothing to link. `false` is the recorded answer rather than a
9914
+ // missing key.
9915
+ scopes: false
9916
+ }
9917
+ };
9918
+
9805
9919
  // lib/connections/vendors/hubspot.js
9806
9920
  var hubspot_default2 = {
9807
9921
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -10177,7 +10291,7 @@ var checkIcon = (owner, icon) => {
10177
10291
  }
10178
10292
  };
10179
10293
  var vendors = Object.freeze(Object.fromEntries(
10180
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10294
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10181
10295
  ));
10182
10296
  var buildVendor = (vendor) => {
10183
10297
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -10204,7 +10318,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
10204
10318
  buildVendor(vendor);
10205
10319
  }
10206
10320
  var build = (manifest) => {
10207
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
10321
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
10208
10322
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
10209
10323
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
10210
10324
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10470,11 +10584,22 @@ var build = (manifest) => {
10470
10584
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10471
10585
  );
10472
10586
  }
10473
- if (declared2.trigger) {
10474
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10587
+ if (declared2.workflow) {
10588
+ if (!declared2.workflow.key) {
10589
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10590
+ }
10591
+ if (typeof declared2.workflow.order !== "number") {
10592
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10593
+ }
10594
+ if (declared2.trigger) {
10595
+ throw new Error(manifest.slug + " step " + type2 + " declares BOTH a trigger and a workflow \u2014 the workflow carries the trigger for every step in it");
10596
+ }
10597
+ }
10598
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10599
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10475
10600
  if (!known2) {
10476
10601
  throw new Error(
10477
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10602
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10478
10603
  );
10479
10604
  }
10480
10605
  }
@@ -1,4 +1,4 @@
1
- import { u as channels } from './index-C59xHago.cjs';
1
+ import { u as channels } from './index-C2rxasGZ.cjs';
2
2
  import { MARKUP } from './billing.cjs';
3
3
  export { LOCAL_FLAT_CENTS, PREMIUM_CPM_CENTS, PREMIUM_RATE_PER_GB, REUSE_FLAT_CENTS, STANDARD_CPM_CENTS, STANDARD_RATE_PER_GB, ai, scrape } from './billing.cjs';
4
4
  export { conversionRate, free, plans, resolvePlan, unitAmountDecimal } from './plans.cjs';
package/dist/pricing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { u as channels } from './index-B546oDNo.js';
1
+ import { u as channels } from './index-DOYCXtd7.js';
2
2
  import { MARKUP } from './billing.js';
3
3
  export { LOCAL_FLAT_CENTS, PREMIUM_CPM_CENTS, PREMIUM_RATE_PER_GB, REUSE_FLAT_CENTS, STANDARD_CPM_CENTS, STANDARD_RATE_PER_GB, ai, scrape } from './billing.js';
4
4
  export { conversionRate, free, plans, resolvePlan, unitAmountDecimal } from './plans.js';
package/dist/pricing.js CHANGED
@@ -746,6 +746,16 @@ var TRIGGERS = Object.freeze({
746
746
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
747
747
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
748
748
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
749
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
750
+ // four workflows they fire were hand-written in drawbridge-sync's register job
751
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
752
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
753
+ // is neither an event Drawbridge raised nor a schedule it keeps.
754
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
755
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
756
+ // The token audit, dispatched programmatically by the register job when a
757
+ // store's grant is exchanged or rotated.
758
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
749
759
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
750
760
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
751
761
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -798,7 +808,7 @@ var OUTCOMES = Object.freeze({
798
808
  unimplemented: "unimplemented",
799
809
  unsupported: "unsupported"
800
810
  });
801
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
811
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
802
812
  var PLATFORM_CREDENTIALS = Object.freeze([
803
813
  // Seals a merchant's stored connection settings. The webhook connection is
804
814
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -5764,7 +5774,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5764
5774
  return fetchAppToken({ clientId, clientSecret, fetcher });
5765
5775
  };
5766
5776
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5767
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
5777
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5768
5778
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5769
5779
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5770
5780
  }
@@ -5778,8 +5788,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5778
5788
  const body = {
5779
5789
  attributes: {
5780
5790
  value: Number(value),
5781
- ...reference && {
5782
- reference: String(reference).slice(0, 128)
5791
+ ...reference2 && {
5792
+ reference: String(reference2).slice(0, 128)
5783
5793
  }
5784
5794
  },
5785
5795
  event_handle: eventHandle,
@@ -7940,6 +7950,12 @@ var shopify_default2 = {
7940
7950
  orderId: String(orderId),
7941
7951
  rate,
7942
7952
  shopId: connection2.source.id,
7953
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
7954
+ // to assume. queue/usage.js named the slug itself and
7955
+ // defaults to it for anything already queued when this
7956
+ // shipped — an app-store vendor billing through its own meter
7957
+ // enqueues the same job with its own slug.
7958
+ slug: connection2.slug,
7943
7959
  // The App Events API returns no event id, so one is generated
7944
7960
  // here — the event handle plus the order id — and sent as the
7945
7961
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -9163,6 +9179,13 @@ var shopify_default2 = {
9163
9179
  key: "Shopify Order Tracking",
9164
9180
  queue: "connection",
9165
9181
  system: true,
9182
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
9183
+ // the one on every stored workflow, because these documents exist on
9184
+ // prod with thousands of runs behind them and provisioning is
9185
+ // idempotent on (connection, system, title) — a different trigger
9186
+ // here would not be picked up by the existing rows anyway, and a
9187
+ // different TITLE would provision a second workflow for every store.
9188
+ trigger: { event: "shopify.order.create", type: "webhook" },
9166
9189
  type: "step.commerce.order.record"
9167
9190
  })
9168
9191
  },
@@ -9173,6 +9196,15 @@ var shopify_default2 = {
9173
9196
  key: "Shopify Product Sync",
9174
9197
  queue: "connection",
9175
9198
  system: true,
9199
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
9200
+ // latter, and it is the right long-term answer — a Drawbridge product
9201
+ // row appearing should pull the vendor's copy, rather than
9202
+ // stream/product.js branching on the slug and enqueueing a
9203
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
9204
+ // stored on prod against the webhook, and changing a live workflow's
9205
+ // trigger is a data migration. So the declaration matches what exists
9206
+ // and `product.insert` stays a separate, migration-gated change.
9207
+ trigger: { event: "shopify.product.update", type: "webhook" },
9176
9208
  type: "step.commerce.product.sync"
9177
9209
  })
9178
9210
  }
@@ -9194,26 +9226,37 @@ var shopify_default2 = {
9194
9226
  type: "step.connection.health.check"
9195
9227
  })
9196
9228
  },
9197
- // Audit-only. The "Shopify Token Activity" system workflow lists these
9198
- // for descriptive grouping, but its audit step docs are written manually
9199
- // at OAuth time — the workflow is never dispatched. Routing is declared
9200
- // defensively so that if it ever IS dispatched, the job lands on a real
9201
- // queue and the handler lookup misses cleanly instead of throwing
9202
- // "Unknown step type".
9229
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
9230
+ // the single genuinely new thing in it.
9231
+ //
9232
+ // Every other system step is its own workflow, titled by its own `key`.
9233
+ // These two are "Shopify Token Activity", one row in the merchant's list
9234
+ // with both steps in it — so they declare `workflow`, which names the
9235
+ // title, the trigger and the ORDER, and the provisioner groups on it.
9236
+ // Without that key the pair could only be provisioned by the hand-written
9237
+ // spec in drawbridge-sync's register job, which is exactly why that spec
9238
+ // outlived every other one.
9239
+ //
9240
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
9241
+ // the workflow is never dispatched; `queue` is declared defensively so
9242
+ // that if it ever IS, the job lands on a real queue and the handler lookup
9243
+ // misses cleanly rather than throwing "Unknown step type".
9203
9244
  token: {
9204
9245
  exchange: () => ({
9205
9246
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
9206
9247
  key: "Shopify Token Exchange",
9207
9248
  queue: "connection",
9208
9249
  system: true,
9209
- type: "step.connection.token.exchange"
9250
+ type: "step.connection.token.exchange",
9251
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
9210
9252
  }),
9211
9253
  refresh: () => ({
9212
9254
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
9213
9255
  key: "Shopify Token Refresh",
9214
9256
  queue: "connection",
9215
9257
  system: true,
9216
- type: "step.connection.token.refresh"
9258
+ type: "step.connection.token.refresh",
9259
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
9217
9260
  })
9218
9261
  }
9219
9262
  }
@@ -9669,7 +9712,7 @@ var webhook_default = {
9669
9712
  // The connect prompt only where connecting is not already the card's whole
9670
9713
  // story: a disconnected or errored document renders a Connect action itself,
9671
9714
  // and a task repeating it talks over the button.
9672
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
9715
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9673
9716
  {
9674
9717
  message: 'Compute HMAC-SHA256( secret, t + "." + raw body ) and constant-time compare it against a v1 in the X-Drawbridge-Signature header; reject timestamps older than five minutes.',
9675
9718
  title: "Requests must be verified",
@@ -9756,6 +9799,77 @@ var attentive_default3 = {
9756
9799
  }
9757
9800
  };
9758
9801
 
9802
+ // lib/connections/icons/brightdata.js
9803
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9804
+ <rect width="500" height="500" fill="#0F58FF"/>
9805
+ <circle cx="250" cy="250" r="52" fill="white"/>
9806
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
9807
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
9808
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
9809
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
9810
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
9811
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
9812
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
9813
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
9814
+ </svg>`;
9815
+
9816
+ // lib/connections/vendors/brightdata.js
9817
+ var brightdata_default2 = {
9818
+ fields: [
9819
+ {
9820
+ credential: "BRIGHTDATA_BROWSER_URI",
9821
+ input: "password",
9822
+ key: "browserUri",
9823
+ // A wss:// endpoint with the account's user and password in its
9824
+ // authority, which is why it is a password field rather than a url one:
9825
+ // the whole string is the credential.
9826
+ label: "Scraping Browser endpoint",
9827
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
9828
+ redact: true,
9829
+ required: true
9830
+ },
9831
+ {
9832
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
9833
+ input: "password",
9834
+ key: "unlockerApiKey",
9835
+ label: "Web Unlocker API key",
9836
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
9837
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
9838
+ // takes the unlimited key deliberately, because there is no way to rotate
9839
+ // this programmatically and a date nothing can act on is a date that only
9840
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
9841
+ // taken instead, the field to add is a stored date and a warning ahead of
9842
+ // it — not a rotation, which the vendor cannot support.
9843
+ message: "Bright Data account settings \u2192 API keys. Taken with an unlimited expiry: their API offers no way to rotate a key, so a dated one can only ever break scraping silently.",
9844
+ redact: true,
9845
+ required: true
9846
+ },
9847
+ {
9848
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
9849
+ input: "text",
9850
+ key: "unlockerZone",
9851
+ label: "Web Unlocker zone",
9852
+ // Not secret — a zone is a label on the account, and it is shown rather
9853
+ // than redacted so an operator can confirm which one is in use without
9854
+ // clearing the field to read it.
9855
+ message: "The zone name the Unlocker requests run against.",
9856
+ required: true
9857
+ }
9858
+ ],
9859
+ icon: brightdata_default,
9860
+ name: "Bright Data",
9861
+ slug: "brightdata",
9862
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
9863
+ urls: {
9864
+ api: "https://docs.brightdata.com/api-reference/authentication",
9865
+ dashboard: "https://brightdata.com/cp/setting/users",
9866
+ // Bright Data has no scope model — a key carries the account's own access —
9867
+ // so there is nothing to link. `false` is the recorded answer rather than a
9868
+ // missing key.
9869
+ scopes: false
9870
+ }
9871
+ };
9872
+
9759
9873
  // lib/connections/vendors/hubspot.js
9760
9874
  var hubspot_default2 = {
9761
9875
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -10131,7 +10245,7 @@ var checkIcon = (owner, icon) => {
10131
10245
  }
10132
10246
  };
10133
10247
  var vendors = Object.freeze(Object.fromEntries(
10134
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10248
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10135
10249
  ));
10136
10250
  var buildVendor = (vendor) => {
10137
10251
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -10158,7 +10272,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
10158
10272
  buildVendor(vendor);
10159
10273
  }
10160
10274
  var build = (manifest) => {
10161
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
10275
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
10162
10276
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
10163
10277
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
10164
10278
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10424,11 +10538,22 @@ var build = (manifest) => {
10424
10538
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10425
10539
  );
10426
10540
  }
10427
- if (declared2.trigger) {
10428
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10541
+ if (declared2.workflow) {
10542
+ if (!declared2.workflow.key) {
10543
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10544
+ }
10545
+ if (typeof declared2.workflow.order !== "number") {
10546
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10547
+ }
10548
+ if (declared2.trigger) {
10549
+ throw new Error(manifest.slug + " step " + type2 + " declares BOTH a trigger and a workflow \u2014 the workflow carries the trigger for every step in it");
10550
+ }
10551
+ }
10552
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10553
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10429
10554
  if (!known2) {
10430
10555
  throw new Error(
10431
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10556
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10432
10557
  );
10433
10558
  }
10434
10559
  }