zapier-platform-core 17.6.0 → 17.7.1
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 +2 -2
- package/src/checks/index.js +1 -1
- package/src/checks/search-is-array-or-envelope.js +60 -0
- package/src/http-middlewares/after/prepare-response.js +32 -7
- package/src/tools/create-logger.js +3 -96
- package/src/tools/secret-scrubber.js +98 -0
- package/types/functions.d.ts +11 -3
- package/types/schemas.generated.d.ts +61 -7
- package/src/checks/search-is-array.js +0 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zapier-platform-core",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.7.1",
|
|
4
4
|
"description": "The core SDK for CLI apps in the Zapier Developer Platform.",
|
|
5
5
|
"repository": "zapier/zapier-platform",
|
|
6
6
|
"homepage": "https://platform.zapier.com/",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"node-fetch": "2.7.0",
|
|
64
64
|
"oauth-sign": "0.9.0",
|
|
65
65
|
"semver": "7.7.1",
|
|
66
|
-
"zapier-platform-schema": "17.
|
|
66
|
+
"zapier-platform-schema": "17.7.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node-fetch": "^2.6.11",
|
package/src/checks/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
createIsObject: require('./create-is-object'),
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
searchIsArrayOrEnvelope: require('./search-is-array-or-envelope'),
|
|
5
5
|
|
|
6
6
|
triggerIsArray: require('./trigger-is-array'),
|
|
7
7
|
triggerIsObject: require('./trigger-is-object'),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { simpleTruncate } = require('../tools/data');
|
|
5
|
+
|
|
6
|
+
const isSearch = require('./is-search');
|
|
7
|
+
|
|
8
|
+
const hasCanPaginate = (searchKey, compiledApp) => {
|
|
9
|
+
const canPaginate =
|
|
10
|
+
compiledApp?.searches?.[searchKey]?.operation?.canPaginate;
|
|
11
|
+
return canPaginate;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Searches should return an array of objects,
|
|
16
|
+
or a response envelope like { results: [...], paging_token: '...' }
|
|
17
|
+
when canPaginate is true.
|
|
18
|
+
*/
|
|
19
|
+
const searchIsArrayOrEnvelope = {
|
|
20
|
+
name: 'searchIsArrayOrEnvelope',
|
|
21
|
+
shouldRun: isSearch,
|
|
22
|
+
run: (method, results, compiledApp) => {
|
|
23
|
+
const searchKey = method.split('.', 2)[1];
|
|
24
|
+
const truncatedResults = simpleTruncate(JSON.stringify(results), 50);
|
|
25
|
+
|
|
26
|
+
if (hasCanPaginate(searchKey, compiledApp)) {
|
|
27
|
+
// if paging is supported and results is an object (indicating pagination), it must have results and paging_token
|
|
28
|
+
if (_.isPlainObject(results)) {
|
|
29
|
+
if (!_.has(results, 'results') || !_.has(results, 'paging_token')) {
|
|
30
|
+
return [
|
|
31
|
+
`Paginated search results must be an object containing results and paging_token, got: ${truncatedResults}`,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
!_.isString(results.paging_token) &&
|
|
36
|
+
!_.isNull(results.paging_token)
|
|
37
|
+
) {
|
|
38
|
+
return [
|
|
39
|
+
`"paging_token" must be a string or null, got: ${typeof results.paging_token}`,
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
// pass to array check below
|
|
43
|
+
results = results.results;
|
|
44
|
+
} else {
|
|
45
|
+
return [
|
|
46
|
+
`Paginated search results must be an object, got: ${typeof results}, (${truncatedResults})`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!_.isArray(results)) {
|
|
52
|
+
return [
|
|
53
|
+
`Search results must be an array, got: ${typeof results}, (${truncatedResults})`,
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
module.exports = searchIsArrayOrEnvelope;
|
|
@@ -2,19 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const querystring = require('querystring');
|
|
5
|
+
|
|
6
|
+
const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
|
|
7
|
+
|
|
5
8
|
const { replaceHeaders } = require('./middleware-utils');
|
|
6
9
|
const { FORM_TYPE } = require('../../tools/http');
|
|
7
10
|
const errors = require('../../errors');
|
|
11
|
+
const {
|
|
12
|
+
findSensitiveValuesFromAuthData,
|
|
13
|
+
} = require('../../tools/secret-scrubber');
|
|
8
14
|
|
|
9
|
-
const
|
|
15
|
+
const buildSensitiveValues = (bundle) => {
|
|
16
|
+
const authData = bundle?.authData || {};
|
|
17
|
+
const result = [
|
|
18
|
+
...findSensitiveValuesFromAuthData(authData),
|
|
19
|
+
...findSensitiveValues(process.env),
|
|
20
|
+
];
|
|
21
|
+
return [...new Set(result)];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const _throwForStatus = (response, bundle) => {
|
|
10
25
|
// calling this always throws, regardless of the skipThrowForStatus value
|
|
11
26
|
// eslint-disable-next-line yoda
|
|
12
27
|
if (400 <= response.status && response.status < 600) {
|
|
28
|
+
// Create a cleaned version of the response to avoid sensitive data leaks
|
|
29
|
+
try {
|
|
30
|
+
// Find sensitive values from environment variables and bundle authData
|
|
31
|
+
const sensitiveValues = buildSensitiveValues(bundle);
|
|
32
|
+
if (sensitiveValues.length > 0 && response?.request?.url) {
|
|
33
|
+
response.request.url = scrub(response.request.url, sensitiveValues);
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// don't fail the whole request if we can't scrub for some reason
|
|
37
|
+
}
|
|
13
38
|
throw new errors.ResponseError(response);
|
|
14
39
|
}
|
|
15
40
|
};
|
|
16
41
|
|
|
17
|
-
const prepareRawResponse = (resp, request) => {
|
|
42
|
+
const prepareRawResponse = (resp, request, bundle) => {
|
|
18
43
|
// TODO: if !2xx should we go ahead and get response.content for them?
|
|
19
44
|
// retain the response signature for raw control
|
|
20
45
|
const extendedResp = {
|
|
@@ -24,7 +49,7 @@ const prepareRawResponse = (resp, request) => {
|
|
|
24
49
|
const outResp = _.extend(resp, extendedResp, replaceHeaders(resp));
|
|
25
50
|
|
|
26
51
|
outResp.throwForStatus = () => {
|
|
27
|
-
_throwForStatus(outResp);
|
|
52
|
+
_throwForStatus(outResp, bundle);
|
|
28
53
|
};
|
|
29
54
|
|
|
30
55
|
Object.defineProperty(outResp, 'content', {
|
|
@@ -39,7 +64,7 @@ const prepareRawResponse = (resp, request) => {
|
|
|
39
64
|
return outResp;
|
|
40
65
|
};
|
|
41
66
|
|
|
42
|
-
const prepareContentResponse = async (resp, request) => {
|
|
67
|
+
const prepareContentResponse = async (resp, request, bundle) => {
|
|
43
68
|
// TODO: does it make sense to not trim the signature? more equivalence to raw...
|
|
44
69
|
const content = await resp.text();
|
|
45
70
|
|
|
@@ -67,14 +92,14 @@ const prepareContentResponse = async (resp, request) => {
|
|
|
67
92
|
} catch (_e) {}
|
|
68
93
|
|
|
69
94
|
outResp.throwForStatus = () => {
|
|
70
|
-
_throwForStatus(outResp);
|
|
95
|
+
_throwForStatus(outResp, bundle);
|
|
71
96
|
};
|
|
72
97
|
|
|
73
98
|
return outResp;
|
|
74
99
|
};
|
|
75
100
|
|
|
76
101
|
// Provide a standardized plain JS responseObj for common consumption, or raw response for streaming.
|
|
77
|
-
const prepareResponse = (resp) => {
|
|
102
|
+
const prepareResponse = (resp, z, bundle) => {
|
|
78
103
|
const request = resp.input;
|
|
79
104
|
delete resp.input;
|
|
80
105
|
|
|
@@ -82,7 +107,7 @@ const prepareResponse = (resp) => {
|
|
|
82
107
|
? prepareRawResponse
|
|
83
108
|
: prepareContentResponse;
|
|
84
109
|
|
|
85
|
-
return responseFunc(resp, request);
|
|
110
|
+
return responseFunc(resp, request, bundle);
|
|
86
111
|
};
|
|
87
112
|
|
|
88
113
|
module.exports = prepareResponse;
|
|
@@ -15,16 +15,8 @@ const {
|
|
|
15
15
|
SAFE_LOG_KEYS,
|
|
16
16
|
} = require('../constants');
|
|
17
17
|
const { unheader } = require('./http');
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
findSensitiveValues,
|
|
21
|
-
recurseExtract,
|
|
22
|
-
} = require('@zapier/secret-scrubber');
|
|
23
|
-
// not really a public function, but it came from here originally
|
|
24
|
-
const {
|
|
25
|
-
isUrlWithSecrets,
|
|
26
|
-
isSensitiveKey,
|
|
27
|
-
} = require('@zapier/secret-scrubber/lib/convenience');
|
|
18
|
+
const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
|
|
19
|
+
const { findSensitiveValuesFromAuthData } = require('./secret-scrubber');
|
|
28
20
|
|
|
29
21
|
// The payload size per request to stream logs. This should be slighly lower
|
|
30
22
|
// than the limit (16 MB) on the server side.
|
|
@@ -32,70 +24,8 @@ const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
|
|
|
32
24
|
|
|
33
25
|
const DEFAULT_LOGGER_TIMEOUT = 200;
|
|
34
26
|
|
|
35
|
-
// Will be initialized lazily
|
|
36
|
-
const SAFE_AUTH_DATA_KEYS = new Set();
|
|
37
|
-
|
|
38
27
|
const sleep = promisify(setTimeout);
|
|
39
28
|
|
|
40
|
-
const initSafeAuthDataKeys = () => {
|
|
41
|
-
// An authData key in (safePrefixes x safeSuffixes) is considered safe to log
|
|
42
|
-
// uncensored
|
|
43
|
-
const safePrefixes = [
|
|
44
|
-
'account',
|
|
45
|
-
'bot_user',
|
|
46
|
-
'cloud',
|
|
47
|
-
'cloud_site',
|
|
48
|
-
'company',
|
|
49
|
-
'domain',
|
|
50
|
-
'email',
|
|
51
|
-
'environment',
|
|
52
|
-
'location',
|
|
53
|
-
'org',
|
|
54
|
-
'organization',
|
|
55
|
-
'project',
|
|
56
|
-
'region',
|
|
57
|
-
'scope',
|
|
58
|
-
'scopes',
|
|
59
|
-
'site',
|
|
60
|
-
'subdomain',
|
|
61
|
-
'team',
|
|
62
|
-
'token_type',
|
|
63
|
-
'user',
|
|
64
|
-
'workspace',
|
|
65
|
-
];
|
|
66
|
-
const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
|
|
67
|
-
for (const prefix of safePrefixes) {
|
|
68
|
-
for (const suffix of safeSuffixes) {
|
|
69
|
-
SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const isUrl = (value) => {
|
|
75
|
-
if (!value || typeof value !== 'string') {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const commonProtocols = [
|
|
79
|
-
'https://',
|
|
80
|
-
'http://',
|
|
81
|
-
'ftp://',
|
|
82
|
-
'ftps://',
|
|
83
|
-
'file://',
|
|
84
|
-
];
|
|
85
|
-
for (const protocol of commonProtocols) {
|
|
86
|
-
if (value.startsWith(protocol)) {
|
|
87
|
-
try {
|
|
88
|
-
// eslint-disable-next-line no-new
|
|
89
|
-
new URL(value);
|
|
90
|
-
return true;
|
|
91
|
-
} catch (e) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
29
|
const MAX_LENGTH = 3500;
|
|
100
30
|
const truncateString = (str) => simpleTruncate(str, MAX_LENGTH, ' [...]');
|
|
101
31
|
|
|
@@ -188,35 +118,12 @@ const attemptFindSecretsInStr = (s, isGettingNewSecret) => {
|
|
|
188
118
|
return findSensitiveValues(parsedRespContent);
|
|
189
119
|
};
|
|
190
120
|
|
|
191
|
-
const isSafeAuthDataKey = (key) => {
|
|
192
|
-
if (SAFE_AUTH_DATA_KEYS.size === 0) {
|
|
193
|
-
initSafeAuthDataKeys();
|
|
194
|
-
}
|
|
195
|
-
return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
|
|
196
|
-
};
|
|
197
|
-
|
|
198
121
|
const buildSensitiveValues = (event, data) => {
|
|
199
122
|
const bundle = event.bundle || {};
|
|
200
123
|
const authData = bundle.authData || {};
|
|
201
|
-
// for the most part, we should censor all the values from authData
|
|
202
|
-
// the exception is safe urls, which should be filtered out - we want those to be logged
|
|
203
|
-
// but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
|
|
204
|
-
// this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
|
|
205
|
-
const sensitiveAuthData = recurseExtract(authData, (key, value) => {
|
|
206
|
-
if (isSensitiveKey(key)) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
if (isSafeAuthDataKey(key)) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
if (isUrl(value) && !isUrlWithSecrets(value)) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
return true;
|
|
216
|
-
});
|
|
217
124
|
|
|
218
125
|
const result = [
|
|
219
|
-
...
|
|
126
|
+
...findSensitiveValuesFromAuthData(authData),
|
|
220
127
|
...findSensitiveValues(process.env),
|
|
221
128
|
...findSensitiveValues(data),
|
|
222
129
|
];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { recurseExtract } = require('@zapier/secret-scrubber');
|
|
4
|
+
const {
|
|
5
|
+
isUrlWithSecrets,
|
|
6
|
+
isSensitiveKey,
|
|
7
|
+
} = require('@zapier/secret-scrubber/lib/convenience');
|
|
8
|
+
|
|
9
|
+
// Will be initialized lazily
|
|
10
|
+
const SAFE_AUTH_DATA_KEYS = new Set();
|
|
11
|
+
|
|
12
|
+
const initSafeAuthDataKeys = () => {
|
|
13
|
+
// An authData key in (safePrefixes x safeSuffixes) is considered safe to log
|
|
14
|
+
// uncensored
|
|
15
|
+
const safePrefixes = [
|
|
16
|
+
'account',
|
|
17
|
+
'bot_user',
|
|
18
|
+
'cloud',
|
|
19
|
+
'cloud_site',
|
|
20
|
+
'company',
|
|
21
|
+
'domain',
|
|
22
|
+
'email',
|
|
23
|
+
'environment',
|
|
24
|
+
'location',
|
|
25
|
+
'org',
|
|
26
|
+
'organization',
|
|
27
|
+
'project',
|
|
28
|
+
'region',
|
|
29
|
+
'scope',
|
|
30
|
+
'scopes',
|
|
31
|
+
'site',
|
|
32
|
+
'subdomain',
|
|
33
|
+
'team',
|
|
34
|
+
'token_type',
|
|
35
|
+
'user',
|
|
36
|
+
'workspace',
|
|
37
|
+
];
|
|
38
|
+
const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
|
|
39
|
+
for (const prefix of safePrefixes) {
|
|
40
|
+
for (const suffix of safeSuffixes) {
|
|
41
|
+
SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isSafeAuthDataKey = (key) => {
|
|
47
|
+
if (SAFE_AUTH_DATA_KEYS.size === 0) {
|
|
48
|
+
initSafeAuthDataKeys();
|
|
49
|
+
}
|
|
50
|
+
return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const isUrl = (value) => {
|
|
54
|
+
if (!value || typeof value !== 'string') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const commonProtocols = [
|
|
58
|
+
'https://',
|
|
59
|
+
'http://',
|
|
60
|
+
'ftp://',
|
|
61
|
+
'ftps://',
|
|
62
|
+
'file://',
|
|
63
|
+
];
|
|
64
|
+
for (const protocol of commonProtocols) {
|
|
65
|
+
if (value.startsWith(protocol)) {
|
|
66
|
+
try {
|
|
67
|
+
// eslint-disable-next-line no-new
|
|
68
|
+
new URL(value);
|
|
69
|
+
return true;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const findSensitiveValuesFromAuthData = (authData) =>
|
|
79
|
+
recurseExtract(authData, (key, value) => {
|
|
80
|
+
// for the most part, we should censor all the values from authData
|
|
81
|
+
// the exception is safe urls, which should be filtered out - we want those to be logged
|
|
82
|
+
// but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
|
|
83
|
+
// this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
|
|
84
|
+
if (isSensitiveKey(key)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (isSafeAuthDataKey(key)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (isUrl(value) && !isUrlWithSecrets(value)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
findSensitiveValuesFromAuthData,
|
|
98
|
+
};
|
package/types/functions.d.ts
CHANGED
|
@@ -118,9 +118,17 @@ export type CreatePerformGet<
|
|
|
118
118
|
> = (z: ZObject, bundle: Bundle<$InputData>) => $Return | Promise<$Return>;
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Helper type for search results that can optionally include pagination.
|
|
122
|
+
* Returns either:
|
|
123
|
+
* - an array of objects, matching the search query.
|
|
124
|
+
* - an object with a `results` array of objects and a `paging_token` string.
|
|
122
125
|
*
|
|
123
|
-
*
|
|
126
|
+
* When `canPaginate` is true for the search, the object shape is required.
|
|
127
|
+
*/
|
|
128
|
+
type SearchResult<T> = T[] | { results: T[], paging_token: string };
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Search for objects on a partner API.
|
|
124
132
|
*
|
|
125
133
|
* @remarks
|
|
126
134
|
* This type requires a one-item array. Multiple items *can* be
|
|
@@ -130,7 +138,7 @@ export type CreatePerformGet<
|
|
|
130
138
|
export type SearchPerform<
|
|
131
139
|
$InputData extends DefaultInputData = DefaultInputData,
|
|
132
140
|
$Return extends {} = {},
|
|
133
|
-
> = (z: ZObject, bundle: Bundle<$InputData>) =>
|
|
141
|
+
> = (z: ZObject, bundle: Bundle<$InputData>) => SearchResult<$Return> | Promise<SearchResult<$Return>>;
|
|
134
142
|
|
|
135
143
|
/**
|
|
136
144
|
* Follow up a search's perform with additional data.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* files, and/or the schema-to-ts tool and run its CLI to regenerate
|
|
5
5
|
* these typings.
|
|
6
6
|
*
|
|
7
|
-
* zapier-platform-schema version: 17.
|
|
7
|
+
* zapier-platform-schema version: 17.7.1
|
|
8
8
|
* schema-to-ts compiler version: 0.1.0
|
|
9
9
|
*/
|
|
10
10
|
import type {
|
|
@@ -131,7 +131,8 @@ export interface BaseApp {
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Represents a simplified semver string, from `0.0.0` to
|
|
134
|
-
* `999.999.999
|
|
134
|
+
* `999.999.999` with optional simplified label. They need to be
|
|
135
|
+
* case-insensitive unique.
|
|
135
136
|
*/
|
|
136
137
|
export type Version = string;
|
|
137
138
|
|
|
@@ -938,6 +939,12 @@ export interface BasicPollingOperation<
|
|
|
938
939
|
* for the window is exceeded.
|
|
939
940
|
*/
|
|
940
941
|
throttle?: ThrottleObject;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
945
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
946
|
+
*/
|
|
947
|
+
skipCleanArrayInputData?: boolean;
|
|
941
948
|
}
|
|
942
949
|
|
|
943
950
|
/**
|
|
@@ -1027,6 +1034,12 @@ export interface BasicHookOperation<
|
|
|
1027
1034
|
* this belongs to a resource that has a top-level sample
|
|
1028
1035
|
*/
|
|
1029
1036
|
sample?: Record<string, unknown>;
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1040
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1041
|
+
*/
|
|
1042
|
+
skipCleanArrayInputData?: boolean;
|
|
1030
1043
|
}
|
|
1031
1044
|
|
|
1032
1045
|
/**
|
|
@@ -1088,6 +1101,12 @@ export interface BasicHookToPollOperation<
|
|
|
1088
1101
|
*/
|
|
1089
1102
|
sample?: Record<string, unknown>;
|
|
1090
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1106
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1107
|
+
*/
|
|
1108
|
+
skipCleanArrayInputData?: boolean;
|
|
1109
|
+
|
|
1091
1110
|
/**
|
|
1092
1111
|
* The maximum amount of time to wait between polling requests in
|
|
1093
1112
|
* seconds. Minimum value is 20s and will default to 20 if not set,
|
|
@@ -1159,6 +1178,12 @@ export interface BasicActionOperation {
|
|
|
1159
1178
|
* for the window is exceeded.
|
|
1160
1179
|
*/
|
|
1161
1180
|
throttle?: ThrottleObject;
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1184
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1185
|
+
*/
|
|
1186
|
+
skipCleanArrayInputData?: boolean;
|
|
1162
1187
|
}
|
|
1163
1188
|
|
|
1164
1189
|
/** Represents the fundamental mechanics of a search. */
|
|
@@ -1193,6 +1218,9 @@ export interface BasicSearchOperation<
|
|
|
1193
1218
|
*/
|
|
1194
1219
|
performGet?: Request | SearchPerformGet<InferInputData<$InputFields>>;
|
|
1195
1220
|
|
|
1221
|
+
/** Does this search support pagination? */
|
|
1222
|
+
canPaginate?: boolean;
|
|
1223
|
+
|
|
1196
1224
|
/** What should the form a user sees and configures look like? */
|
|
1197
1225
|
inputFields?: $InputFields;
|
|
1198
1226
|
|
|
@@ -1226,6 +1254,12 @@ export interface BasicSearchOperation<
|
|
|
1226
1254
|
* for the window is exceeded.
|
|
1227
1255
|
*/
|
|
1228
1256
|
throttle?: ThrottleObject;
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1260
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1261
|
+
*/
|
|
1262
|
+
skipCleanArrayInputData?: boolean;
|
|
1229
1263
|
}
|
|
1230
1264
|
|
|
1231
1265
|
/** Represents the fundamental mechanics of a create. */
|
|
@@ -1296,6 +1330,12 @@ export interface BasicCreateOperation<
|
|
|
1296
1330
|
*/
|
|
1297
1331
|
throttle?: ThrottleObject;
|
|
1298
1332
|
|
|
1333
|
+
/**
|
|
1334
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1335
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1336
|
+
*/
|
|
1337
|
+
skipCleanArrayInputData?: boolean;
|
|
1338
|
+
|
|
1299
1339
|
/**
|
|
1300
1340
|
* Currently an **internal-only** feature. Zapier uses this
|
|
1301
1341
|
* configuration for creating objects in bulk with `performBuffer`.
|
|
@@ -1379,6 +1419,12 @@ export interface BasicOperation {
|
|
|
1379
1419
|
* for the window is exceeded.
|
|
1380
1420
|
*/
|
|
1381
1421
|
throttle?: ThrottleObject;
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* (Experimental) Should empty strings, `null`, `undefined`, and
|
|
1425
|
+
* empty Arrays or objects be removed from `inputData`?
|
|
1426
|
+
*/
|
|
1427
|
+
skipCleanArrayInputData?: boolean;
|
|
1382
1428
|
}
|
|
1383
1429
|
|
|
1384
1430
|
/**
|
|
@@ -1463,11 +1509,7 @@ export interface PlainOutputField {
|
|
|
1463
1509
|
}
|
|
1464
1510
|
|
|
1465
1511
|
/** An array or collection of input field groups. */
|
|
1466
|
-
export type InputFieldGroups =
|
|
1467
|
-
key: Key;
|
|
1468
|
-
label: string;
|
|
1469
|
-
emphasize: boolean;
|
|
1470
|
-
}[];
|
|
1512
|
+
export type InputFieldGroups = InputFieldGroup[];
|
|
1471
1513
|
|
|
1472
1514
|
/**
|
|
1473
1515
|
* Zapier uses this configuration to ensure this action is performed
|
|
@@ -1678,6 +1720,18 @@ export interface PlainInputField {
|
|
|
1678
1720
|
group?: Key;
|
|
1679
1721
|
}
|
|
1680
1722
|
|
|
1723
|
+
/** Object for visual grouping of input fields. */
|
|
1724
|
+
export interface InputFieldGroup {
|
|
1725
|
+
/** The unique identifier for this group. */
|
|
1726
|
+
key: Key;
|
|
1727
|
+
|
|
1728
|
+
/** The human-readable name for the group. */
|
|
1729
|
+
label?: string;
|
|
1730
|
+
|
|
1731
|
+
/** Whether this group should be visually emphasized in the UI. */
|
|
1732
|
+
emphasize?: boolean;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1681
1735
|
/**
|
|
1682
1736
|
* Reference a resource by key and the data it returns. In the
|
|
1683
1737
|
* format of: `{resource_key}.{foreign_key}(.{human_label_key})`.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
const { simpleTruncate } = require('../tools/data');
|
|
5
|
-
|
|
6
|
-
const isSearch = require('./is-search');
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
Searches should always return an array of objects.
|
|
10
|
-
*/
|
|
11
|
-
const searchIsArray = {
|
|
12
|
-
name: 'triggerIsArray',
|
|
13
|
-
shouldRun: isSearch,
|
|
14
|
-
run: (method, results) => {
|
|
15
|
-
if (!_.isArray(results)) {
|
|
16
|
-
const repr = simpleTruncate(JSON.stringify(results), 50);
|
|
17
|
-
return [`Results must be an array, got: ${typeof results}, (${repr})`];
|
|
18
|
-
}
|
|
19
|
-
return [];
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
module.exports = searchIsArray;
|