@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/plans.cjs CHANGED
@@ -486,6 +486,16 @@ var TRIGGERS = Object.freeze({
486
486
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
487
487
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
488
488
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
489
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
490
+ // four workflows they fire were hand-written in drawbridge-sync's register job
491
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
492
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
493
+ // is neither an event Drawbridge raised nor a schedule it keeps.
494
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
495
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
496
+ // The token audit, dispatched programmatically by the register job when a
497
+ // store's grant is exchanged or rotated.
498
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
489
499
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
490
500
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
491
501
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -538,7 +548,7 @@ var OUTCOMES = Object.freeze({
538
548
  unimplemented: "unimplemented",
539
549
  unsupported: "unsupported"
540
550
  });
541
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
551
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
542
552
  var PLATFORM_CREDENTIALS = Object.freeze([
543
553
  // Seals a merchant's stored connection settings. The webhook connection is
544
554
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -5519,7 +5529,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5519
5529
  return fetchAppToken({ clientId, clientSecret, fetcher });
5520
5530
  };
5521
5531
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5522
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
5532
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5523
5533
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5524
5534
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5525
5535
  }
@@ -5533,8 +5543,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5533
5543
  const body = {
5534
5544
  attributes: {
5535
5545
  value: Number(value),
5536
- ...reference && {
5537
- reference: String(reference).slice(0, 128)
5546
+ ...reference2 && {
5547
+ reference: String(reference2).slice(0, 128)
5538
5548
  }
5539
5549
  },
5540
5550
  event_handle: eventHandle,
@@ -7695,6 +7705,12 @@ var shopify_default2 = {
7695
7705
  orderId: String(orderId),
7696
7706
  rate,
7697
7707
  shopId: connection2.source.id,
7708
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
7709
+ // to assume. queue/usage.js named the slug itself and
7710
+ // defaults to it for anything already queued when this
7711
+ // shipped — an app-store vendor billing through its own meter
7712
+ // enqueues the same job with its own slug.
7713
+ slug: connection2.slug,
7698
7714
  // The App Events API returns no event id, so one is generated
7699
7715
  // here — the event handle plus the order id — and sent as the
7700
7716
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -8918,6 +8934,13 @@ var shopify_default2 = {
8918
8934
  key: "Shopify Order Tracking",
8919
8935
  queue: "connection",
8920
8936
  system: true,
8937
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
8938
+ // the one on every stored workflow, because these documents exist on
8939
+ // prod with thousands of runs behind them and provisioning is
8940
+ // idempotent on (connection, system, title) — a different trigger
8941
+ // here would not be picked up by the existing rows anyway, and a
8942
+ // different TITLE would provision a second workflow for every store.
8943
+ trigger: { event: "shopify.order.create", type: "webhook" },
8921
8944
  type: "step.commerce.order.record"
8922
8945
  })
8923
8946
  },
@@ -8928,6 +8951,15 @@ var shopify_default2 = {
8928
8951
  key: "Shopify Product Sync",
8929
8952
  queue: "connection",
8930
8953
  system: true,
8954
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
8955
+ // latter, and it is the right long-term answer — a Drawbridge product
8956
+ // row appearing should pull the vendor's copy, rather than
8957
+ // stream/product.js branching on the slug and enqueueing a
8958
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
8959
+ // stored on prod against the webhook, and changing a live workflow's
8960
+ // trigger is a data migration. So the declaration matches what exists
8961
+ // and `product.insert` stays a separate, migration-gated change.
8962
+ trigger: { event: "shopify.product.update", type: "webhook" },
8931
8963
  type: "step.commerce.product.sync"
8932
8964
  })
8933
8965
  }
@@ -8949,26 +8981,37 @@ var shopify_default2 = {
8949
8981
  type: "step.connection.health.check"
8950
8982
  })
8951
8983
  },
