// runtime/subsystems/integration/webhook-change-source.ts
var WebhookChangeSource = class {
  label;
  queue;
  externalIdSourceField;
  /**
   * Record field carrying the event id, when `webhook.eventIdField` is
   * configured. Used only as the fallback when the queue iterator does NOT
   * yield an `eventId` — see {@link WebhookFetchCallback} for the precedence.
   */
  eventIdSourceField;
  composed;
  constructor(opts) {
    if (opts.config.mode !== "webhook") {
      throw new Error(
        `WebhookChangeSource requires DetectionConfig.mode === 'webhook'; got '${opts.config.mode}'`
      );
    }
    const config = opts.config;
    const externalIdMapping = config.mapping.find(
      (m) => m.target === "external_id"
    );
    if (!externalIdMapping) {
      throw new Error(
        "WebhookChangeSource: DetectionConfig.mapping must include an entry with target 'external_id' so emitted Change<T>.externalId can be populated"
      );
    }
    this.externalIdSourceField = externalIdMapping.source;
    this.eventIdSourceField = config.webhook.eventIdField;
    this.queue = opts.queue;
    this.label = opts.label ?? `webhook-change-source:${externalIdMapping.source}`;
    const inner = (sub, cur) => this.fetch(sub, cur);
    const middlewares = opts.middlewares ?? [];
    this.composed = middlewares.reduceRight(
      (next, mw) => mw(next),
      inner
    );
  }
  listChanges(subscription, cursor) {
    return this.composed(subscription, cursor);
  }
  async *fetch(subscription, cursor) {
    const ctx = {
      subscription,
      cursor
    };
    for await (const {
      record,
      eventId: yieldedEventId,
      cursor: nextCursor
    } of this.queue(ctx)) {
      const externalIdRaw = record[this.externalIdSourceField];
      if (typeof externalIdRaw !== "string" || externalIdRaw.length === 0) {
        throw new Error(
          `WebhookChangeSource: record missing string '${this.externalIdSourceField}' \u2014 emitted records MUST carry the canonical external id keyed by the mapping source`
        );
      }
      const dedupKey = this.deriveDedupKey(yieldedEventId, record);
      const change = {
        externalId: externalIdRaw,
        // Webhook mode cannot distinguish create vs. update vs. delete on
        // its own — the orchestrator's diff stage handles classification.
        // Tombstone / soft-delete detection is consumer-driven (same as
        // poll mode — see ADR-033).
        operation: "updated",
        record,
        cursor: nextCursor ?? null,
        source: "webhook",
        dedupKey
      };
      yield change;
    }
  }
  /**
   * Resolve `Change<T>.dedupKey` with the precedence: yielded `eventId` >
   * `webhook.eventIdField` record extraction > `undefined`. A non-empty
   * yielded `eventId` always wins; otherwise the configured field is read off
   * the record (and must be a non-empty string when the field is configured);
   * with neither, `dedupKey` is `undefined` (the orchestrator then has no
   * delivery-level dedup signal for this change).
   */
  deriveDedupKey(yieldedEventId, record) {
    if (yieldedEventId !== void 0 && yieldedEventId.length > 0) {
      return yieldedEventId;
    }
    if (this.eventIdSourceField === void 0) {
      return void 0;
    }
    const eventIdRaw = record[this.eventIdSourceField];
    if (typeof eventIdRaw !== "string" || eventIdRaw.length === 0) {
      throw new Error(
        `WebhookChangeSource: record missing string '${this.eventIdSourceField}' \u2014 a webhook record MUST carry the event id (DetectionConfig.webhook.eventIdField) so Change<T>.dedupKey can be populated, unless the queue iterator yields an 'eventId' alongside the record`
      );
    }
    return eventIdRaw;
  }
};

export {
  WebhookChangeSource
};
//# sourceMappingURL=chunk-TIZXQU26.js.map