@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.
@@ -501,6 +501,16 @@ var TRIGGERS = Object.freeze({
501
501
  "range.ended": Object.freeze({ event: "range.ended", type: "range" }),
502
502
  "range.started": Object.freeze({ event: "range.started", type: "range" }),
503
503
  "schedule.day": Object.freeze({ event: "day", type: "schedule" }),
504
+ // THE THREE SHOPIFY TRIGGERS, which were never in this vocabulary because the
505
+ // four workflows they fire were hand-written in drawbridge-sync's register job
506
+ // rather than provisioned from the manifest. `webhook` is a fourth trigger
507
+ // TYPE, and it is the honest name: these fire when the vendor calls us, which
508
+ // is neither an event Drawbridge raised nor a schedule it keeps.
509
+ "shopify.order.create": Object.freeze({ event: "shopify.order.create", type: "webhook" }),
510
+ "shopify.product.update": Object.freeze({ event: "shopify.product.update", type: "webhook" }),
511
+ // The token audit, dispatched programmatically by the register job when a
512
+ // store's grant is exchanged or rotated.
513
+ "shopify.token": Object.freeze({ event: "shopify.token", type: "event" }),
504
514
  "schedule.month": Object.freeze({ event: "month", type: "schedule" }),
505
515
  "schedule.week": Object.freeze({ event: "week", type: "schedule" }),
506
516
  "segment.contact.add": Object.freeze({ event: "segment.contact.add", type: "event" }),
@@ -553,7 +563,7 @@ var OUTCOMES = Object.freeze({
553
563
  unimplemented: "unimplemented",
554
564
  unsupported: "unsupported"
555
565
  });
556
- var STATUSES = Object.freeze(["active", "disconnected", "error", "pending"]);
566
+ var STATUSES = Object.freeze(["active", "error", "pending"]);
557
567
  var PLATFORM_CREDENTIALS = Object.freeze([
558
568
  // Seals a merchant's stored connection settings. The webhook connection is
559
569
  // gated on it because its whole credential is a secret WE mint and store, so
@@ -610,10 +620,7 @@ var ERROR_SOURCES = Object.freeze({
610
620
  "shopify.register.webhooks": true
611
621
  });
612
622
  var bearing = (entry) => ERROR_SOURCES[entry == null ? void 0 : entry.source] !== false;
613
- var assess = ({ errors = [], status = null } = {}) => {
614
- if (status === "disconnected") return status;
615
- return (errors || []).some(bearing) ? "error" : "active";
616
- };
623
+ var assess = ({ errors = [] } = {}) => (errors || []).some(bearing) ? "error" : "active";
617
624
  var surviving = ({ errors = [], proved = [] } = {}) => (errors || []).filter((entry) => !proved.includes(entry == null ? void 0 : entry.source));
618
625
  var isStatus = (status) => STATUSES.includes(status);
619
626
 
@@ -805,16 +812,28 @@ var systemSteps = (node, path = []) => Object.entries(node || {}).flatMap(
805
812
  return value && typeof value === "object" ? systemSteps(value, [...path, key]) : [];
806
813
  }
807
814
  );
815
+ var systemWorkflows = (manifest) => {
816
+ var _a, _b, _c;
817
+ const grouped = /* @__PURE__ */ new Map();
818
+ for (const [type, declared] of systemSteps(manifest == null ? void 0 : manifest.steps)) {
819
+ const trigger = ((_a = declared.workflow) == null ? void 0 : _a.trigger) || declared.trigger;
820
+ if (!(trigger == null ? void 0 : trigger.event) || !(trigger == null ? void 0 : trigger.type)) continue;
821
+ const title = ((_b = declared.workflow) == null ? void 0 : _b.key) || declared.key;
822
+ if (!title) continue;
823
+ if (!grouped.has(title)) grouped.set(title, { steps: [], title, trigger });
824
+ grouped.get(title).steps.push({ order: ((_c = declared.workflow) == null ? void 0 : _c.order) ?? 0, type });
825
+ }
826
+ return [...grouped.values()].map((workflow) => ({
827
+ ...workflow,
828
+ steps: workflow.steps.slice().sort((a, b) => a.order - b.order)
829
+ }));
830
+ };
808
831
  var ensureSystemWorkflows = async ({ connections: registry = connections, controller, doc }) => {
809
- var _a, _b;
810
- if (!(doc == null ? void 0 : doc.id) || doc.slug === "shopify") return [];
832
+ if (!(doc == null ? void 0 : doc.id)) return [];
811
833
  const manifest = registry[doc.slug];
812
834
  if (!manifest) return [];
813
835
  const provisioned = [];
814
- for (const [type, declared] of systemSteps(manifest.steps)) {
815
- if (!((_a = declared.trigger) == null ? void 0 : _a.event) || !((_b = declared.trigger) == null ? void 0 : _b.type)) continue;
816
- const title = declared.key;
817
- if (!title) continue;
836
+ for (const { steps: members, title, trigger } of systemWorkflows(manifest)) {
818
837
  const existing = await controller.count({
819
838
  collection: "workflow",
820
839
  query: {
@@ -831,13 +850,10 @@ var ensureSystemWorkflows = async ({ connections: registry = connections, contro
831
850
  connection: doc.id,
832
851
  organization: doc.organization,
833
852
  status: "active",
834
- steps: [{
835
- settings: {},
836
- type
837
- }],
853
+ steps: members.map(({ type }) => ({ settings: {}, type })),
838
854
  system: true,
839
855
  title,
840
- trigger: declared.trigger
856
+ trigger
841
857
  };
842
858
  let created;
843
859
  try {
@@ -854,6 +870,135 @@ var ensureSystemWorkflows = async ({ connections: registry = connections, contro
854
870
  return provisioned;
855
871
  };
856
872
 
873
+ // lib/connections/reference.js
874
+ var MARKER = {
875
+ end: "<!-- /generated -->",
876
+ start: "<!-- generated: drawbridge-utils lib/connections/reference.js \u2014 do not edit by hand -->"
877
+ };
878
+ var slugs = () => Object.keys(connections).sort();
879
+ var slots = (node = HOOKS, path = []) => Object.entries(node).flatMap(
880
+ ([key, value]) => Array.isArray(value) ? value.map((verb) => [...path, key, verb].join(".")) : slots(value, [...path, key])
881
+ );
882
+ var implemented = (hooks, name) => {
883
+ const found = name.split(".").reduce(
884
+ (node, key) => node && typeof node === "object" && Object.hasOwn(node, key) ? node[key] : void 0,
885
+ hooks
886
+ );
887
+ if (typeof found === "function") return "yes";
888
+ if (found && typeof found === "object") return "elsewhere";
889
+ return "no";
890
+ };
891
+ var table = (header, rows) => [
892
+ "| " + header.join(" | ") + " |",
893
+ "|" + header.map(() => "---").join("|") + "|",
894
+ ...rows.map((row2) => "| " + row2.join(" | ") + " |")
895
+ ].join("\n");
896
+ var matrix = () => {
897
+ const columns = slugs();
898
+ return table(
899
+ ["Hook", ...columns],
900
+ slots().map((name) => [
901
+ "`" + name + "`",
902
+ ...columns.map((slug2) => implemented(connections[slug2].hooks, name))
903
+ ])
904
+ );
905
+ };
906
+ var vocabularies = () => {
907
+ const list2 = (values) => values.map((value) => "`" + value + "`").join(", ");
908
+ return table(
909
+ ["Vocabulary", "Values"],
910
+ [
911
+ ["`AUTH_KINDS`", list2(AUTH_KINDS)],
912
+ ["`GROUPS`", list2(GROUPS)],
913
+ ["`INPUTS`", list2(INPUTS)],
914
+ ["`STATUSES`", list2(STATUSES)],
915
+ ["`TRIGGERS`", list2(Object.keys(TRIGGERS).sort())],
916
+ ["`TRIGGER_TYPES`", list2(TRIGGER_TYPES)]
917
+ ]
918
+ );
919
+ };
920
+ var steps = () => {
921
+ const rows = [];
922
+ const walk = (node, path, slug2) => {
923
+ var _a;
924
+ if (!node || typeof node !== "object") return;
925
+ for (const [key, value] of Object.entries(node)) {
926
+ if (typeof value === "function") {
927
+ let declared;
928
+ try {
929
+ declared = value({});
930
+ } catch {
931
+ continue;
932
+ }
933
+ if (!(declared == null ? void 0 : declared.type)) continue;
934
+ const fires = ((_a = declared.workflow) == null ? void 0 : _a.trigger) || declared.trigger;
935
+ rows.push([
936
+ "`" + declared.type + "`",
937
+ slug2,
938
+ declared.hook ? "`" + declared.hook + "`" : "\u2014",
939
+ declared.queue,
940
+ declared.system ? "system" : declared.withdrawn ? "withdrawn" : "builder",
941
+ fires ? "`" + fires.event + "` / `" + fires.type + "`" : (declared.triggers || []).map((t) => "`" + t + "`").join(", ") || "\u2014"
942
+ ]);
943
+ continue;
944
+ }
945
+ walk(value, [...path, key], slug2);
946
+ }
947
+ };
948
+ for (const slug2 of slugs()) walk(connections[slug2].steps, [], slug2);
949
+ return table(["Step", "Connection", "Hook", "Queue", "Shape", "Fired by"], rows.sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1])));
950
+ };
951
+ var workflows = () => table(
952
+ ["Workflow", "Connection", "Steps", "Trigger"],
953
+ slugs().flatMap((slug2) => systemWorkflows(connections[slug2]).map((workflow) => [
954
+ workflow.title,
955
+ slug2,
956
+ workflow.steps.map((step) => "`" + step.type + "`").join(" \u2192 "),
957
+ "`" + workflow.trigger.event + "` / `" + workflow.trigger.type + "`"
958
+ ]))
959
+ );
960
+ var reference = () => [
961
+ MARKER.start,
962
+ "",
963
+ "### The support matrix",
964
+ "",
965
+ "`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.",
966
+ "",
967
+ matrix(),
968
+ "",
969
+ "### The closed vocabularies",
970
+ "",
971
+ vocabularies(),
972
+ "",
973
+ "### Every step",
974
+ "",
975
+ "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.",
976
+ "",
977
+ steps(),
978
+ "",
979
+ "### The system workflows a connection is provisioned with",
980
+ "",
981
+ "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.",
982
+ "",
983
+ workflows(),
984
+ "",
985
+ "### Counts",
986
+ "",
987
+ "- **" + slugs().length + "** connections",
988
+ "- **" + slots().length + "** hook slots",
989
+ "- **" + Object.keys(STEPS).length + "** step types",
990
+ "- **" + Object.keys(TRIGGERS).length + "** triggers",
991
+ "",
992
+ MARKER.end
993
+ ].join("\n");
994
+ var spliced = (document) => {
995
+ const from = document.indexOf(MARKER.start);
996
+ const to = document.indexOf(MARKER.end);
997
+ if (from < 0 || to < 0) return document;
998
+ return document.slice(0, from) + reference() + document.slice(to + MARKER.end.length);
999
+ };
1000
+ var MARKERS = MARKER;
1001
+
857
1002
  // lib/connections/oauth.js
858
1003
  import { createHash, randomBytes } from "crypto";
859
1004
  var credentials = ({ basic, clientId, clientSecret }) => basic ? {
@@ -5822,7 +5967,7 @@ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
5822
5967
  return fetchAppToken({ clientId, clientSecret, fetcher });
5823
5968
  };
5824
5969
  var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
5825
- var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
5970
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference: reference2, revision, shopId, timestamp, value }) => {
5826
5971
  if (!shopId || !eventHandle || !(Number(value) > 0)) {
5827
5972
  throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
5828
5973
  }
@@ -5836,8 +5981,8 @@ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempo
5836
5981
  const body = {
5837
5982
  attributes: {
5838
5983
  value: Number(value),
5839
- ...reference && {
5840
- reference: String(reference).slice(0, 128)
5984
+ ...reference2 && {
5985
+ reference: String(reference2).slice(0, 128)
5841
5986
  }
5842
5987
  },
5843
5988
  event_handle: eventHandle,
@@ -7998,6 +8143,12 @@ var shopify_default2 = {
7998
8143
  orderId: String(orderId),
7999
8144
  rate,
8000
8145
  shopId: connection.source.id,
8146
+ // WHICH VENDOR IS BEING CHARGED, so the sender does not have
8147
+ // to assume. queue/usage.js named the slug itself and
8148
+ // defaults to it for anything already queued when this
8149
+ // shipped — an app-store vendor billing through its own meter
8150
+ // enqueues the same job with its own slug.
8151
+ slug: connection.slug,
8001
8152
  // The App Events API returns no event id, so one is generated
8002
8153
  // here — the event handle plus the order id — and sent as the
8003
8154
  // event's `reference`. queue/usage.js stamps the same id onto
@@ -9221,6 +9372,13 @@ var shopify_default2 = {
9221
9372
  key: "Shopify Order Tracking",
9222
9373
  queue: "connection",
9223
9374
  system: true,
9375
+ // THE TRIGGER THE REGISTER JOB HAND-WROTE. It is byte-identical to
9376
+ // the one on every stored workflow, because these documents exist on
9377
+ // prod with thousands of runs behind them and provisioning is
9378
+ // idempotent on (connection, system, title) — a different trigger
9379
+ // here would not be picked up by the existing rows anyway, and a
9380
+ // different TITLE would provision a second workflow for every store.
9381
+ trigger: { event: "shopify.order.create", type: "webhook" },
9224
9382
  type: "step.commerce.order.record"
9225
9383
  })
9226
9384
  },
@@ -9231,6 +9389,15 @@ var shopify_default2 = {
9231
9389
  key: "Shopify Product Sync",
9232
9390
  queue: "connection",
9233
9391
  system: true,
9392
+ // `shopify.product.update`, NOT `product.insert`. The plan named the
9393
+ // latter, and it is the right long-term answer — a Drawbridge product
9394
+ // row appearing should pull the vendor's copy, rather than
9395
+ // stream/product.js branching on the slug and enqueueing a
9396
+ // vendor-named queue. But a workflow carries ONE trigger, this one is
9397
+ // stored on prod against the webhook, and changing a live workflow's
9398
+ // trigger is a data migration. So the declaration matches what exists
9399
+ // and `product.insert` stays a separate, migration-gated change.
9400
+ trigger: { event: "shopify.product.update", type: "webhook" },
9234
9401
  type: "step.commerce.product.sync"
9235
9402
  })
9236
9403
  }
@@ -9252,26 +9419,37 @@ var shopify_default2 = {
9252
9419
  type: "step.connection.health.check"
9253
9420
  })
9254
9421
  },
9255
- // Audit-only. The "Shopify Token Activity" system workflow lists these
9256
- // for descriptive grouping, but its audit step docs are written manually
9257
- // at OAuth time — the workflow is never dispatched. Routing is declared
9258
- // defensively so that if it ever IS dispatched, the job lands on a real
9259
- // queue and the handler lookup misses cleanly instead of throwing
9260
- // "Unknown step type".
9422
+ // THE ONE WORKFLOW THAT CARRIES TWO STEPS, and the shape the plan called
9423
+ // the single genuinely new thing in it.
9424
+ //
9425
+ // Every other system step is its own workflow, titled by its own `key`.
9426
+ // These two are "Shopify Token Activity", one row in the merchant's list
9427
+ // with both steps in it — so they declare `workflow`, which names the
9428
+ // title, the trigger and the ORDER, and the provisioner groups on it.
9429
+ // Without that key the pair could only be provisioned by the hand-written
9430
+ // spec in drawbridge-sync's register job, which is exactly why that spec
9431
+ // outlived every other one.
9432
+ //
9433
+ // AUDIT-ONLY. The step documents are written manually at OAuth time and
9434
+ // the workflow is never dispatched; `queue` is declared defensively so
9435
+ // that if it ever IS, the job lands on a real queue and the handler lookup
9436
+ // misses cleanly rather than throwing "Unknown step type".
9261
9437
  token: {
9262
9438
  exchange: () => ({
9263
9439
  description: "Records the token exchange that completed an install. Audit only \u2014 never dispatched.",
9264
9440
  key: "Shopify Token Exchange",
9265
9441
  queue: "connection",
9266
9442
  system: true,
9267
- type: "step.connection.token.exchange"
9443
+ type: "step.connection.token.exchange",
9444
+ workflow: { key: "Shopify Token Activity", order: 0, trigger: { event: "shopify.token", type: "event" } }
9268
9445
  }),
9269
9446
  refresh: () => ({
9270
9447
  description: "Records a token rotation. Audit only \u2014 never dispatched.",
9271
9448
  key: "Shopify Token Refresh",
9272
9449
  queue: "connection",
9273
9450
  system: true,
9274
- type: "step.connection.token.refresh"
9451
+ type: "step.connection.token.refresh",
9452
+ workflow: { key: "Shopify Token Activity", order: 1, trigger: { event: "shopify.token", type: "event" } }
9275
9453
  })
9276
9454
  }
9277
9455
  }
@@ -9727,7 +9905,7 @@ var webhook_default = {
9727
9905
  // The connect prompt only where connecting is not already the card's whole
9728
9906
  // story: a disconnected or errored document renders a Connect action itself,
9729
9907
  // and a task repeating it talks over the button.
9730
- tasks: ({ settings, status } = {}) => ["disconnected", "error"].includes(status) ? [] : (settings == null ? void 0 : settings.secret) ? [
9908
+ tasks: ({ settings, status } = {}) => status === "error" ? [] : (settings == null ? void 0 : settings.secret) ? [
9731
9909
  {
9732
9910
  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.',
9733
9911
  title: "Requests must be verified",
@@ -9814,6 +9992,77 @@ var attentive_default3 = {
9814
9992
  }
9815
9993
  };
9816
9994
 
9995
+ // lib/connections/icons/brightdata.js
9996
+ var brightdata_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9997
+ <rect width="500" height="500" fill="#0F58FF"/>
9998
+ <circle cx="250" cy="250" r="52" fill="white"/>
9999
+ <rect x="234" y="70" width="32" height="96" rx="16" fill="white"/>
10000
+ <rect x="234" y="334" width="32" height="96" rx="16" fill="white"/>
10001
+ <rect x="70" y="234" width="96" height="32" rx="16" fill="white"/>
10002
+ <rect x="334" y="234" width="96" height="32" rx="16" fill="white"/>
10003
+ <rect x="117" y="140" width="32" height="96" rx="16" transform="rotate(-45 117 140)" fill="white"/>
10004
+ <rect x="304" y="327" width="32" height="96" rx="16" transform="rotate(-45 304 327)" fill="white"/>
10005
+ <rect x="360" y="117" width="32" height="96" rx="16" transform="rotate(45 360 117)" fill="white"/>
10006
+ <rect x="173" y="304" width="32" height="96" rx="16" transform="rotate(45 173 304)" fill="white"/>
10007
+ </svg>`;
10008
+
10009
+ // lib/connections/vendors/brightdata.js
10010
+ var brightdata_default2 = {
10011
+ fields: [
10012
+ {
10013
+ credential: "BRIGHTDATA_BROWSER_URI",
10014
+ input: "password",
10015
+ key: "browserUri",
10016
+ // A wss:// endpoint with the account's user and password in its
10017
+ // authority, which is why it is a password field rather than a url one:
10018
+ // the whole string is the credential.
10019
+ label: "Scraping Browser endpoint",
10020
+ message: "The wss:// Scraping Browser endpoint, carrying its own credentials. Used to render pages that need a real browser.",
10021
+ redact: true,
10022
+ required: true
10023
+ },
10024
+ {
10025
+ credential: "BRIGHTDATA_UNLOCKER_API_KEY",
10026
+ input: "password",
10027
+ key: "unlockerApiKey",
10028
+ label: "Web Unlocker API key",
10029
+ // NO EXPIRY FIELD, and that is a decision rather than an omission. Bright
10030
+ // Data offers an `Unlimited` expiry and recommends against it; Drawbridge
10031
+ // takes the unlimited key deliberately, because there is no way to rotate
10032
+ // this programmatically and a date nothing can act on is a date that only
10033
+ // surfaces after it has already broken scraping. If a fixed expiry is ever
10034
+ // taken instead, the field to add is a stored date and a warning ahead of
10035
+ // it — not a rotation, which the vendor cannot support.
10036
+ 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.",
10037
+ redact: true,
10038
+ required: true
10039
+ },
10040
+ {
10041
+ credential: "BRIGHTDATA_UNLOCKER_ZONE",
10042
+ input: "text",
10043
+ key: "unlockerZone",
10044
+ label: "Web Unlocker zone",
10045
+ // Not secret — a zone is a label on the account, and it is shown rather
10046
+ // than redacted so an operator can confirm which one is in use without
10047
+ // clearing the field to read it.
10048
+ message: "The zone name the Unlocker requests run against.",
10049
+ required: true
10050
+ }
10051
+ ],
10052
+ icon: brightdata_default,
10053
+ name: "Bright Data",
10054
+ slug: "brightdata",
10055
+ // WHERE THE FACTS CAME FROM, read 2026-09-16.
10056
+ urls: {
10057
+ api: "https://docs.brightdata.com/api-reference/authentication",
10058
+ dashboard: "https://brightdata.com/cp/setting/users",
10059
+ // Bright Data has no scope model — a key carries the account's own access —
10060
+ // so there is nothing to link. `false` is the recorded answer rather than a
10061
+ // missing key.
10062
+ scopes: false
10063
+ }
10064
+ };
10065
+
9817
10066
  // lib/connections/vendors/hubspot.js
9818
10067
  var hubspot_default2 = {
9819
10068
  // WHAT AN ADMIN TYPES on the provider screen. `credential` names the env var a
@@ -10127,7 +10376,7 @@ var twilio_default2 = {
10127
10376
 
10128
10377
  // lib/connections/index.js
10129
10378
  var QUEUES = ["connection", "notification", "segment", "webhook"];
10130
- var implemented = (hooks, path) => {
10379
+ var implemented2 = (hooks, path) => {
10131
10380
  const hook = path.split(".").reduce((node, key) => node == null ? void 0 : node[key], hooks);
10132
10381
  return typeof hook === "function" || !!hook && typeof hook === "object";
10133
10382
  };
@@ -10189,7 +10438,7 @@ var checkIcon = (owner, icon) => {
10189
10438
  }
10190
10439
  };
10191
10440
  var vendors = Object.freeze(Object.fromEntries(
10192
- [attentive_default3, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10441
+ [attentive_default3, brightdata_default2, hubspot_default2, klaviyo_default3, mailchimp_default3, sendgrid_default2, shopify_default3, twilio_default2].map((vendor) => [vendor.slug, vendor])
10193
10442
  ));
10194
10443
  var buildVendor = (vendor) => {
10195
10444
  if (!(vendor == null ? void 0 : vendor.slug)) throw new Error("A vendor needs a slug");
@@ -10216,7 +10465,7 @@ for (const [slug2, vendor] of Object.entries(vendors)) {
10216
10465
  buildVendor(vendor);
10217
10466
  }
10218
10467
  var build = (manifest) => {
10219
- 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;
10468
+ 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;
10220
10469
  if (!(manifest == null ? void 0 : manifest.slug)) throw new Error("A connection needs a slug");
10221
10470
  if (!(manifest == null ? void 0 : manifest.title)) throw new Error(manifest.slug + " needs a title");
10222
10471
  if (manifest == null ? void 0 : manifest.private) throw new Error(manifest.slug + " declares private \u2014 that is feature : false now");
@@ -10245,7 +10494,7 @@ var build = (manifest) => {
10245
10494
  if (!field.hook.startsWith("resources.")) {
10246
10495
  throw new Error(manifest.slug + "." + field.key + " reads from " + field.hook + " \u2014 a picker may only read resources.*");
10247
10496
  }
10248
- if (!implemented(manifest.hooks, field.hook)) {
10497
+ if (!implemented2(manifest.hooks, field.hook)) {
10249
10498
  throw new Error(manifest.slug + "." + field.key + " reads " + field.hook + ", which this vendor does not implement");
10250
10499
  }
10251
10500
  }
@@ -10284,7 +10533,7 @@ var build = (manifest) => {
10284
10533
  }
10285
10534
  let callback = null;
10286
10535
  const type = kinds[0];
10287
- if (["install", "oauth"].includes(type) && !implemented(manifest.hooks, "lifecycle.health")) {
10536
+ if (["install", "oauth"].includes(type) && !implemented2(manifest.hooks, "lifecycle.health")) {
10288
10537
  throw new Error(
10289
10538
  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"
10290
10539
  );
@@ -10313,10 +10562,10 @@ var build = (manifest) => {
10313
10562
  }
10314
10563
  callback = "/api/connection/" + manifest.slug + "/callback";
10315
10564
  }
10316
- 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)) {
10565
+ 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)) {
10317
10566
  throw new Error(manifest.slug + " implements inbound.event but declares no inbound.headers.event");
10318
10567
  }
10319
- 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)) {
10568
+ 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)) {
10320
10569
  throw new Error(manifest.slug + " implements inbound.verify but declares no inbound.headers.signature");
10321
10570
  }
10322
10571
  if (!Array.isArray((_w = manifest == null ? void 0 : manifest.content) == null ? void 0 : _w.guide) || !manifest.content.guide.length) {
@@ -10327,7 +10576,7 @@ var build = (manifest) => {
10327
10576
  if (Array.isArray(node)) {
10328
10577
  for (const verb of node) {
10329
10578
  const hook = declared2 == null ? void 0 : declared2[verb];
10330
- if (hook !== false && !implemented({ [verb]: hook }, verb)) {
10579
+ if (hook !== false && !implemented2({ [verb]: hook }, verb)) {
10331
10580
  throw new Error(manifest.slug + " must answer hooks." + [...path, verb].join(".") + " \u2014 false, a function, or {} if another repo implements it");
10332
10581
  }
10333
10582
  }
@@ -10473,7 +10722,7 @@ var build = (manifest) => {
10473
10722
  if (!QUEUES.includes(declared2 == null ? void 0 : declared2.queue)) {
10474
10723
  throw new Error(manifest.slug + " step " + type2 + " needs a queue \u2014 one of " + QUEUES.join(", "));
10475
10724
  }
10476
- if (declared2.hook && !implemented(manifest.hooks, declared2.hook)) {
10725
+ if (declared2.hook && !implemented2(manifest.hooks, declared2.hook)) {
10477
10726
  throw new Error(manifest.slug + " step " + type2 + " points at hook " + declared2.hook + ", which this vendor does not implement");
10478
10727
  }
10479
10728
  for (const trigger of declared2.triggers || []) {
@@ -10482,11 +10731,22 @@ var build = (manifest) => {
10482
10731
  manifest.slug + " step " + type2 + " may be triggered by " + trigger + ", which is not a trigger \u2014 one of " + Object.keys(TRIGGERS).join(", ")
10483
10732
  );
10484
10733
  }
10485
- if (declared2.trigger) {
10486
- const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === declared2.trigger.event && TRIGGERS[key].type === declared2.trigger.type)];
10734
+ if (declared2.workflow) {
10735
+ if (!declared2.workflow.key) {
10736
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no key \u2014 the title several steps share");
10737
+ }
10738
+ if (typeof declared2.workflow.order !== "number") {
10739
+ throw new Error(manifest.slug + " step " + type2 + " declares a workflow with no order \u2014 two steps in one row need a sequence");
10740
+ }
10741
+ if (declared2.trigger) {
10742
+ 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");
10743
+ }
10744
+ }
10745
+ for (const candidate of [declared2.trigger, (_y = declared2.workflow) == null ? void 0 : _y.trigger].filter(Boolean)) {
10746
+ const known2 = TRIGGERS[Object.keys(TRIGGERS).find((key) => TRIGGERS[key].event === candidate.event && TRIGGERS[key].type === candidate.type)];
10487
10747
  if (!known2) {
10488
10748
  throw new Error(
10489
- manifest.slug + " step " + type2 + " declares trigger { event : " + declared2.trigger.event + ", type : " + declared2.trigger.type + " }, which nothing dispatches"
10749
+ manifest.slug + " step " + type2 + " declares trigger { event : " + candidate.event + ", type : " + candidate.type + " }, which nothing dispatches"
10490
10750
  );
10491
10751
  }
10492
10752
  }
@@ -10556,9 +10816,10 @@ var publicSettingsBySlug = Object.fromEntries(
10556
10816
  var connectionSteps = (env = {}) => Object.entries(availableConnections(env)).flatMap(
10557
10817
  ([slug2, manifest]) => leaves(manifest.steps).map(([, step]) => ({ ...step({}), slug: slug2 }))
10558
10818
  );
10819
+ var implementsHook = (hooks, name) => implemented2(hooks, name);
10559
10820
  var hookSupport = (name) => ({
10560
- no: Object.keys(connections).filter((slug2) => !implemented(connections[slug2].hooks, name)),
10561
- yes: Object.keys(connections).filter((slug2) => implemented(connections[slug2].hooks, name))
10821
+ no: Object.keys(connections).filter((slug2) => !implemented2(connections[slug2].hooks, name)),
10822
+ yes: Object.keys(connections).filter((slug2) => implemented2(connections[slug2].hooks, name))
10562
10823
  });
10563
10824
  var connectFields = (slug2) => {
10564
10825
  var _a;
@@ -10714,6 +10975,7 @@ export {
10714
10975
  HOOK_PROPS,
10715
10976
  HOOK_SLOT_PROPS,
10716
10977
  INPUTS,
10978
+ MARKERS,
10717
10979
  OAUTH_ENDPOINTS,
10718
10980
  OAUTH_FIELDS,
10719
10981
  OUTCOMES,
@@ -10741,6 +11003,7 @@ export {
10741
11003
  effectsOf,
10742
11004
  ensureSystemWorkflows,
10743
11005
  hookSupport,
11006
+ implementsHook,
10744
11007
  isStale,
10745
11008
  isStatus,
10746
11009
  mergeSettings,
@@ -10751,14 +11014,17 @@ export {
10751
11014
  publicSettingsBySlug,
10752
11015
  reconcileConnectionScopes,
10753
11016
  redactSettings,
11017
+ reference,
10754
11018
  resolveConnection,
10755
11019
  runHook,
10756
11020
  scopesMessage,
11021
+ spliced,
10757
11022
  stepLabels,
10758
11023
  stepQueues,
10759
11024
  stepRoutes,
10760
11025
  surviving,
10761
11026
  systemSteps,
11027
+ systemWorkflows,
10762
11028
  tokenSettings,
10763
11029
  vendors
10764
11030
  };