8952
- // Audit-only. The "Shopify Token Activity" system workflow lists these
8953
- // for descriptive grouping, but its audit step docs are written manually
8954
- // at OAuth time — the workflow is never dispatched. Routing is declared
8955
- // defensively so that if it ever IS dispatched, the job lands on a real
8956
- // queue and the handler lookup misses cleanly instead of throwing
8957
- // "Unknown step type".
8984
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
8985
+ // the single genuinely new thing in it.
8986
+ //
8987
+ // Every other system step is its own workflow, titled by its own `key`.
8988
+ // These two are "Shopify Token Activity", one row in the merchant's list
8989
+ // with both steps in it — so they declare `workflow`, which names the
8990
+ // title, the trigger and the ORDER, and the provisioner groups on it.
8991
+ // Without that key the pair could only be provisioned by the hand-written
8992
+ // spec in drawbridge-sync's register job, which is exactly why that spec
8993
+ // outlived every other one.
8994
+ //
8995
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
8996
+ // the workflow is never dispatched; `queue` is declared defensively so
8997
+ // that if it ever IS, the job lands on a real queue and the handler lookup
8998
+ // misses cleanly rather than throwing "Unknown step type".
8958
8999
  token: {
8959
9000
  exchange: () => ({
8960
9001
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
8961
9002
  key: "Shopify Token Exchange",
8962
9003
  queue: "connection",
8963
9004
  system: true,
8964
- type: "step.connection.token.exchange"
9005
+ type: "step.connection.token.exchange",
9006
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
8965
9007
  }),
8966
9008
  refresh: () => ({
8967
9009
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
8968
9010
  key: "Shopify Token Refresh",
8969
9011
  queue: "connection",
8970
9012
  system: true,
8971
- type: "step.connection.token.refresh"
9013
+ type: "step.connection.token.refresh",
9014
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
8972
9015
  })
8973
9016
  }
8974
9017
  }
