@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 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
- return validate('app-definition', appDefinition);
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
- * @param {object} apiModuleDefinition - API module definition object to validate
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
- return validate('api-module-definition', apiModuleDefinition);
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.700.80ec1cc.0",
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": "80ec1cc658ebdfbe53482d0705c42129685eb47f"
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