@porulle/plugin-channel-connector 0.48.1 → 0.49.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/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/service.d.ts +89 -1
- package/dist/service.js +503 -37
- package/package.json +4 -4
- package/src/index.ts +9 -0
- package/src/service.ts +581 -34
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ChannelConnectorPluginOptions } from "./service.js";
|
|
2
2
|
export { mockChannelConnector } from "./mock-connector.js";
|
|
3
3
|
export type { MockChannelConnectorOptions } from "./mock-connector.js";
|
|
4
|
-
export { ChannelConnectorService, CATALOG_OUTBOUND_SUPPRESSION_WINDOW_MS, CATALOG_PUSH_BATCH_SIZES, CATALOG_PUSH_MAX_ATTEMPTS, canCatalogPushTransition, canExportTransition, catalogPushConcurrencyKey, catalogPushRetryDelayMs, CHANNEL_INVENTORY_MAX_ITEMS_PER_INVOCATION, isCatalogPushBreakerOpen, } from "./service.js";
|
|
4
|
+
export { ChannelConnectorService, HERO_IMAGE_BYTE_CAP, selectImportImages, CATALOG_OUTBOUND_SUPPRESSION_WINDOW_MS, CATALOG_PUSH_BATCH_SIZES, CATALOG_PUSH_MAX_ATTEMPTS, canCatalogPushTransition, canExportTransition, catalogPushConcurrencyKey, catalogPushRetryDelayMs, CHANNEL_INVENTORY_MAX_ITEMS_PER_INVOCATION, isCatalogPushBreakerOpen, } from "./service.js";
|
|
5
5
|
export { isValidCatalogMappingFieldPath, matchFieldPath, mergeCatalogFieldMapping, normalizeCatalogFieldMapping, compareCatalogFieldMappingSpecificity, providerCatalogFieldMappingDefaults, selectCatalogFieldMapping, validateCatalogMappingRow, } from "./catalog-field-mapping.js";
|
|
6
|
+
export type { CatalogDeferredMedia, CatalogMediaFailure, CatalogMediaFailureReason, CatalogPageConvergence, ImportImageSelection, } from "./service.js";
|
|
6
7
|
export type { CatalogFieldMapping, CatalogFieldMappingInput, CatalogFieldMappingRow, CatalogFieldTarget, } from "./catalog-field-mapping.js";
|
|
7
8
|
/** ~320 Neon HTTP subrequests per product against a 10,000 per-invocation cap → hard ceiling near 31; 20 leaves margin for heavier products. */
|
|
8
9
|
export declare const CHANNEL_IMPORT_MAX_ITEMS_PER_INVOCATION = 20;
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ChannelConnectorService, catalogPushConcurrencyKey, CHANNEL_INVENTORY_M
|
|
|
8
8
|
import { buildHooks } from "./hooks.js";
|
|
9
9
|
import { oauthStateEventId, signState, verifyState } from "./oauth-state.js";
|
|
10
10
|
export { mockChannelConnector } from "./mock-connector.js";
|
|
11
|
-
export { ChannelConnectorService, CATALOG_OUTBOUND_SUPPRESSION_WINDOW_MS, CATALOG_PUSH_BATCH_SIZES, CATALOG_PUSH_MAX_ATTEMPTS, canCatalogPushTransition, canExportTransition, catalogPushConcurrencyKey, catalogPushRetryDelayMs, CHANNEL_INVENTORY_MAX_ITEMS_PER_INVOCATION, isCatalogPushBreakerOpen, } from "./service.js";
|
|
11
|
+
export { ChannelConnectorService, HERO_IMAGE_BYTE_CAP, selectImportImages, CATALOG_OUTBOUND_SUPPRESSION_WINDOW_MS, CATALOG_PUSH_BATCH_SIZES, CATALOG_PUSH_MAX_ATTEMPTS, canCatalogPushTransition, canExportTransition, catalogPushConcurrencyKey, catalogPushRetryDelayMs, CHANNEL_INVENTORY_MAX_ITEMS_PER_INVOCATION, isCatalogPushBreakerOpen, } from "./service.js";
|
|
12
12
|
export { isValidCatalogMappingFieldPath, matchFieldPath, mergeCatalogFieldMapping, normalizeCatalogFieldMapping, compareCatalogFieldMappingSpecificity, providerCatalogFieldMappingDefaults, selectCatalogFieldMapping, validateCatalogMappingRow, } from "./catalog-field-mapping.js";
|
|
13
13
|
/** ~320 Neon HTTP subrequests per product against a 10,000 per-invocation cap → hard ceiling near 31; 20 leaves margin for heavier products. */
|
|
14
14
|
export const CHANNEL_IMPORT_MAX_ITEMS_PER_INVOCATION = 20;
|
package/dist/service.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Actor, ChannelConnector, ChannelOrderSlice, ChannelPushCatalogField, ChannelPushCatalogImage, ChannelPushCatalogItem, ChannelPushCatalogItemOutcome, ChannelPushCatalogResult, PluginDb, PluginResult, PluginTxFn } from "@porulle/core";
|
|
1
|
+
import type { Actor, ChannelCatalogItem, ChannelConnector, ChannelOrderSlice, ChannelPushCatalogField, ChannelPushCatalogImage, ChannelPushCatalogItem, ChannelPushCatalogItemOutcome, ChannelPushCatalogResult, PluginDb, PluginResult, PluginTxFn } from "@porulle/core";
|
|
2
|
+
import type { ChannelCatalogImage } from "@porulle/core";
|
|
2
3
|
import type { FieldOwner, FieldPath } from "@porulle/core";
|
|
3
4
|
import type { JobsAdapter } from "@porulle/core";
|
|
4
5
|
import { type ChannelCatalogPush, type ChannelCatalogConflict, type ChannelOrderExport, type ChannelRefundRequest, type ConnectedStore } from "./schema.js";
|
|
@@ -231,6 +232,50 @@ export interface CatalogPushPreviewResult {
|
|
|
231
232
|
export declare function canExportTransition(from: ExportState, to: ExportState): boolean;
|
|
232
233
|
export declare function canCatalogPushTransition(from: CatalogPushState, to: CatalogPushState): boolean;
|
|
233
234
|
export declare const CATALOG_OUTBOUND_SUPPRESSION_WINDOW_MS: number;
|
|
235
|
+
/**
|
|
236
|
+
* A hero is streamed inside the page's own invocation, so it is bounded: a 30 MB TIFF a merchant
|
|
237
|
+
* uploaded by mistake must not buffer into a 128 MiB isolate. Anything larger is reported, not
|
|
238
|
+
* stored, and the product still lands — the index reads text first and media later.
|
|
239
|
+
*/
|
|
240
|
+
export declare const HERO_IMAGE_BYTE_CAP: number;
|
|
241
|
+
export type CatalogMediaFailureReason = "too-large" | "download-failed" | "unsupported" | "storage";
|
|
242
|
+
export interface CatalogMediaFailure {
|
|
243
|
+
externalId: string;
|
|
244
|
+
imageExternalId?: string;
|
|
245
|
+
url: string;
|
|
246
|
+
reason: CatalogMediaFailureReason;
|
|
247
|
+
detail: string;
|
|
248
|
+
}
|
|
249
|
+
/** Media the page did NOT fetch: the first photo of each variant the hero does not show. */
|
|
250
|
+
export interface CatalogDeferredMedia {
|
|
251
|
+
externalId: string;
|
|
252
|
+
entityId: string;
|
|
253
|
+
images: ChannelCatalogImage[];
|
|
254
|
+
}
|
|
255
|
+
export interface CatalogPageConvergence extends Record<string, unknown> {
|
|
256
|
+
created: number;
|
|
257
|
+
unchanged: number;
|
|
258
|
+
updated: number;
|
|
259
|
+
/** Input order, failures excluded, no duplicates — the page message is rebuilt from this. */
|
|
260
|
+
entityIds: string[];
|
|
261
|
+
failures: CatalogConvergenceFailure[];
|
|
262
|
+
heroesImported: number;
|
|
263
|
+
mediaFailures: CatalogMediaFailure[];
|
|
264
|
+
deferredMedia: CatalogDeferredMedia[];
|
|
265
|
+
warnings: string[];
|
|
266
|
+
}
|
|
267
|
+
export interface ImportImageSelection {
|
|
268
|
+
hero: ChannelCatalogImage | null;
|
|
269
|
+
/** In variant order; one image per variant the hero does not cover; no url twice. */
|
|
270
|
+
perVariant: ChannelCatalogImage[];
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Ruling 2026-09-22: import the hero plus the FIRST photo of each other variant, nothing more.
|
|
274
|
+
* A "blue long dress" query must be able to show the blue variant, and a fourth photo of the red
|
|
275
|
+
* one adds nothing the index can use. Variants are read off the images' own variant references,
|
|
276
|
+
* so a connector that lists images against variants it does not enumerate still gets one each.
|
|
277
|
+
*/
|
|
278
|
+
export declare function selectImportImages(item: ChannelCatalogItem): ImportImageSelection;
|
|
234
279
|
export declare class ChannelConnectorService {
|
|
235
280
|
private readonly db;
|
|
236
281
|
private readonly services;
|
|
@@ -256,6 +301,28 @@ export declare class ChannelConnectorService {
|
|
|
256
301
|
private setCatalogAttributesIfWritable;
|
|
257
302
|
private upsertOptionAxes;
|
|
258
303
|
private upsertVariants;
|
|
304
|
+
/**
|
|
305
|
+
* The organization's categories, brands and tags, read ONCE per converge run instead of once per
|
|
306
|
+
* product.
|
|
307
|
+
*
|
|
308
|
+
* `applyTaxonomy` takes an `entityId` and is called unconditionally for every item, and each of
|
|
309
|
+
* its three lookups was a whole-table read filtered by `organization_id`. Measured on the deployed
|
|
310
|
+
* Worker: 1.0 call per product per class, three classes, every product — an organization's whole
|
|
311
|
+
* category list re-read for each of twenty products in a batch that cannot have changed it.
|
|
312
|
+
*
|
|
313
|
+
* Rows created DURING the run are appended by the callers below, exactly as they were appended to
|
|
314
|
+
* the per-product arrays before, so an item that introduces a category is still seen by the next
|
|
315
|
+
* item. The cache is cleared at the top of `convergeCatalogItems`, so its lifetime is one converge
|
|
316
|
+
* rather than the lifetime of the service.
|
|
317
|
+
*
|
|
318
|
+
* The staleness window widens from one product to one batch: a category created by ANOTHER process
|
|
319
|
+
* mid-batch is not seen here. That was already true within a product — these lists were always a
|
|
320
|
+
* snapshot — and the create paths below go through `this.catalog`, which refuses a duplicate slug
|
|
321
|
+
* rather than writing one. So the failure mode is unchanged in kind and wider in window, which is
|
|
322
|
+
* the trade this comment exists to state rather than hide.
|
|
323
|
+
*/
|
|
324
|
+
private taxonomyCache;
|
|
325
|
+
private taxonomyFor;
|
|
259
326
|
private applyTaxonomy;
|
|
260
327
|
private applyMedia;
|
|
261
328
|
private getStoreRecord;
|
|
@@ -281,6 +348,27 @@ export declare class ChannelConnectorService {
|
|
|
281
348
|
getStore(orgId: string, id: string): Promise<PluginResult<PublicConnectedStore>>;
|
|
282
349
|
listStores(orgId: string, context?: StoreReadContext): Promise<PluginResult<PublicConnectedStore[]>>;
|
|
283
350
|
validateLineStock(orgId: string, lines: ChannelStockLine[], timeoutMs?: number): Promise<void>;
|
|
351
|
+
/**
|
|
352
|
+
* One page from the connector, nothing written. The host lands the page durably (R2 + its
|
|
353
|
+
* ledger) and hands it to `convergeCatalogPage` from a queue consumer; the two halves are
|
|
354
|
+
* separate so a consumer retry never re-fetches the merchant's API.
|
|
355
|
+
*/
|
|
356
|
+
fetchCatalogPage(orgId: string, storeId: string, cursor: string | null): Promise<PluginResult<{
|
|
357
|
+
items: ChannelCatalogItem[];
|
|
358
|
+
nextCursor: string | null;
|
|
359
|
+
}>>;
|
|
360
|
+
/**
|
|
361
|
+
* Converges a page: items this store has never mapped take the import fast path
|
|
362
|
+
* (`catalog.importProducts`, one transaction, multi-row writes); items already mapped and
|
|
363
|
+
* unchanged cost nothing; items mapped-but-changed, and orphans (an entity of this store with
|
|
364
|
+
* the item's slug but no map row), take the editor path, which owns ownership and conflicts.
|
|
365
|
+
*
|
|
366
|
+
* Media: only each new item's hero is fetched here, streamed under `HERO_IMAGE_BYTE_CAP`, and
|
|
367
|
+
* linked at entity level as `primary` plus to the variants it shows. The first photo of every
|
|
368
|
+
* other variant comes back in `deferredMedia` for the host to land later.
|
|
369
|
+
*/
|
|
370
|
+
convergeCatalogPage(orgId: string, storeId: string, items: ChannelCatalogItem[], actor: Actor): Promise<PluginResult<CatalogPageConvergence>>;
|
|
371
|
+
private importHeroes;
|
|
284
372
|
importCatalog(orgId: string, storeId: string, actor: Actor, options: {
|
|
285
373
|
maxItems: number;
|
|
286
374
|
}): Promise<PluginResult<{
|