@adcp/sdk 14.1.0 → 14.2.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.
- package/dist/lib/core/SingleAgentClient.d.mts +4 -1
- package/dist/lib/core/SingleAgentClient.d.ts +4 -1
- package/dist/lib/core/SingleAgentClient.js +25 -4
- package/dist/lib/core/SingleAgentClient.mjs +25 -4
- package/dist/lib/core/TaskExecutor.d.mts +3 -1
- package/dist/lib/core/TaskExecutor.d.ts +3 -1
- package/dist/lib/core/TaskExecutor.js +15 -11
- package/dist/lib/core/TaskExecutor.mjs +15 -11
- package/dist/lib/core/buyer-account-registry.d.mts +30 -2
- package/dist/lib/core/buyer-account-registry.d.ts +30 -2
- package/dist/lib/core/buyer-account-registry.js +126 -42
- package/dist/lib/core/buyer-account-registry.mjs +127 -43
- package/dist/lib/index.d.mts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.mjs +12 -1
- package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
- package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
- package/dist/lib/net/agent-transport-fetch.js +18 -5
- package/dist/lib/net/agent-transport-fetch.mjs +17 -5
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/errors.js +6 -0
- package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
- package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
- package/dist/lib/signing/brand-jwks.d.mts +2 -0
- package/dist/lib/signing/brand-jwks.d.ts +2 -0
- package/dist/lib/signing/brand-jwks.js +10 -2
- package/dist/lib/signing/brand-jwks.mjs +10 -2
- package/dist/lib/signing/errors.d.mts +4 -2
- package/dist/lib/signing/errors.d.ts +4 -2
- package/dist/lib/signing/errors.js +3 -1
- package/dist/lib/signing/errors.mjs +3 -1
- package/dist/lib/signing/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- package/dist/lib/signing/webhook-verifier.js +2 -2
- package/dist/lib/signing/webhook-verifier.mjs +3 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/accept-proposal.d.ts +19 -1
- package/dist/lib/types/buy-products.d.ts +19 -1
- package/dist/lib/types/check-governance.d.ts +19 -1
- package/dist/lib/types/comply-test-controller.d.ts +19 -1
- package/dist/lib/types/control-media-buy.d.ts +19 -1
- package/dist/lib/types/core.generated.d.mts +14 -1
- package/dist/lib/types/core.generated.d.ts +14 -1
- package/dist/lib/types/create-media-buy.d.ts +14 -1
- package/dist/lib/types/get-media-buys.d.ts +14 -1
- package/dist/lib/types/get-products.d.ts +19 -1
- package/dist/lib/types/list-products.d.ts +19 -1
- package/dist/lib/types/refine-proposals.d.ts +19 -1
- package/dist/lib/types/request-proposals.d.ts +19 -1
- package/dist/lib/types/schemas.generated.d.ts +12 -3
- package/dist/lib/types/schemas.generated.js +4 -1
- package/dist/lib/types/schemas.generated.mjs +4 -1
- package/dist/lib/types/tools.generated.d.mts +14 -1
- package/dist/lib/types/tools.generated.d.ts +14 -1
- package/dist/lib/types/update-media-buy.d.ts +14 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/webhooks/index.d.mts +24 -0
- package/dist/lib/webhooks/index.d.ts +24 -0
- package/dist/lib/webhooks/index.js +50 -24
- package/dist/lib/webhooks/index.mjs +49 -24
- package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
- package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
- package/dist/lib/wholesale-feed-sync/index.js +7 -0
- package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
- package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
- package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.js +153 -297
- package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
- package/docs/README.md +6 -0
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUYER-STORAGE.md +3 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
- package/docs/guides/account-resolution.md +46 -10
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +85 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-resolution-3.3.md +5 -3
- package/docs/recipes/verifying-inbound-webhooks.md +4 -0
- package/package.json +2 -1
- package/skills/adcp-brand.previous/SKILL.md +0 -200
- package/skills/adcp-creative.previous/SKILL.md +0 -305
- package/skills/adcp-governance.previous/SKILL.md +0 -566
- package/skills/adcp-measurement.previous/SKILL.md +0 -136
- package/skills/adcp-media-buy.previous/SKILL.md +0 -556
- package/skills/adcp-si.previous/SKILL.md +0 -206
- package/skills/adcp-signals.previous/SKILL.md +0 -204
|
@@ -23,13 +23,17 @@ __export(sync_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(sync_exports);
|
|
24
24
|
var import_node_events = require("node:events");
|
|
25
25
|
var import_node_util = require("node:util");
|
|
26
|
-
var
|
|
26
|
+
var import_mirror = require('./mirror.js');
|
|
27
27
|
var import_webhook_notification = require('./webhook-notification.js');
|
|
28
28
|
const bootstrapReadFailures = /* @__PURE__ */ new WeakSet();
|
|
29
|
+
function supersededRepairError() {
|
|
30
|
+
const error = new Error("WholesaleFeedSync: repair superseded repeatedly; retry webhook delivery.");
|
|
31
|
+
bootstrapReadFailures.add(error);
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
29
34
|
const DEFAULT_PROBE_INTERVAL_MS = 6e5;
|
|
30
35
|
const DEFAULT_CAPABILITY_REFRESH_INTERVAL_MS = 864e5;
|
|
31
36
|
const DEFAULT_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
32
|
-
const DEFAULT_BOOTSTRAP_PAGE_LIMIT = 100;
|
|
33
37
|
const VERSION_MISMATCH_RECOVERY_ATTEMPTS = 3;
|
|
34
38
|
const VERSION_MISMATCH_RECOVERY_BACKOFF_MS = 5;
|
|
35
39
|
class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
@@ -70,11 +74,10 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
70
74
|
probeTimer = null;
|
|
71
75
|
capabilityTimer = null;
|
|
72
76
|
lifecycleEpoch = 0;
|
|
77
|
+
mirrorRevision = 0;
|
|
73
78
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* agents (products-feed + signals-wholesale-only, or vice versa) this
|
|
77
|
-
* may differ from `signals.mode`.
|
|
79
|
+
* Product index view. `mode` identifies the product sync strategy and may
|
|
80
|
+
* differ from `signals.mode` for agents with mixed feed capabilities.
|
|
78
81
|
*/
|
|
79
82
|
products = {
|
|
80
83
|
list: () => [...this.productIndex.values()],
|
|
@@ -109,19 +112,12 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
109
112
|
},
|
|
110
113
|
_mode: "manual",
|
|
111
114
|
/**
|
|
112
|
-
*
|
|
113
|
-
* `
|
|
114
|
-
* agent only supports brief-mode discovery — `signals.list()` will
|
|
115
|
-
* be empty until adopters call into the agent with their own briefs.
|
|
115
|
+
* True when `get_signals` supports `discovery_mode: 'wholesale'`.
|
|
116
|
+
* Otherwise `list()` is empty; use client brief discovery instead.
|
|
116
117
|
*/
|
|
117
118
|
queryable: true
|
|
118
119
|
};
|
|
119
|
-
|
|
120
|
-
* One-shot console warning when adopters call `signals.list()` or
|
|
121
|
-
* `signals.search()` against an agent that doesn't support wholesale
|
|
122
|
-
* signal enumeration. Without this, empty results read as "no signals
|
|
123
|
-
* match" rather than "the agent doesn't browse, only briefs."
|
|
124
|
-
*/
|
|
120
|
+
// Warn once when empty signal results reflect missing wholesale support.
|
|
125
121
|
warnIfSignalsNotQueryable() {
|
|
126
122
|
if (this.signals.queryable || this.signalsQueryableWarned) return;
|
|
127
123
|
if (this._state === "idle" || this._state === "bootstrapping") return;
|
|
@@ -147,14 +143,9 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
147
143
|
}
|
|
148
144
|
// ====== Lifecycle ======
|
|
149
145
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* Safe to call repeatedly — concurrent calls await the in-flight
|
|
155
|
-
* bootstrap and return when it completes (no duplicate bootstrap, no
|
|
156
|
-
* silent drop). Sequential calls re-probe capabilities and re-bootstrap,
|
|
157
|
-
* equivalent to calling `refresh()` after a mode upgrade.
|
|
146
|
+
* Discover capabilities, bootstrap the mirror, and schedule auto-poll probes
|
|
147
|
+
* for versioned sellers. Concurrent calls share the bootstrap; sequential
|
|
148
|
+
* calls re-probe capabilities and refresh the mirror.
|
|
158
149
|
*/
|
|
159
150
|
async start() {
|
|
160
151
|
if (this.startPromise && this.startPromiseEpoch === this.lifecycleEpoch) return this.startPromise;
|
|
@@ -174,7 +165,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
174
165
|
if (!await this.restorePersistedState(epoch)) return;
|
|
175
166
|
if (!await this.resolveMode(epoch)) return;
|
|
176
167
|
try {
|
|
177
|
-
await this.
|
|
168
|
+
await this.bootstrapWithRetries({ epoch });
|
|
178
169
|
} finally {
|
|
179
170
|
if (this.isLifecycleCurrent(epoch)) {
|
|
180
171
|
if (this._mode === "auto-poll") this.scheduleProbe(epoch);
|
|
@@ -198,6 +189,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
198
189
|
*/
|
|
199
190
|
async reset() {
|
|
200
191
|
this.stop();
|
|
192
|
+
++this.mirrorRevision;
|
|
201
193
|
this.productIndex.clear();
|
|
202
194
|
this.signalIndex.clear();
|
|
203
195
|
this.productWholesaleFeedVersion = void 0;
|
|
@@ -223,7 +215,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
223
215
|
async refresh() {
|
|
224
216
|
const epoch = this.lifecycleEpoch;
|
|
225
217
|
this.emit("resyncing", { reason: "manual" });
|
|
226
|
-
await this.
|
|
218
|
+
await this.bootstrapWithRetries({ emitDiffs: true, epoch, propagateFailure: true });
|
|
227
219
|
}
|
|
228
220
|
/**
|
|
229
221
|
* Apply one legacy-view account-level wholesale feed webhook to the local mirror.
|
|
@@ -272,6 +264,11 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
272
264
|
this.rememberLastWebhookEventId(event.event_id);
|
|
273
265
|
throw err;
|
|
274
266
|
}
|
|
267
|
+
if (!this._lastSyncedAt && event.event_type !== "wholesale_feed.bulk_change") {
|
|
268
|
+
if (!await this.recoverFromVersionMismatch(event, epoch)) return;
|
|
269
|
+
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
275
272
|
if (webhook.previous_wholesale_feed_version && currentVersion && webhook.previous_wholesale_feed_version !== currentVersion) {
|
|
276
273
|
if (!await this.recoverFromVersionMismatch(event, epoch)) return;
|
|
277
274
|
await this.markWebhookProcessed(dedupeKey, eventDedupeKey);
|
|
@@ -352,12 +349,24 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
352
349
|
return true;
|
|
353
350
|
}
|
|
354
351
|
// ====== Private: bootstrap (wholesale enumeration) ======
|
|
352
|
+
async bootstrapWithRetries(options) {
|
|
353
|
+
for (let attempt = 0; attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
354
|
+
const revision = this.mirrorRevision;
|
|
355
|
+
if (await this.bootstrap({ ...options, conditional: attempt === 0 ? void 0 : false })) return;
|
|
356
|
+
if (!this.isLifecycleCurrent(options.epoch) || revision === this.mirrorRevision) return;
|
|
357
|
+
}
|
|
358
|
+
const error = new Error("WholesaleFeedSync: refresh superseded repeatedly; retry start or refresh.");
|
|
359
|
+
this.handleBootstrapFailure({ error });
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
355
362
|
async bootstrap(options = {}) {
|
|
356
363
|
const epoch = options.epoch ?? this.lifecycleEpoch;
|
|
357
364
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
358
365
|
this.setState("bootstrapping");
|
|
359
366
|
const previousLastSyncedAt = this._lastSyncedAt;
|
|
367
|
+
const expectedRevision = this.mirrorRevision;
|
|
360
368
|
let reportedFailure;
|
|
369
|
+
let committed = false;
|
|
361
370
|
try {
|
|
362
371
|
const previousProducts = new Map(this.productIndex);
|
|
363
372
|
const previousSignals = new Map(this.signalIndex);
|
|
@@ -369,8 +378,12 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
369
378
|
const refreshProducts = entities !== "signals";
|
|
370
379
|
const refreshSignals = entities !== "products" && this.signals.queryable;
|
|
371
380
|
if (refreshProducts) {
|
|
372
|
-
productResult = await this.bootstrapProducts(epoch);
|
|
381
|
+
productResult = await this.bootstrapProducts(epoch, options.conditional);
|
|
373
382
|
if (productResult.cancelled) return false;
|
|
383
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
384
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
374
387
|
if (productResult.failure) {
|
|
375
388
|
const failure = productResult.failure;
|
|
376
389
|
reportedFailure = failure.error;
|
|
@@ -384,8 +397,12 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
384
397
|
}
|
|
385
398
|
}
|
|
386
399
|
if (refreshSignals) {
|
|
387
|
-
signalResult = await this.bootstrapSignals(epoch);
|
|
400
|
+
signalResult = await this.bootstrapSignals(epoch, options.conditional);
|
|
388
401
|
if (signalResult.cancelled) return false;
|
|
402
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
403
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
389
406
|
if (signalResult.failure) {
|
|
390
407
|
const failure = signalResult.failure;
|
|
391
408
|
reportedFailure = failure.error;
|
|
@@ -399,6 +416,12 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
399
416
|
}
|
|
400
417
|
}
|
|
401
418
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
419
|
+
if (this.mirrorRevision !== expectedRevision) {
|
|
420
|
+
this.setState(this._lastSyncedAt ? "syncing" : "idle");
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
++this.mirrorRevision;
|
|
424
|
+
committed = true;
|
|
402
425
|
if (refreshProducts && productResult) {
|
|
403
426
|
this.commitProductMetadata(productResult.metadata);
|
|
404
427
|
if (!productResult.unchanged) {
|
|
@@ -430,6 +453,7 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
430
453
|
return true;
|
|
431
454
|
} catch (err) {
|
|
432
455
|
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
456
|
+
if (!committed && this.mirrorRevision !== expectedRevision && err !== reportedFailure) return false;
|
|
433
457
|
if (err === reportedFailure) throw err;
|
|
434
458
|
this._lastSyncedAt = previousLastSyncedAt;
|
|
435
459
|
this.setState(this._lastSyncedAt ? "degraded" : "error");
|
|
@@ -446,102 +470,74 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
446
470
|
if (this.listenerCount("error") > 0) this.emit("error", failure);
|
|
447
471
|
return false;
|
|
448
472
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
let cursor;
|
|
452
|
-
const into = /* @__PURE__ */ new Map();
|
|
453
|
-
let metadata = this.currentProductMetadata();
|
|
454
|
-
do {
|
|
455
|
-
const params = {
|
|
456
|
-
buying_mode: "wholesale",
|
|
457
|
-
pagination: { max_results: DEFAULT_BOOTSTRAP_PAGE_LIMIT, ...cursor && { cursor } },
|
|
458
|
-
...this.account && { account: this.account }
|
|
459
|
-
};
|
|
460
|
-
if (!cursor && metadata.wholesaleFeedVersion && this._capabilities.wholesaleFeedVersioning) {
|
|
461
|
-
params.if_wholesale_feed_version = metadata.wholesaleFeedVersion;
|
|
462
|
-
if (metadata.pricingVersion) params.if_pricing_version = metadata.pricingVersion;
|
|
463
|
-
}
|
|
464
|
-
const result = await this.client.getProducts(params);
|
|
465
|
-
if (!this.isLifecycleCurrent(epoch)) return { cancelled: true, unchanged: false, items: into, metadata };
|
|
466
|
-
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
467
|
-
return {
|
|
468
|
-
cancelled: false,
|
|
469
|
-
unchanged: false,
|
|
470
|
-
items: into,
|
|
471
|
-
metadata,
|
|
472
|
-
failure: { error: new Error(result.error ?? "Product bootstrap failed"), adcpError: result.adcpError }
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
const body = result.data;
|
|
476
|
-
if (!body)
|
|
477
|
-
return {
|
|
478
|
-
cancelled: false,
|
|
479
|
-
unchanged: false,
|
|
480
|
-
items: into,
|
|
481
|
-
metadata,
|
|
482
|
-
failure: { error: new Error("Product bootstrap did not return a completed catalog") }
|
|
483
|
-
};
|
|
484
|
-
if (body.unchanged) {
|
|
485
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
486
|
-
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
487
|
-
}
|
|
488
|
-
const products = Array.isArray(body.products) ? body.products : [];
|
|
489
|
-
for (const product of products) {
|
|
490
|
-
const id = product.product_id;
|
|
491
|
-
if (typeof id === "string") into.set(id, product);
|
|
492
|
-
}
|
|
493
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
494
|
-
cursor = body.pagination?.has_more ? body.pagination?.cursor : void 0;
|
|
495
|
-
} while (cursor);
|
|
496
|
-
return { cancelled: false, unchanged: false, items: into, metadata };
|
|
473
|
+
bootstrapProducts(epoch, conditional) {
|
|
474
|
+
return this.bootstrapFeed("product", epoch, conditional);
|
|
497
475
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (result.success === false || result.status !== void 0 && result.status !== "completed" && result.status !== "success") {
|
|
515
|
-
return {
|
|
516
|
-
cancelled: false,
|
|
517
|
-
unchanged: false,
|
|
518
|
-
items: into,
|
|
519
|
-
metadata,
|
|
520
|
-
failure: { error: new Error(result.error ?? "Signal bootstrap failed"), adcpError: result.adcpError }
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
const body = result.data;
|
|
524
|
-
if (!body)
|
|
525
|
-
return {
|
|
526
|
-
cancelled: false,
|
|
527
|
-
unchanged: false,
|
|
528
|
-
items: into,
|
|
529
|
-
metadata,
|
|
530
|
-
failure: { error: new Error("Signal bootstrap did not return a completed catalog") }
|
|
531
|
-
};
|
|
532
|
-
if (body.unchanged) {
|
|
533
|
-
metadata = mergeFeedMetadata(metadata, body);
|
|
534
|
-
return { cancelled: false, unchanged: true, items: into, metadata };
|
|
476
|
+
bootstrapSignals(epoch, conditional) {
|
|
477
|
+
return this.bootstrapFeed("signal", epoch, conditional);
|
|
478
|
+
}
|
|
479
|
+
async bootstrapFeed(entity, epoch, conditional) {
|
|
480
|
+
const store = new import_mirror.InMemoryWholesaleFeedMirrorStore();
|
|
481
|
+
const scope = { agentUrl: "shell", accountKey: "shell", entity };
|
|
482
|
+
const metadata = entity === "product" ? this.currentProductMetadata() : this.currentSignalMetadata();
|
|
483
|
+
const previous = entity === "product" ? [...this.productIndex.values()] : [...this.signalIndex.values()];
|
|
484
|
+
store.restore(scope, { revision: 0, items: previous, ...metadata });
|
|
485
|
+
let readException;
|
|
486
|
+
const read = async (call) => {
|
|
487
|
+
try {
|
|
488
|
+
return await call();
|
|
489
|
+
} catch (cause) {
|
|
490
|
+
readException = cause instanceof Error ? cause : new Error(String(cause));
|
|
491
|
+
throw cause;
|
|
535
492
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
493
|
+
};
|
|
494
|
+
const outcome = await (0, import_mirror.refreshWholesaleFeed)({
|
|
495
|
+
client: {
|
|
496
|
+
getAdcpCapabilities: this.client.getAdcpCapabilities.bind(this.client),
|
|
497
|
+
getProducts: (params) => read(() => this.client.getProducts(params)),
|
|
498
|
+
getSignals: (params) => read(() => this.client.getSignals(params))
|
|
499
|
+
},
|
|
500
|
+
store,
|
|
501
|
+
scope,
|
|
502
|
+
account: this.account,
|
|
503
|
+
conditional: conditional ?? this._capabilities.wholesaleFeedVersioning,
|
|
504
|
+
isCurrent: () => this.isLifecycleCurrent(epoch)
|
|
505
|
+
});
|
|
506
|
+
if (outcome.outcome === "superseded") return { cancelled: true, unchanged: false, items: /* @__PURE__ */ new Map(), metadata };
|
|
507
|
+
if (outcome.outcome === "degraded") {
|
|
508
|
+
if (readException) throw readException;
|
|
509
|
+
return {
|
|
510
|
+
cancelled: false,
|
|
511
|
+
unchanged: false,
|
|
512
|
+
items: /* @__PURE__ */ new Map(),
|
|
513
|
+
metadata,
|
|
514
|
+
failure: {
|
|
515
|
+
error: new Error(
|
|
516
|
+
`${entity === "product" ? "Product" : "Signal"} bootstrap failed: ${outcome.cause.message}`,
|
|
517
|
+
{
|
|
518
|
+
cause: outcome.cause
|
|
519
|
+
}
|
|
520
|
+
),
|
|
521
|
+
adcpError: outcome.error
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const snapshot = outcome.snapshot;
|
|
526
|
+
return {
|
|
527
|
+
cancelled: false,
|
|
528
|
+
unchanged: outcome.outcome === "unchanged",
|
|
529
|
+
items: new Map(
|
|
530
|
+
snapshot.items.map((item) => [
|
|
531
|
+
entity === "product" ? item.product_id : item.signal_agent_segment_id,
|
|
532
|
+
item
|
|
533
|
+
])
|
|
534
|
+
),
|
|
535
|
+
metadata: {
|
|
536
|
+
wholesaleFeedVersion: snapshot.wholesaleFeedVersion,
|
|
537
|
+
pricingVersion: snapshot.pricingVersion,
|
|
538
|
+
cacheScope: snapshot.cacheScope
|
|
540
539
|
}
|
|
541
|
-
|
|
542
|
-
cursor = body.pagination?.has_more ? body.pagination?.cursor : void 0;
|
|
543
|
-
} while (cursor);
|
|
544
|
-
return { cancelled: false, unchanged: false, items: into, metadata };
|
|
540
|
+
};
|
|
545
541
|
}
|
|
546
542
|
async recoverFromBulkChange(event, epoch = this.lifecycleEpoch) {
|
|
547
543
|
this.emit("resyncing", { reason: "bulk_change" });
|
|
@@ -552,14 +548,22 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
552
548
|
);
|
|
553
549
|
}
|
|
554
550
|
const entities = affected === "product" ? "products" : "signals";
|
|
555
|
-
|
|
551
|
+
for (let attempt = 0; attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
552
|
+
if (await this.bootstrap({ emitDiffs: true, entities, epoch, propagateFailure: true })) return true;
|
|
553
|
+
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
554
|
+
}
|
|
555
|
+
throw supersededRepairError();
|
|
556
556
|
}
|
|
557
557
|
async recoverFromVersionMismatch(event, epoch = this.lifecycleEpoch) {
|
|
558
558
|
this.emit("resyncing", { reason: "version_mismatch" });
|
|
559
559
|
const beforeVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
560
560
|
for (let attempt = 1; attempt <= VERSION_MISMATCH_RECOVERY_ATTEMPTS; attempt++) {
|
|
561
561
|
const recovered = await this.bootstrap({ emitDiffs: true, epoch, propagateFailure: true });
|
|
562
|
-
if (!recovered)
|
|
562
|
+
if (!recovered) {
|
|
563
|
+
if (!this.isLifecycleCurrent(epoch)) return false;
|
|
564
|
+
if (attempt === VERSION_MISMATCH_RECOVERY_ATTEMPTS) throw supersededRepairError();
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
563
567
|
const afterVersion = this.currentWholesaleFeedVersionForEvent(event);
|
|
564
568
|
if (afterVersion !== beforeVersion) return true;
|
|
565
569
|
if (attempt < VERSION_MISMATCH_RECOVERY_ATTEMPTS) {
|
|
@@ -713,63 +717,16 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
713
717
|
}
|
|
714
718
|
}
|
|
715
719
|
applyEvent(event) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
case "product.priced": {
|
|
728
|
-
const payload = event.payload;
|
|
729
|
-
const existing = this.productIndex.get(payload.product_id);
|
|
730
|
-
if (existing && Array.isArray(payload.pricing_options)) {
|
|
731
|
-
this.productIndex.set(payload.product_id, {
|
|
732
|
-
...existing,
|
|
733
|
-
pricing_options: payload.pricing_options
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
case "product.removed": {
|
|
739
|
-
const payload = event.payload;
|
|
740
|
-
if (typeof payload.product_id === "string") this.productIndex.delete(payload.product_id);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
case "signal.created":
|
|
744
|
-
case "signal.updated": {
|
|
745
|
-
const payload = event.payload;
|
|
746
|
-
if (typeof payload.signal_agent_segment_id !== "string") return;
|
|
747
|
-
if (payload.signal) {
|
|
748
|
-
this.signalIndex.set(payload.signal_agent_segment_id, payload.signal);
|
|
749
|
-
}
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
752
|
-
case "signal.priced": {
|
|
753
|
-
const payload = event.payload;
|
|
754
|
-
const existing = this.signalIndex.get(payload.signal_agent_segment_id);
|
|
755
|
-
if (existing && Array.isArray(payload.pricing_options)) {
|
|
756
|
-
this.signalIndex.set(payload.signal_agent_segment_id, {
|
|
757
|
-
...existing,
|
|
758
|
-
pricing_options: payload.pricing_options
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
case "signal.removed": {
|
|
764
|
-
const payload = event.payload;
|
|
765
|
-
if (typeof payload.signal_agent_segment_id === "string") {
|
|
766
|
-
this.signalIndex.delete(payload.signal_agent_segment_id);
|
|
767
|
-
}
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
case "wholesale_feed.bulk_change":
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
720
|
+
++this.mirrorRevision;
|
|
721
|
+
const entity = event.event_type.startsWith("product.") ? "product" : "signal";
|
|
722
|
+
const items = (0, import_mirror.applyWholesaleFeedEvent)(
|
|
723
|
+
entity,
|
|
724
|
+
entity === "product" ? [...this.productIndex.values()] : [...this.signalIndex.values()],
|
|
725
|
+
event
|
|
726
|
+
);
|
|
727
|
+
if (!items) return;
|
|
728
|
+
if (entity === "product") this.productIndex = new Map(items.map((item) => [item.product_id, item]));
|
|
729
|
+
else this.signalIndex = new Map(items.map((item) => [item.signal_agent_segment_id, item]));
|
|
773
730
|
}
|
|
774
731
|
emitTypedEvent(event) {
|
|
775
732
|
switch (event.event_type) {
|
|
@@ -803,107 +760,23 @@ class WholesaleFeedSync extends import_node_events.EventEmitter {
|
|
|
803
760
|
}
|
|
804
761
|
// ====== Private: diff emission (auto-poll / manual refresh) ======
|
|
805
762
|
emitDiffs(previousProducts, previousSignals) {
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
763
|
+
const events = [
|
|
764
|
+
...(0, import_mirror.diffWholesaleFeed)(
|
|
765
|
+
"product",
|
|
766
|
+
[...previousProducts.values()],
|
|
767
|
+
[...this.productIndex.values()],
|
|
768
|
+
this.productCacheScope
|
|
769
|
+
),
|
|
770
|
+
...(0, import_mirror.diffWholesaleFeed)(
|
|
771
|
+
"signal",
|
|
772
|
+
[...previousSignals.values()],
|
|
773
|
+
[...this.signalIndex.values()],
|
|
774
|
+
this.signalCacheScope
|
|
775
|
+
)
|
|
776
|
+
];
|
|
777
|
+
for (const event of events) {
|
|
821
778
|
this.emit("event", { event, synthetic: true });
|
|
822
|
-
this.emit(
|
|
823
|
-
};
|
|
824
|
-
for (const [id, product] of this.productIndex) {
|
|
825
|
-
const prev = previousProducts.get(id);
|
|
826
|
-
if (!prev) {
|
|
827
|
-
emit(
|
|
828
|
-
"product.created",
|
|
829
|
-
makeEvent("product.created", "product", id, {
|
|
830
|
-
product_id: id,
|
|
831
|
-
product,
|
|
832
|
-
applies_to: { scope: this.productCacheScope }
|
|
833
|
-
})
|
|
834
|
-
);
|
|
835
|
-
} else if (priceChanged(prev, product)) {
|
|
836
|
-
emit(
|
|
837
|
-
"product.priced",
|
|
838
|
-
makeEvent("product.priced", "product", id, {
|
|
839
|
-
product_id: id,
|
|
840
|
-
pricing_options: product.pricing_options ?? [],
|
|
841
|
-
applies_to: { scope: this.productCacheScope }
|
|
842
|
-
})
|
|
843
|
-
);
|
|
844
|
-
} else if (!(0, import_node_util.isDeepStrictEqual)(prev, product)) {
|
|
845
|
-
emit(
|
|
846
|
-
"product.updated",
|
|
847
|
-
makeEvent("product.updated", "product", id, {
|
|
848
|
-
product_id: id,
|
|
849
|
-
product,
|
|
850
|
-
applies_to: { scope: this.productCacheScope }
|
|
851
|
-
})
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
for (const [id] of previousProducts) {
|
|
856
|
-
if (!this.productIndex.has(id)) {
|
|
857
|
-
emit(
|
|
858
|
-
"product.removed",
|
|
859
|
-
makeEvent("product.removed", "product", id, {
|
|
860
|
-
product_id: id,
|
|
861
|
-
applies_to: { scope: this.productCacheScope }
|
|
862
|
-
})
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
for (const [id, signal] of this.signalIndex) {
|
|
867
|
-
const prev = previousSignals.get(id);
|
|
868
|
-
if (!prev) {
|
|
869
|
-
emit(
|
|
870
|
-
"signal.created",
|
|
871
|
-
makeEvent("signal.created", "signal", id, {
|
|
872
|
-
signal_agent_segment_id: id,
|
|
873
|
-
signal,
|
|
874
|
-
applies_to: { scope: this.signalCacheScope }
|
|
875
|
-
})
|
|
876
|
-
);
|
|
877
|
-
} else if (signalPriceChanged(prev, signal)) {
|
|
878
|
-
emit(
|
|
879
|
-
"signal.priced",
|
|
880
|
-
makeEvent("signal.priced", "signal", id, {
|
|
881
|
-
signal_agent_segment_id: id,
|
|
882
|
-
pricing_options: signal.pricing_options ?? [],
|
|
883
|
-
applies_to: { scope: this.signalCacheScope }
|
|
884
|
-
})
|
|
885
|
-
);
|
|
886
|
-
} else if (!(0, import_node_util.isDeepStrictEqual)(prev, signal)) {
|
|
887
|
-
emit(
|
|
888
|
-
"signal.updated",
|
|
889
|
-
makeEvent("signal.updated", "signal", id, {
|
|
890
|
-
signal_agent_segment_id: id,
|
|
891
|
-
signal,
|
|
892
|
-
applies_to: { scope: this.signalCacheScope }
|
|
893
|
-
})
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
for (const [id] of previousSignals) {
|
|
898
|
-
if (!this.signalIndex.has(id)) {
|
|
899
|
-
emit(
|
|
900
|
-
"signal.removed",
|
|
901
|
-
makeEvent("signal.removed", "signal", id, {
|
|
902
|
-
signal_agent_segment_id: id,
|
|
903
|
-
applies_to: { scope: this.signalCacheScope }
|
|
904
|
-
})
|
|
905
|
-
);
|
|
906
|
-
}
|
|
779
|
+
this.emit(event.event_type, { event, synthetic: true });
|
|
907
780
|
}
|
|
908
781
|
}
|
|
909
782
|
// ====== Private: search ======
|
|
@@ -1080,16 +953,6 @@ function assertOptionalDate(value, field, invalid) {
|
|
|
1080
953
|
function parsePersistedDate(value) {
|
|
1081
954
|
return value === void 0 ? void 0 : new Date(value);
|
|
1082
955
|
}
|
|
1083
|
-
function priceChanged(prev, next) {
|
|
1084
|
-
const a = prev.pricing_options;
|
|
1085
|
-
const b = next.pricing_options;
|
|
1086
|
-
return !(0, import_node_util.isDeepStrictEqual)(a, b);
|
|
1087
|
-
}
|
|
1088
|
-
function signalPriceChanged(prev, next) {
|
|
1089
|
-
const a = prev.pricing_options;
|
|
1090
|
-
const b = next.pricing_options;
|
|
1091
|
-
return !(0, import_node_util.isDeepStrictEqual)(a, b);
|
|
1092
|
-
}
|
|
1093
956
|
function isUuidV7(value) {
|
|
1094
957
|
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);
|
|
1095
958
|
}
|
|
@@ -1099,13 +962,6 @@ function compareUuidV7(a, b) {
|
|
|
1099
962
|
const normalizedB = b.toLowerCase();
|
|
1100
963
|
return normalizedA === normalizedB ? 0 : normalizedA > normalizedB ? 1 : -1;
|
|
1101
964
|
}
|
|
1102
|
-
function mergeFeedMetadata(current, body) {
|
|
1103
|
-
return {
|
|
1104
|
-
wholesaleFeedVersion: typeof body.wholesale_feed_version === "string" ? body.wholesale_feed_version : current.wholesaleFeedVersion,
|
|
1105
|
-
pricingVersion: typeof body.pricing_version === "string" ? body.pricing_version : current.pricingVersion,
|
|
1106
|
-
cacheScope: body.cache_scope === "public" || body.cache_scope === "account" ? body.cache_scope : current.cacheScope
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
965
|
async function sleep(ms) {
|
|
1110
966
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1111
967
|
}
|