@porulle/plugin-channel-connector 0.47.0 → 0.48.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/service.d.ts CHANGED
@@ -291,6 +291,18 @@ export declare class ChannelConnectorService {
291
291
  conflicts?: CatalogFieldConflict[];
292
292
  warnings?: string[];
293
293
  failures?: CatalogConvergenceFailure[];
294
+ /**
295
+ * The entities this batch committed — see `CatalogConvergenceStats.entityIds`.
296
+ *
297
+ * Declared on the BOUNDED overload only. This is the one a per-batch step wrapper calls, so it
298
+ * is the one with a page to name. The unbounded overload walks a whole catalogue and would hand
299
+ * back thousands of ids across a durable step boundary, which is the opposite of the point.
300
+ *
301
+ * Required rather than optional here, unlike its siblings, for the reason the implementation
302
+ * states: a caller must be able to tell "this batch committed nothing" from "this build does
303
+ * not report entities", and one of those is `[]` while the other is `undefined`.
304
+ */
305
+ entityIds: string[];
294
306
  }>>;
295
307
  importCatalog(orgId: string, storeId: string, actor: Actor, options?: undefined): Promise<PluginResult<{
296
308
  imported: number;
package/dist/service.js CHANGED
@@ -1733,6 +1733,7 @@ export class ChannelConnectorService {
1733
1733
  const conflicts = [];
1734
1734
  const warnings = [];
1735
1735
  const failures = [];
1736
+ const entityIds = [];
1736
1737
  while (remaining > 0) {
1737
1738
  const page = await connector.importCatalog(store, pageCursor ?? undefined);
1738
1739
  if (!page.ok)
@@ -1759,6 +1760,7 @@ export class ChannelConnectorService {
1759
1760
  conflicts.push(...result.value.conflicts);
1760
1761
  warnings.push(...result.value.warnings);
1761
1762
  failures.push(...result.value.failures);
1763
+ entityIds.push(...result.value.entityIds);
1762
1764
  offset += result.value.consumed;
1763
1765
  if (offset >= pageItems.length) {
1764
1766
  if (page.value.nextCursor) {
@@ -1793,6 +1795,13 @@ export class ChannelConnectorService {
1793
1795
  // Always surfaced when non-empty. A silently dropped product is worse than the halt this
1794
1796
  // replaced: the caller must be able to see which externalIds did not land.
1795
1797
  ...(failures.length > 0 ? { failures } : {}),
1798
+ // UNCONDITIONAL, unlike every optional field above it, and the asymmetry is deliberate.
1799
+ // The caller turns this into one queue message naming the page it just converged. If the key
1800
+ // were omitted when empty, a caller reading `outcome.entityIds` could not tell "this batch
1801
+ // committed nothing" from "this plugin version does not report entities" — both read as
1802
+ // `undefined`, and the second one silently produces an import that enqueues nothing. An
1803
+ // empty array says the first; a missing key says the second. They deserve different answers.
1804
+ entityIds,
1796
1805
  });
1797
1806
  }
1798
1807
  async promoteLegacyAttributes(orgId, storeId, actor, dryRun) {
@@ -1958,6 +1967,7 @@ export class ChannelConnectorService {
1958
1967
  conflicts: [],
1959
1968
  warnings: [],
1960
1969
  failures: [], // a dry run converges nothing, so it can fail nothing
1970
+ entityIds: [], // ...and commits nothing, so it names nothing
1961
1971
  };
1962
1972
  const assets = await this.db.select().from(mediaAssets).where(eq(mediaAssets.organizationId, orgId));
1963
1973
  for (const item of items) {
@@ -2090,6 +2100,8 @@ export class ChannelConnectorService {
2090
2100
  const conflicts = [];
2091
2101
  const warnings = [];
2092
2102
  const failures = [];
2103
+ const entityIds = [];
2104
+ const committed = new Set();
2093
2105
  for (const item of items) {
2094
2106
  consumed += 1;
2095
2107
  try {
@@ -2288,6 +2300,14 @@ export class ChannelConnectorService {
2288
2300
  }).where(eq(channelEntityMap.id, entityMapping.id));
2289
2301
  }
2290
2302
  await this.db.update(channelEntityMap).set({ lastSyncedAt }).where(and(eq(channelEntityMap.organizationId, orgId), eq(channelEntityMap.storeId, storeId), eq(channelEntityMap.entityId, entityId), eq(channelEntityMap.kind, "variant")));
2303
+ // LAST statement of the try, and that position is the whole guarantee: every failure path
2304
+ // above reaches `continue` before here, so an id is recorded only once the item's writes are
2305
+ // done. De-duplicated because a connector returning one externalId twice in a page would
2306
+ // otherwise have the consumer pay for the same entity twice.
2307
+ if (!committed.has(entityId)) {
2308
+ committed.add(entityId);
2309
+ entityIds.push(entityId);
2310
+ }
2291
2311
  }
2292
2312
  catch (error) {
2293
2313
  // Anything the stages throw rather than returning. Same disposition: record it against the
@@ -2308,6 +2328,7 @@ export class ChannelConnectorService {
2308
2328
  conflicts,
2309
2329
  warnings,
2310
2330
  failures,
2331
+ entityIds,
2311
2332
  });
2312
2333
  }
2313
2334
  async reconcile(orgId, storeId, actor) {