@porulle/plugin-channel-connector 0.73.3 → 0.74.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
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
- import type { Actor, ChannelCancelOrderInput, ChannelCatalogItem, ChannelConnector, ChannelOrderSlice, ChannelPushCatalogField, ChannelPushCatalogImage, ChannelPushCatalogItem, ChannelPushCatalogItemOutcome, ChannelPushCatalogResult, ChannelStore, PluginDb, PluginResult, PluginTxFn } from "@porulle/core";
2
+ import type { Actor, ChannelCancelOrderInput, ChannelCatalogItem, ChannelConnector, ChannelWebhookEvent, ChannelOrderSlice, ChannelPushCatalogField, ChannelPushCatalogImage, ChannelPushCatalogItem, ChannelPushCatalogItemOutcome, ChannelPushCatalogResult, ChannelStore, PluginDb, PluginResult, PluginTxFn } from "@porulle/core";
3
3
  import type { ChannelCatalogImage } from "@porulle/core";
4
4
  import type { FieldOwner, FieldPath } from "@porulle/core";
5
5
  import type { JobsAdapter } from "@porulle/core";
6
6
  import { type ChannelCatalogPush, type ChannelCatalogConflict, type ChannelOrderExport, type ChannelRefundRequest, type ConnectedStore } from "./schema.js";
7
+ import type { StoreHealth } from "./schema.js";
7
8
  import { type CatalogFieldMapping, type CatalogFieldTarget } from "./catalog-field-mapping.js";
8
9
  export type ExportState = ChannelOrderExport["state"];
9
10
  export type CatalogPushState = ChannelCatalogPush["state"];
@@ -26,6 +27,10 @@ export declare const CHANNEL_INVENTORY_MAX_ITEMS_PER_INVOCATION = 20;
26
27
  * The reason a platform order is cancelled under when its STORE cancelled it. The cancel hook skips
27
28
  * cancelling at the store for exactly this reason, so the two directions cannot loop.
28
29
  */
30
+ /** How stale a store order may be before a read point queues a refresh of it. */
31
+ export declare const REMOTE_ORDER_REFRESH_MS: number;
32
+ /** How often a merchant's visit may make the plugin call a store to check its health. */
33
+ export declare const STORE_HEALTH_INTERVAL_MS: number;
29
34
  export declare const CHANNEL_ORDER_CANCELLED_REASON = "channel_order_cancelled";
30
35
  export declare function catalogPushRetryDelayMs(attempts: number): number;
