@beseif-solutions/prow-core 0.0.38 → 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/dist/entities/events.d.ts +27 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/minified-utils/context.d.ts +2 -2
- package/dist/minified-utils/context.js +36 -43
- package/dist/minified-utils/fields.d.ts +4 -4
- package/dist/minified-utils/fields.js +177 -88
- package/dist/minified-utils/where.js +1 -1
- package/package.json +1 -1
- package/src/entities/events.ts +15 -14
- package/src/index.ts +1 -1
- package/src/minified-utils/context.ts +39 -54
- package/src/minified-utils/fields.ts +173 -99
- package/src/minified-utils/where.ts +2 -2
- package/tsconfig.json +1 -2
|
@@ -26,8 +26,11 @@ type EventConfig = Pick<CoreConfig, `credentials` | `outputs`> & {
|
|
|
26
26
|
outputs: string[];
|
|
27
27
|
};
|
|
28
28
|
sources?: string[];
|
|
29
|
+
fields?: Record<string, any>;
|
|
29
30
|
};
|
|
30
|
-
type CommonEvents<Config extends EventConfig = {
|
|
31
|
+
type CommonEvents<Config extends EventConfig = {
|
|
32
|
+
fields: Record<string, any>;
|
|
33
|
+
}> = Common<Config> & {
|
|
31
34
|
model: `event`;
|
|
32
35
|
categorization: {
|
|
33
36
|
department: string;
|
|
@@ -49,7 +52,9 @@ export type Request = {
|
|
|
49
52
|
query: Record<string, string>;
|
|
50
53
|
body: any;
|
|
51
54
|
};
|
|
52
|
-
export type ImmediateTrigger<Config extends EventConfig = {
|
|
55
|
+
export type ImmediateTrigger<Config extends EventConfig = {
|
|
56
|
+
fields: Record<string, any>;
|
|
57
|
+
}> = CommonEvents<Config> & {
|
|
53
58
|
type: `trigger`;
|
|
54
59
|
immediate: true;
|
|
55
60
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>];
|
|
@@ -57,7 +62,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
57
62
|
mount?: CoreFunction<{
|
|
58
63
|
credentials: Config[`credentials`];
|
|
59
64
|
inputs: {
|
|
60
|
-
fields:
|
|
65
|
+
fields: Config[`fields`];
|
|
61
66
|
link: string;
|
|
62
67
|
};
|
|
63
68
|
outputs: {
|
|
@@ -68,7 +73,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
68
73
|
unmount?: CoreFunction<{
|
|
69
74
|
credentials: Config[`credentials`];
|
|
70
75
|
inputs: {
|
|
71
|
-
fields:
|
|
76
|
+
fields: Config[`fields`];
|
|
72
77
|
link: string;
|
|
73
78
|
setup?: any;
|
|
74
79
|
};
|
|
@@ -79,7 +84,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
79
84
|
challenge?: CoreFunction<{
|
|
80
85
|
credentials: Config[`credentials`];
|
|
81
86
|
inputs: {
|
|
82
|
-
fields:
|
|
87
|
+
fields: Config[`fields`];
|
|
83
88
|
link: string;
|
|
84
89
|
request: Request;
|
|
85
90
|
setup?: any;
|
|
@@ -92,7 +97,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
92
97
|
handle: CoreFunction<{
|
|
93
98
|
credentials: Config[`credentials`];
|
|
94
99
|
inputs: {
|
|
95
|
-
fields:
|
|
100
|
+
fields: Config[`fields`];
|
|
96
101
|
request: Request;
|
|
97
102
|
setup?: any;
|
|
98
103
|
};
|
|
@@ -104,7 +109,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
104
109
|
fire?: CoreFunction<{
|
|
105
110
|
credentials: Config[`credentials`];
|
|
106
111
|
inputs: {
|
|
107
|
-
fields:
|
|
112
|
+
fields: Config[`fields`];
|
|
108
113
|
link: string;
|
|
109
114
|
setup?: any;
|
|
110
115
|
};
|
|
@@ -115,7 +120,7 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
115
120
|
poll?: CoreFunction<{
|
|
116
121
|
credentials: Config[`credentials`];
|
|
117
122
|
inputs: {
|
|
118
|
-
fields:
|
|
123
|
+
fields: Config[`fields`];
|
|
119
124
|
last_schedule?: string;
|
|
120
125
|
limit?: number;
|
|
121
126
|
};
|
|
@@ -126,7 +131,9 @@ export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
126
131
|
}>;
|
|
127
132
|
};
|
|
128
133
|
};
|
|
129
|
-
export type ScheduledTrigger<Config extends EventConfig = {
|
|
134
|
+
export type ScheduledTrigger<Config extends EventConfig = {
|
|
135
|
+
fields: Record<string, any>;
|
|
136
|
+
}> = CommonEvents<Config> & {
|
|
130
137
|
type: `trigger`;
|
|
131
138
|
immediate: false;
|
|
132
139
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>];
|
|
@@ -134,7 +141,7 @@ export type ScheduledTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
134
141
|
poll: CoreFunction<{
|
|
135
142
|
credentials: Config[`credentials`];
|
|
136
143
|
inputs: {
|
|
137
|
-
fields:
|
|
144
|
+
fields: Config[`fields`];
|
|
138
145
|
last_schedule?: string;
|
|
139
146
|
limit?: number;
|
|
140
147
|
};
|
|
@@ -145,8 +152,12 @@ export type ScheduledTrigger<Config extends EventConfig = {}> = CommonEvents<Con
|
|
|
145
152
|
}>;
|
|
146
153
|
};
|
|
147
154
|
};
|
|
148
|
-
export type Trigger<Config extends EventConfig = {
|
|
149
|
-
|
|
155
|
+
export type Trigger<Config extends EventConfig = {
|
|
156
|
+
fields: Record<string, any>;
|
|
157
|
+
}> = ImmediateTrigger<Config> | ScheduledTrigger<Config>;
|
|
158
|
+
export type Action<Config extends EventConfig = {
|
|
159
|
+
fields: Record<string, any>;
|
|
160
|
+
}> = CommonEvents<Config> & {
|
|
150
161
|
type: `action`;
|
|
151
162
|
inputs: [DefaultConnectableEvent<Config[`flags`][`inputs`][number]>, ...AdditionalConnectableEvent<Config[`flags`][`inputs`][number]>[]];
|
|
152
163
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>, ...(AdditionalConnectableEvent<Config[`flags`][`outputs`][number]> | ErrorConnectableEvent<Config[`flags`][`outputs`][number]>)[]];
|
|
@@ -154,7 +165,7 @@ export type Action<Config extends EventConfig = {}> = CommonEvents<Config> & {
|
|
|
154
165
|
execute: CoreFunction<{
|
|
155
166
|
credentials: Config[`credentials`];
|
|
156
167
|
inputs: {
|
|
157
|
-
fields:
|
|
168
|
+
fields: Config[`fields`];
|
|
158
169
|
flags: Partial<Record<Config[`flags`][`inputs`][number], true>>;
|
|
159
170
|
};
|
|
160
171
|
outputs: {
|
|
@@ -164,7 +175,9 @@ export type Action<Config extends EventConfig = {}> = CommonEvents<Config> & {
|
|
|
164
175
|
}>;
|
|
165
176
|
};
|
|
166
177
|
};
|
|
167
|
-
export type Event<Config extends EventConfig = {
|
|
178
|
+
export type Event<Config extends EventConfig = {
|
|
179
|
+
fields: Record<string, any>;
|
|
180
|
+
}> = Action<Config> | Trigger<Config>;
|
|
168
181
|
export declare const scheduledTriggerSchema: () => Joi.ObjectSchema<any>;
|
|
169
182
|
export declare const immediateTriggerSchema: () => Joi.ObjectSchema<any>;
|
|
170
183
|
export declare const triggerSchema: () => Joi.AlternativesSchema<any>;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Credentials, credentialsSchema, OAuthCredentials, oauthCredentialsSchem
|
|
|
4
4
|
export { Action, actionOutputSchema, actionSchema, Event, eventSchema, ImmediateTrigger, immediateTriggerSchema, Request, ScheduledTrigger, scheduledTriggerSchema, Trigger, triggerOutputSchema, triggerSchema } from './entities/events';
|
|
5
5
|
export { Provider, providerSchema } from './entities/provider';
|
|
6
6
|
export { Source } from './entities/source';
|
|
7
|
-
export { extract, Field, fieldSchema, fieldsSchema, File, SelectFieldChoice, validate, ValidationResult } from './minified-utils/fields';
|
|
7
|
+
export { alter, extract, Field, fieldSchema, fieldsSchema, File, SelectFieldChoice, validate, ValidationResult } from './minified-utils/fields';
|
|
8
8
|
export { i18n, LocalizedField, LocalizedProperties, localizeFields, localizeGroup, localizeInputs, localizeOutputs, localizeProperties } from './minified-utils/locales';
|
|
9
9
|
export { Properties } from './minified-utils/properties';
|
|
10
10
|
export { Protocol } from './minified-utils/protocol';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Protocol = exports.Properties = exports.localizeProperties = exports.localizeOutputs = exports.localizeInputs = exports.localizeGroup = exports.localizeFields = exports.i18n = exports.validate = exports.fieldsSchema = exports.fieldSchema = exports.extract = exports.providerSchema = exports.triggerSchema = exports.triggerOutputSchema = exports.scheduledTriggerSchema = exports.immediateTriggerSchema = exports.eventSchema = exports.actionSchema = exports.actionOutputSchema = exports.staticCredentialsSchema = exports.sessionCredentialsSchema = exports.oauthCredentialsSchema = exports.credentialsSchema = exports.idSchema = exports.core = void 0;
|
|
3
|
+
exports.Protocol = exports.Properties = exports.localizeProperties = exports.localizeOutputs = exports.localizeInputs = exports.localizeGroup = exports.localizeFields = exports.i18n = exports.validate = exports.fieldsSchema = exports.fieldSchema = exports.extract = exports.alter = exports.providerSchema = exports.triggerSchema = exports.triggerOutputSchema = exports.scheduledTriggerSchema = exports.immediateTriggerSchema = exports.eventSchema = exports.actionSchema = exports.actionOutputSchema = exports.staticCredentialsSchema = exports.sessionCredentialsSchema = exports.oauthCredentialsSchema = exports.credentialsSchema = exports.idSchema = exports.core = void 0;
|
|
4
4
|
var core_1 = require("./core");
|
|
5
5
|
Object.defineProperty(exports, "core", { enumerable: true, get: function () { return core_1.core; } });
|
|
6
6
|
var commons_1 = require("./entities/commons");
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "triggerSchema", { enumerable: true, get: functio
|
|
|
21
21
|
var provider_1 = require("./entities/provider");
|
|
22
22
|
Object.defineProperty(exports, "providerSchema", { enumerable: true, get: function () { return provider_1.providerSchema; } });
|
|
23
23
|
var fields_1 = require("./minified-utils/fields");
|
|
24
|
+
Object.defineProperty(exports, "alter", { enumerable: true, get: function () { return fields_1.alter; } });
|
|
24
25
|
Object.defineProperty(exports, "extract", { enumerable: true, get: function () { return fields_1.extract; } });
|
|
25
26
|
Object.defineProperty(exports, "fieldSchema", { enumerable: true, get: function () { return fields_1.fieldSchema; } });
|
|
26
27
|
Object.defineProperty(exports, "fieldsSchema", { enumerable: true, get: function () { return fields_1.fieldsSchema; } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Field } from "./fields";
|
|
1
2
|
declare class Context {
|
|
2
|
-
|
|
3
|
-
readonly resolve: (source: any, data: Record<string, any>) => any;
|
|
3
|
+
readonly resolve: (field: Field, source: any, data: Record<string, any>) => any;
|
|
4
4
|
}
|
|
5
5
|
declare const context: Context;
|
|
6
6
|
export default context;
|
|
@@ -7,14 +7,14 @@ const handlebars_1 = __importDefault(require("handlebars"));
|
|
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
8
|
class Context {
|
|
9
9
|
constructor() {
|
|
10
|
-
this.resolve = (source, data) => {
|
|
11
|
-
let isObject = false;
|
|
12
|
-
let unresolved;
|
|
10
|
+
this.resolve = (field, source, data) => {
|
|
13
11
|
if (lodash_1.default.isNull(source) || lodash_1.default.isUndefined(source)) {
|
|
14
12
|
return source;
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
let serialized = false;
|
|
15
|
+
let unresolved;
|
|
16
|
+
if ((lodash_1.default.isObject(source) || lodash_1.default.isArray(source)) && field.type === `dict` && !field.items) {
|
|
17
|
+
serialized = true;
|
|
18
18
|
unresolved = JSON.stringify(source);
|
|
19
19
|
}
|
|
20
20
|
else if (typeof source === `string`) {
|
|
@@ -26,50 +26,43 @@ class Context {
|
|
|
26
26
|
if (!unresolved || !unresolved.includes(`{{`)) {
|
|
27
27
|
return source;
|
|
28
28
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
const clonedData = lodash_1.default.cloneDeep(data);
|
|
30
|
+
let resolved = unresolved;
|
|
31
|
+
const process = handlebars_1.default.parseWithoutProcessing(unresolved);
|
|
32
|
+
for (const x of process.body) {
|
|
33
|
+
if (x.type === `MustacheStatement`) {
|
|
34
|
+
const path = x[`path`].original;
|
|
35
|
+
const expression = x[`escaped`] ? `{{${path}}}` : `{{{${path}}}}`;
|
|
36
|
+
const get = lodash_1.default.get(clonedData, path);
|
|
37
|
+
if (get) {
|
|
38
|
+
const shouldInfer = field.as_array && lodash_1.default.isArray(get)
|
|
39
|
+
|| field.type === `dict` && lodash_1.default.isObject(get)
|
|
40
|
+
|| field.type === `file` && lodash_1.default.isObject(get)
|
|
41
|
+
|| field.type === `number` && typeof get === `number`
|
|
42
|
+
|| field.type === `string` && typeof get === `string`
|
|
43
|
+
|| field.type === `boolean` && typeof get === `boolean`
|
|
44
|
+
|| field.type === `any`;
|
|
45
|
+
if (unresolved === expression && shouldInfer) {
|
|
46
|
+
resolved = get;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (serialized) {
|
|
51
|
+
resolved = resolved.replace(`"${expression}"`, lodash_1.default.isObject(get) ? JSON.stringify(get) : `"${get}"`);
|
|
46
52
|
}
|
|
53
|
+
resolved = resolved.replace(expression, get);
|
|
47
54
|
}
|
|
48
55
|
}
|
|
56
|
+
else {
|
|
57
|
+
resolved = resolved.replace(expression, ``);
|
|
58
|
+
}
|
|
49
59
|
}
|
|
50
|
-
return this.resolve(isObject ? JSON.parse(unresolved) : unresolved, clonedData);
|
|
51
60
|
}
|
|
52
|
-
|
|
61
|
+
const parsed = serialized ? JSON.parse(resolved) : resolved;
|
|
62
|
+
return (typeof resolved === `string` ? resolved : JSON.stringify(resolved)).includes(`{{`) ?
|
|
63
|
+
this.resolve(field, parsed, clonedData)
|
|
64
|
+
: parsed;
|
|
53
65
|
};
|
|
54
|
-
handlebars_1.default.registerHelper({
|
|
55
|
-
eq: (v1, v2) => v1 === v2,
|
|
56
|
-
ne: (v1, v2) => v1 !== v2,
|
|
57
|
-
lt: (v1, v2) => v1 < v2,
|
|
58
|
-
gt: (v1, v2) => v1 > v2,
|
|
59
|
-
lte: (v1, v2) => v1 <= v2,
|
|
60
|
-
gte: (v1, v2) => v1 >= v2,
|
|
61
|
-
and: (v1, v2) => v1 && v2,
|
|
62
|
-
or: (v1, v2) => v1 || v2,
|
|
63
|
-
"+": (v1, v2) => v1 + v2,
|
|
64
|
-
"-": (v1, v2) => v1 - v2,
|
|
65
|
-
"*": (v1, v2) => v1 * v2,
|
|
66
|
-
"/": (v1, v2) => v1 / v2,
|
|
67
|
-
"sum": (v1, v2) => v1 + v2,
|
|
68
|
-
"subtract": (v1, v2) => v1 - v2,
|
|
69
|
-
"multiply": (v1, v2) => v1 * v2,
|
|
70
|
-
"divide": (v1, v2) => v1 / v2,
|
|
71
|
-
"abs": (v1) => Math.abs(v1),
|
|
72
|
-
});
|
|
73
66
|
}
|
|
74
67
|
}
|
|
75
68
|
const context = new Context();
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import Joi from "joi";
|
|
2
|
-
import { OneOf } from "./types";
|
|
3
2
|
import { Where } from "./where";
|
|
4
3
|
export type Condition = {
|
|
5
4
|
condition: Where;
|
|
6
|
-
} & OneOf<{
|
|
7
5
|
overrides: Partial<Field>;
|
|
8
|
-
|
|
9
|
-
}>;
|
|
6
|
+
};
|
|
10
7
|
type Common = {
|
|
11
8
|
key: string;
|
|
12
9
|
altered_by?: Condition[];
|
|
13
10
|
disabled?: boolean;
|
|
14
11
|
required?: boolean;
|
|
15
12
|
as_password?: boolean;
|
|
13
|
+
show?: boolean;
|
|
16
14
|
show_label?: boolean;
|
|
17
15
|
} & ({
|
|
18
16
|
as_array?: false;
|
|
@@ -30,6 +28,7 @@ type Any = string | number | boolean | Any[] | {
|
|
|
30
28
|
type AnyInputField<Extend = Record<never, never>> = {
|
|
31
29
|
type: `any`;
|
|
32
30
|
default?: Any | Any[];
|
|
31
|
+
allow_nullish?: boolean;
|
|
33
32
|
} & (SelectField<Any, Extend> | {});
|
|
34
33
|
type StringInputField<Extend = Record<never, never>> = {
|
|
35
34
|
type: `string`;
|
|
@@ -127,6 +126,7 @@ export type ValidationResult<Extended = Record<string, never>> = {
|
|
|
127
126
|
valid: boolean;
|
|
128
127
|
fields: Field<Extended & ValidationExtended>[];
|
|
129
128
|
};
|
|
129
|
+
export declare const alter: (field: Field, relative: string, data: Record<string, any>) => Field<Record<never, never>>;
|
|
130
130
|
export declare const validate: <I>(fields: Field<I>[], data: Record<string, any>, options?: {
|
|
131
131
|
secure?: boolean;
|
|
132
132
|
break?: boolean;
|
|
@@ -3,23 +3,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.extract = exports.validate = exports.fieldsSchema = exports.fieldSchema = void 0;
|
|
6
|
+
exports.extract = exports.validate = exports.alter = exports.fieldsSchema = exports.fieldSchema = void 0;
|
|
7
7
|
const joi_1 = __importDefault(require("joi"));
|
|
8
8
|
const lodash_1 = __importDefault(require("lodash"));
|
|
9
9
|
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
|
10
10
|
const context_1 = __importDefault(require("./context"));
|
|
11
11
|
const where_1 = require("./where");
|
|
12
12
|
const conditionSchema = () => joi_1.default.object({
|
|
13
|
-
condition: joi_1.default.object(),
|
|
14
|
-
overrides: joi_1.default.object(),
|
|
15
|
-
|
|
16
|
-
}).xor(`overrides`, `show`);
|
|
13
|
+
condition: joi_1.default.object().required(),
|
|
14
|
+
overrides: joi_1.default.object().required(),
|
|
15
|
+
});
|
|
17
16
|
const commonFieldSchema = () => joi_1.default.object({
|
|
18
17
|
key: joi_1.default.string().required(),
|
|
19
18
|
altered_by: joi_1.default.array().items(conditionSchema()),
|
|
20
19
|
disabled: joi_1.default.bool(),
|
|
21
20
|
required: joi_1.default.bool(),
|
|
22
21
|
as_password: joi_1.default.bool(),
|
|
22
|
+
show: joi_1.default.bool(),
|
|
23
23
|
show_label: joi_1.default.bool().default(true),
|
|
24
24
|
as_array: joi_1.default.bool(),
|
|
25
25
|
array_min: joi_1.default.when(`as_array`, {
|
|
@@ -152,6 +152,29 @@ const fileSchema = () => joi_1.default.object({
|
|
|
152
152
|
type: joi_1.default.string().required(),
|
|
153
153
|
content: joi_1.default.string().required(),
|
|
154
154
|
});
|
|
155
|
+
const fileReplacer = (field) => ({
|
|
156
|
+
key: field.key,
|
|
157
|
+
type: `dict`,
|
|
158
|
+
items: [
|
|
159
|
+
{
|
|
160
|
+
key: `name`,
|
|
161
|
+
type: `string`,
|
|
162
|
+
required: true,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: `type`,
|
|
166
|
+
type: `string`,
|
|
167
|
+
required: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
key: `content`,
|
|
171
|
+
type: `string`,
|
|
172
|
+
required: true,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
default: field.default,
|
|
176
|
+
...lodash_1.default.pick(field, [`altered_by`, `disabled`, `required`, `as_password`, `show`, `show_label`, `as_array`, `array_min`, `array_max`, `array_length`]),
|
|
177
|
+
});
|
|
155
178
|
const fileInputFieldSchema = () => joi_1.default.object({
|
|
156
179
|
default: defaultSchema(fileSchema()),
|
|
157
180
|
accept: joi_1.default.alternatives(joi_1.default.string(), joi_1.default.array().items(joi_1.default.string())).optional(),
|
|
@@ -204,11 +227,17 @@ const fieldSchema = () => inputFieldBaseSchema()
|
|
|
204
227
|
exports.fieldSchema = fieldSchema;
|
|
205
228
|
const fieldsSchema = () => joi_1.default.array().items((0, exports.fieldSchema)());
|
|
206
229
|
exports.fieldsSchema = fieldsSchema;
|
|
207
|
-
const
|
|
230
|
+
const getSimpleFieldSchema = (field) => {
|
|
231
|
+
if (field.as_array) {
|
|
232
|
+
throw new Error(`Cannot use as_array with simple field schema generator`);
|
|
233
|
+
}
|
|
208
234
|
let typeSchema;
|
|
209
235
|
switch (field.type) {
|
|
210
236
|
case `any`:
|
|
211
237
|
typeSchema = anySchema();
|
|
238
|
+
if (field.allow_nullish) {
|
|
239
|
+
typeSchema = typeSchema.allow(null, ``, 0, undefined);
|
|
240
|
+
}
|
|
212
241
|
break;
|
|
213
242
|
case `boolean`:
|
|
214
243
|
typeSchema = joi_1.default.bool();
|
|
@@ -285,37 +314,13 @@ const getFieldSchema = (field) => {
|
|
|
285
314
|
}
|
|
286
315
|
break;
|
|
287
316
|
case `dict`:
|
|
288
|
-
let objSchema;
|
|
289
317
|
if (field.items) {
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
objSchema = joi_1.default.object().unknown(true);
|
|
318
|
+
throw new Error(`No simple field`);
|
|
294
319
|
}
|
|
295
|
-
typeSchema = joi_1.default.
|
|
296
|
-
if (!helpers.original && !field.required) {
|
|
297
|
-
return helpers.original;
|
|
298
|
-
}
|
|
299
|
-
const parsed = JSON.parse(value);
|
|
300
|
-
const { value: objValue, error } = objSchema.validate(parsed, { abortEarly: false });
|
|
301
|
-
if (error) {
|
|
302
|
-
throw error;
|
|
303
|
-
}
|
|
304
|
-
return objValue;
|
|
305
|
-
}));
|
|
320
|
+
typeSchema = joi_1.default.object().unknown(true);
|
|
306
321
|
break;
|
|
307
322
|
case `file`:
|
|
308
|
-
typeSchema =
|
|
309
|
-
if (!helpers.original && !field.required) {
|
|
310
|
-
return helpers.original;
|
|
311
|
-
}
|
|
312
|
-
const parsed = JSON.parse(value);
|
|
313
|
-
const { value: fileValue, error } = fileSchema().validate(parsed, { abortEarly: false });
|
|
314
|
-
if (error) {
|
|
315
|
-
throw error;
|
|
316
|
-
}
|
|
317
|
-
return fileValue;
|
|
318
|
-
}));
|
|
323
|
+
typeSchema = fileSchema();
|
|
319
324
|
break;
|
|
320
325
|
}
|
|
321
326
|
if (`format` in field && field.format === `select`) {
|
|
@@ -327,77 +332,162 @@ const getFieldSchema = (field) => {
|
|
|
327
332
|
}
|
|
328
333
|
}
|
|
329
334
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (field.array_min) {
|
|
336
|
-
arraySchema = arraySchema.min(field.array_min);
|
|
337
|
-
}
|
|
338
|
-
if (field.array_max) {
|
|
339
|
-
arraySchema = arraySchema.max(field.array_max);
|
|
340
|
-
}
|
|
341
|
-
typeSchema = joi_1.default.alternatives(arraySchema, joi_1.default.string().external(async (value, helpers) => {
|
|
342
|
-
if (!helpers.original && !field.required) {
|
|
343
|
-
return helpers.original;
|
|
344
|
-
}
|
|
345
|
-
const parsed = JSON.parse(value);
|
|
346
|
-
const { value: arrValue, error } = arraySchema.validate(parsed, { abortEarly: false });
|
|
347
|
-
if (error) {
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
return arrValue;
|
|
351
|
-
}));
|
|
335
|
+
return getGeneralSchema(field, typeSchema);
|
|
336
|
+
};
|
|
337
|
+
const getArrayFieldSchema = (field, items) => {
|
|
338
|
+
if (!field.as_array) {
|
|
339
|
+
throw new Error(`as_array required for array schema generator`);
|
|
352
340
|
}
|
|
353
|
-
|
|
354
|
-
|
|
341
|
+
let schema = joi_1.default.array().ordered(...items);
|
|
342
|
+
if (field.array_length) {
|
|
343
|
+
schema = schema.length(field.array_length);
|
|
355
344
|
}
|
|
356
|
-
|
|
345
|
+
if (field.array_min) {
|
|
346
|
+
schema = schema.min(field.array_min);
|
|
347
|
+
}
|
|
348
|
+
if (field.array_max) {
|
|
349
|
+
schema = schema.max(field.array_max);
|
|
350
|
+
}
|
|
351
|
+
return getGeneralSchema(field, schema);
|
|
357
352
|
};
|
|
358
|
-
const
|
|
353
|
+
const getObjectFieldSchema = (field, items) => {
|
|
354
|
+
if (field.type !== `dict`) {
|
|
355
|
+
throw new Error(`type must be dict for object schema generator`);
|
|
356
|
+
}
|
|
357
|
+
if (!field.items) {
|
|
358
|
+
throw new Error(`items required for object schema generator`);
|
|
359
|
+
}
|
|
359
360
|
let schema = joi_1.default.object();
|
|
360
|
-
for (const
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
for (const item of items) {
|
|
362
|
+
schema = schema.concat(joi_1.default.object({
|
|
363
|
+
[item.key]: item.schema,
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
if (field.unknown) {
|
|
367
|
+
schema = schema.unknown(field.unknown);
|
|
368
|
+
}
|
|
369
|
+
return getGeneralSchema(field, schema);
|
|
370
|
+
};
|
|
371
|
+
const getGeneralSchema = (field, schema) => {
|
|
372
|
+
if (field.required) {
|
|
373
|
+
schema = schema.required();
|
|
374
|
+
}
|
|
375
|
+
if (field.disabled) {
|
|
376
|
+
schema = schema.forbidden();
|
|
363
377
|
}
|
|
364
378
|
return schema;
|
|
365
379
|
};
|
|
366
380
|
const convertPath = (parts) => parts.map((p, i) => typeof p === `string` ? (i === 0 ? p : `.${p}`) : `[${p}]`).join(``);
|
|
367
381
|
const secure = async (field, validation) => field.as_password ? lodash_1.default.omit(validation, [`value`, `original`]) : validation;
|
|
368
|
-
const
|
|
382
|
+
const relative_condition = (condition, relative) => {
|
|
369
383
|
try {
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
384
|
+
const newCondition = lodash_1.default.cloneDeep(condition);
|
|
385
|
+
for (const key of Object.keys(newCondition)) {
|
|
386
|
+
if (key.startsWith(`@relative`)) {
|
|
387
|
+
const newKey = key.replace(`@relative`, relative ? relative : ``);
|
|
388
|
+
newCondition[newKey] = newCondition[key];
|
|
389
|
+
delete newCondition[key];
|
|
375
390
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
}
|
|
392
|
+
return newCondition;
|
|
393
|
+
}
|
|
394
|
+
catch (e) {
|
|
395
|
+
throw e;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const alter = (field, relative, data) => {
|
|
399
|
+
const cloned = lodash_1.default.cloneDeep(field);
|
|
400
|
+
for (const alteration of cloned.altered_by) {
|
|
401
|
+
const condition = relative_condition(alteration.condition, relative);
|
|
402
|
+
const matches = (0, where_1.where)(data, [condition]);
|
|
403
|
+
if (matches) {
|
|
404
|
+
lodash_1.default.assign(cloned, alteration.overrides);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return cloned;
|
|
408
|
+
};
|
|
409
|
+
exports.alter = alter;
|
|
410
|
+
const recursive_schema = async (field, data, options) => {
|
|
411
|
+
try {
|
|
412
|
+
let newField = lodash_1.default.cloneDeep(field);
|
|
413
|
+
if (newField.altered_by?.length > 0) {
|
|
414
|
+
newField = (0, exports.alter)(newField, options.relative, data);
|
|
415
|
+
if (options.alter) {
|
|
416
|
+
delete newField.altered_by;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const alteredField = lodash_1.default.cloneDeep(newField);
|
|
420
|
+
if (newField.type === `file`) {
|
|
421
|
+
newField = fileReplacer(newField);
|
|
422
|
+
}
|
|
423
|
+
const relativeKey = options.relative ?
|
|
424
|
+
options.relative.endsWith(`]`) ?
|
|
425
|
+
options.array ? options.relative : `${options.relative}.${newField.key}`
|
|
426
|
+
: `${options.relative}.${newField.key}`
|
|
427
|
+
: newField.key;
|
|
428
|
+
const value = lodash_1.default.get(data, relativeKey, newField.default);
|
|
429
|
+
const resolved = context_1.default.resolve(newField, value, options.context);
|
|
430
|
+
lodash_1.default.set(data, relativeKey, resolved);
|
|
431
|
+
let calculatedSchema;
|
|
432
|
+
if (newField.as_array && resolved) {
|
|
433
|
+
const itemSchemas = [];
|
|
434
|
+
if (lodash_1.default.isArray(resolved)) {
|
|
435
|
+
for (let i = 0; i < (resolved || []).length; i++) {
|
|
436
|
+
const { schema: itemSchema } = await recursive_schema(lodash_1.default.omit(newField, [`as_array`, `array_min`, `array_max`, `array_length`]), data, { alter: false, relative: `${relativeKey}[${i}]`, array: true, context: options.context });
|
|
437
|
+
if (itemSchema) {
|
|
438
|
+
itemSchemas.push(itemSchema);
|
|
387
439
|
}
|
|
388
440
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
441
|
+
}
|
|
442
|
+
calculatedSchema = getArrayFieldSchema(newField, itemSchemas);
|
|
443
|
+
}
|
|
444
|
+
else if (newField.type === `dict` && newField.items) {
|
|
445
|
+
const items = [];
|
|
446
|
+
if (lodash_1.default.isObject(resolved)) {
|
|
447
|
+
for (const item of newField.items) {
|
|
448
|
+
const recursive = await recursive_schema(item, data, { alter: options.alter && true, relative: relativeKey, context: options.context });
|
|
449
|
+
if (recursive) {
|
|
450
|
+
items.push({ key: item.key, schema: recursive.schema, field: recursive.field });
|
|
451
|
+
}
|
|
392
452
|
}
|
|
393
453
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
454
|
+
calculatedSchema = getObjectFieldSchema(newField, items);
|
|
455
|
+
newField.items = items.map((i) => i.field);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
calculatedSchema = getSimpleFieldSchema(newField);
|
|
459
|
+
}
|
|
460
|
+
if (!calculatedSchema) {
|
|
461
|
+
throw new Error(`Could not resolve schema for field ${newField.key}`);
|
|
462
|
+
}
|
|
463
|
+
if (typeof newField.show === `boolean` && !newField.show) {
|
|
464
|
+
lodash_1.default.unset(data, relativeKey);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
else if (newField.disabled) {
|
|
468
|
+
lodash_1.default.unset(data, relativeKey);
|
|
469
|
+
}
|
|
470
|
+
return { schema: calculatedSchema, field: field.type === `file` ? alteredField : newField };
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
throw e;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const validate_internal = async (fields, data, options) => {
|
|
477
|
+
try {
|
|
478
|
+
const response = { valid: true, fields: [] };
|
|
479
|
+
for (const field of fields) {
|
|
480
|
+
const value = lodash_1.default.get(data, field.key, field.default);
|
|
481
|
+
const cloned = lodash_1.default.cloneDeep(data);
|
|
482
|
+
const recursive = await recursive_schema(field, cloned, { alter: true, context: options.context });
|
|
483
|
+
if (!recursive) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const resolved = lodash_1.default.get(cloned, field.key, field.default);
|
|
397
487
|
let validation;
|
|
398
488
|
let error;
|
|
399
489
|
try {
|
|
400
|
-
validation = await schema.validateAsync(resolved, { abortEarly: false });
|
|
490
|
+
validation = await recursive.schema.validateAsync(resolved, { abortEarly: false });
|
|
401
491
|
}
|
|
402
492
|
catch (e) {
|
|
403
493
|
error = e;
|
|
@@ -422,10 +512,9 @@ const validate_internal = async (fields, data, options) => {
|
|
|
422
512
|
value: validation,
|
|
423
513
|
};
|
|
424
514
|
}
|
|
425
|
-
lodash_1.default.set(clonedData, field.key, validation);
|
|
426
515
|
response.valid && (response.valid = error ? false : true);
|
|
427
516
|
response.fields.push({
|
|
428
|
-
...field,
|
|
517
|
+
...recursive.field,
|
|
429
518
|
...options.secure ? await secure(field, extended) : extended,
|
|
430
519
|
});
|
|
431
520
|
}
|
package/package.json
CHANGED
package/src/entities/events.ts
CHANGED
|
@@ -33,9 +33,10 @@ type EventConfig = Pick<CoreConfig, `credentials` | `outputs`> & {
|
|
|
33
33
|
outputs: string[],
|
|
34
34
|
},
|
|
35
35
|
sources?: string[],
|
|
36
|
+
fields?: Record<string, any>,
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
type CommonEvents<Config extends EventConfig = {}> = Common<Config> & {
|
|
39
|
+
type CommonEvents<Config extends EventConfig = { fields: Record<string, any> }> = Common<Config> & {
|
|
39
40
|
model: `event`,
|
|
40
41
|
categorization: {
|
|
41
42
|
department: string,
|
|
@@ -59,75 +60,75 @@ export type Request = {
|
|
|
59
60
|
body: any,
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
export type ImmediateTrigger<Config extends EventConfig = {}> = CommonEvents<Config> & {
|
|
63
|
+
export type ImmediateTrigger<Config extends EventConfig = { fields: Record<string, any> }> = CommonEvents<Config> & {
|
|
63
64
|
type: `trigger`,
|
|
64
65
|
immediate: true,
|
|
65
66
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>],
|
|
66
67
|
functions: {
|
|
67
68
|
mount?: CoreFunction<{
|
|
68
69
|
credentials: Config[`credentials`],
|
|
69
|
-
inputs: { fields:
|
|
70
|
+
inputs: { fields: Config[`fields`], link: string },
|
|
70
71
|
outputs: { done: boolean, setup?: any },
|
|
71
72
|
}>,
|
|
72
73
|
unmount?: CoreFunction<{
|
|
73
74
|
credentials: Config[`credentials`],
|
|
74
|
-
inputs: { fields:
|
|
75
|
+
inputs: { fields: Config[`fields`], link: string, setup?: any },
|
|
75
76
|
outputs: { done: boolean },
|
|
76
77
|
}>,
|
|
77
78
|
challenge?: CoreFunction<{
|
|
78
79
|
credentials: Config[`credentials`],
|
|
79
|
-
inputs: { fields:
|
|
80
|
+
inputs: { fields: Config[`fields`], link: string, request: Request, setup?: any },
|
|
80
81
|
outputs: { done: boolean, response?: any },
|
|
81
82
|
}>,
|
|
82
83
|
handle: CoreFunction<{
|
|
83
84
|
credentials: Config[`credentials`],
|
|
84
|
-
inputs: { fields:
|
|
85
|
+
inputs: { fields: Config[`fields`], request: Request, setup?: any },
|
|
85
86
|
outputs: { records: Config[`outputs`][], flags: Partial<Record<Config[`flags`][`outputs`][number], true>> },
|
|
86
87
|
}>,
|
|
87
88
|
fire?: CoreFunction<{
|
|
88
89
|
credentials: Config[`credentials`],
|
|
89
|
-
inputs: { fields:
|
|
90
|
+
inputs: { fields: Config[`fields`], link: string, setup?: any },
|
|
90
91
|
outputs: { done: boolean },
|
|
91
92
|
}>,
|
|
92
93
|
poll?: CoreFunction<{
|
|
93
94
|
credentials: Config[`credentials`],
|
|
94
|
-
inputs: { fields:
|
|
95
|
+
inputs: { fields: Config[`fields`], last_schedule?: string, limit?: number },
|
|
95
96
|
outputs: { records: Config[`outputs`][], flags: Partial<Record<Config[`flags`][`outputs`][number], true>> },
|
|
96
97
|
}>,
|
|
97
98
|
},
|
|
98
99
|
};
|
|
99
100
|
|
|
100
|
-
export type ScheduledTrigger<Config extends EventConfig = {}> = CommonEvents<Config> & {
|
|
101
|
+
export type ScheduledTrigger<Config extends EventConfig = { fields: Record<string, any> }> = CommonEvents<Config> & {
|
|
101
102
|
type: `trigger`,
|
|
102
103
|
immediate: false,
|
|
103
104
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>],
|
|
104
105
|
functions: {
|
|
105
106
|
poll: CoreFunction<{
|
|
106
107
|
credentials: Config[`credentials`],
|
|
107
|
-
inputs: { fields:
|
|
108
|
+
inputs: { fields: Config[`fields`], last_schedule?: string, limit?: number },
|
|
108
109
|
outputs: { records: Config[`outputs`][], flags: Partial<Record<Config[`flags`][`outputs`][number], true>> },
|
|
109
110
|
}>,
|
|
110
111
|
},
|
|
111
112
|
};
|
|
112
113
|
|
|
113
|
-
export type Trigger<Config extends EventConfig = {}> =
|
|
114
|
+
export type Trigger<Config extends EventConfig = { fields: Record<string, any> }> =
|
|
114
115
|
| ImmediateTrigger<Config>
|
|
115
116
|
| ScheduledTrigger<Config>;
|
|
116
117
|
|
|
117
|
-
export type Action<Config extends EventConfig = {}> = CommonEvents<Config> & {
|
|
118
|
+
export type Action<Config extends EventConfig = { fields: Record<string, any> }> = CommonEvents<Config> & {
|
|
118
119
|
type: `action`,
|
|
119
120
|
inputs: [DefaultConnectableEvent<Config[`flags`][`inputs`][number]>, ...AdditionalConnectableEvent<Config[`flags`][`inputs`][number]>[]],
|
|
120
121
|
outputs: [DefaultConnectableEvent<Config[`flags`][`outputs`][number]>, ...(AdditionalConnectableEvent<Config[`flags`][`outputs`][number]> | ErrorConnectableEvent<Config[`flags`][`outputs`][number]>)[]],
|
|
121
122
|
functions: {
|
|
122
123
|
execute: CoreFunction<{
|
|
123
124
|
credentials: Config[`credentials`],
|
|
124
|
-
inputs: { fields:
|
|
125
|
+
inputs: { fields: Config[`fields`], flags: Partial<Record<Config[`flags`][`inputs`][number], true>> },
|
|
125
126
|
outputs: { output: Config[`outputs`], flags: Partial<Record<Config[`flags`][`outputs`][number], true>> },
|
|
126
127
|
}>,
|
|
127
128
|
},
|
|
128
129
|
};
|
|
129
130
|
|
|
130
|
-
export type Event<Config extends EventConfig = {}> =
|
|
131
|
+
export type Event<Config extends EventConfig = { fields: Record<string, any> }> =
|
|
131
132
|
| Action<Config>
|
|
132
133
|
| Trigger<Config>;
|
|
133
134
|
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Credentials, credentialsSchema, OAuthCredentials, oauthCredentialsSchem
|
|
|
4
4
|
export { Action, actionOutputSchema, actionSchema, Event, eventSchema, ImmediateTrigger, immediateTriggerSchema, Request, ScheduledTrigger, scheduledTriggerSchema, Trigger, triggerOutputSchema, triggerSchema } from './entities/events';
|
|
5
5
|
export { Provider, providerSchema } from './entities/provider';
|
|
6
6
|
export { Source } from './entities/source';
|
|
7
|
-
export { extract, Field, fieldSchema, fieldsSchema, File, SelectFieldChoice, validate, ValidationResult } from './minified-utils/fields';
|
|
7
|
+
export { alter, extract, Field, fieldSchema, fieldsSchema, File, SelectFieldChoice, validate, ValidationResult } from './minified-utils/fields';
|
|
8
8
|
export { i18n, LocalizedField, LocalizedProperties, localizeFields, localizeGroup, localizeInputs, localizeOutputs, localizeProperties } from './minified-utils/locales';
|
|
9
9
|
export { Properties } from './minified-utils/properties';
|
|
10
10
|
export { Protocol } from './minified-utils/protocol';
|
|
@@ -1,43 +1,16 @@
|
|
|
1
1
|
import Handlebars from "handlebars";
|
|
2
2
|
import _ from "lodash";
|
|
3
|
+
import { Field } from "./fields";
|
|
3
4
|
|
|
4
5
|
class Context {
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// logic and math helpers
|
|
9
|
-
Handlebars.registerHelper({
|
|
10
|
-
eq: (v1: any, v2: any) => v1 === v2,
|
|
11
|
-
ne: (v1: any, v2: any) => v1 !== v2,
|
|
12
|
-
lt: (v1: any, v2: any) => v1 < v2,
|
|
13
|
-
gt: (v1: any, v2: any) => v1 > v2,
|
|
14
|
-
lte: (v1: any, v2: any) => v1 <= v2,
|
|
15
|
-
gte: (v1: any, v2: any) => v1 >= v2,
|
|
16
|
-
and: (v1: any, v2: any) => v1 && v2,
|
|
17
|
-
or: (v1: any, v2: any) => v1 || v2,
|
|
18
|
-
"+": (v1: number, v2: number) => v1 + v2,
|
|
19
|
-
"-": (v1: number, v2: number) => v1 - v2,
|
|
20
|
-
"*": (v1: number, v2: number) => v1 * v2,
|
|
21
|
-
"/": (v1: number, v2: number) => v1 / v2,
|
|
22
|
-
"sum": (v1: number, v2: number) => v1 + v2,
|
|
23
|
-
"subtract": (v1: number, v2: number) => v1 - v2,
|
|
24
|
-
"multiply": (v1: number, v2: number) => v1 * v2,
|
|
25
|
-
"divide": (v1: number, v2: number) => v1 / v2,
|
|
26
|
-
"abs": (v1: number) => Math.abs(v1),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// TODO: register helpers
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public readonly resolve = (source: any, data: Record<string, any>) => {
|
|
34
|
-
let isObject = false;
|
|
35
|
-
let unresolved: string;
|
|
36
|
-
|
|
7
|
+
public readonly resolve = (field: Field, source: any, data: Record<string, any>) => {
|
|
37
8
|
if (_.isNull(source) || _.isUndefined(source)) { return source; }
|
|
38
9
|
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
let serialized = false;
|
|
11
|
+
let unresolved: string;
|
|
12
|
+
if ((_.isObject(source) || _.isArray(source)) && field.type === `dict` && !field.items) {
|
|
13
|
+
serialized = true;
|
|
41
14
|
unresolved = JSON.stringify(source);
|
|
42
15
|
} else if (typeof source === `string`) {
|
|
43
16
|
unresolved = source;
|
|
@@ -45,32 +18,44 @@ class Context {
|
|
|
45
18
|
|
|
46
19
|
if (!unresolved || !unresolved.includes(`{{`)) { return source; }
|
|
47
20
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
21
|
+
const clonedData = _.cloneDeep(data);
|
|
22
|
+
|
|
23
|
+
let resolved: any = unresolved;
|
|
24
|
+
const process = Handlebars.parseWithoutProcessing(unresolved);
|
|
25
|
+
for (const x of process.body) {
|
|
26
|
+
if (x.type === `MustacheStatement`) {
|
|
27
|
+
const path = x[`path`].original;
|
|
28
|
+
const expression = x[`escaped`] ? `{{${path}}}` : `{{{${path}}}}`;
|
|
29
|
+
const get = _.get(clonedData, path);
|
|
30
|
+
|
|
31
|
+
if (get) {
|
|
32
|
+
const shouldInfer =
|
|
33
|
+
field.as_array && _.isArray(get)
|
|
34
|
+
|| field.type === `dict` && _.isObject(get)
|
|
35
|
+
|| field.type === `file` && _.isObject(get)
|
|
36
|
+
|| field.type === `number` && typeof get === `number`
|
|
37
|
+
|| field.type === `string` && typeof get === `string`
|
|
38
|
+
|| field.type === `boolean` && typeof get === `boolean`
|
|
39
|
+
|| field.type === `any`;
|
|
40
|
+
|
|
41
|
+
if (unresolved === expression && shouldInfer) {
|
|
42
|
+
resolved = get;
|
|
43
|
+
break;
|
|
44
|
+
} else {
|
|
45
|
+
if (serialized) { resolved = resolved.replace(`"${expression}"`, _.isObject(get) ? JSON.stringify(get) : `"${get}"`); }
|
|
46
|
+
resolved = resolved.replace(expression, get);
|
|
65
47
|
|
|
66
48
|
}
|
|
67
|
-
}
|
|
49
|
+
} else { resolved = resolved.replace(expression, ``); }
|
|
68
50
|
}
|
|
69
|
-
|
|
70
|
-
return this.resolve(isObject ? JSON.parse(unresolved) : unresolved, clonedData);
|
|
71
51
|
}
|
|
72
52
|
|
|
73
|
-
|
|
53
|
+
const parsed = serialized ? JSON.parse(resolved) : resolved;
|
|
54
|
+
|
|
55
|
+
// recursive resolution
|
|
56
|
+
return (typeof resolved === `string` ? resolved : JSON.stringify(resolved)).includes(`{{`) ?
|
|
57
|
+
this.resolve(field, parsed, clonedData)
|
|
58
|
+
: parsed;
|
|
74
59
|
};
|
|
75
60
|
|
|
76
61
|
}
|
|
@@ -2,15 +2,12 @@ import Joi, { DateSchema, NumberSchema, StringSchema } from "joi";
|
|
|
2
2
|
import _ from "lodash";
|
|
3
3
|
import moment from "moment-timezone";
|
|
4
4
|
import context from "./context";
|
|
5
|
-
import { OneOf } from "./types";
|
|
6
5
|
import { where, Where } from "./where";
|
|
7
6
|
|
|
8
7
|
export type Condition = {
|
|
9
8
|
condition: Where,
|
|
10
|
-
} & OneOf<{
|
|
11
9
|
overrides: Partial<Field>,
|
|
12
|
-
|
|
13
|
-
}>;
|
|
10
|
+
};
|
|
14
11
|
|
|
15
12
|
type Common = {
|
|
16
13
|
key: string,
|
|
@@ -18,6 +15,7 @@ type Common = {
|
|
|
18
15
|
disabled?: boolean,
|
|
19
16
|
required?: boolean,
|
|
20
17
|
as_password?: boolean,
|
|
18
|
+
show?: boolean,
|
|
21
19
|
show_label?: boolean,
|
|
22
20
|
} & ({
|
|
23
21
|
as_array?: false,
|
|
@@ -47,6 +45,7 @@ type Any = string | number | boolean | Any[] | { [key: string]: Any };
|
|
|
47
45
|
type AnyInputField<Extend = Record<never, never>> = {
|
|
48
46
|
type: `any`,
|
|
49
47
|
default?: Any | Any[],
|
|
48
|
+
allow_nullish?: boolean, // allow null values
|
|
50
49
|
} & (
|
|
51
50
|
| SelectField<Any, Extend>
|
|
52
51
|
| {}
|
|
@@ -181,10 +180,9 @@ type ValidationExtended = {
|
|
|
181
180
|
|
|
182
181
|
const conditionSchema = () =>
|
|
183
182
|
Joi.object({
|
|
184
|
-
condition: Joi.object(),
|
|
185
|
-
overrides: Joi.object(),
|
|
186
|
-
|
|
187
|
-
}).xor(`overrides`, `show`);
|
|
183
|
+
condition: Joi.object().required(),
|
|
184
|
+
overrides: Joi.object().required(),
|
|
185
|
+
});
|
|
188
186
|
|
|
189
187
|
const commonFieldSchema = () =>
|
|
190
188
|
Joi.object({
|
|
@@ -193,6 +191,7 @@ const commonFieldSchema = () =>
|
|
|
193
191
|
disabled: Joi.bool(),
|
|
194
192
|
required: Joi.bool(),
|
|
195
193
|
as_password: Joi.bool(),
|
|
194
|
+
show: Joi.bool(),
|
|
196
195
|
show_label: Joi.bool().default(true),
|
|
197
196
|
as_array: Joi.bool(),
|
|
198
197
|
array_min: Joi.when(`as_array`, {
|
|
@@ -363,6 +362,31 @@ const fileSchema = () => Joi.object({
|
|
|
363
362
|
content: Joi.string().required(), // base64 encoded content
|
|
364
363
|
});
|
|
365
364
|
|
|
365
|
+
// ÑAPA: translate file to structured dict
|
|
366
|
+
const fileReplacer = (field: Common & FileInputField): Field => ({
|
|
367
|
+
key: field.key,
|
|
368
|
+
type: `dict`,
|
|
369
|
+
items: [
|
|
370
|
+
{
|
|
371
|
+
key: `name`,
|
|
372
|
+
type: `string`,
|
|
373
|
+
required: true,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
key: `type`,
|
|
377
|
+
type: `string`,
|
|
378
|
+
required: true,
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
key: `content`,
|
|
382
|
+
type: `string`,
|
|
383
|
+
required: true,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
default: field.default,
|
|
387
|
+
..._.pick(field, [`altered_by`, `disabled`, `required`, `as_password`, `show`, `show_label`, `as_array`, `array_min`, `array_max`, `array_length`]),
|
|
388
|
+
}) as Field;
|
|
389
|
+
|
|
366
390
|
const fileInputFieldSchema = () =>
|
|
367
391
|
Joi.object({
|
|
368
392
|
default: defaultSchema(fileSchema()),
|
|
@@ -425,11 +449,15 @@ export const fieldSchema = () =>
|
|
|
425
449
|
export const fieldsSchema = () =>
|
|
426
450
|
Joi.array().items(fieldSchema());
|
|
427
451
|
|
|
428
|
-
const
|
|
452
|
+
const getSimpleFieldSchema = (field: Field) => {
|
|
453
|
+
if (field.as_array) { throw new Error(`Cannot use as_array with simple field schema generator`); }
|
|
454
|
+
|
|
429
455
|
let typeSchema: Joi.Schema;
|
|
430
456
|
switch (field.type) {
|
|
431
457
|
case `any`:
|
|
432
458
|
typeSchema = anySchema();
|
|
459
|
+
// allow null values for any type
|
|
460
|
+
if (field.allow_nullish) { typeSchema = typeSchema.allow(null, ``, 0, undefined); }
|
|
433
461
|
break;
|
|
434
462
|
case `boolean`:
|
|
435
463
|
typeSchema = Joi.bool();
|
|
@@ -486,43 +514,11 @@ const getFieldSchema = (field: Field) => {
|
|
|
486
514
|
}
|
|
487
515
|
break;
|
|
488
516
|
case `dict`:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
objSchema = getFieldsSchema(field.items).unknown(field.unknown || false);
|
|
492
|
-
} else {
|
|
493
|
-
objSchema = Joi.object().unknown(true);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
typeSchema = Joi.alternatives(
|
|
497
|
-
objSchema,
|
|
498
|
-
// support for stringified objects
|
|
499
|
-
Joi.string().external(async (value, helpers) => {
|
|
500
|
-
if (!helpers.original && !field.required) { return helpers.original; }
|
|
501
|
-
|
|
502
|
-
const parsed = JSON.parse(value);
|
|
503
|
-
const { value: objValue, error } = objSchema.validate(parsed, { abortEarly: false });
|
|
504
|
-
if (error) { throw error; }
|
|
505
|
-
|
|
506
|
-
return objValue;
|
|
507
|
-
})
|
|
508
|
-
);
|
|
509
|
-
|
|
517
|
+
if (field.items) { throw new Error(`No simple field`); }
|
|
518
|
+
typeSchema = Joi.object().unknown(true);
|
|
510
519
|
break;
|
|
511
520
|
case `file`:
|
|
512
|
-
typeSchema =
|
|
513
|
-
fileSchema(),
|
|
514
|
-
// support for stringified objects
|
|
515
|
-
Joi.string().external(async (value, helpers) => {
|
|
516
|
-
if (!helpers.original && !field.required) { return helpers.original; }
|
|
517
|
-
|
|
518
|
-
const parsed = JSON.parse(value);
|
|
519
|
-
const { value: fileValue, error } = fileSchema().validate(parsed, { abortEarly: false });
|
|
520
|
-
if (error) { throw error; }
|
|
521
|
-
|
|
522
|
-
return fileValue;
|
|
523
|
-
})
|
|
524
|
-
);
|
|
525
|
-
|
|
521
|
+
typeSchema = fileSchema();
|
|
526
522
|
break;
|
|
527
523
|
}
|
|
528
524
|
|
|
@@ -536,41 +532,40 @@ const getFieldSchema = (field: Field) => {
|
|
|
536
532
|
}
|
|
537
533
|
}
|
|
538
534
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (field.array_length) { arraySchema = arraySchema.length(field.array_length); }
|
|
543
|
-
if (field.array_min) { arraySchema = arraySchema.min(field.array_min); }
|
|
544
|
-
if (field.array_max) { arraySchema = arraySchema.max(field.array_max); }
|
|
545
|
-
|
|
546
|
-
typeSchema = Joi.alternatives(
|
|
547
|
-
arraySchema,
|
|
548
|
-
// support for stringified arrays
|
|
549
|
-
Joi.string().external(async (value, helpers) => {
|
|
550
|
-
if (!helpers.original && !field.required) { return helpers.original; }
|
|
535
|
+
return getGeneralSchema(field, typeSchema);
|
|
536
|
+
};
|
|
551
537
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (error) { throw error; }
|
|
538
|
+
const getArrayFieldSchema = (field: Field, items: Joi.Schema[]) => {
|
|
539
|
+
if (!field.as_array) { throw new Error(`as_array required for array schema generator`); }
|
|
555
540
|
|
|
556
|
-
|
|
557
|
-
})
|
|
558
|
-
);
|
|
559
|
-
}
|
|
541
|
+
let schema = Joi.array().ordered(...items);
|
|
560
542
|
|
|
561
|
-
if (field.
|
|
543
|
+
if (field.array_length) { schema = schema.length(field.array_length); }
|
|
544
|
+
if (field.array_min) { schema = schema.min(field.array_min); }
|
|
545
|
+
if (field.array_max) { schema = schema.max(field.array_max); }
|
|
562
546
|
|
|
563
|
-
return field
|
|
547
|
+
return getGeneralSchema(field, schema);
|
|
564
548
|
};
|
|
565
549
|
|
|
566
|
-
const
|
|
567
|
-
|
|
550
|
+
const getObjectFieldSchema = (field: Field, items: { key: string, schema: Joi.Schema }[]) => {
|
|
551
|
+
if (field.type !== `dict`) { throw new Error(`type must be dict for object schema generator`); }
|
|
552
|
+
if (!field.items) { throw new Error(`items required for object schema generator`); }
|
|
568
553
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
schema = schema.concat(Joi.object({
|
|
554
|
+
let schema = Joi.object();
|
|
555
|
+
for (const item of items) {
|
|
556
|
+
schema = schema.concat(Joi.object({
|
|
557
|
+
[item.key]: item.schema,
|
|
558
|
+
}));
|
|
572
559
|
}
|
|
573
560
|
|
|
561
|
+
if (field.unknown) { schema = schema.unknown(field.unknown); }
|
|
562
|
+
|
|
563
|
+
return getGeneralSchema(field, schema);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const getGeneralSchema = (field: Field, schema: Joi.Schema): Joi.Schema => {
|
|
567
|
+
if (field.required) { schema = schema.required(); }
|
|
568
|
+
if (field.disabled) { schema = schema.forbidden(); }
|
|
574
569
|
return schema;
|
|
575
570
|
};
|
|
576
571
|
|
|
@@ -585,45 +580,125 @@ export type ValidationResult<Extended = Record<string, never>> = {
|
|
|
585
580
|
fields: Field<Extended & ValidationExtended>[],
|
|
586
581
|
};
|
|
587
582
|
|
|
588
|
-
const
|
|
583
|
+
const relative_condition = (condition: Where, relative: string): typeof condition => {
|
|
589
584
|
try {
|
|
590
|
-
const
|
|
585
|
+
const newCondition = _.cloneDeep(condition);
|
|
586
|
+
for (const key of Object.keys(newCondition)) {
|
|
587
|
+
if (key.startsWith(`@relative`)) {
|
|
588
|
+
const newKey = key.replace(`@relative`, relative ? relative : ``);
|
|
589
|
+
newCondition[newKey] = newCondition[key];
|
|
590
|
+
delete newCondition[key];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return newCondition;
|
|
595
|
+
} catch (e) { throw e; }
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
export const alter = (field: Field, relative: string, data: Record<string, any>) => {
|
|
599
|
+
const cloned = _.cloneDeep(field);
|
|
600
|
+
for (const alteration of cloned.altered_by) {
|
|
601
|
+
const condition = relative_condition(alteration.condition, relative);
|
|
602
|
+
|
|
603
|
+
const matches = where(data, [condition]);
|
|
604
|
+
if (matches) { _.assign(cloned, alteration.overrides); }
|
|
605
|
+
}
|
|
606
|
+
return cloned;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const recursive_schema = async (field: Field, data: Record<string, any>, options: { alter: boolean, relative?: string, array?: boolean, context: Record<string, any> }): Promise<{ schema: Joi.Schema, field: Field }> => {
|
|
610
|
+
try {
|
|
611
|
+
let newField: Field = _.cloneDeep(field);
|
|
612
|
+
|
|
613
|
+
// alterations over already resolved data (previous fields)
|
|
614
|
+
if (newField.altered_by?.length > 0) {
|
|
615
|
+
newField = alter(newField, options.relative, data);
|
|
591
616
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
617
|
+
// remove altered_by only if it's not an array -> array alterations must be kept for front-end rendering
|
|
618
|
+
if (options.alter) { delete newField.altered_by; }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const alteredField: Field = _.cloneDeep(newField);
|
|
622
|
+
|
|
623
|
+
// ÑAPA: if the field is a file, replace it
|
|
624
|
+
if (newField.type === `file`) { newField = fileReplacer(newField); }
|
|
625
|
+
|
|
626
|
+
const relativeKey = options.relative ?
|
|
627
|
+
options.relative.endsWith(`]`) ?
|
|
628
|
+
options.array ? options.relative : `${options.relative}.${newField.key}`
|
|
629
|
+
: `${options.relative}.${newField.key}`
|
|
630
|
+
: newField.key;
|
|
631
|
+
|
|
632
|
+
const value = _.get(data, relativeKey, newField.default);
|
|
633
|
+
|
|
634
|
+
// resolve value with context (even if it's not valid)
|
|
635
|
+
const resolved = context.resolve(newField, value, options.context);
|
|
636
|
+
_.set(data, relativeKey, resolved);
|
|
637
|
+
|
|
638
|
+
// get schema for field with data
|
|
639
|
+
let calculatedSchema: Joi.Schema;
|
|
640
|
+
if (newField.as_array && resolved) {
|
|
641
|
+
const itemSchemas: Joi.Schema[] = [];
|
|
642
|
+
if (_.isArray(resolved)) {
|
|
643
|
+
for (let i = 0; i < (resolved || []).length; i++) {
|
|
644
|
+
const { schema: itemSchema } = await recursive_schema(
|
|
645
|
+
_.omit(newField, [`as_array`, `array_min`, `array_max`, `array_length`]) as Field,
|
|
646
|
+
data, { alter: false, relative: `${relativeKey}[${i}]`, array: true, context: options.context });
|
|
647
|
+
if (itemSchema) { itemSchemas.push(itemSchema); }
|
|
609
648
|
}
|
|
649
|
+
}
|
|
610
650
|
|
|
611
|
-
|
|
651
|
+
calculatedSchema = getArrayFieldSchema(newField, itemSchemas);
|
|
652
|
+
} else if (newField.type === `dict` && newField.items) {
|
|
653
|
+
const items: { key: string, schema: Joi.Schema, field: Field }[] = [];
|
|
612
654
|
|
|
613
|
-
|
|
655
|
+
if (_.isObject(resolved)) {
|
|
656
|
+
for (const item of newField.items) {
|
|
657
|
+
const recursive = await recursive_schema(item, data, { alter: options.alter && true, relative: relativeKey, context: options.context });
|
|
658
|
+
if (recursive) { items.push({ key: item.key, schema: recursive.schema, field: recursive.field }); }
|
|
659
|
+
}
|
|
614
660
|
}
|
|
615
661
|
|
|
616
|
-
|
|
662
|
+
calculatedSchema = getObjectFieldSchema(newField, items);
|
|
663
|
+
newField.items = items.map((i) => i.field);
|
|
664
|
+
} else {
|
|
665
|
+
calculatedSchema = getSimpleFieldSchema(newField);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (!calculatedSchema) { throw new Error(`Could not resolve schema for field ${newField.key}`); }
|
|
669
|
+
|
|
670
|
+
if (typeof newField.show === `boolean` && !newField.show) {
|
|
671
|
+
// if the field is hidden, we don't need to validate it
|
|
672
|
+
_.unset(data, relativeKey);
|
|
673
|
+
return;
|
|
674
|
+
} else if (newField.disabled) {
|
|
675
|
+
// TODO: check this
|
|
676
|
+
_.unset(data, relativeKey);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return { schema: calculatedSchema, field: field.type === `file` ? alteredField : newField };
|
|
680
|
+
} catch (e) { throw e; }
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
const validate_internal = async (fields: Field[], data: Record<string, any>, options: { secure: boolean, break: boolean, context: Record<string, any> }): Promise<ValidationResult> => {
|
|
684
|
+
try {
|
|
685
|
+
const response: ValidationResult<{}> = { valid: true, fields: [] };
|
|
686
|
+
|
|
687
|
+
for (const field of fields) {
|
|
688
|
+
|
|
689
|
+
const value = _.get(data, field.key, field.default);
|
|
690
|
+
|
|
691
|
+
const cloned = _.cloneDeep(data);
|
|
692
|
+
|
|
693
|
+
const recursive = await recursive_schema(field, cloned, { alter: true, context: options.context });
|
|
694
|
+
if (!recursive) { continue; }
|
|
617
695
|
|
|
618
|
-
|
|
619
|
-
const resolved = context.resolve(value, options.context);
|
|
696
|
+
const resolved = _.get(cloned, field.key, field.default);
|
|
620
697
|
|
|
621
|
-
// validate resolved value
|
|
622
|
-
const schema = getFieldSchema(field);
|
|
623
698
|
let validation: any;
|
|
624
699
|
let error: any;
|
|
625
700
|
try {
|
|
626
|
-
validation = await schema.validateAsync(resolved, { abortEarly: false });
|
|
701
|
+
validation = await recursive.schema.validateAsync(resolved, { abortEarly: false });
|
|
627
702
|
} catch (e) { error = e; }
|
|
628
703
|
|
|
629
704
|
let extended: ValidationExtended;
|
|
@@ -646,10 +721,9 @@ const validate_internal = async (fields: Field[], data: Record<string, any>, opt
|
|
|
646
721
|
};
|
|
647
722
|
}
|
|
648
723
|
|
|
649
|
-
_.set(clonedData, field.key, validation);
|
|
650
724
|
response.valid &&= error ? false : true;
|
|
651
725
|
response.fields.push({
|
|
652
|
-
...field as any,
|
|
726
|
+
...recursive.field as any,
|
|
653
727
|
...options.secure ? await secure(field, extended) : extended,
|
|
654
728
|
});
|
|
655
729
|
|
|
@@ -313,8 +313,8 @@ const processWhere = (json: Record<string, any>, conditions: Where[], flags?: Fl
|
|
|
313
313
|
} else if (isWhere(cond)) {
|
|
314
314
|
matchesWhere &&= processWhere(value, [cond], flags);
|
|
315
315
|
} else {
|
|
316
|
-
// TODO
|
|
317
|
-
|
|
316
|
+
// TODO: what is this case?
|
|
317
|
+
matchesWhere &&= false;
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
if (!matchesWhere) { break; }
|