@friggframework/schemas 2.0.0--canary.700.80ec1cc.0 → 2.0.0--canary.702.047d3c4.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/README.md +26 -0
- package/api-module-definition.js +93 -0
- package/app-definition.js +113 -0
- package/index.js +26 -4
- package/package.json +4 -2
- package/schemas/api-module-definition.schema.json +23 -0
- package/schemas/app-definition.schema.json +533 -163
package/README.md
CHANGED
|
@@ -41,6 +41,32 @@ if (result.valid) {
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
### Integration classes and defaults (ADR-051)
|
|
45
|
+
|
|
46
|
+
Apps list integration classes, which JSON Schema cannot describe.
|
|
47
|
+
`validateAppDefinition` validates a view of the definition in which each
|
|
48
|
+
class in `integrations`, `adminScripts` and `reports` is replaced by
|
|
49
|
+
`{ Definition }`; `frigg validate` checks the classes themselves.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
const { applyAppDefinitionDefaults } = require('@friggframework/schemas');
|
|
53
|
+
// or, without loading Ajv:
|
|
54
|
+
// require('@friggframework/schemas/app-definition').applyAppDefinitionDefaults
|
|
55
|
+
|
|
56
|
+
const definition = applyAppDefinitionDefaults(require('./index').Definition);
|
|
57
|
+
definition.user.individualUserRequired; // true (schema default)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`applyAppDefinitionDefaults` returns a copy with every `default` declared in
|
|
61
|
+
the app-definition schema applied (only inside blocks the app sets, plus
|
|
62
|
+
blocks that have a default themselves, such as `user`). core's
|
|
63
|
+
`loadAppDefinition` and devtools' `createFriggInfrastructure` call it, so a
|
|
64
|
+
schema default is the runtime default. A schema default must equal what the
|
|
65
|
+
code does when the key is absent; the contract test in
|
|
66
|
+
`packages/devtools/infrastructure/app-definition-schema-contract.test.js`
|
|
67
|
+
checks this for the infrastructure builders. Keys nothing reads are marked
|
|
68
|
+
`"deprecated": true`.
|
|
69
|
+
|
|
44
70
|
### Advanced Validation
|
|
45
71
|
|
|
46
72
|
```javascript
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API module definition helpers shared by validators and the frigg CLI.
|
|
3
|
+
* Real Definitions carry classes and functions that JSON Schema cannot
|
|
4
|
+
* describe; validators check the serialisable view produced here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
if (value === null || typeof value !== 'object') return false;
|
|
9
|
+
const proto = Object.getPrototypeOf(value);
|
|
10
|
+
return proto === Object.prototype || proto === null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Schema descriptor for a JavaScript function (see api-module-definition
|
|
15
|
+
* schema: `{ type: "function", async?: boolean }`).
|
|
16
|
+
*
|
|
17
|
+
* @param {Function} fn
|
|
18
|
+
* @returns {{ type: 'function', async?: boolean }}
|
|
19
|
+
*/
|
|
20
|
+
function toFunctionDescriptor(fn) {
|
|
21
|
+
const descriptor = { type: 'function' };
|
|
22
|
+
if (fn && fn.constructor && fn.constructor.name === 'AsyncFunction') {
|
|
23
|
+
descriptor.async = true;
|
|
24
|
+
}
|
|
25
|
+
return descriptor;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* JSON Schema cannot describe a JavaScript class or function. Validators
|
|
30
|
+
* therefore check this serialisable view: the API class becomes an empty
|
|
31
|
+
* object, functions become `{ type: "function" }` descriptors, and
|
|
32
|
+
* `undefined` env values are dropped (unset `process.env.*` entries).
|
|
33
|
+
*
|
|
34
|
+
* @param {object} definition the `Definition` exported by an API module
|
|
35
|
+
* @returns {object} a view suitable for the api-module-definition schema
|
|
36
|
+
*/
|
|
37
|
+
function toApiModuleDefinitionSchemaView(definition) {
|
|
38
|
+
if (!isPlainObject(definition)) return definition;
|
|
39
|
+
|
|
40
|
+
const view = { ...definition };
|
|
41
|
+
|
|
42
|
+
if (typeof view.API === 'function') {
|
|
43
|
+
view.API = {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof view.getName === 'function') {
|
|
47
|
+
view.getName = toFunctionDescriptor(view.getName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isPlainObject(view.requiredAuthMethods)) {
|
|
51
|
+
const methods = { ...view.requiredAuthMethods };
|
|
52
|
+
for (const [key, value] of Object.entries(methods)) {
|
|
53
|
+
if (typeof value === 'function') {
|
|
54
|
+
methods[key] = toFunctionDescriptor(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
view.requiredAuthMethods = methods;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (isPlainObject(view.env)) {
|
|
61
|
+
const env = {};
|
|
62
|
+
for (const [key, value] of Object.entries(view.env)) {
|
|
63
|
+
if (value !== undefined) env[key] = value;
|
|
64
|
+
}
|
|
65
|
+
view.env = env;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isPlainObject(view.manager)) {
|
|
69
|
+
const manager = { ...view.manager };
|
|
70
|
+
for (const key of ['Entity', 'Credential']) {
|
|
71
|
+
if (typeof manager[key] === 'function') {
|
|
72
|
+
manager[key] = {};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (isPlainObject(manager.methods)) {
|
|
76
|
+
const methods = { ...manager.methods };
|
|
77
|
+
for (const [key, value] of Object.entries(methods)) {
|
|
78
|
+
if (typeof value === 'function') {
|
|
79
|
+
methods[key] = {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
manager.methods = methods;
|
|
83
|
+
}
|
|
84
|
+
view.manager = manager;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return view;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
toApiModuleDefinitionSchemaView,
|
|
92
|
+
toFunctionDescriptor,
|
|
93
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App definition helpers shared by the runtime (core) and the tooling
|
|
3
|
+
* (devtools, frigg CLI). See ADR-051.
|
|
4
|
+
*
|
|
5
|
+
* This file loads only the JSON schema, not Ajv, so the runtime can apply
|
|
6
|
+
* the schema defaults at cold start without compiling a validator.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const appDefinitionSchema = require('./schemas/app-definition.schema.json');
|
|
10
|
+
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object') return false;
|
|
13
|
+
const proto = Object.getPrototypeOf(value);
|
|
14
|
+
return proto === Object.prototype || proto === null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function cloneDefault(value) {
|
|
18
|
+
// Schema defaults are JSON values: a JSON round trip copies them fully.
|
|
19
|
+
return value === undefined ? value : JSON.parse(JSON.stringify(value));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copy plain objects and arrays, keep everything else (classes, functions,
|
|
24
|
+
* instances) by reference.
|
|
25
|
+
*/
|
|
26
|
+
function clonePlain(value) {
|
|
27
|
+
if (Array.isArray(value)) return value.map(clonePlain);
|
|
28
|
+
if (isPlainObject(value)) {
|
|
29
|
+
const copy = {};
|
|
30
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
31
|
+
copy[key] = clonePlain(inner);
|
|
32
|
+
}
|
|
33
|
+
return copy;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function applyDefaultsInPlace(schema, data) {
|
|
39
|
+
if (!schema || !isPlainObject(data)) return;
|
|
40
|
+
const properties = schema.properties || {};
|
|
41
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
42
|
+
if (data[key] === undefined && propertySchema.default !== undefined) {
|
|
43
|
+
data[key] = cloneDefault(propertySchema.default);
|
|
44
|
+
}
|
|
45
|
+
if (isPlainObject(data[key]) && propertySchema.properties) {
|
|
46
|
+
applyDefaultsInPlace(propertySchema, data[key]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Return a copy of the app definition with every `default` that the
|
|
53
|
+
* app-definition schema declares applied, the way Ajv `useDefaults` does:
|
|
54
|
+
* only for properties of objects that exist (a block the app leaves out is
|
|
55
|
+
* not created unless the block itself has a default, as `user` does). It
|
|
56
|
+
* also copies the deprecated `user.password` into `user.usePassword` when
|
|
57
|
+
* only the old spelling is set. Classes and other non-plain values are kept by reference.
|
|
58
|
+
* The input is not mutated, and the function is idempotent.
|
|
59
|
+
*
|
|
60
|
+
* This is the only place schema defaults are applied. core's
|
|
61
|
+
* loadAppDefinition and devtools' createFriggInfrastructure both call it,
|
|
62
|
+
* so a default in the schema is a default at runtime.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} definition the `Definition` exported by the backend
|
|
65
|
+
* @returns {object} the definition with defaults applied
|
|
66
|
+
*/
|
|
67
|
+
function applyAppDefinitionDefaults(definition) {
|
|
68
|
+
if (!isPlainObject(definition)) return definition;
|
|
69
|
+
const copy = clonePlain(definition);
|
|
70
|
+
// user.password is the deprecated spelling of user.usePassword.
|
|
71
|
+
if (
|
|
72
|
+
isPlainObject(copy.user) &&
|
|
73
|
+
copy.user.usePassword === undefined &&
|
|
74
|
+
typeof copy.user.password === 'boolean'
|
|
75
|
+
) {
|
|
76
|
+
copy.user.usePassword = copy.user.password;
|
|
77
|
+
}
|
|
78
|
+
applyDefaultsInPlace(appDefinitionSchema, copy);
|
|
79
|
+
return copy;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Keys whose entries are classes with a static Definition.
|
|
83
|
+
const CLASS_LIST_KEYS = ['integrations', 'adminScripts', 'reports'];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* JSON Schema cannot describe a JavaScript class, and apps list integration
|
|
87
|
+
* (and admin script, and report) classes. Validators therefore check this serialisable view, in which each
|
|
88
|
+
* class is replaced by `{ Definition }` (its static Definition). The schema
|
|
89
|
+
* describes that view; `frigg validate` checks the Definition itself.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} definition the `Definition` exported by the backend
|
|
92
|
+
* @returns {object} a shallow copy with the class lists mapped to views
|
|
93
|
+
*/
|
|
94
|
+
function toAppDefinitionSchemaView(definition) {
|
|
95
|
+
if (!isPlainObject(definition)) return definition;
|
|
96
|
+
const view = { ...definition };
|
|
97
|
+
for (const key of CLASS_LIST_KEYS) {
|
|
98
|
+
if (Array.isArray(definition[key])) {
|
|
99
|
+
view[key] = definition[key].map((entry) =>
|
|
100
|
+
typeof entry === 'function'
|
|
101
|
+
? { Definition: entry.Definition }
|
|
102
|
+
: entry
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return view;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
applyAppDefinitionDefaults,
|
|
111
|
+
toAppDefinitionSchemaView,
|
|
112
|
+
appDefinitionSchema,
|
|
113
|
+
};
|
package/index.js
CHANGED
|
@@ -12,6 +12,13 @@ const path = require('path');
|
|
|
12
12
|
|
|
13
13
|
// Import middleware
|
|
14
14
|
const schemaValidationMiddleware = require('./middleware/schema-validation');
|
|
15
|
+
const {
|
|
16
|
+
applyAppDefinitionDefaults,
|
|
17
|
+
toAppDefinitionSchemaView,
|
|
18
|
+
} = require('./app-definition');
|
|
19
|
+
const {
|
|
20
|
+
toApiModuleDefinitionSchemaView,
|
|
21
|
+
} = require('./api-module-definition');
|
|
15
22
|
|
|
16
23
|
// Initialize AJV with formats
|
|
17
24
|
const ajv = new Ajv({
|
|
@@ -76,7 +83,13 @@ function validate(schemaName, data) {
|
|
|
76
83
|
* @returns {object} - Validation result
|
|
77
84
|
*/
|
|
78
85
|
function validateAppDefinition(appDefinition) {
|
|
79
|
-
|
|
86
|
+
// Apps list integration classes, which JSON Schema cannot describe:
|
|
87
|
+
// validate the serialisable view and return the original definition.
|
|
88
|
+
const result = validate(
|
|
89
|
+
'app-definition',
|
|
90
|
+
toAppDefinitionSchemaView(appDefinition)
|
|
91
|
+
);
|
|
92
|
+
return { ...result, data: appDefinition };
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
/**
|
|
@@ -89,12 +102,18 @@ function validateIntegrationDefinition(integrationDefinition) {
|
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
/**
|
|
92
|
-
* Validate API Module Definition
|
|
93
|
-
*
|
|
105
|
+
* Validate API Module Definition.
|
|
106
|
+
* Real Definitions carry classes and functions; validate the serialisable
|
|
107
|
+
* view and return the original definition (same pattern as validateAppDefinition).
|
|
108
|
+
* @param {object} apiModuleDefinition - API module definition object to validate
|
|
94
109
|
* @returns {object} - Validation result
|
|
95
110
|
*/
|
|
96
111
|
function validateApiModuleDefinition(apiModuleDefinition) {
|
|
97
|
-
|
|
112
|
+
const result = validate(
|
|
113
|
+
'api-module-definition',
|
|
114
|
+
toApiModuleDefinitionSchemaView(apiModuleDefinition)
|
|
115
|
+
);
|
|
116
|
+
return { ...result, data: apiModuleDefinition };
|
|
98
117
|
}
|
|
99
118
|
|
|
100
119
|
/**
|
|
@@ -357,6 +376,9 @@ module.exports = {
|
|
|
357
376
|
// Validation functions
|
|
358
377
|
validate,
|
|
359
378
|
validateAppDefinition,
|
|
379
|
+
applyAppDefinitionDefaults,
|
|
380
|
+
toAppDefinitionSchemaView,
|
|
381
|
+
toApiModuleDefinitionSchemaView,
|
|
360
382
|
validateIntegrationDefinition,
|
|
361
383
|
validateApiModuleDefinition,
|
|
362
384
|
validateServerlessConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/schemas",
|
|
3
|
-
"version": "2.0.0--canary.
|
|
3
|
+
"version": "2.0.0--canary.702.047d3c4.0",
|
|
4
4
|
"description": "Canonical JSON Schema definitions for Frigg Framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"validators/",
|
|
40
40
|
"mocks/",
|
|
41
41
|
"middleware/",
|
|
42
|
+
"app-definition.js",
|
|
43
|
+
"api-module-definition.js",
|
|
42
44
|
"index.js"
|
|
43
45
|
],
|
|
44
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "047d3c404aa8c9636d6c2c442d28ce5e5fcf6b39"
|
|
45
47
|
}
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
"minLength": 1,
|
|
28
28
|
"maxLength": 100
|
|
29
29
|
},
|
|
30
|
+
"modelName": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Entity/credential model name used by the Module runtime (e.g. 'Fathom'). Distinct from moduleName, which is the package identifier.",
|
|
33
|
+
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
|
34
|
+
"minLength": 1,
|
|
35
|
+
"maxLength": 100
|
|
36
|
+
},
|
|
30
37
|
"requiredAuthMethods": {
|
|
31
38
|
"type": "object",
|
|
32
39
|
"description": "Required authentication methods and configuration",
|
|
@@ -90,6 +97,22 @@
|
|
|
90
97
|
"type": {"type": "string", "enum": ["function"]},
|
|
91
98
|
"async": {"type": "boolean", "default": true}
|
|
92
99
|
}
|
|
100
|
+
},
|
|
101
|
+
"getAuthorizationRequirements": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"description": "Method that returns the authorization requirements (type, jsonSchema/uiSchema) for non-OAuth flows such as API key",
|
|
104
|
+
"properties": {
|
|
105
|
+
"type": {"type": "string", "enum": ["function"]},
|
|
106
|
+
"async": {"type": "boolean", "default": true}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"setAuthParams": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"description": "Method that applies authorization parameters (e.g. API key) to the API instance",
|
|
112
|
+
"properties": {
|
|
113
|
+
"type": {"type": "string", "enum": ["function"]},
|
|
114
|
+
"async": {"type": "boolean", "default": true}
|
|
115
|
+
}
|
|
93
116
|
}
|
|
94
117
|
},
|
|
95
118
|
"additionalProperties": false
|