@decocms/apps-sfmc-personalization 8.1.0-next.0 → 8.1.0-next.2
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 +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +18 -11
- package/src/index.ts +4 -4
- package/src/__tests__/client.test.ts +0 -48
- package/tsconfig.json +0 -7
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PersonalizationEvent, PersonalizationResponse } from "./types.ts";
|
|
2
|
+
export type { CampaignResponse, PersonalizationEvent, PersonalizationLineItem, PersonalizationProduct, PersonalizationResponse, } from "./types.ts";
|
|
3
|
+
export interface SfmcPersonalizationConfig {
|
|
4
|
+
/** The account's API origin, e.g. `https://<account>.<instance>.evergage.com`. */
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
/** The dataset events are sent to. */
|
|
7
|
+
dataset: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class SfmcPersonalizationError extends Error {
|
|
10
|
+
readonly operation: string;
|
|
11
|
+
readonly status: number;
|
|
12
|
+
constructor(operation: string, status: number);
|
|
13
|
+
}
|
|
14
|
+
export declare function createSfmcPersonalizationClient(config: SfmcPersonalizationConfig, options?: {
|
|
15
|
+
fetch?: typeof globalThis.fetch;
|
|
16
|
+
}): {
|
|
17
|
+
/** Sends an interaction and returns the campaigns it triggered. */
|
|
18
|
+
sendEvent(event: PersonalizationEvent): Promise<PersonalizationResponse>;
|
|
19
|
+
};
|
|
20
|
+
export type SfmcPersonalizationClient = ReturnType<typeof createSfmcPersonalizationClient>;
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEhF,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAE/C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;IAFzB,YACW,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EAGxB;CACF;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,yBAAyB,EACjC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAO;IAQ/C,mEAAmE;IAC7D,SAAS,QAAQ,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC;EAYjF;AAED,MAAM,MAAM,yBAAyB,GAAG,UAAU,CAAC,OAAO,+BAA+B,CAAC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@decocms/apps-sfmc-personalization`: a thin client for Salesforce
|
|
3
|
+
* Marketing Cloud Personalization (formerly Evergage). See /next/upstream-clients.
|
|
4
|
+
*
|
|
5
|
+
* Every request goes through `createInstrumentedFetch` with provider
|
|
6
|
+
* `sfmc-personalization`. No retries or circuit breaker. Reading the
|
|
7
|
+
* shopper's cookie, converting products to commerce types and caching belong
|
|
8
|
+
* to the site (platform templates and site code).
|
|
9
|
+
*/
|
|
10
|
+
import { createInstrumentedFetch } from "@decocms/blocks/fetch";
|
|
11
|
+
export class SfmcPersonalizationError extends Error {
|
|
12
|
+
operation;
|
|
13
|
+
status;
|
|
14
|
+
constructor(operation, status) {
|
|
15
|
+
super(`sfmc-personalization ${operation} failed with HTTP ${status}`);
|
|
16
|
+
this.operation = operation;
|
|
17
|
+
this.status = status;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function createSfmcPersonalizationClient(config, options = {}) {
|
|
21
|
+
const request = createInstrumentedFetch({
|
|
22
|
+
provider: "sfmc-personalization",
|
|
23
|
+
fetch: options.fetch,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
/** Sends an interaction and returns the campaigns it triggered. */
|
|
27
|
+
async sendEvent(event) {
|
|
28
|
+
const url = new URL(`/api2/event/${encodeURIComponent(config.dataset)}`, config.baseUrl);
|
|
29
|
+
const response = await request(url, {
|
|
30
|
+
operation: "sendEvent",
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(event),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new SfmcPersonalizationError("sendEvent", response.status);
|
|
37
|
+
return (await response.json());
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAkBhE,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAEtC,SAAS;IACT,MAAM;IAFjB,YACW,SAAiB,EACjB,MAAc;QAEvB,KAAK,CAAC,wBAAwB,SAAS,qBAAqB,MAAM,EAAE,CAAC,CAAC;yBAH7D,SAAS;sBACT,MAAM;IAGjB,CAAC;CACF;AAED,MAAM,UAAU,+BAA+B,CAC7C,MAAiC,EACjC,OAAO,GAAwC,EAAE;IAEjD,MAAM,OAAO,GAAG,uBAAuB,CAAC;QACtC,QAAQ,EAAE,sBAAsB;QAChC,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;IAEH,OAAO;QACL,mEAAmE;QACnE,KAAK,CAAC,SAAS,CAAC,KAA2B;YACzC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,eAAe,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YACzF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;gBAClC,SAAS,EAAE,WAAW;gBACtB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;aAC5B,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,wBAAwB,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnF,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QAC5D,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Salesforce Marketing Cloud Personalization (formerly Evergage): request and
|
|
3
|
+
* response shapes of the Event API (`POST /api2/event/{dataset}`).
|
|
4
|
+
*
|
|
5
|
+
* Product fields are configured per dataset, so `PersonalizationProduct`
|
|
6
|
+
* keeps the common fields and accepts whatever else the dataset exposes.
|
|
7
|
+
*/
|
|
8
|
+
export interface PersonalizationProduct {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
/** List price. */
|
|
12
|
+
price: number;
|
|
13
|
+
/** Promotional price, when one is active. */
|
|
14
|
+
salePrice?: number;
|
|
15
|
+
inventoryCount: number;
|
|
16
|
+
imageUrls: string[];
|
|
17
|
+
/** Absolute product URL. */
|
|
18
|
+
url: string;
|
|
19
|
+
/** ISO 4217 currency code. */
|
|
20
|
+
currency: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
itemType?: string;
|
|
23
|
+
categories?: string[];
|
|
24
|
+
/** Whatever else the dataset's catalog schema defines. */
|
|
25
|
+
[customField: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
/** A cart line, sent with cart-aware interactions. */
|
|
28
|
+
export interface PersonalizationLineItem {
|
|
29
|
+
catalogObjectType: string;
|
|
30
|
+
catalogObjectId: string;
|
|
31
|
+
quantity: number;
|
|
32
|
+
price: number;
|
|
33
|
+
}
|
|
34
|
+
/** The interaction a shopper had; the response holds the campaigns it triggered. */
|
|
35
|
+
export interface PersonalizationEvent {
|
|
36
|
+
source: {
|
|
37
|
+
channel: string;
|
|
38
|
+
url: string;
|
|
39
|
+
};
|
|
40
|
+
interaction: {
|
|
41
|
+
name: string;
|
|
42
|
+
lineItems?: PersonalizationLineItem[];
|
|
43
|
+
};
|
|
44
|
+
user: {
|
|
45
|
+
anonymousId?: string;
|
|
46
|
+
encryptedId?: string;
|
|
47
|
+
attributes?: Record<string, unknown>;
|
|
48
|
+
};
|
|
49
|
+
flags?: {
|
|
50
|
+
nonInteractive?: boolean;
|
|
51
|
+
doNotTrack?: boolean;
|
|
52
|
+
};
|
|
53
|
+
pageView?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface CampaignResponse {
|
|
56
|
+
campaignId: string;
|
|
57
|
+
payload: {
|
|
58
|
+
experience?: string;
|
|
59
|
+
headerText?: string;
|
|
60
|
+
products?: PersonalizationProduct[];
|
|
61
|
+
userGroup?: string;
|
|
62
|
+
[field: string]: unknown;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface PersonalizationResponse {
|
|
66
|
+
campaignResponses?: CampaignResponse[];
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,0DAA0D;IAC1D,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,uBAAuB,EAAE,CAAC;KACvC,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,CAAC;IACF,KAAK,CAAC,EAAE;QACN,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,sBAAsB,EAAE,CAAC;QACpC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Salesforce Marketing Cloud Personalization (formerly Evergage): request and
|
|
3
|
+
* response shapes of the Event API (`POST /api2/event/{dataset}`).
|
|
4
|
+
*
|
|
5
|
+
* Product fields are configured per dataset, so `PersonalizationProduct`
|
|
6
|
+
* keeps the common fields and accepts whatever else the dataset exposes.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-sfmc-personalization",
|
|
3
|
-
"version": "8.1.0-next.
|
|
3
|
+
"version": "8.1.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Thin client for Salesforce Marketing Cloud Personalization (formerly Evergage)",
|
|
6
6
|
"repository": {
|
|
@@ -8,22 +8,29 @@
|
|
|
8
8
|
"url": "https://github.com/decocms/blocks.git",
|
|
9
9
|
"directory": "packages/apps-sfmc-personalization"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"!src/**/__tests__"
|
|
16
|
+
],
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
12
19
|
"exports": {
|
|
13
|
-
".":
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"source": "./src/index.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
14
25
|
},
|
|
15
26
|
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
27
|
+
"build": "node ../../scripts/tsc.mjs --clean dist -p tsconfig.build.json",
|
|
28
|
+
"prepack": "npm run build",
|
|
17
29
|
"test": "vitest run --root ../.. packages/apps-sfmc-personalization/",
|
|
18
|
-
"typecheck": "tsc
|
|
19
|
-
"lint:unused": "knip"
|
|
30
|
+
"typecheck": "node ../../scripts/tsc.mjs -p tsconfig.json"
|
|
20
31
|
},
|
|
21
32
|
"dependencies": {
|
|
22
|
-
"@decocms/blocks": "8.1.0-next.
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"knip": "^5.86.0",
|
|
26
|
-
"typescript": "^5.9.0"
|
|
33
|
+
"@decocms/blocks": "8.1.0-next.2"
|
|
27
34
|
},
|
|
28
35
|
"publishConfig": {
|
|
29
36
|
"registry": "https://registry.npmjs.org",
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Every request goes through `createInstrumentedFetch` with provider
|
|
6
6
|
* `sfmc-personalization`. No retries or circuit breaker. Reading the
|
|
7
7
|
* shopper's cookie, converting products to commerce types and caching belong
|
|
8
|
-
* to the site (platform templates and
|
|
8
|
+
* to the site (platform templates and site code).
|
|
9
9
|
*/
|
|
10
10
|
import { createInstrumentedFetch } from "@decocms/blocks/fetch";
|
|
11
|
-
import type { PersonalizationEvent, PersonalizationResponse } from "./types";
|
|
11
|
+
import type { PersonalizationEvent, PersonalizationResponse } from "./types.ts";
|
|
12
12
|
|
|
13
13
|
export type {
|
|
14
14
|
CampaignResponse,
|
|
@@ -16,7 +16,7 @@ export type {
|
|
|
16
16
|
PersonalizationLineItem,
|
|
17
17
|
PersonalizationProduct,
|
|
18
18
|
PersonalizationResponse,
|
|
19
|
-
} from "./types";
|
|
19
|
+
} from "./types.ts";
|
|
20
20
|
|
|
21
21
|
export interface SfmcPersonalizationConfig {
|
|
22
22
|
/** The account's API origin, e.g. `https://<account>.<instance>.evergage.com`. */
|
|
@@ -36,7 +36,7 @@ export class SfmcPersonalizationError extends Error {
|
|
|
36
36
|
|
|
37
37
|
export function createSfmcPersonalizationClient(
|
|
38
38
|
config: SfmcPersonalizationConfig,
|
|
39
|
-
options: { fetch?: typeof fetch } = {},
|
|
39
|
+
options: { fetch?: typeof globalThis.fetch } = {},
|
|
40
40
|
) {
|
|
41
41
|
const request = createInstrumentedFetch({
|
|
42
42
|
provider: "sfmc-personalization",
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Marketing Cloud Personalization client (/next/upstream-clients): the
|
|
3
|
-
* Event API through the instrumented fetch, errors without bodies.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, expect, it, vi } from "vitest";
|
|
6
|
-
import { createSfmcPersonalizationClient, SfmcPersonalizationError } from "../index";
|
|
7
|
-
|
|
8
|
-
function fakeFetch(status: number, body: unknown) {
|
|
9
|
-
return vi.fn(
|
|
10
|
-
async (_input: string | URL | Request, _init?: RequestInit) =>
|
|
11
|
-
new Response(JSON.stringify(body), { status }),
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const config = { baseUrl: "https://acme.us-1.evergage.com", dataset: "engage" };
|
|
16
|
-
const event = {
|
|
17
|
-
source: { channel: "WebServer", url: "https://shop.example.com/" },
|
|
18
|
-
interaction: { name: "Personalization Campaigns" },
|
|
19
|
-
user: { anonymousId: "anon-1" },
|
|
20
|
-
flags: { nonInteractive: true, doNotTrack: false },
|
|
21
|
-
pageView: false,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
describe("createSfmcPersonalizationClient", () => {
|
|
25
|
-
it("sendEvent posts the event to the dataset and returns the campaigns", async () => {
|
|
26
|
-
const campaigns = { campaignResponses: [{ campaignId: "c1", payload: { products: [] } }] };
|
|
27
|
-
const fetch = fakeFetch(200, campaigns);
|
|
28
|
-
const sfmc = createSfmcPersonalizationClient(config, { fetch });
|
|
29
|
-
|
|
30
|
-
await expect(sfmc.sendEvent(event)).resolves.toEqual(campaigns);
|
|
31
|
-
|
|
32
|
-
const [url, init] = fetch.mock.calls[0];
|
|
33
|
-
expect(String(url)).toBe("https://acme.us-1.evergage.com/api2/event/engage");
|
|
34
|
-
expect(init?.method).toBe("POST");
|
|
35
|
-
expect(JSON.parse(String(init?.body))).toEqual(event);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("throws the operation and status, never the body", async () => {
|
|
39
|
-
const fetch = fakeFetch(500, { error: "anon-1 not found" });
|
|
40
|
-
const sfmc = createSfmcPersonalizationClient(config, { fetch });
|
|
41
|
-
|
|
42
|
-
const error = await sfmc.sendEvent(event).catch((e: unknown) => e);
|
|
43
|
-
|
|
44
|
-
expect(error).toBeInstanceOf(SfmcPersonalizationError);
|
|
45
|
-
expect(error).toMatchObject({ operation: "sendEvent", status: 500 });
|
|
46
|
-
expect((error as Error).message).not.toContain("anon-1");
|
|
47
|
-
});
|
|
48
|
-
});
|