@scopieflows/app-salesforce-marketing-cloud 0.1.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/package.json +16 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +99 -0
- package/src/index.js.map +1 -0
- package/src/lib/actions/get-data-extension-rows.d.ts +7 -0
- package/src/lib/actions/get-data-extension-rows.js +90 -0
- package/src/lib/actions/get-data-extension-rows.js.map +1 -0
- package/src/lib/actions/trigger-journey-event.d.ts +5 -0
- package/src/lib/actions/trigger-journey-event.js +64 -0
- package/src/lib/actions/trigger-journey-event.js.map +1 -0
- package/src/lib/actions/upsert-data-extension-row.d.ts +4 -0
- package/src/lib/actions/upsert-data-extension-row.js +48 -0
- package/src/lib/actions/upsert-data-extension-row.js.map +1 -0
- package/src/lib/common/index.d.ts +9 -0
- package/src/lib/common/index.js +47 -0
- package/src/lib/common/index.js.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scopieflows/app-salesforce-marketing-cloud",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./src/index.js",
|
|
5
|
+
"types": "./src/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.lib.json && cp package.json dist/",
|
|
8
|
+
"lint": "eslint 'src/**/*.ts'"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@scopieflows/pieces-common": "0.12.0",
|
|
12
|
+
"@scopieflows/pieces-framework": "0.26.0",
|
|
13
|
+
"@scopieflows/shared": "0.50.0",
|
|
14
|
+
"tslib": "2.6.2"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const sfmcAuth: import("@scopieflows/pieces-framework").OAuth2Property<import("@scopieflows/pieces-framework").OAuth2Props>;
|
|
2
|
+
export declare const salesforceMarketingCloud: import("@scopieflows/pieces-framework").Piece<import("@scopieflows/pieces-framework").OAuth2Property<import("@scopieflows/pieces-framework").OAuth2Props>>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.salesforceMarketingCloud = exports.sfmcAuth = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@scopieflows/pieces-framework");
|
|
6
|
+
const shared_1 = require("@scopieflows/shared");
|
|
7
|
+
const pieces_common_1 = require("@scopieflows/pieces-common");
|
|
8
|
+
const upsert_data_extension_row_1 = require("./lib/actions/upsert-data-extension-row");
|
|
9
|
+
const get_data_extension_rows_1 = require("./lib/actions/get-data-extension-rows");
|
|
10
|
+
const trigger_journey_event_1 = require("./lib/actions/trigger-journey-event");
|
|
11
|
+
exports.sfmcAuth = pieces_framework_1.PieceAuth.OAuth2({
|
|
12
|
+
description: `Connect your Salesforce Marketing Cloud account.
|
|
13
|
+
|
|
14
|
+
To set this up you need a **Connected App** in SFMC:
|
|
15
|
+
|
|
16
|
+
1. In SFMC go to **Setup > Apps > Installed Packages**
|
|
17
|
+
2. Click **New** and create a package
|
|
18
|
+
3. Click **Add Component > API Integration > Web App**
|
|
19
|
+
4. Set the redirect URI to the one shown below
|
|
20
|
+
5. Select the scopes your actions need (at minimum: **Data Extensions Read/Write**, **Journeys Read/Execute**)
|
|
21
|
+
6. Copy the **Client ID**, **Client Secret**, and **Auth Base URI** from the package details`,
|
|
22
|
+
required: true,
|
|
23
|
+
props: {
|
|
24
|
+
authBaseUri: pieces_framework_1.Property.ShortText({
|
|
25
|
+
displayName: 'Auth Base URI',
|
|
26
|
+
description: 'Your tenant-specific authentication subdomain. Find it in SFMC under Setup > Apps > Installed Packages — it looks like "mc123abc.auth.marketingcloudapis.com".',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
authUrl: 'https://{authBaseUri}/v2/authorize',
|
|
31
|
+
tokenUrl: 'https://{authBaseUri}/v2/token',
|
|
32
|
+
scope: ['offline'],
|
|
33
|
+
validate: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
34
|
+
var _b;
|
|
35
|
+
try {
|
|
36
|
+
const authValue = auth;
|
|
37
|
+
const restInstanceUrl = (_b = authValue.data) === null || _b === void 0 ? void 0 : _b['rest_instance_url'];
|
|
38
|
+
if (!restInstanceUrl) {
|
|
39
|
+
return {
|
|
40
|
+
valid: false,
|
|
41
|
+
error: 'Missing rest_instance_url in token response. Ensure your connected app has the required API scopes.',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
yield pieces_common_1.httpClient.sendRequest({
|
|
45
|
+
method: pieces_common_1.HttpMethod.GET,
|
|
46
|
+
url: `${restInstanceUrl.replace(/\/$/, '')}/platform/v1/endpoints`,
|
|
47
|
+
authentication: {
|
|
48
|
+
type: pieces_common_1.AuthenticationType.BEARER_TOKEN,
|
|
49
|
+
token: authValue.access_token,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return { valid: true };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (message.includes('ENOTFOUND') || message.includes('getaddrinfo')) {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
error: 'Could not reach SFMC. Check your Auth Base URI.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
error: `Connection test failed: ${message}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
exports.salesforceMarketingCloud = (0, pieces_framework_1.createPiece)({
|
|
70
|
+
displayName: 'Salesforce Marketing Cloud',
|
|
71
|
+
description: 'Digital marketing automation and analytics platform for email, mobile, social, and web marketing.',
|
|
72
|
+
minimumSupportedRelease: '0.36.1',
|
|
73
|
+
logoUrl: '/pieces/salesforce-marketing-cloud.png',
|
|
74
|
+
categories: [shared_1.PieceCategory.MARKETING],
|
|
75
|
+
auth: exports.sfmcAuth,
|
|
76
|
+
authors: [],
|
|
77
|
+
actions: [
|
|
78
|
+
upsert_data_extension_row_1.upsertDataExtensionRow,
|
|
79
|
+
get_data_extension_rows_1.getDataExtensionRows,
|
|
80
|
+
trigger_journey_event_1.triggerJourneyEvent,
|
|
81
|
+
(0, pieces_common_1.createCustomApiCallAction)({
|
|
82
|
+
baseUrl: (auth) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const restInstanceUrl = (_a = auth.data) === null || _a === void 0 ? void 0 : _a['rest_instance_url'];
|
|
85
|
+
return restInstanceUrl
|
|
86
|
+
? restInstanceUrl.replace(/\/$/, '')
|
|
87
|
+
: '';
|
|
88
|
+
},
|
|
89
|
+
auth: exports.sfmcAuth,
|
|
90
|
+
authMapping: (auth) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
return ({
|
|
92
|
+
Authorization: `Bearer ${auth.access_token}`,
|
|
93
|
+
});
|
|
94
|
+
}),
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
triggers: [],
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const getDataExtensionRows: import("@scopieflows/pieces-framework").IAction<import("@scopieflows/pieces-framework").OAuth2Property<import("@scopieflows/pieces-framework").OAuth2Props>, {
|
|
2
|
+
externalKey: import("@scopieflows/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
top: import("@scopieflows/pieces-framework").NumberProperty<false>;
|
|
4
|
+
skip: import("@scopieflows/pieces-framework").NumberProperty<false>;
|
|
5
|
+
orderBy: import("@scopieflows/pieces-framework").ShortTextProperty<false>;
|
|
6
|
+
filter: import("@scopieflows/pieces-framework").ShortTextProperty<false>;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDataExtensionRows = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@scopieflows/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@scopieflows/pieces-common");
|
|
7
|
+
const __1 = require("../../");
|
|
8
|
+
const common_1 = require("../common");
|
|
9
|
+
exports.getDataExtensionRows = (0, pieces_framework_1.createAction)({
|
|
10
|
+
auth: __1.sfmcAuth,
|
|
11
|
+
name: 'get_data_extension_rows',
|
|
12
|
+
displayName: 'Get Data Extension Rows',
|
|
13
|
+
description: 'Retrieves rows from a Data Extension. Returns up to the specified number of rows.',
|
|
14
|
+
props: {
|
|
15
|
+
externalKey: pieces_framework_1.Property.ShortText({
|
|
16
|
+
displayName: 'Data Extension External Key',
|
|
17
|
+
description: 'The external key of the Data Extension. Find it in SFMC under Email Studio > Subscribers > Data Extensions — click the Data Extension and copy the "External Key" value.',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
top: pieces_framework_1.Property.Number({
|
|
21
|
+
displayName: 'Max Rows',
|
|
22
|
+
description: 'Maximum number of rows to return.',
|
|
23
|
+
required: false,
|
|
24
|
+
defaultValue: 50,
|
|
25
|
+
}),
|
|
26
|
+
skip: pieces_framework_1.Property.Number({
|
|
27
|
+
displayName: 'Skip Rows',
|
|
28
|
+
description: 'Number of rows to skip (for pagination).',
|
|
29
|
+
required: false,
|
|
30
|
+
defaultValue: 0,
|
|
31
|
+
}),
|
|
32
|
+
orderBy: pieces_framework_1.Property.ShortText({
|
|
33
|
+
displayName: 'Order By',
|
|
34
|
+
description: 'Column name to sort results by (e.g. "EmailAddress asc" or "CreatedDate desc"). Leave empty for default order.',
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
filter: pieces_framework_1.Property.ShortText({
|
|
38
|
+
displayName: 'Filter',
|
|
39
|
+
description: 'Simple filter expression (e.g. "Status eq \'Active\'"). Leave empty to return all rows. See SFMC REST API docs for filter syntax.',
|
|
40
|
+
required: false,
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
run(context) {
|
|
44
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
var _a;
|
|
46
|
+
const { externalKey, top, skip, orderBy, filter } = context.propsValue;
|
|
47
|
+
const auth = context.auth;
|
|
48
|
+
const queryParams = {};
|
|
49
|
+
if (top !== undefined && top !== null) {
|
|
50
|
+
queryParams['$top'] = String(top);
|
|
51
|
+
}
|
|
52
|
+
if (skip !== undefined && skip !== null && skip > 0) {
|
|
53
|
+
queryParams['$skip'] = String(skip);
|
|
54
|
+
}
|
|
55
|
+
if (orderBy) {
|
|
56
|
+
queryParams['$orderBy'] = orderBy;
|
|
57
|
+
}
|
|
58
|
+
if (filter) {
|
|
59
|
+
queryParams['$filter'] = filter;
|
|
60
|
+
}
|
|
61
|
+
const response = yield (0, common_1.sfmcApiCall)({
|
|
62
|
+
auth,
|
|
63
|
+
method: pieces_common_1.HttpMethod.GET,
|
|
64
|
+
path: `/data/v1/customobjectdata/key/${encodeURIComponent(externalKey)}/rowset`,
|
|
65
|
+
queryParams,
|
|
66
|
+
});
|
|
67
|
+
const items = (_a = response.body.items) !== null && _a !== void 0 ? _a : [];
|
|
68
|
+
return items.map((item) => {
|
|
69
|
+
const flat = {};
|
|
70
|
+
for (const [key, value] of Object.entries(item)) {
|
|
71
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
72
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
73
|
+
flat[`${key}_${nestedKey}`] = nestedValue !== null && nestedValue !== void 0 ? nestedValue : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (Array.isArray(value)) {
|
|
77
|
+
flat[key] = value
|
|
78
|
+
.map((v) => (typeof v === 'object' ? JSON.stringify(v) : String(v)))
|
|
79
|
+
.join(', ');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
flat[key] = value !== null && value !== void 0 ? value : null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return flat;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=get-data-extension-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-data-extension-rows.js","sourceRoot":"","sources":["../../../../src/lib/actions/get-data-extension-rows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const triggerJourneyEvent: import("@scopieflows/pieces-framework").IAction<import("@scopieflows/pieces-framework").OAuth2Property<import("@scopieflows/pieces-framework").OAuth2Props>, {
|
|
2
|
+
eventDefinitionKey: import("@scopieflows/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
contactKey: import("@scopieflows/pieces-framework").ShortTextProperty<true>;
|
|
4
|
+
data: import("@scopieflows/pieces-framework").ObjectProperty<false>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.triggerJourneyEvent = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@scopieflows/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@scopieflows/pieces-common");
|
|
7
|
+
const __1 = require("../../");
|
|
8
|
+
const common_1 = require("../common");
|
|
9
|
+
exports.triggerJourneyEvent = (0, pieces_framework_1.createAction)({
|
|
10
|
+
auth: __1.sfmcAuth,
|
|
11
|
+
name: 'trigger_journey_event',
|
|
12
|
+
displayName: 'Trigger Journey Event',
|
|
13
|
+
description: 'Fires an entry event to inject a contact into a Journey Builder journey.',
|
|
14
|
+
props: {
|
|
15
|
+
eventDefinitionKey: pieces_framework_1.Property.ShortText({
|
|
16
|
+
displayName: 'Event Definition Key',
|
|
17
|
+
description: 'The unique key of the journey entry event. Find it in Journey Builder: open the journey, click the entry event, and copy the "Event Definition Key".',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
contactKey: pieces_framework_1.Property.ShortText({
|
|
21
|
+
displayName: 'Contact Key',
|
|
22
|
+
description: 'The unique identifier for the contact entering the journey (usually the Subscriber Key or email address).',
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
data: pieces_framework_1.Property.Object({
|
|
26
|
+
displayName: 'Event Data',
|
|
27
|
+
description: 'Key-value pairs to pass as event data to the journey (e.g. {"EmailAddress": "john@example.com", "FirstName": "John"}).',
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
run(context) {
|
|
32
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const { eventDefinitionKey, contactKey, data } = context.propsValue;
|
|
34
|
+
const auth = context.auth;
|
|
35
|
+
const payload = {
|
|
36
|
+
ContactKey: contactKey,
|
|
37
|
+
EventDefinitionKey: eventDefinitionKey,
|
|
38
|
+
};
|
|
39
|
+
if (data && Object.keys(data).length > 0) {
|
|
40
|
+
payload['Data'] = data;
|
|
41
|
+
}
|
|
42
|
+
const response = yield (0, common_1.sfmcApiCall)({
|
|
43
|
+
auth,
|
|
44
|
+
method: pieces_common_1.HttpMethod.POST,
|
|
45
|
+
path: '/interaction/v1/events',
|
|
46
|
+
body: payload,
|
|
47
|
+
});
|
|
48
|
+
const body = response.body;
|
|
49
|
+
const flat = {};
|
|
50
|
+
for (const [key, value] of Object.entries(body)) {
|
|
51
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
52
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
53
|
+
flat[`${key}_${nestedKey}`] = nestedValue !== null && nestedValue !== void 0 ? nestedValue : null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
flat[key] = value !== null && value !== void 0 ? value : null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return flat;
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=trigger-journey-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-journey-event.js","sourceRoot":"","sources":["../../../../src/lib/actions/trigger-journey-event.ts"],"names":[],"mappings":";;;;AAAA,oEAAuE;AACvE,8DAAwD;AACxD,8BAAkC;AAClC,sCAAwC;AAG3B,QAAA,mBAAmB,GAAG,IAAA,+BAAY,EAAC;IAC/C,IAAI,EAAE,YAAQ;IACd,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,uBAAuB;IACpC,WAAW,EACV,0EAA0E;IAC3E,KAAK,EAAE;QACN,kBAAkB,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACtC,WAAW,EAAE,sBAAsB;YACnC,WAAW,EACV,sJAAsJ;YACvJ,QAAQ,EAAE,IAAI;SACd,CAAC;QACF,UAAU,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC9B,WAAW,EAAE,aAAa;YAC1B,WAAW,EACV,2GAA2G;YAC5G,QAAQ,EAAE,IAAI;SACd,CAAC;QACF,IAAI,EAAE,2BAAQ,CAAC,MAAM,CAAC;YACrB,WAAW,EAAE,YAAY;YACzB,WAAW,EACV,wHAAwH;YACzH,QAAQ,EAAE,KAAK;SACf,CAAC;KACF;IACK,GAAG,CAAC,OAAO;;YAChB,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;YACpE,MAAM,IAAI,GAAG,OAAO,CAAC,IAA2B,CAAC;YAEjD,MAAM,OAAO,GAA4B;gBACxC,UAAU,EAAE,UAAU;gBACtB,kBAAkB,EAAE,kBAAkB;aACtC,CAAC;YACF,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;YACxB,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAW,EAA0B;gBAC3D,IAAI;gBACJ,MAAM,EAAE,0BAAU,CAAC,IAAI;gBACvB,IAAI,EAAE,wBAAwB;gBAC9B,IAAI,EAAE,OAAO;aACb,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC3B,MAAM,IAAI,GAA4B,EAAE,CAAC;YACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1E,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CACpD,KAAgC,CAChC,EAAE,CAAC;wBACH,IAAI,CAAC,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC,GAAG,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,IAAI,CAAC;oBACnD,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,IAAI,CAAC;gBAC3B,CAAC;YACF,CAAC;YACD,OAAO,IAAI,CAAC;QACb,CAAC;KAAA;CACD,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const upsertDataExtensionRow: import("@scopieflows/pieces-framework").IAction<import("@scopieflows/pieces-framework").OAuth2Property<import("@scopieflows/pieces-framework").OAuth2Props>, {
|
|
2
|
+
externalKey: import("@scopieflows/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
rows: import("@scopieflows/pieces-framework").JsonProperty<true>;
|
|
4
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertDataExtensionRow = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@scopieflows/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@scopieflows/pieces-common");
|
|
7
|
+
const __1 = require("../../");
|
|
8
|
+
const common_1 = require("../common");
|
|
9
|
+
exports.upsertDataExtensionRow = (0, pieces_framework_1.createAction)({
|
|
10
|
+
auth: __1.sfmcAuth,
|
|
11
|
+
name: 'upsert_data_extension_row',
|
|
12
|
+
displayName: 'Create or Update Data Extension Row',
|
|
13
|
+
description: 'Inserts or updates one or more rows in a Data Extension. If a row with the same primary key exists, it will be updated; otherwise a new row is created.',
|
|
14
|
+
props: {
|
|
15
|
+
externalKey: pieces_framework_1.Property.ShortText({
|
|
16
|
+
displayName: 'Data Extension External Key',
|
|
17
|
+
description: 'The external key of the Data Extension. Find it in SFMC under Email Studio > Subscribers > Data Extensions — click the Data Extension and copy the "External Key" value.',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
rows: pieces_framework_1.Property.Json({
|
|
21
|
+
displayName: 'Row Data (JSON)',
|
|
22
|
+
description: 'A JSON array of objects to upsert. Each object should have a "keys" property with column name/value pairs.\n\nExample:\n```json\n[{"keys": {"EmailAddress": "john@example.com", "FirstName": "John"}}]\n```\n\nFor a single row you can also pass just the keys object:\n```json\n{"EmailAddress": "john@example.com", "FirstName": "John"}\n```',
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
run(context) {
|
|
27
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const { externalKey, rows } = context.propsValue;
|
|
29
|
+
const auth = context.auth;
|
|
30
|
+
const parsed = typeof rows === 'string' ? JSON.parse(rows) : rows;
|
|
31
|
+
let payload;
|
|
32
|
+
if (Array.isArray(parsed)) {
|
|
33
|
+
payload = parsed.map((item) => item['keys'] ? item : { keys: item });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
payload = [{ keys: parsed }];
|
|
37
|
+
}
|
|
38
|
+
const response = yield (0, common_1.sfmcApiCall)({
|
|
39
|
+
auth,
|
|
40
|
+
method: pieces_common_1.HttpMethod.POST,
|
|
41
|
+
path: `/hub/v1/dataevents/key:${encodeURIComponent(externalKey)}/rowset`,
|
|
42
|
+
body: payload,
|
|
43
|
+
});
|
|
44
|
+
return response.body;
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=upsert-data-extension-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upsert-data-extension-row.js","sourceRoot":"","sources":["../../../../src/lib/actions/upsert-data-extension-row.ts"],"names":[],"mappings":";;;;AAAA,oEAAuE;AACvE,8DAAwD;AACxD,8BAAkC;AAClC,sCAAwC;AAG3B,QAAA,sBAAsB,GAAG,IAAA,+BAAY,EAAC;IAClD,IAAI,EAAE,YAAQ;IACd,IAAI,EAAE,2BAA2B;IACjC,WAAW,EAAE,qCAAqC;IAClD,WAAW,EACV,yJAAyJ;IAC1J,KAAK,EAAE;QACN,WAAW,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC/B,WAAW,EAAE,6BAA6B;YAC1C,WAAW,EACV,0KAA0K;YAC3K,QAAQ,EAAE,IAAI;SACd,CAAC;QACF,IAAI,EAAE,2BAAQ,CAAC,IAAI,CAAC;YACnB,WAAW,EAAE,iBAAiB;YAC9B,WAAW,EACV,kVAAkV;YACnV,QAAQ,EAAE,IAAI;SACd,CAAC;KACF;IACK,GAAG,CAAC,OAAO;;YAChB,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;YACjD,MAAM,IAAI,GAAG,OAAO,CAAC,IAA2B,CAAC;YAEjD,MAAM,MAAM,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAElE,IAAI,OAAkC,CAAC;YACvC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAA6B,EAAE,EAAE,CACtD,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CACpC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACP,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9B,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAW,EAA0B;gBAC3D,IAAI;gBACJ,MAAM,EAAE,0BAAU,CAAC,IAAI;gBACvB,IAAI,EAAE,0BAA0B,kBAAkB,CAAC,WAAW,CAAC,SAAS;gBACxE,IAAI,EAAE,OAAO;aACb,CAAC,CAAC;YAEH,OAAO,QAAQ,CAAC,IAAI,CAAC;QACtB,CAAC;KAAA;CACD,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HttpMethod, HttpMessageBody, HttpResponse } from '@scopieflows/pieces-common';
|
|
2
|
+
import { OAuth2PropertyValue } from '@scopieflows/pieces-framework';
|
|
3
|
+
export declare function sfmcApiCall<T extends HttpMessageBody>({ auth, method, path, body, queryParams, }: {
|
|
4
|
+
auth: OAuth2PropertyValue;
|
|
5
|
+
method: HttpMethod;
|
|
6
|
+
path: string;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
queryParams?: Record<string, string>;
|
|
9
|
+
}): Promise<HttpResponse<T>>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sfmcApiCall = sfmcApiCall;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_common_1 = require("@scopieflows/pieces-common");
|
|
6
|
+
function getRestInstanceUrl(auth) {
|
|
7
|
+
var _a;
|
|
8
|
+
const restInstanceUrl = (_a = auth.data) === null || _a === void 0 ? void 0 : _a['rest_instance_url'];
|
|
9
|
+
if (!restInstanceUrl) {
|
|
10
|
+
throw new Error('Missing rest_instance_url in OAuth response. This usually means the SFMC connected app does not have the correct API permissions enabled.');
|
|
11
|
+
}
|
|
12
|
+
return restInstanceUrl.replace(/\/$/, '');
|
|
13
|
+
}
|
|
14
|
+
function mapSfmcError(error) {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
if (message.includes('ENOTFOUND') || message.includes('getaddrinfo')) {
|
|
17
|
+
throw new Error('Could not reach the SFMC auth endpoint. Check your Auth Base URI — it should look like "mc123abc.auth.marketingcloudapis.com".');
|
|
18
|
+
}
|
|
19
|
+
if (message.includes('invalid_client') || message.includes('Unauthorized')) {
|
|
20
|
+
throw new Error('Authentication failed. Check your Client ID, Client Secret, and that the connected app has the required package scopes.');
|
|
21
|
+
}
|
|
22
|
+
if (message.includes('redirect_uri')) {
|
|
23
|
+
throw new Error('Redirect URI mismatch. The redirect URI configured in your SFMC connected app must exactly match the one used by ScopieFlows (no trailing slash differences).');
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`SFMC API error: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
function sfmcApiCall(_a) {
|
|
28
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, method, path, body, queryParams, }) {
|
|
29
|
+
const baseUrl = getRestInstanceUrl(auth);
|
|
30
|
+
try {
|
|
31
|
+
return yield pieces_common_1.httpClient.sendRequest({
|
|
32
|
+
method,
|
|
33
|
+
url: `${baseUrl}${path}`,
|
|
34
|
+
authentication: {
|
|
35
|
+
type: pieces_common_1.AuthenticationType.BEARER_TOKEN,
|
|
36
|
+
token: auth.access_token,
|
|
37
|
+
},
|
|
38
|
+
queryParams,
|
|
39
|
+
body,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
mapSfmcError(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/common/index.ts"],"names":[],"mappings":";;AAyCA,kCA4BC;;AArED,8DAMoC;AAGpC,SAAS,kBAAkB,CAAC,IAAyB;;IACpD,MAAM,eAAe,GAAG,MAAA,IAAI,CAAC,IAAI,0CAAG,mBAAmB,CAAuB,CAAC;IAC/E,IAAI,CAAC,eAAe,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACd,2IAA2I,CAC3I,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IACnC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEvE,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACd,gIAAgI,CAChI,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CACd,yHAAyH,CACzH,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACd,+JAA+J,CAC/J,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,SAAsB,WAAW;iEAA4B,EAC5D,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,WAAW,GAOX;QACA,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC;YACJ,OAAO,MAAM,0BAAU,CAAC,WAAW,CAAI;gBACtC,MAAM;gBACN,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,EAAE;gBACxB,cAAc,EAAE;oBACf,IAAI,EAAE,kCAAkB,CAAC,YAAY;oBACrC,KAAK,EAAE,IAAI,CAAC,YAAY;iBACxB;gBACD,WAAW;gBACX,IAAI;aACJ,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;CAAA"}
|