@porulle/plugin-channel-connector 0.10.8 → 0.13.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.
@@ -1,5 +1,5 @@
1
1
  import { CommerceValidationError } from "@porulle/core";
2
- import type { ChannelCatalogItem, ChannelInventoryLevel, ChannelOrderSlice } from "@porulle/core";
2
+ import type { ChannelCatalogItem, ChannelConnectorError, ChannelInventoryLevel, ChannelOrderSlice, ChannelPushCatalogItem, ChannelPushCatalogPreviousField, ChannelStore } from "@porulle/core";
3
3
  export interface MockChannelConnectorOptions {
4
4
  catalog?: ChannelCatalogItem[];
5
5
  inventory?: ChannelInventoryLevel[];
@@ -7,6 +7,9 @@ export interface MockChannelConnectorOptions {
7
7
  throwOnInventory?: boolean;
8
8
  inventoryDelayMs?: number;
9
9
  onFetchInventory?: (ids: string[]) => void;
10
+ pushCatalogFailures?: Record<string, ChannelConnectorError>;
11
+ pushCatalogTransportError?: ChannelConnectorError;
12
+ onPushCatalog?: (items: ChannelPushCatalogItem[]) => void;
10
13
  }
11
14
  export declare function mockChannelConnector(options?: MockChannelConnectorOptions): {
12
15
  providerId: string;
@@ -14,13 +17,14 @@ export declare function mockChannelConnector(options?: MockChannelConnectorOptio
14
17
  importCatalog: true;
15
18
  importInventory: true;
16
19
  pushOrder: true;
20
+ pushCatalog: true;
17
21
  receiveWebhooks: true;
18
22
  };
19
- importCatalog(): Promise<import("@porulle/core").Result<{
23
+ importCatalog(_store?: ChannelStore): Promise<import("@porulle/core").Result<{
20
24
  items: ChannelCatalogItem[];
21
25
  nextCursor: null;
22
26
  }, never>>;
23
- fetchInventory(_store: import("@porulle/core").ChannelStore, ids: string[] | undefined): Promise<{
27
+ fetchInventory(_store: ChannelStore, ids: string[] | undefined): Promise<{
24
28
  ok: false;
25
29
  error: CommerceValidationError;
26
30
  } | {
@@ -28,11 +32,32 @@ export declare function mockChannelConnector(options?: MockChannelConnectorOptio
28
32
  value: ChannelInventoryLevel[];
29
33
  meta?: Record<string, unknown>;
30
34
  }>;
31
- pushOrder(_store: import("@porulle/core").ChannelStore, slice: ChannelOrderSlice): Promise<import("@porulle/core").Result<{
35
+ pushOrder(_store: ChannelStore, slice: ChannelOrderSlice): Promise<import("@porulle/core").Result<{
32
36
  remoteOrderId: string;
33
37
  remoteUrl: string;
34
38
  }, never>>;
35
- fetchOrderStatus(_store: import("@porulle/core").ChannelStore, remoteId: string): Promise<{
39
+ pushCatalog(_store: ChannelStore, items: ChannelPushCatalogItem[], opts?: {
40
+ dryRun?: boolean;
41
+ }): Promise<{
42
+ ok: false;
43
+ error: ChannelConnectorError;
44
+ } | {
45
+ ok: true;
46
+ value: {
47
+ outcomes: ({
48
+ externalId: string;
49
+ ok: boolean;
50
+ error: ChannelConnectorError;
51
+ } | {
52
+ previousFields?: ChannelPushCatalogPreviousField[];
53
+ externalId: string;
54
+ ok: boolean;
55
+ error?: never;
56
+ })[];
57
+ };
58
+ meta?: Record<string, unknown>;
59
+ }>;
60
+ fetchOrderStatus(_store: ChannelStore, remoteId: string): Promise<{
36
61
  ok: false;
37
62
  error: CommerceValidationError;
38
63
  } | {
@@ -42,7 +67,7 @@ export declare function mockChannelConnector(options?: MockChannelConnectorOptio
42
67
  };
43
68
  meta?: Record<string, unknown>;
44
69
  }>;
45
- verifyWebhook(store: import("@porulle/core").ChannelStore, request: Request): Promise<{
70
+ verifyWebhook(store: ChannelStore, request: Request): Promise<{
46
71
  ok: false;
47
72
  error: CommerceValidationError;
48
73
  } | {
@@ -1,16 +1,67 @@
1
1
  import { CommerceValidationError, Err, Ok, defineChannelConnector, } from "@porulle/core";
2
+ const defaultCatalog = [{
3
+ externalId: "mock-product-1",
4
+ slug: "mock-channel-product",
5
+ title: "Mock Channel Product",
6
+ description: "Imported through the mock connector.",
7
+ attributes: [{
8
+ locale: "en",
9
+ title: "Mock Channel Product",
10
+ subtitle: "A complete mock catalog item",
11
+ description: "Imported through the mock connector.",
12
+ richDescription: { blocks: [{ type: "paragraph", text: "Mock product details." }] },
13
+ seoTitle: "Mock Channel Product | Porulle",
14
+ seoDescription: "A mock product with the complete channel catalog shape.",
15
+ }],
16
+ images: [
17
+ {
18
+ externalId: "mock-image-primary",
19
+ url: "https://mock.channel.test/images/mock-product-1-primary.jpg",
20
+ alt: "Mock Channel Product",
21
+ role: "primary",
22
+ sortOrder: 0,
23
+ },
24
+ {
25
+ externalId: "mock-image-variant",
26
+ url: "https://mock.channel.test/images/mock-variant-1.jpg",
27
+ alt: "Mock Channel Product blue variant",
28
+ role: "gallery",
29
+ sortOrder: 1,
30
+ variantExternalIds: ["mock-variant-1"],
31
+ },
32
+ ],
33
+ options: [{
34
+ name: "color",
35
+ displayName: "Color",
36
+ sortOrder: 0,
37
+ values: [{ value: "blue", displayValue: "Blue", sortOrder: 0 }],
38
+ }],
39
+ tags: ["mock", "featured"],
40
+ brand: "Porulle",
41
+ categories: ["mock-products"],
42
+ status: "active",
43
+ variants: [{
44
+ externalId: "mock-variant-1",
45
+ sku: "MOCK-SKU-1",
46
+ barcode: "0123456789012",
47
+ optionValues: { color: "blue" },
48
+ prices: [{ currency: "USD", amount: 2500 }],
49
+ }],
50
+ }];
2
51
  export function mockChannelConnector(options = {}) {
3
52
  const orders = new Map();
53
+ const catalog = new Map();
4
54
  return defineChannelConnector({
5
55
  providerId: "mock",
6
56
  capabilities: {
7
57
  importCatalog: true,
8
58
  importInventory: true,
9
59
  pushOrder: true,
60
+ pushCatalog: true,
10
61
  receiveWebhooks: true,
11
62
  },
12
- async importCatalog() {
13
- return Ok({ items: options.catalog ?? [], nextCursor: null });
63
+ async importCatalog(_store) {
64
+ return Ok({ items: options.catalog ?? defaultCatalog, nextCursor: null });
14
65
  },
15
66
  async fetchInventory(_store, ids) {
16
67
  const requestedIds = ids ?? [];
@@ -33,6 +84,30 @@ export function mockChannelConnector(options = {}) {
33
84
  remoteUrl: `https://mock.channel.test/orders/${remoteOrderId}`,
34
85
  });
35
86
  },
87
+ async pushCatalog(_store, items, opts) {
88
+ if (options.pushCatalogTransportError)
89
+ return Err(options.pushCatalogTransportError);
90
+ const outcomes = items.map((item) => {
91
+ const error = options.pushCatalogFailures?.[item.externalId];
92
+ if (error)
93
+ return { externalId: item.externalId, ok: false, error };
94
+ const previous = catalog.get(item.externalId);
95
+ const previousFields = previous
96
+ ? [...previous.fields, ...(previous.variants ?? []).flatMap((variant) => variant.fields)]
97
+ .map((field) => ({ fieldPath: field.fieldPath, value: structuredClone(field.value) }))
98
+ : [];
99
+ if (opts?.dryRun !== true)
100
+ catalog.set(item.externalId, structuredClone(item));
101
+ return {
102
+ externalId: item.externalId,
103
+ ok: true,
104
+ ...(previousFields.length > 0 ? { previousFields } : {}),
105
+ };
106
+ });
107
+ if (opts?.dryRun !== true)
108
+ options.onPushCatalog?.(structuredClone(items));
109
+ return Ok({ outcomes });
110
+ },
36
111
  async fetchOrderStatus(_store, remoteId) {
37
112
  if (!orders.has(remoteId)) {
38
113
  return Err(new CommerceValidationError(`Mock order "${remoteId}" was not found.`));