@isomorph.ai/app-sdk 1.6.2 → 1.7.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/index.d.ts +38 -2
- package/dist/index.js +16 -5
- package/dist/integration-execute-input-rules.d.ts +110 -1
- package/dist/integration-execute-input-rules.js +102 -31
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -49,12 +49,46 @@ export interface SlackMessagePostRequest {
|
|
|
49
49
|
*/
|
|
50
50
|
mode?: 'app' | 'user';
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* One condition on a warehouse read; a read's conditions are AND-ed. `isNull`/`isNotNull` take no value, `in`/`notIn`
|
|
54
|
+
* a non-empty list of one type, every other op one value (a boolean only with `eq`/`ne`). The column and op are
|
|
55
|
+
* literals in the call; the value may be any expression.
|
|
56
|
+
*/
|
|
57
|
+
export interface WarehouseFilter {
|
|
58
|
+
column: string;
|
|
59
|
+
op: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'isNull' | 'isNotNull';
|
|
60
|
+
value?: string | number | boolean | Array<string | number>;
|
|
61
|
+
}
|
|
62
|
+
/** One aggregate a warehouse read returns under `as`: `count` may count `'*'`; sum, avg, min and max need a column. */
|
|
63
|
+
export interface WarehouseAggregate {
|
|
64
|
+
fn: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
65
|
+
column: string;
|
|
66
|
+
as: string;
|
|
67
|
+
}
|
|
68
|
+
/** One sort key: a returned column or an aggregate's alias; `direction` defaults to `'asc'`. */
|
|
69
|
+
export interface WarehouseOrder {
|
|
70
|
+
column: string;
|
|
71
|
+
direction?: 'asc' | 'desc';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Filter, group and sort in the warehouse, never by pulling raw rows to total in code. A read names `columns`
|
|
75
|
+
* (a plain read) or `aggregates` (grouped by `groupBy`, or one row for the whole view), never both. Every column
|
|
76
|
+
* the read names must be approved for the view.
|
|
77
|
+
*/
|
|
52
78
|
export interface WarehouseViewReadRequest {
|
|
53
79
|
operation: 'warehouse.view.read';
|
|
54
80
|
resource: string;
|
|
55
81
|
input: {
|
|
56
|
-
/** 1 to 32 approved column names, or `['*']` when IT exposes all current and future columns. */
|
|
57
|
-
columns
|
|
82
|
+
/** A plain read: 1 to 32 approved column names, or `['*']` when IT exposes all current and future columns. */
|
|
83
|
+
columns?: string[];
|
|
84
|
+
/** 1 to 16 conditions, all of which a row must meet. */
|
|
85
|
+
where?: WarehouseFilter[];
|
|
86
|
+
/** 1 to 8 columns to group the aggregates by; needs `aggregates`. */
|
|
87
|
+
groupBy?: string[];
|
|
88
|
+
/** 1 to 16 aggregates; each row carries the `groupBy` columns, then each alias. */
|
|
89
|
+
aggregates?: WarehouseAggregate[];
|
|
90
|
+
/** 1 to 8 sort keys. A grouped read with none is sorted by its `groupBy` columns; a plain read with none is unsorted. */
|
|
91
|
+
orderBy?: WarehouseOrder[];
|
|
58
92
|
/** How many rows to read: an integer from 1 to 1000; default 100. */
|
|
59
93
|
limit?: number;
|
|
60
94
|
};
|
|
@@ -112,6 +146,8 @@ export interface SlackMessagePostResult {
|
|
|
112
146
|
}
|
|
113
147
|
export interface WarehouseViewReadResult {
|
|
114
148
|
rows: Array<Record<string, unknown>>;
|
|
149
|
+
/** True when more rows matched than `limit`: the rows are incomplete, so never total or average them in code. */
|
|
150
|
+
truncated: boolean;
|
|
115
151
|
}
|
|
116
152
|
export interface GmailThreadSummary {
|
|
117
153
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,18 @@ export class IsomorphError extends Error {
|
|
|
16
16
|
this.name = 'IsomorphError';
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
// The warehouse rules the generated table cannot state, with governance's
|
|
20
|
+
// sentences: a read names columns or aggregates (one of them; it once needed
|
|
21
|
+
// columns), and "*" stands alone. Governance owns the query's other rules.
|
|
22
|
+
function warehouseReadProblem({ columns, aggregates }) {
|
|
23
|
+
if (columns != null && aggregates != null)
|
|
24
|
+
return { field: 'columns', message: 'a warehouse read names columns or aggregates, not both' };
|
|
25
|
+
if (columns == null && aggregates == null)
|
|
26
|
+
return { field: 'columns', message: 'a warehouse read needs columns or aggregates' };
|
|
27
|
+
if (columns?.includes('*') && columns.length !== 1)
|
|
28
|
+
return { field: 'columns', message: 'columns may use "*" only as the sole entry' };
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
19
31
|
/** The AI reason code carried by an error, when it is a governed-AI refusal. */
|
|
20
32
|
export function aiErrorCode(error) {
|
|
21
33
|
const code = error instanceof IsomorphError ? error.details?.code : undefined;
|
|
@@ -659,11 +671,10 @@ export function createClient(options = {}) {
|
|
|
659
671
|
// `isomorph check` (which runs retained journeys through this client)
|
|
660
672
|
// and the deployed app fail the same way at build time, not at runtime.
|
|
661
673
|
const checked = validateIntegrationExecuteInput(request.operation, request.input);
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return Promise.reject(new IsomorphError('VALIDATION_FAILED', 'columns may use "*" only as the sole entry', undefined, { code: 'INPUT_INVALID', field: 'columns' }));
|
|
674
|
+
const problem = !checked.ok ? checked.problem
|
|
675
|
+
: request.operation === 'warehouse.view.read' ? warehouseReadProblem(request.input ?? {}) : undefined;
|
|
676
|
+
if (problem) {
|
|
677
|
+
return Promise.reject(new IsomorphError('VALIDATION_FAILED', problem.message, undefined, { code: 'INPUT_INVALID', field: problem.field }));
|
|
667
678
|
}
|
|
668
679
|
// Exactly the contract's fields go on the wire: `mode` is one of them
|
|
669
680
|
// for a Slack post only (which approved mode it runs under); an
|
|
@@ -28,7 +28,30 @@ export type IntegrationExecuteInputFieldRule = {
|
|
|
28
28
|
maxLength?: number;
|
|
29
29
|
pattern?: string;
|
|
30
30
|
};
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'enum';
|
|
33
|
+
required: boolean;
|
|
34
|
+
values: readonly string[];
|
|
35
|
+
default?: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'object[]';
|
|
38
|
+
required: boolean;
|
|
39
|
+
minItems: number;
|
|
40
|
+
maxItems: number;
|
|
41
|
+
fields: Readonly<Record<string, IntegrationExecuteInputFieldRule>>;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'value';
|
|
44
|
+
required: boolean;
|
|
45
|
+
types: readonly IntegrationExecuteInputValueType[];
|
|
46
|
+
maxLength?: number;
|
|
47
|
+
list?: {
|
|
48
|
+
minItems: number;
|
|
49
|
+
maxItems: number;
|
|
50
|
+
types: readonly IntegrationExecuteInputValueType[];
|
|
51
|
+
};
|
|
31
52
|
};
|
|
53
|
+
/** The scalar types a `value` input accepts, alone or in its list form. */
|
|
54
|
+
export type IntegrationExecuteInputValueType = 'string' | 'number' | 'boolean';
|
|
32
55
|
export type IntegrationExecuteOperationRule = {
|
|
33
56
|
idempotencyKey: boolean;
|
|
34
57
|
fields: Record<string, IntegrationExecuteInputFieldRule>;
|
|
@@ -144,7 +167,7 @@ export declare const IntegrationExecuteInputRules: {
|
|
|
144
167
|
readonly fields: {
|
|
145
168
|
readonly columns: {
|
|
146
169
|
readonly kind: "string[]";
|
|
147
|
-
readonly required:
|
|
170
|
+
readonly required: false;
|
|
148
171
|
readonly minItems: 1;
|
|
149
172
|
readonly maxItems: 32;
|
|
150
173
|
readonly uniqueItems: false;
|
|
@@ -153,6 +176,92 @@ export declare const IntegrationExecuteInputRules: {
|
|
|
153
176
|
readonly pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$";
|
|
154
177
|
};
|
|
155
178
|
};
|
|
179
|
+
readonly where: {
|
|
180
|
+
readonly kind: "object[]";
|
|
181
|
+
readonly required: false;
|
|
182
|
+
readonly minItems: 1;
|
|
183
|
+
readonly maxItems: 16;
|
|
184
|
+
readonly fields: {
|
|
185
|
+
readonly column: {
|
|
186
|
+
readonly kind: "string";
|
|
187
|
+
readonly required: true;
|
|
188
|
+
readonly minLength: 0;
|
|
189
|
+
readonly pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$";
|
|
190
|
+
};
|
|
191
|
+
readonly op: {
|
|
192
|
+
readonly kind: "enum";
|
|
193
|
+
readonly required: true;
|
|
194
|
+
readonly values: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "isNull", "isNotNull"];
|
|
195
|
+
};
|
|
196
|
+
readonly value: {
|
|
197
|
+
readonly kind: "value";
|
|
198
|
+
readonly required: false;
|
|
199
|
+
readonly types: readonly ["string", "number", "boolean"];
|
|
200
|
+
readonly maxLength: 256;
|
|
201
|
+
readonly list: {
|
|
202
|
+
readonly minItems: 1;
|
|
203
|
+
readonly maxItems: 100;
|
|
204
|
+
readonly types: readonly ["string", "number"];
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
readonly groupBy: {
|
|
210
|
+
readonly kind: "string[]";
|
|
211
|
+
readonly required: false;
|
|
212
|
+
readonly minItems: 1;
|
|
213
|
+
readonly maxItems: 8;
|
|
214
|
+
readonly uniqueItems: false;
|
|
215
|
+
readonly item: {
|
|
216
|
+
readonly minLength: 0;
|
|
217
|
+
readonly pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$";
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
readonly aggregates: {
|
|
221
|
+
readonly kind: "object[]";
|
|
222
|
+
readonly required: false;
|
|
223
|
+
readonly minItems: 1;
|
|
224
|
+
readonly maxItems: 16;
|
|
225
|
+
readonly fields: {
|
|
226
|
+
readonly fn: {
|
|
227
|
+
readonly kind: "enum";
|
|
228
|
+
readonly required: true;
|
|
229
|
+
readonly values: readonly ["count", "sum", "avg", "min", "max"];
|
|
230
|
+
};
|
|
231
|
+
readonly column: {
|
|
232
|
+
readonly kind: "string";
|
|
233
|
+
readonly required: true;
|
|
234
|
+
readonly minLength: 0;
|
|
235
|
+
readonly pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$";
|
|
236
|
+
};
|
|
237
|
+
readonly as: {
|
|
238
|
+
readonly kind: "string";
|
|
239
|
+
readonly required: true;
|
|
240
|
+
readonly minLength: 0;
|
|
241
|
+
readonly pattern: "^[a-z_][a-z0-9_]{0,62}$";
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
readonly orderBy: {
|
|
246
|
+
readonly kind: "object[]";
|
|
247
|
+
readonly required: false;
|
|
248
|
+
readonly minItems: 1;
|
|
249
|
+
readonly maxItems: 8;
|
|
250
|
+
readonly fields: {
|
|
251
|
+
readonly column: {
|
|
252
|
+
readonly kind: "string";
|
|
253
|
+
readonly required: true;
|
|
254
|
+
readonly minLength: 0;
|
|
255
|
+
readonly pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$";
|
|
256
|
+
};
|
|
257
|
+
readonly direction: {
|
|
258
|
+
readonly kind: "enum";
|
|
259
|
+
readonly required: false;
|
|
260
|
+
readonly values: readonly ["asc", "desc"];
|
|
261
|
+
readonly default: "asc";
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
};
|
|
156
265
|
readonly limit: {
|
|
157
266
|
readonly kind: "integer";
|
|
158
267
|
readonly required: false;
|
|
@@ -39,7 +39,22 @@ export const IntegrationExecuteInputRules = {
|
|
|
39
39
|
"warehouse.view.read": {
|
|
40
40
|
idempotencyKey: false,
|
|
41
41
|
fields: {
|
|
42
|
-
columns: { kind: "string[]", required:
|
|
42
|
+
columns: { kind: "string[]", required: false, minItems: 1, maxItems: 32, uniqueItems: false, item: { minLength: 0, pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$" } },
|
|
43
|
+
where: { kind: "object[]", required: false, minItems: 1, maxItems: 16, fields: {
|
|
44
|
+
column: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" },
|
|
45
|
+
op: { kind: "enum", required: true, values: ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "isNull", "isNotNull"] },
|
|
46
|
+
value: { kind: "value", required: false, types: ["string", "number", "boolean"], maxLength: 256, list: { minItems: 1, maxItems: 100, types: ["string", "number"] } },
|
|
47
|
+
} },
|
|
48
|
+
groupBy: { kind: "string[]", required: false, minItems: 1, maxItems: 8, uniqueItems: false, item: { minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" } },
|
|
49
|
+
aggregates: { kind: "object[]", required: false, minItems: 1, maxItems: 16, fields: {
|
|
50
|
+
fn: { kind: "enum", required: true, values: ["count", "sum", "avg", "min", "max"] },
|
|
51
|
+
column: { kind: "string", required: true, minLength: 0, pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$" },
|
|
52
|
+
as: { kind: "string", required: true, minLength: 0, pattern: "^[a-z_][a-z0-9_]{0,62}$" },
|
|
53
|
+
} },
|
|
54
|
+
orderBy: { kind: "object[]", required: false, minItems: 1, maxItems: 8, fields: {
|
|
55
|
+
column: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" },
|
|
56
|
+
direction: { kind: "enum", required: false, values: ["asc", "desc"], default: "asc" },
|
|
57
|
+
} },
|
|
43
58
|
limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
|
|
44
59
|
},
|
|
45
60
|
},
|
|
@@ -60,55 +75,111 @@ function matches(pattern, value) {
|
|
|
60
75
|
}
|
|
61
76
|
return compiled.test(value);
|
|
62
77
|
}
|
|
78
|
+
function refuse(field, message) { return { ok: false, problem: { field, message } }; }
|
|
79
|
+
const valueNouns = { string: ['a string', 'strings'], number: ['a number', 'numbers'], boolean: ['a boolean', 'booleans'] };
|
|
80
|
+
function either(parts) {
|
|
81
|
+
return parts.length > 1 ? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}` : parts.join('');
|
|
82
|
+
}
|
|
83
|
+
function isScalar(types, maxLength, value) {
|
|
84
|
+
if (typeof value === 'string')
|
|
85
|
+
return types.includes('string') && (maxLength === undefined || value.length <= maxLength);
|
|
86
|
+
if (typeof value === 'number')
|
|
87
|
+
return types.includes('number') && Number.isFinite(value);
|
|
88
|
+
return typeof value === 'boolean' && types.includes('boolean');
|
|
89
|
+
}
|
|
63
90
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
91
|
+
* Checks one object's fields in rule order and applies defaults: the input
|
|
92
|
+
* itself (where null counts as absent), or one entry of an object[] input
|
|
93
|
+
* (where only a missing key is absent, so null is refused like any wrong value).
|
|
67
94
|
*/
|
|
68
|
-
|
|
69
|
-
const rule = IntegrationExecuteInputRules[operation];
|
|
70
|
-
if (!rule)
|
|
71
|
-
return { ok: false, problem: { field: 'operation', message: `${operation} is not a declared integration operation` } };
|
|
72
|
-
const raw = input === undefined ? {} : input;
|
|
73
|
-
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
74
|
-
return { ok: false, problem: { field: 'input', message: 'input must be an object' } };
|
|
75
|
-
const given = raw;
|
|
95
|
+
function checkFields(operation, prefix, fields, given, nullIsAbsent) {
|
|
76
96
|
for (const name of Object.keys(given))
|
|
77
|
-
if (!Object.prototype.hasOwnProperty.call(
|
|
78
|
-
return
|
|
97
|
+
if (!Object.prototype.hasOwnProperty.call(fields, name))
|
|
98
|
+
return refuse(prefix + name, `${prefix}${name} is not an input of ${operation}`);
|
|
79
99
|
const out = {};
|
|
80
|
-
for (const [name, field] of Object.entries(
|
|
100
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
101
|
+
const path = prefix + name;
|
|
81
102
|
const value = given[name];
|
|
82
|
-
if (value === undefined || value === null) {
|
|
103
|
+
if (value === undefined || (nullIsAbsent && value === null)) {
|
|
83
104
|
if (field.required)
|
|
84
|
-
return
|
|
85
|
-
if (field.kind === 'integer' && field.default !== undefined)
|
|
105
|
+
return refuse(path, `${path} is required for ${operation}`);
|
|
106
|
+
if ((field.kind === 'integer' || field.kind === 'enum') && field.default !== undefined)
|
|
86
107
|
out[name] = field.default;
|
|
87
108
|
continue;
|
|
88
109
|
}
|
|
89
|
-
|
|
110
|
+
const checked = checkField(operation, path, name, field, value);
|
|
111
|
+
if (!checked.ok)
|
|
112
|
+
return checked;
|
|
113
|
+
out[name] = checked.value;
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, value: out };
|
|
116
|
+
}
|
|
117
|
+
function checkField(operation, path, name, field, value) {
|
|
118
|
+
switch (field.kind) {
|
|
119
|
+
case 'integer':
|
|
90
120
|
if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum)
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
|
|
121
|
+
return refuse(path, `${path} must be an integer between ${field.minimum} and ${field.maximum}`);
|
|
122
|
+
return { ok: true, value };
|
|
123
|
+
case 'string':
|
|
94
124
|
if (typeof value !== 'string' || value.length < field.minLength || (field.maxLength !== undefined && value.length > field.maxLength))
|
|
95
|
-
return
|
|
125
|
+
return refuse(path, field.maxLength !== undefined ? `${path} must be ${field.minLength} to ${field.maxLength} characters` : `${path} must be a string of at least ${field.minLength} characters`);
|
|
96
126
|
if (field.pattern && !matches(field.pattern, value))
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
127
|
+
return refuse(path, `${path} must match ${field.pattern}`);
|
|
128
|
+
return { ok: true, value };
|
|
129
|
+
case 'enum':
|
|
130
|
+
if (typeof value !== 'string' || !field.values.includes(value))
|
|
131
|
+
return refuse(path, `${path} must be one of ${field.values.join(', ')}`);
|
|
132
|
+
return { ok: true, value };
|
|
133
|
+
case 'string[]': {
|
|
100
134
|
if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems)
|
|
101
|
-
return
|
|
135
|
+
return refuse(path, `${path} must list ${field.minItems} to ${field.maxItems} items`);
|
|
102
136
|
const seen = new Set();
|
|
103
137
|
for (const [index, item] of value.entries()) {
|
|
104
138
|
if (typeof item !== 'string' || item.length < field.item.minLength || (field.item.maxLength !== undefined && item.length > field.item.maxLength) || (field.item.pattern !== undefined && !matches(field.item.pattern, item)))
|
|
105
|
-
return
|
|
139
|
+
return refuse(path, `${path}[${index}] is not a valid ${name} entry`);
|
|
106
140
|
if (field.uniqueItems && seen.has(item))
|
|
107
|
-
return
|
|
141
|
+
return refuse(path, `${path} must not repeat an entry`);
|
|
108
142
|
seen.add(item);
|
|
109
143
|
}
|
|
144
|
+
return { ok: true, value };
|
|
145
|
+
}
|
|
146
|
+
case 'object[]': {
|
|
147
|
+
if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems)
|
|
148
|
+
return refuse(path, `${path} must list ${field.minItems} to ${field.maxItems} items`);
|
|
149
|
+
const entries = [];
|
|
150
|
+
for (const [index, item] of value.entries()) {
|
|
151
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item))
|
|
152
|
+
return refuse(path, `${path}[${index}] is not a valid ${name} entry`);
|
|
153
|
+
const entry = checkFields(operation, `${path}[${index}].`, field.fields, item, false);
|
|
154
|
+
if (!entry.ok)
|
|
155
|
+
return entry;
|
|
156
|
+
entries.push(entry.value);
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, value: entries };
|
|
159
|
+
}
|
|
160
|
+
case 'value': {
|
|
161
|
+
const { types, maxLength, list } = field;
|
|
162
|
+
if (isScalar(types, maxLength, value) || (list !== undefined && Array.isArray(value) && value.length >= list.minItems && value.length <= list.maxItems && value.every(item => isScalar(list.types, maxLength, item))))
|
|
163
|
+
return { ok: true, value };
|
|
164
|
+
const shapes = types.map(type => (type === 'string' && maxLength !== undefined ? `a string of at most ${maxLength} characters` : valueNouns[type][0]));
|
|
165
|
+
if (list !== undefined)
|
|
166
|
+
shapes.push(`a list of ${list.minItems} to ${list.maxItems} ${either(list.types.map(type => valueNouns[type][1]))}`);
|
|
167
|
+
return refuse(path, `${path} must be ${either(shapes)}`);
|
|
110
168
|
}
|
|
111
|
-
out[name] = value;
|
|
112
169
|
}
|
|
113
|
-
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Validates one operation's input against the contract and returns it with the
|
|
173
|
+
* contract's defaults applied. The message names the field and its allowed
|
|
174
|
+
* range; it is the sentence the platform answers with, so a caller may show it.
|
|
175
|
+
*/
|
|
176
|
+
export function validateIntegrationExecuteInput(operation, input) {
|
|
177
|
+
const rule = IntegrationExecuteInputRules[operation];
|
|
178
|
+
if (!rule)
|
|
179
|
+
return refuse('operation', `${operation} is not a declared integration operation`);
|
|
180
|
+
const raw = input === undefined ? {} : input;
|
|
181
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
182
|
+
return refuse('input', 'input must be an object');
|
|
183
|
+
const checked = checkFields(operation, '', rule.fields, raw, true);
|
|
184
|
+
return checked.ok ? { ok: true, input: checked.value } : checked;
|
|
114
185
|
}
|