@@ -9424,7 +9467,7 @@ var webhook_default = {
9424
9467
  // The connect prompt only where connecting is not already the card's whole
9425
9468
  // story: a disconnected or errored document renders a Connect action itself,
9426
9469
  // and a task repeating it talks over the button.
9427
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
9470
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9428
9471
  {
9429
9472
  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.',
9430
9473
  title: "Requests must be verified",
@@ -9511,6 +9554,77 @@ var attentive_default3 = {
9511
9554
  }
9512
9555
  };
9513
9556
 
9557
+ // lib/connections/icons/brightdata.js
9558
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9559
+ <rect width="500" height="500" fill="#0F58FF"/>
9560
+ <circle cx="250" cy="250" r="52" fill="white"/>
9561
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
9562
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
9563
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
9564
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
9565
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
9566
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
9567
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
9568
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
9569
+ </svg>`;
9570
+
9571
+ // lib/connections/vendors/brightdata.js
9572
+ var brightdata_default2 = {
9573
+ fields: [
9574
+ {
9575
+ credential: "BRIGHTDATA_BROWSER_URI",
9576
+ input: "password",
9577
+ key: "browserUri",
9578
+ // A wss:// endpoint with the account's user and password in its
9579
+ // authority, which is why it is a password field rather than a url one:
9580
+ // the whole string is the credential.
9581
+ label: "Scraping Browser endpoint",
9582
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
9583
+ redact: true,
9584
+ required: true
9585
+ },
9586
+ {
9587
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
9588
+ input: "password",
9589
+ key: "unlockerApiKey",
9590
+ label: "Web Unlocker API key",
9591
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
9592
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
9593
+ // takes the unlimited key deliberately, because there is no way to rotate
9594
+ // this programmatically and a date nothing can act on is a date that only
9595
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
9596
+ // taken instead, the field to add is a stored date and a warning ahead of
9597
+ // it — not a rotation, which the vendor cannot support.
9598
+ 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.",
9599
+ redact: true,
9600
+ required: true
9601
+ },
9602
+ {
9603
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
9604
+ input: "text",
9605
+ key: "unlockerZone",
9606
+ label: "Web Unlocker zone",
9607
+ // Not secret — a zone is a label on the account, and it is shown rather
9608
+ // than redacted so an operator can confirm which one is in use without
9609
+ // clearing the field to read it.
9610
+ message: "The zone name the Unlocker requests run against.",
9611
+ required: true
9612
+ }
9613
+ ],
9614
+ icon: brightdata_default,
9615
+ name: "Bright Data",
9616
+ slug: "brightdata",
9617
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
9618
+ urls: {
9619
+ api: "https://docs.brightdata.com/api-reference/authentication",
9620
+ dashboard: "https://brightdata.com/cp/setting/users",
9621
+ // Bright Data has no scope model — a key carries the account's own access —
9622
+ // so there is nothing to link. `false` is the recorded answer rather than a
9623
+ // missing key.
9624
+ scopes: false
9625
+ }
9626
+ };
9627
+
9514
9628
  // lib/connections/vendors/hubspot.js
9515
9629
  var hubspot_default2 = {
9516
9630
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -9886,7 +10000,7 @@ var checkIcon = (owner, icon) => {
9886
10000
  }
9887
10001
  };
9888
10002
  var vendors = Object.freeze(Object.fromEntries(
9889
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10003
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
9890
10004
  ));
9891
10005
  var buildVendor = (vendor) => {
9892
10006
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -9913,7 +10027,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
9913
10027
  buildVendor(vendor);
9914
10028
  }
9915
10029
  var build = (manifest) => {
9916
- 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;
10030
+ 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;
9917
10031
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
9918
10032
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
9919
10033
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10179,11 +10293,22 @@ var build = (manifest) => {
10179
10293
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10180
10294
  );
10181
10295
  }
10182
- if (declared2.trigger) {
10183
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10296
+ if (declared2.workflow) {
10297
+ if (!declared2.workflow.key) {
10298
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10299
+ }
10300
+ if (typeof declared2.workflow.order !== "number") {
10301
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10302
+ }
10303
+ if (declared2.trigger) {
10304
+ 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");
10305
+ }
10306
+ }
10307
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10308
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10184
10309
  if (!known2) {
10185
10310
  throw new Error(
10186
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10311
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10187
10312
  );
10188
10313
  }
10189
10314
  }
package/dist/plans.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { organization, page, fields, field, connection } from './features.cjs';
2
2
  import { infinite, gigabyte } from './index.cjs';
3
- import './index-C59xHago.cjs';
3
+ import './index-C2rxasGZ.cjs';
4
4
  import './usage.cjs';
5
5
  import './encrypt.cjs';
6
6
  import 'crypto';
package/dist/plans.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { organization, page, fields, field, connection } from './features.js';
2
2
  import { infinite, gigabyte } from './index.js';
3
- import './index-B546oDNo.js';
3
+ import './index-DOYCXtd7.js';
4
4
  import './usage.js';
5
5
  import './encrypt.js';
6
6
  import 'crypto';
package/dist/plans.js CHANGED
@@ -453,6 +453,16 @@ var TRIGGERS = Object.freeze({
453
453
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
454
454
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
455
455
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
456
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
457
+ // four workflows they fire were hand-written in drawbridge-sync's register job
458
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
459
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
460
+ // is neither an event Drawbridge raised nor a schedule it keeps.
461
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
462
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
463
+ // The token audit, dispatched programmatically by the register job when a
464
+ // store's grant is exchanged or rotated.
465
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
456
466
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
457
467
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
458
468
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -505,7 +515,7 @@ var OUTCOMES = Object.freeze({
505
515
  unimplemented: "unimplemented",
506
516
  unsupported: "unsupported"
507
517
  });
508
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
518
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
509
519
  var PLATFORM_CREDENTIALS = Object.freeze([
510
520
  // Seals a merchant's stored connection settings. The webhook connection is
511
521
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -5486,7 +5496,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5486
5496
  return fetchAppToken({ clientId, clientSecret, fetcher });
5487
5497
  };
5488
5498
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5489
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
5499
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5490
5500
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5491
5501
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5492
5502
  }
@@ -5500,8 +5510,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5500
5510
  const body = {
5501
5511
  attributes: {
5502
5512
  value: Number(value),
5503
- ...reference && {
5504
- reference: String(reference).slice(0, 128)
5513
+ ...reference2 && {
5514
+ reference: String(reference2).slice(0, 128)
5505
5515
  }
5506
5516
  },
5507
5517
  event_handle: eventHandle,
@@ -7662,6 +7672,12 @@ var shopify_default2 = {
7662
7672
  orderId: String(orderId),
7663
7673
  rate,
7664
7674
  shopId: connection2.source.id,
7675
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
7676
+ // to assume. queue/usage.js named the slug itself and
7677
+ // defaults to it for anything already queued when this
7678
+ // shipped — an app-store vendor billing through its own meter
7679
+ // enqueues the same job with its own slug.
7680
+ slug: connection2.slug,
7665
7681
  // The App Events API returns no event id, so one is generated
7666
7682
  // here — the event handle plus the order id — and sent as the
7667
7683
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -8885,6 +8901,13 @@ var shopify_default2 = {
8885
8901
  key: "Shopify Order Tracking",
8886
8902
  queue: "connection",
8887
8903
  system: true,
8904
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
8905
+ // the one on every stored workflow, because these documents exist on
8906
+ // prod with thousands of runs behind them and provisioning is
8907
+ // idempotent on (connection, system, title) — a different trigger
8908
+ // here would not be picked up by the existing rows anyway, and a
8909
+ // different TITLE would provision a second workflow for every store.
8910
+ trigger: { event: "shopify.order.create", type: "webhook" },
8888
8911
  type: "step.commerce.order.record"
8889
8912
  })
8890
8913
  },
@@ -8895,6 +8918,15 @@ var shopify_default2 = {
8895
8918
  key: "Shopify Product Sync",
8896
8919
  queue: "connection",
8897
8920
  system: true,
8921
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
8922
+ // latter, and it is the right long-term answer — a Drawbridge product
8923
+ // row appearing should pull the vendor's copy, rather than
8924
+ // stream/product.js branching on the slug and enqueueing a
8925
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
8926
+ // stored on prod against the webhook, and changing a live workflow's
8927
+ // trigger is a data migration. So the declaration matches what exists
8928
+ // and `product.insert` stays a separate, migration-gated change.
8929
+ trigger: { event: "shopify.product.update", type: "webhook" },
8898
8930
  type: "step.commerce.product.sync"
8899
8931
  })
8900
8932
  }
@@ -8916,26 +8948,37 @@ var shopify_default2 = {
8916
8948
  type: "step.connection.health.check"
8917
8949
  })
8918
8950
  },
8919
- // Audit-only. The "Shopify Token Activity" system workflow lists these
8920
- // for descriptive grouping, but its audit step docs are written manually
8921
- // at OAuth time — the workflow is never dispatched. Routing is declared
8922
- // defensively so that if it ever IS dispatched, the job lands on a real
8923
- // queue and the handler lookup misses cleanly instead of throwing
8924
- // "Unknown step type".
8951
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
8952
+ // the single genuinely new thing in it.
8953
+ //
8954
+ // Every other system step is its own workflow, titled by its own `key`.
8955
+ // These two are "Shopify Token Activity", one row in the merchant's list
8956
+ // with both steps in it — so they declare `workflow`, which names the
8957
+ // title, the trigger and the ORDER, and the provisioner groups on it.
8958
+ // Without that key the pair could only be provisioned by the hand-written
8959
+ // spec in drawbridge-sync's register job, which is exactly why that spec
8960
+ // outlived every other one.
8961
+ //
8962
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
8963
+ // the workflow is never dispatched; `queue` is declared defensively so
8964
+ // that if it ever IS, the job lands on a real queue and the handler lookup
8965
+ // misses cleanly rather than throwing "Unknown step type".
8925
8966
  token: {
8926
8967
  exchange: () => ({
8927
8968
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
8928
8969
  key: "Shopify Token Exchange",
8929
8970
  queue: "connection",
8930
8971
  system: true,
8931
- type: "step.connection.token.exchange"
8972
+ type: "step.connection.token.exchange",
8973
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
8932
8974
  }),
8933
8975
  refresh: () => ({
8934
8976
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
8935
8977
  key: "Shopify Token Refresh",
8936
8978
  queue: "connection",
8937
8979
  system: true,
8938
- type: "step.connection.token.refresh"
8980
+ type: "step.connection.token.refresh",
8981
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
8939
8982
  })
8940
8983
  }
8941
8984
  }
@@ -9391,7 +9434,7 @@ var webhook_default = {
9391
9434
  // The connect prompt only where connecting is not already the card's whole
9392
9435
  // story: a disconnected or errored document renders a Connect action itself,
9393
9436
  // and a task repeating it talks over the button.
9394
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
9437
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9395
9438
  {
9396
9439
  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.',
9397
9440
  title: "Requests must be verified",
@@ -9478,6 +9521,77 @@ var attentive_default3 = {
9478
9521
  }
9479
9522
  };
9480
9523
 
9524
+ // lib/connections/icons/brightdata.js
9525
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9526
+ <rect width="500" height="500" fill="#0F58FF"/>
9527
+ <circle cx="250" cy="250" r="52" fill="white"/>
9528
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
9529
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
9530
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
9531
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
9532
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
9533
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
9534
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
9535
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
9536
+ </svg>`;
9537
+
9538
+ // lib/connections/vendors/brightdata.js
9539
+ var brightdata_default2 = {
9540
+ fields: [
9541
+ {
9542
+ credential: "BRIGHTDATA_BROWSER_URI",
9543
+ input: "password",
9544
+ key: "browserUri",
9545
+ // A wss:// endpoint with the account's user and password in its
9546
+ // authority, which is why it is a password field rather than a url one:
9547
+ // the whole string is the credential.
9548
+ label: "Scraping Browser endpoint",
9549
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
9550
+ redact: true,
9551
+ required: true
9552
+ },
9553
+ {
9554
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
9555
+ input: "password",
9556
+ key: "unlockerApiKey",
9557
+ label: "Web Unlocker API key",
9558
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
9559
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
9560
+ // takes the unlimited key deliberately, because there is no way to rotate
9561
+ // this programmatically and a date nothing can act on is a date that only
9562
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
9563
+ // taken instead, the field to add is a stored date and a warning ahead of
9564
+ // it — not a rotation, which the vendor cannot support.
9565
+ 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.",
9566
+ redact: true,
9567
+ required: true
9568
+ },
9569
+ {
9570
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
9571
+ input: "text",
9572
+ key: "unlockerZone",
9573
+ label: "Web Unlocker zone",
9574
+ // Not secret — a zone is a label on the account, and it is shown rather
9575
+ // than redacted so an operator can confirm which one is in use without
9576
+ // clearing the field to read it.
9577
+ message: "The zone name the Unlocker requests run against.",
9578
+ required: true
9579
+ }
9580
+ ],
9581
+ icon: brightdata_default,
9582
+ name: "Bright Data",
9583
+ slug: "brightdata",
9584
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
9585
+ urls: {
9586
+ api: "https://docs.brightdata.com/api-reference/authentication",
9587
+ dashboard: "https://brightdata.com/cp/setting/users",
9588
+ // Bright Data has no scope model — a key carries the account's own access —
9589
+ // so there is nothing to link. `false` is the recorded answer rather than a
9590
+ // missing key.
9591
+ scopes: false
9592
+ }
9593
+ };
9594
+
9481
9595
  // lib/connections/vendors/hubspot.js
9482
9596
  var hubspot_default2 = {
9483
9597
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -9853,7 +9967,7 @@ var checkIcon = (owner, icon) => {
9853
9967
  }
9854
9968
  };
9855
9969
  var vendors = Object.freeze(Object.fromEntries(
9856
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
9970
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
9857
9971
  ));
9858
9972
  var buildVendor = (vendor) => {
9859
9973
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -9880,7 +9994,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
9880
9994
  buildVendor(vendor);
9881
9995
  }
9882
9996
  var build = (manifest) => {
9883
- 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;
9997
+ 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;
9884
9998
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
9885
9999
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
9886
10000
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10146,11 +10260,22 @@ var build = (manifest) => {
10146
10260
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10147
10261
  );
10148
10262
  }
10149
- if (declared2.trigger) {
10150
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10263
+ if (declared2.workflow) {
10264
+ if (!declared2.workflow.key) {
10265
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10266
+ }
10267
+ if (typeof declared2.workflow.order !== "number") {
10268
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10269
+ }
10270
+ if (declared2.trigger) {
10271
+ 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");
10272
+ }
10273
+ }
10274
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10275
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10151
10276
  if (!known2) {
10152
10277
  throw new Error(
10153
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10278
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10154
10279
  );
10155
10280
  }
10156
10281
  }