@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.
@@ -42,6 +42,7 @@ __export(connections_exports, {
42
42
  HOOK_PROPS: () => HOOK_PROPS,
43
43
  HOOK_SLOT_PROPS: () => HOOK_SLOT_PROPS,
44
44
  INPUTS: () => INPUTS,
45
+ MARKERS: () => MARKERS,
45
46
  OAUTH_ENDPOINTS: () => OAUTH_ENDPOINTS,
46
47
  OAUTH_FIELDS: () => OAUTH_FIELDS,
47
48
  OUTCOMES: () => OUTCOMES,
@@ -69,6 +70,7 @@ __export(connections_exports, {
69
70
  effectsOf: () => effectsOf,
70
71
  ensureSystemWorkflows: () => ensureSystemWorkflows,
71
72
  hookSupport: () => hookSupport,
73
+ implementsHook: () => implementsHook,
72
74
  isStale: () => isStale,
73
75
  isStatus: () => isStatus,
74
76
  mergeSettings: () => mergeSettings,
@@ -79,14 +81,17 @@ __export(connections_exports, {
79
81
  publicSettingsBySlug: () => publicSettingsBySlug,
80
82
  reconcileConnectionScopes: () => reconcileConnectionScopes,
81
83
  redactSettings: () => redactSettings,
84
+ reference: () => reference,
82
85
  resolveConnection: () => resolveConnection,
83
86
  runHook: () => runHook,
84
87
  scopesMessage: () => scopesMessage,
88
+ spliced: () => spliced,
85
89
  stepLabels: () => stepLabels,
86
90
  stepQueues: () => stepQueues,
87
91
  stepRoutes: () => stepRoutes,
88
92
  surviving: () => surviving,
89
93
  systemSteps: () => systemSteps,
94
+ systemWorkflows: () => systemWorkflows,
90
95
  tokenSettings: () => tokenSettings,
91
96
  vendors: () => vendors
92
97
  });
@@ -589,6 +594,16 @@ var TRIGGERS = Object.freeze({
589
594
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
590
595
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
591
596
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
597
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
598
+ // four workflows they fire were hand-written in drawbridge-sync's register job
599
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
600
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
601
+ // is neither an event Drawbridge raised nor a schedule it keeps.
602
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
603
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
604
+ // The token audit, dispatched programmatically by the register job when a
605
+ // store's grant is exchanged or rotated.
606
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
592
607
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
593
608
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
594
609
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -641,7 +656,7 @@ var OUTCOMES = Object.freeze({
641
656
  unimplemented: "unimplemented",
642
657
  unsupported: "unsupported"
643
658
  });
644
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
659
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
645
660
  var PLATFORM_CREDENTIALS = Object.freeze([
646
661
  // Seals a merchant's stored connection settings. The webhook connection is
647
662
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -698,10 +713,7 @@ var ERROR_SOURCES = Object.freeze({
698
713
  "shopify.register.webhooks": true
699
714
  });
700
715
  var bearing = (entry) => ERROR_SOURCES[entry == null ? void 0 : entry.source] !== false;
701
- var assess = ({ errors = [], status = null } = {}) => {
702
- if (status === "disconnected") return status;
703
- return (errors || []).some(bearing) ? "error" : "active";
704
- };
716
+ var assess = ({ errors = [] } = {}) => (errors || []).some(bearing) ? "error" : "active";
705
717
  var surviving = ({ errors = [], proved = [] } = {}) => (errors || []).filter((entry) => !proved.includes(entry == null ? void 0 : entry.source));
706
718
  var isStatus = (status) => STATUSES.includes(status);
707
719
 
@@ -893,16 +905,28 @@ var systemSteps = (node, path = []) => Object.entries(node || {}).flatMap(
893
905
  return value && typeof value === "object" ? systemSteps(value, [...path, key]) : [];
894
906
  }
895
907
  );
908
+ var systemWorkflows = (manifest) => {
909
+ var _a, _b, _c;
910
+ const grouped = /* @__PURE__ */ new Map();
911
+ for (const [type, declared] of systemSteps(manifest == null ? void 0 : manifest.steps)) {
912
+ const trigger = ((_a = declared.workflow) == null ? void 0 : _a.trigger) || declared.trigger;
913
+ if (!(trigger == null ? void 0 : trigger.event) || !(trigger == null ? void 0 : trigger.type)) continue;
914
+ const title = ((_b = declared.workflow) == null ? void 0 : _b.key) || declared.key;
915
+ if (!title) continue;
916
+ if (!grouped.has(title)) grouped.set(title, { steps: [], title, trigger });
917
+ grouped.get(title).steps.push({ order: ((_c = declared.workflow) == null ? void 0 : _c.order) ?? 0, type });
918
+ }
919
+ return [...grouped.values()].map((workflow) => ({
920
+ ...workflow,
921
+ steps: workflow.steps.slice().sort((a, b) => a.order - b.order)
922
+ }));
923
+ };
896
924
  var ensureSystemWorkflows = async ({ connections: registry = connections, controller, doc }) => {
897
- var _a, _b;
898
- if (!(doc == null ? void 0 : doc.id) || doc.slug === "shopify") return [];
925
+ if (!(doc == null ? void 0 : doc.id)) return [];
899
926
  const manifest = registry[doc.slug];
900
927
  if (!manifest) return [];
901
928
  const provisioned = [];
902
- for (const [type, declared] of systemSteps(manifest.steps)) {
903
- if (!((_a = declared.trigger) == null ? void 0 : _a.event) || !((_b = declared.trigger) == null ? void 0 : _b.type)) continue;
904
- const title = declared.key;
905
- if (!title) continue;
929
+ for (const { steps: members, title, trigger } of systemWorkflows(manifest)) {
906
930
  const existing = await controller.count({
907
931
  collection: "workflow",
908
932
  query: {
@@ -919,13 +943,10 @@ var ensureSystemWorkflows = async ({ connections: registry = connections, contro
919
943
  connection: doc.id,
920
944
  organization: doc.organization,
921
945
  status: "active",
922
- steps: [{
923
- settings: {},
924
- type
925
- }],
946
+ steps: members.map(({ type }) => ({ settings: {}, type })),
926
947
  system: true,
927
948
  title,
928
- trigger: declared.trigger
949
+ trigger
929
950
  };
930
951
  let created;
931
952
  try {
@@ -942,6 +963,135 @@ var ensureSystemWorkflows = async ({ connections: registry = connections, contro
942
963
  return provisioned;
943
964
  };
944
965
 
966
+ // lib/connections/reference.js
967
+ var MARKER = {
968
+ end: "<!-- /generated -->",
969
+ start: "<!-- generated: drawbridge-utils lib/connections/reference.js \u2014 do not edit by hand -->"
970
+ };
971
+ var slugs = () => Object.keys(connections).sort();
972
+ var slots = (node = HOOKS, path = []) => Object.entries(node).flatMap(
973
+ ([key, value]) => Array.isArray(value) ? value.map((verb) => [...path, key, verb].join(".")) : slots(value, [...path, key])
974
+ );
975
+ var implemented = (hooks, name) => {
976
+ const found = name.split(".").reduce(
977
+ (node, key) => node && typeof node === "object" && Object.hasOwn(node, key) ? node[key] : void 0,
978
+ hooks
979
+ );
980
+ if (typeof found === "function") return "yes";
981
+ if (found && typeof found === "object") return "elsewhere";
982
+ return "no";
983
+ };
984
+ var table = (header, rows) => [
985
+ "| " + header.join(" | ") + " |",
986
+ "|" + header.map(() => "---").join("|") + "|",
987
+ ...rows.map((row2) => "| " + row2.join(" | ") + " |")
988
+ ].join("\n");
989
+ var matrix = () => {
990
+ const columns = slugs();
991
+ return table(
992
+ ["Hook", ...columns],
993
+ slots().map((name) => [
994
+ "`" + name + "`",
995
+ ...columns.map((slug2) => implemented(connections[slug2].hooks, name))
996
+ ])
997
+ );
998
+ };
999
+ var vocabularies = () => {
1000
+ const list2 = (values) => values.map((value) => "`" + value + "`").join(", ");
1001
+ return table(
1002
+ ["Vocabulary", "Values"],
1003
+ [
1004
+ ["`AUTH_KINDS`", list2(AUTH_KINDS)],
1005
+ ["`GROUPS`", list2(GROUPS)],
1006
+ ["`INPUTS`", list2(INPUTS)],
1007
+ ["`STATUSES`", list2(STATUSES)],
1008
+ ["`TRIGGERS`", list2(Object.keys(TRIGGERS).sort())],
1009
+ ["`TRIGGER_TYPES`", list2(TRIGGER_TYPES)]
1010
+ ]
1011
+ );
1012
+ };
1013
+ var steps = () => {
1014
+ const rows = [];
1015
+ const walk = (node, path, slug2) => {
1016
+ var _a;
1017
+ if (!node || typeof node !== "object") return;
1018
+ for (const [key, value] of Object.entries(node)) {
1019
+ if (typeof value === "function") {
1020
+ let declared;
1021
+ try {
1022
+ declared = value({});
1023
+ } catch {
1024
+ continue;
1025
+ }
1026
+ if (!(declared == null ? void 0 : declared.type)) continue;
1027
+ const fires = ((_a = declared.workflow) == null ? void 0 : _a.trigger) || declared.trigger;
1028
+ rows.push([
1029
+ "`" + declared.type + "`",
1030
+ slug2,
1031
+ declared.hook ? "`" + declared.hook + "`" : "\u2014",
1032
+ declared.queue,
1033
+ declared.system ? "system" : declared.withdrawn ? "withdrawn" : "builder",
1034
+ fires ? "`" + fires.event + "` / `" + fires.type + "`" : (declared.triggers || []).map((t) => "`" + t + "`").join(", ") || "\u2014"
1035
+ ]);
1036
+ continue;
1037
+ }
1038
+ walk(value, [...path, key], slug2);
1039
+ }
1040
+ };
1041
+ for (const slug2 of slugs()) walk(connections[slug2].steps, [], slug2);
1042
+ return table(["Step", "Connection", "Hook", "Queue", "Shape", "Fired by"], rows.sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1])));
1043
+ };
1044
+ var workflows = () => table(
1045
+ ["Workflow", "Connection", "Steps", "Trigger"],
1046
+ slugs().flatMap((slug2) => systemWorkflows(connections[slug2]).map((workflow) => [
1047
+ workflow.title,
1048
+ slug2,
1049
+ workflow.steps.map((step) => "`" + step.type + "`").join(" \u2192 "),
1050
+ "`" + workflow.trigger.event + "` / `" + workflow.trigger.type + "`"
1051
+ ]))
1052
+ );
1053
+ var reference = () => [
1054
+ MARKER.start,
1055
+ "",
1056
+ "### The support matrix",
1057
+ "",
1058
+ "`yes` is a body in this package. `elsewhere` is declared here and implemented in the repo holding the dependencies \u2014 a different fact from `no`, and collapsing the two is how a table tells a reader the package can do something it cannot.",
1059
+ "",
1060
+ matrix(),
1061
+ "",
1062
+ "### The closed vocabularies",
1063
+ "",
1064
+ vocabularies(),
1065
+ "",
1066
+ "### Every step",
1067
+ "",
1068
+ "A step with no hook is real: Shopify's token-audit pair is declared so a stray dispatch lands on a real queue, and is never fired.",
1069
+ "",
1070
+ steps(),
1071
+ "",
1072
+ "### The system workflows a connection is provisioned with",
1073
+ "",
1074
+ "Idempotent on (connection, system, title), so the TITLE is load-bearing \u2014 one that varied by account would provision a second workflow every time it changed.",
1075
+ "",
1076
+ workflows(),
1077
+ "",
1078
+ "### Counts",
1079
+ "",
1080
+ "- **" + slugs().length + "** connections",
1081
+ "- **" + slots().length + "** hook slots",
1082
+ "- **" + Object.keys(STEPS).length + "** step types",
1083
+ "- **" + Object.keys(TRIGGERS).length + "** triggers",
1084
+ "",
1085
+ MARKER.end
1086
+ ].join("\n");
1087
+ var spliced = (document) => {
1088
+ const from = document.indexOf(MARKER.start);
1089
+ const to = document.indexOf(MARKER.end);
1090
+ if (from < 0 || to < 0) return document;
1091
+ return document.slice(0, from) + reference() + document.slice(to + MARKER.end.length);
1092
+ };
1093
+ var MARKERS = MARKER;
1094
+
945
1095
  // lib/connections/oauth.js
946
1096
  var import_node_crypto = require("crypto");
947
1097
  var credentials = ({ basic, clientId, clientSecret }) => basic ? {
@@ -5910,7 +6060,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5910
6060
  return fetchAppToken({ clientId, clientSecret, fetcher });
5911
6061
  };
5912
6062
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5913
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
6063
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5914
6064
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5915
6065
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5916
6066
  }
@@ -5924,8 +6074,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5924
6074
  const body = {
5925
6075
  attributes: {
5926
6076
  value: Number(value),
5927
- ...reference && {
5928
- reference: String(reference).slice(0, 128)
6077
+ ...reference2 && {
6078
+ reference: String(reference2).slice(0, 128)
5929
6079
  }
5930
6080
  },
5931
6081
  event_handle: eventHandle,
@@ -8086,6 +8236,12 @@ var shopify_default2 = {
8086
8236
  orderId: String(orderId),
8087
8237
  rate,
8088
8238
  shopId: connection.source.id,
8239
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
8240
+ // to assume. queue/usage.js named the slug itself and
8241
+ // defaults to it for anything already queued when this
8242
+ // shipped — an app-store vendor billing through its own meter
8243
+ // enqueues the same job with its own slug.
8244
+ slug: connection.slug,
8089
8245
  // The App Events API returns no event id, so one is generated
8090
8246
  // here — the event handle plus the order id — and sent as the
8091
8247
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -9309,6 +9465,13 @@ var shopify_default2 = {
9309
9465
  key: "Shopify Order Tracking",
9310
9466
  queue: "connection",
9311
9467
  system: true,
9468
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
9469
+ // the one on every stored workflow, because these documents exist on
9470
+ // prod with thousands of runs behind them and provisioning is
9471
+ // idempotent on (connection, system, title) — a different trigger
9472
+ // here would not be picked up by the existing rows anyway, and a
9473
+ // different TITLE would provision a second workflow for every store.
9474
+ trigger: { event: "shopify.order.create", type: "webhook" },
9312
9475
  type: "step.commerce.order.record"
9313
9476
  })
9314
9477
  },
@@ -9319,6 +9482,15 @@ var shopify_default2 = {
9319
9482
  key: "Shopify Product Sync",
9320
9483
  queue: "connection",
9321
9484
  system: true,
9485
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
9486
+ // latter, and it is the right long-term answer — a Drawbridge product
9487
+ // row appearing should pull the vendor's copy, rather than
9488
+ // stream/product.js branching on the slug and enqueueing a
9489
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
9490
+ // stored on prod against the webhook, and changing a live workflow's
9491
+ // trigger is a data migration. So the declaration matches what exists
9492
+ // and `product.insert` stays a separate, migration-gated change.
9493
+ trigger: { event: "shopify.product.update", type: "webhook" },
9322
9494
  type: "step.commerce.product.sync"
9323
9495
  })
9324
9496
  }
@@ -9340,26 +9512,37 @@ var shopify_default2 = {
9340
9512
  type: "step.connection.health.check"
9341
9513
  })
9342
9514
  },
9343
- // Audit-only. The "Shopify Token Activity" system workflow lists these
9344
- // for descriptive grouping, but its audit step docs are written manually
9345
- // at OAuth time — the workflow is never dispatched. Routing is declared
9346
- // defensively so that if it ever IS dispatched, the job lands on a real
9347
- // queue and the handler lookup misses cleanly instead of throwing
9348
- // "Unknown step type".
9515
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
9516
+ // the single genuinely new thing in it.
9517
+ //
9518
+ // Every other system step is its own workflow, titled by its own `key`.
9519
+ // These two are "Shopify Token Activity", one row in the merchant's list
9520
+ // with both steps in it — so they declare `workflow`, which names the
9521
+ // title, the trigger and the ORDER, and the provisioner groups on it.
9522
+ // Without that key the pair could only be provisioned by the hand-written
9523
+ // spec in drawbridge-sync's register job, which is exactly why that spec
9524
+ // outlived every other one.
9525
+ //
9526
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
9527
+ // the workflow is never dispatched; `queue` is declared defensively so
9528
+ // that if it ever IS, the job lands on a real queue and the handler lookup
9529
+ // misses cleanly rather than throwing "Unknown step type".
9349
9530
  token: {
9350
9531
  exchange: () => ({
9351
9532
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
9352
9533
  key: "Shopify Token Exchange",
9353
9534
  queue: "connection",
9354
9535
  system: true,
9355
- type: "step.connection.token.exchange"
9536
+ type: "step.connection.token.exchange",
9537
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
9356
9538
  }),
9357
9539
  refresh: () => ({
9358
9540
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
9359
9541
  key: "Shopify Token Refresh",
9360
9542
  queue: "connection",
9361
9543
  system: true,
9362
- type: "step.connection.token.refresh"
9544
+ type: "step.connection.token.refresh",
9545
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
9363
9546
  })
9364
9547
  }
9365
9548
  }
@@ -9815,7 +9998,7 @@ var webhook_default = {
9815
9998
  // The connect prompt only where connecting is not already the card's whole
9816
9999
  // story: a disconnected or errored document renders a Connect action itself,
9817
10000
  // and a task repeating it talks over the button.
9818
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
10001
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9819
10002
  {
9820
10003
  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.',
9821
10004
  title: "Requests must be verified",
@@ -9902,6 +10085,77 @@ var attentive_default3 = {
9902
10085
  }
9903
10086
  };
9904
10087
 
10088
+ // lib/connections/icons/brightdata.js
10089
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
10090
+ <rect width="500" height="500" fill="#0F58FF"/>
10091
+ <circle cx="250" cy="250" r="52" fill="white"/>
10092
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
10093
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
10094
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
10095
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
10096
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
10097
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
10098
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
10099
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
10100
+ </svg>`;
10101
+
10102
+ // lib/connections/vendors/brightdata.js
10103
+ var brightdata_default2 = {
10104
+ fields: [
10105
+ {
10106
+ credential: "BRIGHTDATA_BROWSER_URI",
10107
+ input: "password",
10108
+ key: "browserUri",
10109
+ // A wss:// endpoint with the account's user and password in its
10110
+ // authority, which is why it is a password field rather than a url one:
10111
+ // the whole string is the credential.
10112
+ label: "Scraping Browser endpoint",
10113
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
10114
+ redact: true,
10115
+ required: true
10116
+ },
10117
+ {
10118
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
10119
+ input: "password",
10120
+ key: "unlockerApiKey",
10121
+ label: "Web Unlocker API key",
10122
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
10123
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
10124
+ // takes the unlimited key deliberately, because there is no way to rotate
10125
+ // this programmatically and a date nothing can act on is a date that only
10126
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
10127
+ // taken instead, the field to add is a stored date and a warning ahead of
10128
+ // it — not a rotation, which the vendor cannot support.
10129
+ 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.",
10130
+ redact: true,
10131
+ required: true
10132
+ },
10133
+ {
10134
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
10135
+ input: "text",
10136
+ key: "unlockerZone",
10137
+ label: "Web Unlocker zone",
10138
+ // Not secret — a zone is a label on the account, and it is shown rather
10139
+ // than redacted so an operator can confirm which one is in use without
10140
+ // clearing the field to read it.
10141
+ message: "The zone name the Unlocker requests run against.",
10142
+ required: true
10143
+ }
10144
+ ],
10145
+ icon: brightdata_default,
10146
+ name: "Bright Data",
10147
+ slug: "brightdata",
10148
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
10149
+ urls: {
10150
+ api: "https://docs.brightdata.com/api-reference/authentication",
10151
+ dashboard: "https://brightdata.com/cp/setting/users",
10152
+ // Bright Data has no scope model — a key carries the account's own access —
10153
+ // so there is nothing to link. `false` is the recorded answer rather than a
10154
+ // missing key.
10155
+ scopes: false
10156
+ }
10157
+ };
10158
+
9905
10159
  // lib/connections/vendors/hubspot.js
9906
10160
  var hubspot_default2 = {
9907
10161
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -10215,7 +10469,7 @@ var twilio_default2 = {
10215
10469
 
10216
10470
  // lib/connections/index.js
10217
10471
  var QUEUES = ["connection", "notification", "segment", "webhook"];
10218
- var implemented = (hooks, path) => {
10472
+ var implemented2 = (hooks, path) => {
10219
10473
  const hook = path.split(".").reduce((node, key) => node == null ? void 0 : node[key], hooks);
10220
10474
  return typeof hook === "function" || !!hook && typeof hook === "object";
10221
10475
  };
@@ -10277,7 +10531,7 @@ var checkIcon = (owner, icon) => {
10277
10531
  }
10278
10532
  };
10279
10533
  var vendors = Object.freeze(Object.fromEntries(
10280
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10534
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10281
10535
  ));
10282
10536
  var buildVendor = (vendor) => {
10283
10537
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -10304,7 +10558,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
10304
10558
  buildVendor(vendor);
10305
10559
  }
10306
10560
  var build = (manifest) => {
10307
- 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;
10561
+ 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;
10308
10562
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
10309
10563
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
10310
10564
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10333,7 +10587,7 @@ var build = (manifest) => {
10333
10587
  if (!field.hook.startsWith("resources.")) {
10334
10588
  throw new Error(manifest.slug + "." + field.key + " reads from " + field.hook + " \u2014 a picker may only read resources.*");
10335
10589
  }
10336
- if (!implemented(manifest.hooks, field.hook)) {
10590
+ if (!implemented2(manifest.hooks, field.hook)) {
10337
10591
  throw new Error(manifest.slug + "." + field.key + " reads " + field.hook + ", which this vendor does not implement");
10338
10592
  }
10339
10593
  }
@@ -10372,7 +10626,7 @@ var build = (manifest) => {
10372
10626
  }
10373
10627
  let callback = null;
10374
10628
  const type = kinds[0];
10375
- if (["install", "oauth"].includes(type) && !implemented(manifest.hooks, "lifecycle.health")) {
10629
+ if (["install", "oauth"].includes(type) && !implemented2(manifest.hooks, "lifecycle.health")) {
10376
10630
  throw new Error(
10377
10631
  manifest.slug + " is " + type + " and must implement hooks.lifecycle.health \u2014 a credential somebody else can revoke, expire or narrow tells us nothing when it dies"
10378
10632
  );
@@ -10401,10 +10655,10 @@ var build = (manifest) => {
10401
10655
  }
10402
10656
  callback = "/api/connection/" + manifest.slug + "/callback";
10403
10657
  }
10404
- if (implemented(manifest.hooks, "inbound.event") && typeof ((_n = (_m = manifest.hooks) == null ? void 0 : _m.inbound) == null ? void 0 : _n.event) !== "function" && !((_r = (_q = (_p = (_o = manifest.hooks) == null ? void 0 : _o.inbound) == null ? void 0 : _p.config) == null ? void 0 : _q.headers) == null ? void 0 : _r.event)) {
10658
+ if (implemented2(manifest.hooks, "inbound.event") && typeof ((_n = (_m = manifest.hooks) == null ? void 0 : _m.inbound) == null ? void 0 : _n.event) !== "function" && !((_r = (_q = (_p = (_o = manifest.hooks) == null ? void 0 : _o.inbound) == null ? void 0 : _p.config) == null ? void 0 : _q.headers) == null ? void 0 : _r.event)) {
10405
10659
  throw new Error(manifest.slug + " implements inbound.event but declares no inbound.headers.event");
10406
10660
  }
10407
- if (implemented(manifest.hooks, "inbound.verify") && !((_v = (_u = (_t = (_s = manifest.hooks) == null ? void 0 : _s.inbound) == null ? void 0 : _t.config) == null ? void 0 : _u.headers) == null ? void 0 : _v.signature)) {
10661
+ if (implemented2(manifest.hooks, "inbound.verify") && !((_v = (_u = (_t = (_s = manifest.hooks) == null ? void 0 : _s.inbound) == null ? void 0 : _t.config) == null ? void 0 : _u.headers) == null ? void 0 : _v.signature)) {
10408
10662
  throw new Error(manifest.slug + " implements inbound.verify but declares no inbound.headers.signature");
10409
10663
  }
10410
10664
  if (!Array.isArray((_w = manifest == null ? void 0 : manifest.content) == null ? void 0 : _w.guide) || !manifest.content.guide.length) {
@@ -10415,7 +10669,7 @@ var build = (manifest) => {
10415
10669
  if (Array.isArray(node)) {
10416
10670
  for (const verb of node) {
10417
10671
  const hook = declared2 == null ? void 0 : declared2[verb];
10418
- if (hook !== false && !implemented({ [verb]: hook }, verb)) {
10672
+ if (hook !== false && !implemented2({ [verb]: hook }, verb)) {
10419
10673
  throw new Error(manifest.slug + " must answer hooks." + [...path, verb].join(".") + " \u2014 false, a function, or {} if another repo implements it");
10420
10674
  }
10421
10675
  }
@@ -10561,7 +10815,7 @@ var build = (manifest) => {
10561
10815
  if (!QUEUES.includes(declared2 == null ? void 0 : declared2.queue)) {
10562
10816
  throw new Error(manifest.slug + " step " + type2 + " needs a queue \u2014 one of " + QUEUES.join(", "));
10563
10817
  }
10564
- if (declared2.hook && !implemented(manifest.hooks, declared2.hook)) {
10818
+ if (declared2.hook && !implemented2(manifest.hooks, declared2.hook)) {
10565
10819
  throw new Error(manifest.slug + " step " + type2 + " points at hook " + declared2.hook + ", which this vendor does not implement");
10566
10820
  }
10567
10821
  for (const trigger of declared2.triggers || []) {
@@ -10570,11 +10824,22 @@ var build = (manifest) => {
10570
10824
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10571
10825
  );
10572
10826
  }
10573
- if (declared2.trigger) {
10574
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10827
+ if (declared2.workflow) {
10828
+ if (!declared2.workflow.key) {
10829
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10830
+ }
10831
+ if (typeof declared2.workflow.order !== "number") {
10832
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10833
+ }
10834
+ if (declared2.trigger) {
10835
+ 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");
10836
+ }
10837
+ }
10838
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10839
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10575
10840
  if (!known2) {
10576
10841
  throw new Error(
10577
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10842
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10578
10843
  );
10579
10844
  }
10580
10845
  }
@@ -10644,9 +10909,10 @@ var publicSettingsBySlug = Object.fromEntries(
10644
10909
  var connectionSteps = (env = {}) => Object.entries(availableConnections(env)).flatMap(
10645
10910
  ([slug2, manifest]) => leaves(manifest.steps).map(([, step]) => ({ ...step({}), slug: slug2 }))
10646
10911
  );
10912
+ var implementsHook = (hooks, name) => implemented2(hooks, name);
10647
10913
  var hookSupport = (name) => ({
10648
- no: Object.keys(connections).filter((slug2) => !implemented(connections[slug2].hooks, name)),
10649
- yes: Object.keys(connections).filter((slug2) => implemented(connections[slug2].hooks, name))
10914
+ no: Object.keys(connections).filter((slug2) => !implemented2(connections[slug2].hooks, name)),
10915
+ yes: Object.keys(connections).filter((slug2) => implemented2(connections[slug2].hooks, name))
10650
10916
  });
10651
10917
  var connectFields = (slug2) => {
10652
10918
  var _a;
@@ -10803,6 +11069,7 @@ var resolveConnection = (item, data, env = {}) => {
10803
11069
  HOOK_PROPS,
10804
11070
  HOOK_SLOT_PROPS,
10805
11071
  INPUTS,
11072
+ MARKERS,
10806
11073
  OAUTH_ENDPOINTS,
10807
11074
  OAUTH_FIELDS,
10808
11075
  OUTCOMES,
@@ -10830,6 +11097,7 @@ var resolveConnection = (item, data, env = {}) => {
10830
11097
  effectsOf,
10831
11098
  ensureSystemWorkflows,
10832
11099
  hookSupport,
11100
+ implementsHook,
10833
11101
  isStale,
10834
11102
  isStatus,
10835
11103
  mergeSettings,
@@ -10840,14 +11108,17 @@ var resolveConnection = (item, data, env = {}) => {
10840
11108
  publicSettingsBySlug,
10841
11109
  reconcileConnectionScopes,
10842
11110
  redactSettings,
11111
+ reference,
10843
11112
  resolveConnection,
10844
11113
  runHook,
10845
11114
  scopesMessage,
11115
+ spliced,
10846
11116
  stepLabels,
10847
11117
  stepQueues,
10848
11118
  stepRoutes,
10849
11119
  surviving,
10850
11120
  systemSteps,
11121
+ systemWorkflows,
10851
11122
  tokenSettings,
10852
11123
  vendors
10853
11124
  });
@@ -1,4 +1,4 @@
1
- export { A as ANSWER_KEYS, a as AUTH_KINDS, b as AUTH_TYPES, E as ERROR_SOURCES, G as GROUPS, H as HOOKS, c as HOOK_CONFIG, d as HOOK_EFFECTS, e as HOOK_NAMES, f as HOOK_OPTIONS, g as HOOK_PROPS, h as HOOK_SLOT_PROPS, I as INPUTS, O as OAUTH_ENDPOINTS, i as OAUTH_FIELDS, j as OUTCOMES, P as PLATFORM_CREDENTIALS, R as RETIRED, S as STATUSES, k as STEPS, l as STEP_TYPES, T as TRIGGERS, m as TRIGGER_TYPES, W as WRITE_OPERATIONS, n as accessToken, o as answerOf, p as assess, q as availableConnections, r as build, s as buildVendor, t as catalogConnections, v as connectFields, w as connectionSettings, x as connectionSteps, y as connections, z as effectsOf, B as ensureSystemWorkflows, C as hookSupport, D as isStale, F as isStatus, J as mergeSettings, K as perform, L as projectConnection, M as publicConnectionKeys, N as publicSettingsBySlug, Q as reconcileConnectionScopes, U as redactSettings, V as resolveConnection, X as runHook, Y as scopesMessage, Z as stepLabels, _ as stepQueues, $ as stepRoutes, a0 as surviving, a1 as systemSteps, a2 as tokenSettings, a3 as vendors } from '../index-C59xHago.cjs';
1
+ export { A as ANSWER_KEYS, a as AUTH_KINDS, b as AUTH_TYPES, E as ERROR_SOURCES, G as GROUPS, H as HOOKS, c as HOOK_CONFIG, d as HOOK_EFFECTS, e as HOOK_NAMES, f as HOOK_OPTIONS, g as HOOK_PROPS, h as HOOK_SLOT_PROPS, I as INPUTS, M as MARKERS, O as OAUTH_ENDPOINTS, i as OAUTH_FIELDS, j as OUTCOMES, P as PLATFORM_CREDENTIALS, R as RETIRED, S as STATUSES, k as STEPS, l as STEP_TYPES, T as TRIGGERS, m as TRIGGER_TYPES, W as WRITE_OPERATIONS, n as accessToken, o as answerOf, p as assess, q as availableConnections, r as build, s as buildVendor, t as catalogConnections, v as connectFields, w as connectionSettings, x as connectionSteps, y as connections, z as effectsOf, B as ensureSystemWorkflows, C as hookSupport, D as implementsHook, F as isStale, J as isStatus, K as mergeSettings, L as perform, N as projectConnection, Q as publicConnectionKeys, U as publicSettingsBySlug, V as reconcileConnectionScopes, X as redactSettings, Y as reference, Z as resolveConnection, _ as runHook, $ as scopesMessage, a0 as spliced, a1 as stepLabels, a2 as stepQueues, a3 as stepRoutes, a4 as surviving, a5 as systemSteps, a6 as systemWorkflows, a7 as tokenSettings, a8 as vendors } from '../index-C2rxasGZ.cjs';
2
2
  export { authToken, consentUrl, pkcePair } from './oauth.cjs';
3
3
  import '../usage.cjs';
4
4
  import '../encrypt.cjs';
@@ -1,4 +1,4 @@
1
- export { A as ANSWER_KEYS, a as AUTH_KINDS, b as AUTH_TYPES, E as ERROR_SOURCES, G as GROUPS, H as HOOKS, c as HOOK_CONFIG, d as HOOK_EFFECTS, e as HOOK_NAMES, f as HOOK_OPTIONS, g as HOOK_PROPS, h as HOOK_SLOT_PROPS, I as INPUTS, O as OAUTH_ENDPOINTS, i as OAUTH_FIELDS, j as OUTCOMES, P as PLATFORM_CREDENTIALS, R as RETIRED, S as STATUSES, k as STEPS, l as STEP_TYPES, T as TRIGGERS, m as TRIGGER_TYPES, W as WRITE_OPERATIONS, n as accessToken, o as answerOf, p as assess, q as availableConnections, r as build, s as buildVendor, t as catalogConnections, v as connectFields, w as connectionSettings, x as connectionSteps, y as connections, z as effectsOf, B as ensureSystemWorkflows, C as hookSupport, D as isStale, F as isStatus, J as mergeSettings, K as perform, L as projectConnection, M as publicConnectionKeys, N as publicSettingsBySlug, Q as reconcileConnectionScopes, U as redactSettings, V as resolveConnection, X as runHook, Y as scopesMessage, Z as stepLabels, _ as stepQueues, $ as stepRoutes, a0 as surviving, a1 as systemSteps, a2 as tokenSettings, a3 as vendors } from '../index-B546oDNo.js';
1
+ export { A as ANSWER_KEYS, a as AUTH_KINDS, b as AUTH_TYPES, E as ERROR_SOURCES, G as GROUPS, H as HOOKS, c as HOOK_CONFIG, d as HOOK_EFFECTS, e as HOOK_NAMES, f as HOOK_OPTIONS, g as HOOK_PROPS, h as HOOK_SLOT_PROPS, I as INPUTS, M as MARKERS, O as OAUTH_ENDPOINTS, i as OAUTH_FIELDS, j as OUTCOMES, P as PLATFORM_CREDENTIALS, R as RETIRED, S as STATUSES, k as STEPS, l as STEP_TYPES, T as TRIGGERS, m as TRIGGER_TYPES, W as WRITE_OPERATIONS, n as accessToken, o as answerOf, p as assess, q as availableConnections, r as build, s as buildVendor, t as catalogConnections, v as connectFields, w as connectionSettings, x as connectionSteps, y as connections, z as effectsOf, B as ensureSystemWorkflows, C as hookSupport, D as implementsHook, F as isStale, J as isStatus, K as mergeSettings, L as perform, N as projectConnection, Q as publicConnectionKeys, U as publicSettingsBySlug, V as reconcileConnectionScopes, X as redactSettings, Y as reference, Z as resolveConnection, _ as runHook, $ as scopesMessage, a0 as spliced, a1 as stepLabels, a2 as stepQueues, a3 as stepRoutes, a4 as surviving, a5 as systemSteps, a6 as systemWorkflows, a7 as tokenSettings, a8 as vendors } from '../index-DOYCXtd7.js';
2
2
  export { authToken, consentUrl, pkcePair } from './oauth.js';
3
3
  import '../usage.js';
4
4
  import '../encrypt.js';