@adcp/sdk 14.1.0 → 14.3.0

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.
Files changed (139) hide show
  1. package/dist/lib/core/SingleAgentClient.d.mts +4 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +4 -1
  3. package/dist/lib/core/SingleAgentClient.js +25 -4
  4. package/dist/lib/core/SingleAgentClient.mjs +25 -4
  5. package/dist/lib/core/TaskExecutor.d.mts +3 -1
  6. package/dist/lib/core/TaskExecutor.d.ts +3 -1
  7. package/dist/lib/core/TaskExecutor.js +15 -11
  8. package/dist/lib/core/TaskExecutor.mjs +15 -11
  9. package/dist/lib/core/buyer-account-registry.d.mts +30 -2
  10. package/dist/lib/core/buyer-account-registry.d.ts +30 -2
  11. package/dist/lib/core/buyer-account-registry.js +126 -42
  12. package/dist/lib/core/buyer-account-registry.mjs +127 -43
  13. package/dist/lib/index.d.mts +3 -2
  14. package/dist/lib/index.d.ts +3 -2
  15. package/dist/lib/index.js +9 -0
  16. package/dist/lib/index.mjs +12 -1
  17. package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
  18. package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
  19. package/dist/lib/net/agent-transport-fetch.js +18 -5
  20. package/dist/lib/net/agent-transport-fetch.mjs +17 -5
  21. package/dist/lib/reporting/inspection.d.mts +3 -3
  22. package/dist/lib/reporting/inspection.d.ts +3 -3
  23. package/dist/lib/reporting/inspection.js +229 -84
  24. package/dist/lib/reporting/inspection.mjs +229 -84
  25. package/dist/lib/reporting/ledger/status-ingest.js +3 -19
  26. package/dist/lib/reporting/ledger/status-ingest.mjs +2 -18
  27. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  28. package/dist/lib/server/create-adcp-server.js +5 -2
  29. package/dist/lib/server/create-adcp-server.mjs +5 -2
  30. package/dist/lib/signing/agent-resolver/errors.d.mts +2 -0
  31. package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
  32. package/dist/lib/signing/agent-resolver/errors.js +6 -0
  33. package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
  34. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
  35. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
  36. package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
  37. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
  38. package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
  39. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
  40. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
  41. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
  42. package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
  43. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
  44. package/dist/lib/signing/brand-jwks.d.mts +2 -0
  45. package/dist/lib/signing/brand-jwks.d.ts +2 -0
  46. package/dist/lib/signing/brand-jwks.js +10 -2
  47. package/dist/lib/signing/brand-jwks.mjs +10 -2
  48. package/dist/lib/signing/canonicalize.d.mts +2 -0
  49. package/dist/lib/signing/canonicalize.d.ts +2 -0
  50. package/dist/lib/signing/canonicalize.js +1 -1
  51. package/dist/lib/signing/canonicalize.mjs +1 -1
  52. package/dist/lib/signing/errors.d.mts +4 -2
  53. package/dist/lib/signing/errors.d.ts +4 -2
  54. package/dist/lib/signing/errors.js +3 -1
  55. package/dist/lib/signing/errors.mjs +3 -1
  56. package/dist/lib/signing/verifier.js +2 -2
  57. package/dist/lib/signing/verifier.mjs +2 -2
  58. package/dist/lib/signing/webhook-verifier.js +2 -2
  59. package/dist/lib/signing/webhook-verifier.mjs +3 -3
  60. package/dist/lib/testing/storyboard/request-signing/builder.js +13 -1
  61. package/dist/lib/testing/storyboard/request-signing/builder.mjs +13 -1
  62. package/dist/lib/testing/storyboard/request-signing/grader.js +1 -0
  63. package/dist/lib/testing/storyboard/request-signing/grader.mjs +1 -0
  64. package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -1
  65. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -1
  66. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  67. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  68. package/dist/lib/types/accept-proposal.d.ts +19 -1
  69. package/dist/lib/types/buy-products.d.ts +19 -1
  70. package/dist/lib/types/check-governance.d.ts +19 -1
  71. package/dist/lib/types/comply-test-controller.d.ts +19 -1
  72. package/dist/lib/types/control-media-buy.d.ts +19 -1
  73. package/dist/lib/types/core.generated.d.mts +14 -1
  74. package/dist/lib/types/core.generated.d.ts +14 -1
  75. package/dist/lib/types/create-media-buy.d.ts +14 -1
  76. package/dist/lib/types/get-media-buys.d.ts +14 -1
  77. package/dist/lib/types/get-products.d.ts +19 -1
  78. package/dist/lib/types/list-products.d.ts +19 -1
  79. package/dist/lib/types/refine-proposals.d.ts +19 -1
  80. package/dist/lib/types/request-proposals.d.ts +19 -1
  81. package/dist/lib/types/schemas.generated.d.ts +12 -3
  82. package/dist/lib/types/schemas.generated.js +4 -1
  83. package/dist/lib/types/schemas.generated.mjs +4 -1
  84. package/dist/lib/types/tools.generated.d.mts +14 -1
  85. package/dist/lib/types/tools.generated.d.ts +14 -1
  86. package/dist/lib/types/update-media-buy.d.ts +14 -1
  87. package/dist/lib/utils/pointer-utils.d.mts +2 -0
  88. package/dist/lib/utils/pointer-utils.d.ts +2 -0
  89. package/dist/lib/utils/pointer-utils.js +43 -0
  90. package/dist/lib/utils/pointer-utils.mjs +19 -0
  91. package/dist/lib/validation/diagnostic-limits.d.mts +4 -0
  92. package/dist/lib/validation/diagnostic-limits.d.ts +4 -0
  93. package/dist/lib/validation/diagnostic-limits.js +57 -0
  94. package/dist/lib/validation/diagnostic-limits.mjs +32 -0
  95. package/dist/lib/validation/schema-errors.d.mts +3 -1
  96. package/dist/lib/validation/schema-errors.d.ts +3 -1
  97. package/dist/lib/validation/schema-errors.js +18 -3
  98. package/dist/lib/validation/schema-errors.mjs +18 -3
  99. package/dist/lib/validation/schema-validator.js +56 -4
  100. package/dist/lib/validation/schema-validator.mjs +56 -4
  101. package/dist/lib/version.d.mts +3 -3
  102. package/dist/lib/version.d.ts +3 -3
  103. package/dist/lib/version.js +3 -3
  104. package/dist/lib/version.mjs +3 -3
  105. package/dist/lib/webhooks/index.d.mts +24 -0
  106. package/dist/lib/webhooks/index.d.ts +24 -0
  107. package/dist/lib/webhooks/index.js +50 -24
  108. package/dist/lib/webhooks/index.mjs +49 -24
  109. package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
  110. package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
  111. package/dist/lib/wholesale-feed-sync/index.js +7 -0
  112. package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
  113. package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
  114. package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
  115. package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
  116. package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
  117. package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
  118. package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
  119. package/dist/lib/wholesale-feed-sync/sync.js +153 -297
  120. package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
  121. package/docs/README.md +6 -0
  122. package/docs/TYPE-SUMMARY.md +2 -2
  123. package/docs/guides/BUYER-STORAGE.md +3 -0
  124. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
  125. package/docs/guides/account-resolution.md +46 -10
  126. package/docs/llms.txt +2 -2
  127. package/docs/migration-14.0-to-14.1.md +85 -0
  128. package/docs/migration-14.x-rc-worksheet.md +4 -4
  129. package/docs/migration-agent-resolution-3.3.md +5 -3
  130. package/docs/recipes/verifying-inbound-webhooks.md +4 -0
  131. package/package.json +3 -2
  132. package/skills/call-adcp-agent/SKILL.md +4 -1
  133. package/skills/adcp-brand.previous/SKILL.md +0 -200
  134. package/skills/adcp-creative.previous/SKILL.md +0 -305
  135. package/skills/adcp-governance.previous/SKILL.md +0 -566
  136. package/skills/adcp-measurement.previous/SKILL.md +0 -136
  137. package/skills/adcp-media-buy.previous/SKILL.md +0 -556
  138. package/skills/adcp-si.previous/SKILL.md +0 -206
  139. package/skills/adcp-signals.previous/SKILL.md +0 -204