31
36
  export interface CatalogPushJobResult extends Record<string, unknown> {
@@ -374,6 +379,10 @@ export interface CatalogPageConvergence extends Record<string, unknown> {
374
379
  heroesImported: number;
375
380
  mediaFailures: CatalogMediaFailure[];
376
381
  deferredMedia: CatalogDeferredMedia[];
382
+ /** Fields the store's value did not overwrite because the platform owns them. */
383
+ skipped: CatalogFieldSkip[];
384
+ /** Shared fields both sides changed, held for an operator. */
385
+ conflicts: CatalogFieldConflict[];
377
386
  warnings: string[];
378
387
  }
379
388
  export interface ImportImageSelection {
@@ -493,6 +502,8 @@ export declare class ChannelConnectorService {
493
502
  /** The slug an existing product converges to: its current one while that is still in the family
494
503
  * of the handle the store sends, so a slug is never recomputed out from under a shared link. */
495
504
  private slugToKeep;
505
+ /** This organization's store of `provider` at `storeDomain`, whatever its status. */
506
+ storeByDomain(orgId: string, provider: string, storeDomain: string): Promise<PublicConnectedStore | undefined>;
496
507
  getStoresByDomain(shopDomain: string): Promise<ConnectedStore[]>;
497
508
  resolveCatalogFieldMapping(store: Pick<ConnectedStore, "provider" | "catalogFieldMapping">, filterableCustomFields?: ReadonlySet<string> | Readonly<Record<string, boolean>>, warnings?: string[]): CatalogFieldMapping;
498
509
  buildCatalogPushItems(orgId: string, storeId: string, entityIds: string[], options?: BuildCatalogPushItemsOptions): Promise<PluginResult<BuildCatalogPushItemsResult>>;
@@ -511,12 +522,37 @@ export declare class ChannelConnectorService {
511
522
  * provider that subscribes per store is registered after commit, at an absolute address; the
512
523
  * consumer's follow-on work ({@link AfterStoreConnected}) runs after that.
513
524
  */
525
+ /**
526
+ * Connects a store and runs its follow-on work (subscribe, first import) before answering. For a
527
+ * provider whose callback must be answered at once, see {@link saveConnectingStore} and
528
+ * {@link completeConnect}, which this is the two halves of.
529
+ */
514
530
  connectStore(orgId: string, input: {
515
531
  provider: string;
516
532
  credentials: Record<string, unknown>;
517
533
  storeDomain: string;
518
534
  webhookSecret?: string;
519
535
  }, actor: StoreConnectActor): Promise<PluginResult<PublicConnectedStore>>;
536
+ /**
537
+ * Writes the store with its credentials in status `connecting`, bound to the actor's vendor, and
538
+ * nothing else: no call to the store. Reconnecting a store this organization already has refreshes
539
+ * its row instead of adding one.
540
+ */
541
+ saveConnectingStore(orgId: string, input: {
542
+ provider: string;
543
+ credentials: Record<string, unknown>;
544
+ storeDomain: string;
545
+ webhookSecret?: string;
546
+ }, actor: StoreConnectActor): Promise<PluginResult<PublicConnectedStore>>;
547
+ /**
548
+ * The work after the credentials are saved: subscribe the store to its connector's topics (all or
549
+ * none — a partial subscription is removed), mark it `connected`, then the host's follow-on work.
550
+ * A failure leaves the store in `error` with the reason the merchant will read, unless the host
551
+ * already moved it (it disconnects a store it refuses).
552
+ */
553
+ completeConnect(orgId: string, storeId: string, actor: StoreConnectActor): Promise<PluginResult<PublicConnectedStore>>;
554
+ /** Where a store that subscribes per store delivers its webhooks: absolute, on this deployment's public origin. */
555
+ webhookCallbackUrl(storeId: string): string;
520
556
  /** The store with credentials good for a call the host makes itself, e.g. its own Admin API write. */
521
557
  liveStore(orgId: string, storeId: string): Promise<PluginResult<ChannelStore>>;
522
558
  /** The consumer's claims for a connection starting from this request. See {@link ConnectClaims}. */
@@ -529,6 +565,17 @@ export declare class ChannelConnectorService {
529
565
  reachableStore(orgId: string, id: string, context: StoreReadContext | undefined): Promise<PluginResult<ConnectedStore>>;
530
566
  disconnectStore(orgId: string, id: string, context?: StoreReadContext): Promise<PluginResult<PublicConnectedStore>>;
531
567
  disconnectStoreSystem(orgId: string, id: string, redactDomain?: boolean): Promise<PluginResult<PublicConnectedStore>>;
568
+ /**
569
+ * Checks the store's webhook subscriptions and key and repairs what it can, when a merchant looks:
570
+ * there is no scheduled check. At most once per {@link STORE_HEALTH_INTERVAL_MS} per store; inside
571
+ * that window the last result is answered without calling the store.
572
+ */
573
+ checkStoreHealth(orgId: string, id: string, context?: StoreReadContext): Promise<PluginResult<StoreHealth & {
574
+ status: ConnectedStore["status"];
575
+ statusReason: string | null;
576
+ lastEventAt: Date | null;
577
+ cached: boolean;
578
+ }>>;
532
579
  getStore(orgId: string, id: string, context?: StoreReadContext): Promise<PluginResult<PublicConnectedStore>>;
533
580
  listStores(orgId: string, context?: StoreReadContext): Promise<PluginResult<PublicConnectedStore[]>>;
534
581
  validateLineStock(orgId: string, lines: ChannelStockLine[], timeoutMs?: number): Promise<void>;
@@ -624,28 +671,38 @@ export declare class ChannelConnectorService {
624
671
  * the whole inventory and every map row on every 20-level step.
625
672
  */
626
673
  private syncInventoryPage;
627
- handleWebhook(orgId: string, storeId: string, event: {
628
- id: string;
629
- type: string;
630
- data: unknown;
631
- }): Promise<PluginResult<{
632
- processed: true;
674
+ /**
675
+ * One verified store delivery. The store's connector says what it means ({@link ChannelEvent}); this
676
+ * acts on the meaning and never on a topic or payload field. A delivery the connector does not act
677
+ * on is logged as unmapped and answered `processed: false`, never reported as applied.
678
+ */
679
+ handleWebhook(orgId: string, storeId: string, delivery: ChannelWebhookEvent): Promise<PluginResult<{
680
+ processed: boolean;
633
681
  data?: ChannelComplianceData;
634
682
  redacted?: number;
635
683
  }>>;
684
+ private applyChannelEvent;
636
685
  private complianceEmail;
637
686
  private channelCustomerExports;
638
687
  private channelCustomerDataRequest;
639
688
  private redactCustomerData;
640
689
  private redactShopData;
641
- private resolveOrderId;
690
+ /**
691
+ * For a read point (a shopper opening their order): each of the order's store orders not read for
692
+ * {@link REMOTE_ORDER_REFRESH_MS} is queued for one refresh, so a store-side cancel, shipment or
693
+ * refund whose delivery never arrived still reaches the platform. No schedule: someone looked.
694
+ * Answers how many were queued.
695
+ */
696
+ refreshStaleRemoteOrders(orgId: string, orderId: string, jobs: JobsAdapter): Promise<number>;
697
+ /** The store's current state of one order we pushed, applied as if its delivery had arrived. */
698
+ refreshRemoteOrder(orgId: string, storeId: string, remoteOrderId: string): Promise<PluginResult<{
699
+ events: number;
700
+ }>>;
701
+ private orderForRemote;
642
702
  /** Archives this store's product mapped to `externalId`, unless the platform owns its status. */
643
703
  private archiveMappedProduct;
644
704
  private setMappedInventory;
645
705
  private setInventoryLevel;
646
- /** This store's mapped variant whose import recorded `metadata.inventoryItemId`; null when none, or when two claim it. */
647
- private variantForInventoryItem;
648
- private convergeCatalogItem;
649
706
  private createRefundRequest;
650
707
  private executeRefund;
651
708
  listRefundRequests(orgId: string): Promise<PluginResult<ChannelRefundRequest[]>>;
@@ -665,10 +722,10 @@ export declare class ChannelConnectorService {
665
722
  */
666
723
  cancelRemoteOrders(orgId: string, orderId: string, input: ChannelCancelOrderInput): Promise<PluginResult<number>>;
667
724
  /**
668
- * One core fulfilment record per store fulfilment the order body carries, keyed on the store's
669
- * fulfilment id (`metadata.channelFulfillmentId`) so a replay records nothing twice. Each records
670
- * the lines it shipped, matched by the store's variant id; one whose lines cannot be matched
671
- * records every line not yet fulfilled. A fulfilment the store cancelled is not a parcel.
725
+ * One core fulfilment record per store parcel, keyed on the store's parcel id
726
+ * (`metadata.channelFulfillmentId`) so a replay records nothing twice. Each records the lines it
727
+ * shipped, matched by the store's variant id; one whose lines cannot be matched (or that names
728
+ * none) records every line not yet fulfilled.
672
729
  */
673
730
  private recordChannelFulfillments;
674
731
  /** Every line with quantity still to ship, for a parcel whose own lines could not be matched. */