@@ -0,0 +1,322 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { randomUUID } from "node:crypto";
3
+ import { parseWholesaleFeedWebhookNotification } from "./webhook-notification.mjs";
4
+ class InMemoryWholesaleFeedMirrorStore {
5
+ /** Fail closed at capacity rather than evicting receipts inside the delivery retry window. */
6
+ constructor(maxWebhookReceipts = 1e4) {
7
+ this.maxWebhookReceipts = maxWebhookReceipts;
8
+ if (!Number.isSafeInteger(maxWebhookReceipts) || maxWebhookReceipts < 1)
9
+ throw new Error("maxWebhookReceipts must be a positive integer.");
10
+ }
11
+ maxWebhookReceipts;
12
+ records = /* @__PURE__ */ new Map();
13
+ receipts = /* @__PURE__ */ new Map();
14
+ receiptCount = 0;
15
+ async hasWebhookReceipt(scope, idempotencyKey) {
16
+ return this.receipts.get(this.key(scope))?.has(idempotencyKey) ?? false;
17
+ }
18
+ key(scope) {
19
+ return JSON.stringify([scope.agentUrl, scope.accountKey, scope.entity]);
20
+ }
21
+ async read(scope) {
22
+ return structuredClone(this.records.get(this.key(scope)) ?? { revision: 0, items: [], cacheScope: "public" });
23
+ }
24
+ /** Seed a detached record when restoring a shell snapshot. */
25
+ restore(scope, snapshot) {
26
+ this.records.set(this.key(scope), structuredClone(snapshot));
27
+ }
28
+ async commit(scope, expectedRevision, change) {
29
+ const key = this.key(scope);
30
+ const prior = this.records.get(key) ?? { revision: 0, items: [], cacheScope: "public" };
31
+ if (prior.revision !== expectedRevision) return false;
32
+ const items = new Map(prior.items.map((item) => [itemId(scope.entity, item), item]));
33
+ for (const id of change.deletes) items.delete(id);
34
+ for (const item of change.upserts) items.set(itemId(scope.entity, item), structuredClone(item));
35
+ const { upserts: _upserts, deletes: _deletes, webhookReceipt, ...metadata } = change;
36
+ const next = {
37
+ ...prior,
38
+ ...structuredClone(metadata),
39
+ revision: prior.revision + 1,
40
+ items: [...items.values()],
41
+ error: void 0
42
+ };
43
+ if (webhookReceipt) {
44
+ const receipts = this.receipts.get(key) ?? /* @__PURE__ */ new Set();
45
+ if (!receipts.has(webhookReceipt.idempotencyKey)) {
46
+ if (this.receiptCount >= this.maxWebhookReceipts)
47
+ throw new Error("Webhook receipt capacity reached; use durable storage or increase maxWebhookReceipts.");
48
+ receipts.add(webhookReceipt.idempotencyKey);
49
+ ++this.receiptCount;
50
+ }
51
+ this.receipts.set(key, receipts);
52
+ }
53
+ this.records.set(key, next);
54
+ return true;
55
+ }
56
+ async recordError(scope, expectedRevision, error) {
57
+ const key = this.key(scope);
58
+ const prior = this.records.get(key) ?? { revision: 0, items: [], cacheScope: "public" };
59
+ if (prior.revision !== expectedRevision) return false;
60
+ this.records.set(key, { ...prior, revision: prior.revision + 1, error: structuredClone(error) });
61
+ return true;
62
+ }
63
+ }
64
+ async function refreshWholesaleFeed(options) {
65
+ return refreshMirror(options);
66
+ }
67
+ async function refreshMirror(options, receipt, expectedWebhookVersion) {
68
+ options = { ...options, scope: structuredClone(options.scope), account: structuredClone(options.account) };
69
+ const { store, scope, client } = options;
70
+ const previous = await store.read(scope);
71
+ if (options.isCurrent && !options.isCurrent()) return { outcome: "superseded" };
72
+ let cursor;
73
+ const cursors = /* @__PURE__ */ new Set();
74
+ const items = /* @__PURE__ */ new Map();
75
+ let wholesaleFeedVersion;
76
+ let pricingVersion;
77
+ let cacheScope = previous.cacheScope;
78
+ let unchanged = false;
79
+ let page = 0;
80
+ try {
81
+ do {
82
+ if (++page > 1e4) throw new Error("Wholesale feed pagination exceeded its page limit.");
83
+ const params = {
84
+ ...scope.entity === "product" ? { buying_mode: "wholesale" } : { discovery_mode: "wholesale" },
85
+ pagination: { max_results: 100, ...cursor && { cursor } },
86
+ ...options.account && { account: options.account },
87
+ ...!cursor && options.conditional !== false && previous.wholesaleFeedVersion && {
88
+ if_wholesale_feed_version: previous.wholesaleFeedVersion,
89
+ ...previous.pricingVersion && { if_pricing_version: previous.pricingVersion }
90
+ }
91
+ };
92
+ const result = await (scope.entity === "product" ? client.getProducts(params) : client.getSignals(params));
93
+ if (options.isCurrent && !options.isCurrent()) return { outcome: "superseded" };
94
+ if (result.success === false || result.status !== void 0 && !["completed", "success"].includes(result.status)) {
95
+ throw Object.assign(new Error(result.error ?? "Wholesale feed refresh failed."), {
96
+ adcpError: result.adcpError
97
+ });
98
+ }
99
+ const body = result.data;
100
+ if (!body) throw new Error("Wholesale feed did not return a completed catalog.");
101
+ if (body.unchanged) {
102
+ if (cursor || !("if_wholesale_feed_version" in params) || body.wholesale_feed_version !== previous.wholesaleFeedVersion || "if_pricing_version" in params && body.pricing_version !== previous.pricingVersion || body.cache_scope !== void 0 && body.cache_scope !== previous.cacheScope) {
103
+ throw new Error("Wholesale feed unchanged response must match both requested version tokens.");
104
+ }
105
+ unchanged = true;
106
+ }
107
+ if (page > 1 && (body.wholesale_feed_version !== void 0 && body.wholesale_feed_version !== wholesaleFeedVersion || body.pricing_version !== void 0 && body.pricing_version !== pricingVersion || (body.cache_scope ?? cacheScope) !== cacheScope)) {
108
+ throw new Error("Wholesale feed versions changed during pagination.");
109
+ }
110
+ wholesaleFeedVersion = body.wholesale_feed_version ?? wholesaleFeedVersion;
111
+ pricingVersion = body.pricing_version ?? pricingVersion;
112
+ cacheScope = body.cache_scope ?? cacheScope;
113
+ if (unchanged) break;
114
+ const rows = scope.entity === "product" ? body.products : body.signals;
115
+ if (!Array.isArray(rows)) throw new Error("Wholesale feed response is missing its catalog rows.");
116
+ for (const item of rows) {
117
+ const id = itemId(scope.entity, item);
118
+ if (!id) throw new Error("Wholesale feed row is missing its entity id.");
119
+ items.set(id, item);
120
+ }
121
+ cursor = body.pagination?.has_more ? body.pagination.cursor : void 0;
122
+ if (body.pagination?.has_more && (!cursor || cursors.has(cursor)))
123
+ throw new Error("Wholesale feed pagination did not advance.");
124
+ if (cursor) cursors.add(cursor);
125
+ } while (cursor);
126
+ if (receipt && expectedWebhookVersion !== void 0 && wholesaleFeedVersion === previous.wholesaleFeedVersion && wholesaleFeedVersion !== expectedWebhookVersion)
127
+ throw new Error("Wholesale repair has not observed the webhook version; retry delivery.");
128
+ } catch (cause) {
129
+ if (options.isCurrent && !options.isCurrent()) return { outcome: "superseded" };
130
+ const errorCause = cause instanceof Error ? cause : new Error(String(cause));
131
+ const error = cause?.adcpError ?? {
132
+ code: "SERVICE_UNAVAILABLE",
133
+ recovery: "transient",
134
+ message: "Wholesale feed refresh failed.",
135
+ synthetic: true
136
+ };
137
+ if (!await store.recordError(scope, previous.revision, error)) return { outcome: "superseded" };
138
+ return { outcome: "degraded", error, cause: errorCause };
139
+ }
140
+ const nextItems = unchanged ? previous.items : [...items.values()];
141
+ const next = {
142
+ ...previous,
143
+ items: nextItems,
144
+ wholesaleFeedVersion,
145
+ pricingVersion,
146
+ cacheScope,
147
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
148
+ revision: previous.revision + 1,
149
+ error: void 0,
150
+ ...receipt && {
151
+ lastWebhookEventId: latestEventId(previous.lastWebhookEventId, receipt.eventId),
152
+ lastWebhookIdempotencyKey: receipt.idempotencyKey
153
+ }
154
+ };
155
+ const change = rowDiff(scope.entity, previous.items, nextItems);
156
+ if (options.isCurrent && !options.isCurrent()) return { outcome: "superseded" };
157
+ if (!await store.commit(scope, previous.revision, {
158
+ ...change,
159
+ wholesaleFeedVersion,
160
+ pricingVersion,
161
+ cacheScope,
162
+ lastSyncedAt: next.lastSyncedAt,
163
+ ...receipt && {
164
+ webhookReceipt: receipt,
165
+ lastWebhookEventId: next.lastWebhookEventId,
166
+ lastWebhookIdempotencyKey: receipt.idempotencyKey
167
+ }
168
+ }))
169
+ return { outcome: "superseded" };
170
+ return unchanged ? { outcome: "unchanged", snapshot: next } : {
171
+ outcome: "applied",
172
+ snapshot: next,
173
+ diff: diffWholesaleFeed(scope.entity, previous.items, nextItems, cacheScope, true)
174
+ };
175
+ }
176
+ async function applyWholesaleFeedWebhook(options) {
177
+ options = {
178
+ ...options,
179
+ scope: structuredClone(options.scope),
180
+ account: structuredClone(options.account),
181
+ webhookScope: structuredClone(options.webhookScope),
182
+ webhook: structuredClone(options.webhook)
183
+ };
184
+ for (let attempt = 0; attempt < 3; attempt++) {
185
+ const outcome = await applyWebhookOnce(options);
186
+ if (outcome.outcome !== "superseded" || options.isCurrent && !options.isCurrent()) return outcome;
187
+ }
188
+ return { outcome: "superseded" };
189
+ }
190
+ async function applyWebhookOnce(options) {
191
+ const { webhook, scope, store } = options;
192
+ parseWholesaleFeedWebhookNotification(webhook);
193
+ const event = webhook.event;
194
+ if (!event || webhook.notification_type !== event.event_type || webhook.notification_id !== event.event_id)
195
+ throw new Error("Wholesale webhook event identity does not match its envelope.");
196
+ const entity = event.event_type === "wholesale_feed.bulk_change" ? event.payload.affected_entity_type : event.event_type.startsWith("product.") ? "product" : event.event_type.startsWith("signal.") ? "signal" : void 0;
197
+ if (entity !== scope.entity) throw new Error("Wholesale webhook entity does not match this mirror.");
198
+ const accountId = options.webhookScope?.accountId ?? (options.account && "account_id" in options.account ? options.account.account_id : void 0);
199
+ if (!accountId) throw new Error("Wholesale webhook application requires an expected account_id.");
200
+ if (webhook.account_id !== accountId || options.webhookScope?.subscriberId && options.webhookScope.subscriberId !== webhook.subscriber_id)
201
+ throw new Error("Wholesale webhook subscription does not match this mirror.");
202
+ const applies = event.payload.applies_to;
203
+ if (applies?.scope && applies.scope !== webhook.cache_scope || webhook.cache_scope === "account" && applies?.account_ids && !applies.account_ids.includes(accountId))
204
+ throw new Error("Wholesale webhook cache scope does not match this mirror.");
205
+ const previous = await store.read(scope);
206
+ if (await store.hasWebhookReceipt(scope, webhook.idempotency_key) || previous.lastWebhookEventId === event.event_id)
207
+ return { outcome: "unchanged", snapshot: previous };
208
+ if (previous.cacheScope !== webhook.cache_scope)
209
+ return refreshMirror(
210
+ { ...options, conditional: false },
211
+ { eventId: event.event_id, idempotencyKey: webhook.idempotency_key },
212
+ webhook.wholesale_feed_version
213
+ );
214
+ const stale = previous.lastWebhookEventId && isUuidV7(event.event_id) && isUuidV7(previous.lastWebhookEventId) && event.event_id.toLowerCase() <= previous.lastWebhookEventId.toLowerCase();
215
+ if (event.event_type === "wholesale_feed.bulk_change" || stale || !previous.wholesaleFeedVersion || webhook.previous_wholesale_feed_version !== previous.wholesaleFeedVersion) {
216
+ return refreshMirror(
217
+ options,
218
+ { eventId: event.event_id, idempotencyKey: webhook.idempotency_key },
219
+ stale ? void 0 : webhook.wholesale_feed_version
220
+ );
221
+ }
222
+ const items = applyWholesaleFeedEvent(scope.entity, previous.items, event);
223
+ if (!items)
224
+ return refreshMirror(
225
+ options,
226
+ { eventId: event.event_id, idempotencyKey: webhook.idempotency_key },
227
+ webhook.wholesale_feed_version
228
+ );
229
+ const next = {
230
+ ...previous,
231
+ items,
232
+ revision: previous.revision + 1,
233
+ wholesaleFeedVersion: webhook.wholesale_feed_version,
234
+ cacheScope: webhook.cache_scope,
235
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
236
+ lastWebhookEventId: event.event_id,
237
+ lastWebhookIdempotencyKey: webhook.idempotency_key,
238
+ error: void 0
239
+ };
240
+ if (options.isCurrent && !options.isCurrent()) return { outcome: "superseded" };
241
+ if (!await store.commit(scope, previous.revision, {
242
+ ...rowDiff(scope.entity, previous.items, items),
243
+ wholesaleFeedVersion: next.wholesaleFeedVersion,
244
+ pricingVersion: next.pricingVersion,
245
+ cacheScope: next.cacheScope,
246
+ lastSyncedAt: next.lastSyncedAt,
247
+ lastWebhookEventId: event.event_id,
248
+ lastWebhookIdempotencyKey: webhook.idempotency_key,
249
+ webhookReceipt: { eventId: event.event_id, idempotencyKey: webhook.idempotency_key }
250
+ }))
251
+ return { outcome: "superseded" };
252
+ return { outcome: "applied", snapshot: next, diff: [event] };
253
+ }
254
+ function applyWholesaleFeedEvent(entity, previous, event) {
255
+ const items = new Map(previous.map((item) => [itemId(entity, item), item]));
256
+ const payload = event.payload;
257
+ const id = entity === "product" ? payload.product_id : payload.signal_agent_segment_id;
258
+ if (!id) return void 0;
259
+ if (event.event_type.endsWith(".removed")) items.delete(id);
260
+ else if (event.event_type.endsWith(".priced")) {
261
+ const prior = items.get(id);
262
+ if (!prior || !Array.isArray(payload.pricing_options)) return void 0;
263
+ items.set(id, { ...prior, pricing_options: payload.pricing_options });
264
+ } else {
265
+ const item = entity === "product" ? payload.product : payload.signal;
266
+ if (!item || itemId(entity, item) !== id) return void 0;
267
+ items.set(id, item);
268
+ }
269
+ return [...items.values()];
270
+ }
271
+ function diffWholesaleFeed(entity, previous, next, cacheScope, includeAllChanges = false) {
272
+ const before = new Map(previous.map((item) => [itemId(entity, item), item]));
273
+ const after = new Map(next.map((item) => [itemId(entity, item), item]));
274
+ const events = [];
275
+ const emit = (id, action, data) => events.push({
276
+ event_id: randomUUID(),
277
+ event_type: `${entity}.${action}`,
278
+ entity_type: entity,
279
+ entity_id: id,
280
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
281
+ payload: {
282
+ [entity === "product" ? "product_id" : "signal_agent_segment_id"]: id,
283
+ ...data,
284
+ applies_to: { scope: cacheScope }
285
+ }
286
+ });
287
+ for (const [id, item] of after) {
288
+ const prior = before.get(id);
289
+ if (!prior) emit(id, "created", { [entity]: item });
290
+ else if (!isDeepStrictEqual(prior.pricing_options, item.pricing_options)) {
291
+ emit(id, "priced", { pricing_options: item.pricing_options ?? [] });
292
+ if (includeAllChanges && !isDeepStrictEqual({ ...prior, pricing_options: item.pricing_options }, item))
293
+ emit(id, "updated", { [entity]: item });
294
+ } else if (!isDeepStrictEqual(prior, item)) emit(id, "updated", { [entity]: item });
295
+ }
296
+ for (const id of before.keys()) if (!after.has(id)) emit(id, "removed", {});
297
+ return events;
298
+ }
299
+ function itemId(entity, item) {
300
+ return entity === "product" ? item.product_id : item.signal_agent_segment_id;
301
+ }
302
+ function rowDiff(entity, previous, next) {
303
+ const before = new Map(previous.map((item) => [itemId(entity, item), item]));
304
+ const ids = new Set(next.map((item) => itemId(entity, item)));
305
+ return {
306
+ upserts: next.filter((item) => !isDeepStrictEqual(before.get(itemId(entity, item)), item)),
307
+ deletes: [...before.keys()].filter((id) => !ids.has(id))
308
+ };
309
+ }
310
+ function isUuidV7(value) {
311
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
312
+ }
313
+ function latestEventId(previous, next) {
314
+ return previous && isUuidV7(previous) && (!isUuidV7(next) || previous.toLowerCase() >= next.toLowerCase()) ? previous : next;
315
+ }
316
+ export {
317
+ InMemoryWholesaleFeedMirrorStore,
318
+ applyWholesaleFeedEvent,
319
+ applyWholesaleFeedWebhook,
320
+ diffWholesaleFeed,
321
+ refreshWholesaleFeed
322
+ };
@@ -4,12 +4,8 @@ import type { WholesaleFeedSyncConfig, WholesaleFeedSyncEvents, WholesaleFeedSyn
4
4
  type Product = LegacyWholesaleProduct;
5
5
  type Signal = LegacyWholesaleSignal;
6
6
  /**
7
- * In-memory mirror of an AdCP agent's wholesale product and signal feeds.
8
- *
9
- * Discovers the agent's wholesale-feed capabilities at `start()`, picks the
10
- * highest-capability sync strategy the agent supports, and maintains a
11
- * local index for zero-latency lookups. Falls back gracefully to manual
12
- * bootstrap when the agent does not advertise conditional-fetch tokens.
7
+ * In-memory wholesale product/signal mirror. `start()` discovers capabilities;
8
+ * versioned sellers use auto-poll, others require manual refresh.
13
9
  *
14
10
  * @example
15
11
  * ```ts
@@ -63,11 +59,10 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
63
59
  private probeTimer;
64
60
  private capabilityTimer;
65
61
  private lifecycleEpoch;
62
+ private mirrorRevision;
66
63
  /**
67
- * Read-only view of the in-memory product index. The `mode` reflects the
68
- * sync strategy for product events specifically — in mixed-capability
69
- * agents (products-feed + signals-wholesale-only, or vice versa) this
70
- * may differ from `signals.mode`.
64
+ * Product index view. `mode` identifies the product sync strategy and may
65
+ * differ from `signals.mode` for agents with mixed feed capabilities.
71
66
  */
72
67
  readonly products: {
73
68
  list: () => Product[];
@@ -86,30 +81,17 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
86
81
  readonly mode: WholesaleFeedSyncMode;
87
82
  _mode: WholesaleFeedSyncMode;
88
83
  /**
89
- * `true` when the agent supports `discovery_mode: 'wholesale'` on
90
- * `get_signals` (i.e., signals are browsable). When `false`, the
91
- * agent only supports brief-mode discovery — `signals.list()` will
92
- * be empty until adopters call into the agent with their own briefs.
84
+ * True when `get_signals` supports `discovery_mode: 'wholesale'`.
85
+ * Otherwise `list()` is empty; use client brief discovery instead.
93
86
  */
94
87
  queryable: boolean;
95
88
  };
96
- /**
97
- * One-shot console warning when adopters call `signals.list()` or
98
- * `signals.search()` against an agent that doesn't support wholesale
99
- * signal enumeration. Without this, empty results read as "no signals
100
- * match" rather than "the agent doesn't browse, only briefs."
101
- */
102
89
  private warnIfSignalsNotQueryable;
103
90
  constructor(config: WholesaleFeedSyncConfig);
104
91
  /**
105
- * Probe the agent's capabilities, pick a sync mode, and bootstrap the
106
- * in-memory replica via wholesale enumeration. In `'auto-poll'` mode
107
- * starts the conditional wholesale-feed version probe loop.
108
- *
109
- * Safe to call repeatedly — concurrent calls await the in-flight
110
- * bootstrap and return when it completes (no duplicate bootstrap, no
111
- * silent drop). Sequential calls re-probe capabilities and re-bootstrap,
112
- * equivalent to calling `refresh()` after a mode upgrade.
92
+ * Discover capabilities, bootstrap the mirror, and schedule auto-poll probes
93
+ * for versioned sellers. Concurrent calls share the bootstrap; sequential
94
+ * calls re-probe capabilities and refresh the mirror.
113
95
  */
114
96
  start(): Promise<void>;
115
97
  private startInner;
@@ -139,11 +121,12 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
139
121
  get lastSyncedAt(): Date | undefined;
140
122
  get lastEventAt(): Date | undefined;
141
123
  private resolveMode;
124
+ private bootstrapWithRetries;
142
125
  private bootstrap;
143
126
  private handleBootstrapFailure;
144
- /** Returns `true` when the seller short-circuited with `unchanged: true`. */
145
127
  private bootstrapProducts;
146
128
  private bootstrapSignals;
129
+ private bootstrapFeed;
147
130
  private recoverFromBulkChange;
148
131
  private recoverFromVersionMismatch;
149
132
  private scheduleProbe;
@@ -4,12 +4,8 @@ import type { WholesaleFeedSyncConfig, WholesaleFeedSyncEvents, WholesaleFeedSyn
4
4
  type Product = LegacyWholesaleProduct;
5
5
  type Signal = LegacyWholesaleSignal;
6
6
  /**
7
- * In-memory mirror of an AdCP agent's wholesale product and signal feeds.
8
- *
9
- * Discovers the agent's wholesale-feed capabilities at `start()`, picks the
10
- * highest-capability sync strategy the agent supports, and maintains a
11
- * local index for zero-latency lookups. Falls back gracefully to manual
12
- * bootstrap when the agent does not advertise conditional-fetch tokens.
7
+ * In-memory wholesale product/signal mirror. `start()` discovers capabilities;
8
+ * versioned sellers use auto-poll, others require manual refresh.
13
9
  *
14
10
  * @example
15
11
  * ```ts
@@ -63,11 +59,10 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
63
59
  private probeTimer;
64
60
  private capabilityTimer;
65
61
  private lifecycleEpoch;
62
+ private mirrorRevision;
66
63
  /**
67
- * Read-only view of the in-memory product index. The `mode` reflects the
68
- * sync strategy for product events specifically — in mixed-capability
69
- * agents (products-feed + signals-wholesale-only, or vice versa) this
70
- * may differ from `signals.mode`.
64
+ * Product index view. `mode` identifies the product sync strategy and may
65
+ * differ from `signals.mode` for agents with mixed feed capabilities.
71
66
  */
72
67
  readonly products: {
73
68
  list: () => Product[];
@@ -86,30 +81,17 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
86
81
  readonly mode: WholesaleFeedSyncMode;
87
82
  _mode: WholesaleFeedSyncMode;
88
83
  /**
89
- * `true` when the agent supports `discovery_mode: 'wholesale'` on
90
- * `get_signals` (i.e., signals are browsable). When `false`, the
91
- * agent only supports brief-mode discovery — `signals.list()` will
92
- * be empty until adopters call into the agent with their own briefs.
84
+ * True when `get_signals` supports `discovery_mode: 'wholesale'`.
85
+ * Otherwise `list()` is empty; use client brief discovery instead.
93
86
  */
94
87
  queryable: boolean;
95
88
  };
96
- /**
97
- * One-shot console warning when adopters call `signals.list()` or
98
- * `signals.search()` against an agent that doesn't support wholesale
99
- * signal enumeration. Without this, empty results read as "no signals
100
- * match" rather than "the agent doesn't browse, only briefs."
101
- */
102
89
  private warnIfSignalsNotQueryable;
103
90
  constructor(config: WholesaleFeedSyncConfig);
104
91
  /**
105
- * Probe the agent's capabilities, pick a sync mode, and bootstrap the
106
- * in-memory replica via wholesale enumeration. In `'auto-poll'` mode
107
- * starts the conditional wholesale-feed version probe loop.
108
- *
109
- * Safe to call repeatedly — concurrent calls await the in-flight
110
- * bootstrap and return when it completes (no duplicate bootstrap, no
111
- * silent drop). Sequential calls re-probe capabilities and re-bootstrap,
112
- * equivalent to calling `refresh()` after a mode upgrade.
92
+ * Discover capabilities, bootstrap the mirror, and schedule auto-poll probes
93
+ * for versioned sellers. Concurrent calls share the bootstrap; sequential
94
+ * calls re-probe capabilities and refresh the mirror.
113
95
  */
114
96
  start(): Promise<void>;
115
97
  private startInner;
@@ -139,11 +121,12 @@ export declare class WholesaleFeedSync extends EventEmitter<WholesaleFeedSyncEve
139
121
  get lastSyncedAt(): Date | undefined;
140
122
  get lastEventAt(): Date | undefined;
141
123
  private resolveMode;
124
+ private bootstrapWithRetries;
142
125
  private bootstrap;
143
126
  private handleBootstrapFailure;
144
- /** Returns `true` when the seller short-circuited with `unchanged: true`. */
145
127
  private bootstrapProducts;
146
128
  private bootstrapSignals;
129
+ private bootstrapFeed;
147
130
  private recoverFromBulkChange;
148
131
  private recoverFromVersionMismatch;
149
132
  private scheduleProbe;