@friggframework/schemas 2.0.0--canary.701.3b01ea3.0 → 2.0.0--canary.705.f070722.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
@@ -95,6 +95,31 @@ const appSchema = getSchema('app-definition');
95
95
  console.log('App definition schema:', appSchema);
96
96
  ```
97
97
 
98
+ ## Schema Identifiers and Hosting
99
+
100
+ Every schema's `$id` is its public URL:
101
+
102
+ ```
103
+ https://friggframework.org/schemas/<name>/v<major>.<minor>.json
104
+ ```
105
+
106
+ for example `https://friggframework.org/schemas/app-definition/v1.0.json`.
107
+ friggframework.org serves each file at that URL (with
108
+ `Content-Type: application/schema+json` and open CORS), so editors can fetch
109
+ it from a `$schema` reference. The URL is a name, not a dependency: this
110
+ package, `frigg validate` and the runtime resolve every `$id` from the
111
+ installed files and never fetch it.
112
+
113
+ - A version is immutable once a stable release ships it. A backward-compatible
114
+ change gets a new minor version, a breaking change a new major version.
115
+ - `validate()` and `getSchema()` accept a schema name, its `$id`, or the
116
+ unversioned id it had before 2.0.1 (kept as an alias for one release).
117
+ - The website copies live in `website/schemas/` and are committed. After you
118
+ change a schema, run `npm run sync:hosted` here and commit the result;
119
+ `tests/hosted-schemas.test.js` fails while a copy is missing or stale.
120
+
121
+ The rules are in ADR-051, "Hosted schemas" amendment.
122
+
98
123
  ## Available Schemas
99
124
 
100
125
  ### App Definition Schema
@@ -172,9 +197,7 @@ Defines the structure for Frigg API modules.
172
197
  **Key Properties:**
173
198
  - `moduleName` (required): Module identifier
174
199
  - `getName` (required): Function returning module name
175
- - `requiredAuthMethods` (required): Authentication configuration (includes optional `getAuthorizationRequirements` and `setAuthParams` for API-key modules)
176
- - `modelName`: Entity/credential model name used by the Module runtime
177
- - Real Definitions (classes/functions) are accepted by `validateApiModuleDefinition` via a schema view.
200
+ - `requiredAuthMethods` (required): Authentication configuration
178
201
  - `env`: Environment variable configuration
179
202
  - `config`: Module-specific configuration
180
203
 
@@ -355,7 +378,12 @@ Validates core data models (User, Entity, Credential).
355
378
  Returns all available schemas as an object.
356
379
 
357
380
  #### `getSchema(schemaName)`
358
- Returns a specific schema by name.
381
+ Returns a specific schema by name (`app-definition`), by `$id`, or by its legacy unversioned id.
382
+
383
+ #### `parseSchemaId(id)`
384
+ Returns `{ name, version, hostedPath }` for an `$id` on the
385
+ `https://friggframework.org/schemas/<name>/v<x.y>.json` pattern, or `null`.
386
+ `SCHEMA_ID_PATTERN` and `LEGACY_SCHEMA_IDS` are exported too.
359
387
 
360
388
  ### Utility Functions
361
389
 
package/index.js CHANGED
@@ -16,9 +16,7 @@ const {
16
16
  applyAppDefinitionDefaults,
17
17
  toAppDefinitionSchemaView,
18
18
  } = require('./app-definition');
19
- const {
20
- toApiModuleDefinitionSchemaView,
21
- } = require('./api-module-definition');
19
+ const schemaIds = require('./schema-ids');
22
20
 
23
21
  // Initialize AJV with formats
24
22
  const ajv = new Ajv({
@@ -52,7 +50,16 @@ schemaFiles.forEach(file => {
52
50
  const schemaName = file.replace('.schema.json', '');
53
51
 
54
52
  schemas[schemaName] = schemaContent;
55
- ajv.addSchema(schemaContent, schemaName);
53
+ schemaIds.addSchemaWithAliases(ajv, schemaContent, schemaName);
54
+ });
55
+
56
+ // Schema name for each `$id` and legacy id, so getSchema() also accepts a URL.
57
+ const schemaNamesById = {};
58
+ Object.entries(schemas).forEach(([schemaName, schema]) => {
59
+ schemaNamesById[schema.$id] = schemaName;
60
+ (schemaIds.LEGACY_SCHEMA_IDS[schemaName] || []).forEach(legacyId => {
61
+ schemaNamesById[legacyId] = schemaName;
62
+ });
56
63
  });
57
64
 
58
65
  /**
@@ -102,18 +109,12 @@ function validateIntegrationDefinition(integrationDefinition) {
102
109
  }
103
110
 
104
111
  /**
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
112
+ * Validate API Module Definition
113
+ * @param {object} apiModuleDefinition - API module definition object to validate
109
114
  * @returns {object} - Validation result
110
115
  */
111
116
  function validateApiModuleDefinition(apiModuleDefinition) {
112
- const result = validate(
113
- 'api-module-definition',
114
- toApiModuleDefinitionSchemaView(apiModuleDefinition)
115
- );
116
- return { ...result, data: apiModuleDefinition };
117
+ return validate('api-module-definition', apiModuleDefinition);
117
118
  }
118
119
 
119
120
  /**
@@ -341,15 +342,16 @@ function getSchemas() {
341
342
  }
342
343
 
343
344
  /**
344
- * Get a specific schema by name
345
- * @param {string} schemaName - Name of the schema to retrieve
345
+ * Get a specific schema by name or `$id`
346
+ * @param {string} schemaName - Schema name (`app-definition`), its `$id`, or a legacy unversioned id
346
347
  * @returns {object} - Schema object
347
348
  */
348
349
  function getSchema(schemaName) {
349
- if (!schemas[schemaName]) {
350
+ const name = schemas[schemaName] ? schemaName : schemaNamesById[schemaName];
351
+ if (!name) {
350
352
  throw new Error(`Schema '${schemaName}' not found. Available schemas: ${Object.keys(schemas).join(', ')}`);
351
353
  }
352
- return schemas[schemaName];
354
+ return schemas[name];
353
355
  }
354
356
 
355
357
  /**
@@ -378,7 +380,6 @@ module.exports = {
378
380
  validateAppDefinition,
379
381
  applyAppDefinitionDefaults,
380
382
  toAppDefinitionSchemaView,
381
- toApiModuleDefinitionSchemaView,
382
383
  validateIntegrationDefinition,
383
384
  validateApiModuleDefinition,
384
385
  validateServerlessConfig,
@@ -411,6 +412,11 @@ module.exports = {
411
412
  schemas,
412
413
  ajv,
413
414
 
415
+ // Schema identifiers (https://friggframework.org/schemas/<name>/v<x.y>.json)
416
+ SCHEMA_ID_PATTERN: schemaIds.SCHEMA_ID_PATTERN,
417
+ LEGACY_SCHEMA_IDS: schemaIds.LEGACY_SCHEMA_IDS,
418
+ parseSchemaId: schemaIds.parseSchemaId,
419
+
414
420
  // Middleware exports
415
421
  middleware: schemaValidationMiddleware,
416
422
  validateBody: schemaValidationMiddleware.validateBody,
@@ -9,6 +9,7 @@ const Ajv = require('ajv');
9
9
  const addFormats = require('ajv-formats');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
+ const { addSchemaWithAliases } = require('../schema-ids');
12
13
 
13
14
  // Initialize AJV with formats
14
15
  const ajv = new Ajv({
@@ -33,7 +34,7 @@ schemaFiles.forEach(file => {
33
34
  if (fs.existsSync(schemaPath)) {
34
35
  const schemaContent = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
35
36
  const schemaName = file.replace('.schema.json', '');
36
- ajv.addSchema(schemaContent, schemaName);
37
+ addSchemaWithAliases(ajv, schemaContent, schemaName);
37
38
  }
38
39
  });
39
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friggframework/schemas",
3
- "version": "2.0.0--canary.701.3b01ea3.0",
3
+ "version": "2.0.0--canary.705.f070722.0",
4
4
  "description": "Canonical JSON Schema definitions for Frigg Framework",
5
5
  "main": "index.js",
6
6
  "author": "",
@@ -19,7 +19,9 @@
19
19
  "scripts": {
20
20
  "test": "jest",
21
21
  "validate": "node scripts/validate-schemas.js",
22
- "build": "node scripts/build-schemas.js"
22
+ "build": "node scripts/build-schemas.js",
23
+ "sync:hosted": "node scripts/sync-hosted-schemas.js",
24
+ "check:hosted": "node scripts/sync-hosted-schemas.js --check"
23
25
  },
24
26
  "keywords": [
25
27
  "frigg",
@@ -40,8 +42,8 @@
40
42
  "mocks/",
41
43
  "middleware/",
42
44
  "app-definition.js",
43
- "api-module-definition.js",
45
+ "schema-ids.js",
44
46
  "index.js"
45
47
  ],
46
- "gitHead": "3b01ea3abd65f1771e9bb1f143168da6acff4125"
48
+ "gitHead": "f070722ad939d3221446e24653352373a5f4e0f6"
47
49
  }
package/schema-ids.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Schema identifiers (ADR-051, "Hosted schemas" amendment).
3
+ *
4
+ * Every Frigg JSON Schema has the `$id`
5
+ * https://friggframework.org/schemas/<name>/v<major>.<minor>.json
6
+ * and friggframework.org serves the file at exactly that URL. The `$id` is a
7
+ * name, not a location the runtime reads: validation always resolves it from
8
+ * this package and never fetches it.
9
+ */
10
+
11
+ const SCHEMA_BASE_URL = 'https://friggframework.org/';
12
+
13
+ const SCHEMA_ID_PATTERN =
14
+ /^https:\/\/friggframework\.org\/schemas\/([a-z0-9]+(?:-[a-z0-9]+)*)\/v(0|[1-9]\d*)\.(0|[1-9]\d*)\.json$/;
15
+
16
+ /**
17
+ * Unversioned ids the schemas carried before 2.0.1. They never resolved over
18
+ * HTTP, but code may pass them to `validate()`/`getSchema()` or Ajv, so the
19
+ * package still registers them as aliases of the current schema.
20
+ *
21
+ * @deprecated Remove in the release after 2.0.1.
22
+ */
23
+ const LEGACY_SCHEMA_IDS = Object.freeze({
24
+ 'api-authorization': ['https://friggframework.org/schemas/api-authorization.json'],
25
+ 'api-credentials': ['https://friggframework.org/schemas/api-credentials.json'],
26
+ 'api-entities': ['https://friggframework.org/schemas/api-entities.json'],
27
+ 'api-proxy': ['https://friggframework.org/schemas/api-proxy.json'],
28
+ 'api-module-definition': ['https://schemas.friggframework.org/api-module-definition.schema.json'],
29
+ 'app-definition': ['https://schemas.friggframework.org/app-definition.schema.json'],
30
+ 'core-models': ['https://schemas.friggframework.org/core-models.schema.json'],
31
+ 'environment-config': ['https://schemas.friggframework.org/environment-config.schema.json'],
32
+ 'integration-definition': ['https://schemas.friggframework.org/integration-definition.schema.json'],
33
+ 'serverless-config': ['https://schemas.friggframework.org/serverless-config.schema.json'],
34
+ });
35
+
36
+ /**
37
+ * Parse a schema `$id`.
38
+ * @param {string} id
39
+ * @returns {{ name: string, version: string, hostedPath: string } | null}
40
+ * `hostedPath` is the path under the website root (`schemas/<name>/v1.0.json`).
41
+ */
42
+ function parseSchemaId(id) {
43
+ const match = typeof id === 'string' ? SCHEMA_ID_PATTERN.exec(id) : null;
44
+ if (!match) return null;
45
+ const [, name, major, minor] = match;
46
+ const version = `${major}.${minor}`;
47
+ return { name, version, hostedPath: `schemas/${name}/v${version}.json` };
48
+ }
49
+
50
+ /**
51
+ * Register a schema with Ajv under its file name, its `$id`, and its legacy ids.
52
+ * @param {import('ajv').default} ajv
53
+ * @param {object} schema - parsed schema (its `$id` registers automatically)
54
+ * @param {string} name - file name without `.schema.json`
55
+ */
56
+ function addSchemaWithAliases(ajv, schema, name) {
57
+ ajv.addSchema(schema, name);
58
+ for (const legacyId of LEGACY_SCHEMA_IDS[name] || []) {
59
+ ajv.addSchema({ ...schema, $id: legacyId });
60
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ SCHEMA_BASE_URL,
65
+ SCHEMA_ID_PATTERN,
66
+ LEGACY_SCHEMA_IDS,
67
+ parseSchemaId,
68
+ addSchemaWithAliases,
69
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://friggframework.org/schemas/api-authorization.json",
3
+ "$id": "https://friggframework.org/schemas/api-authorization/v1.0.json",
4
4
  "title": "Frigg Authorization API Schemas",
5
5
  "description": "JSON schemas for authorization and authentication API requests and responses",
6
6
  "definitions": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://friggframework.org/schemas/api-credentials.json",
3
+ "$id": "https://friggframework.org/schemas/api-credentials/v1.0.json",
4
4
  "title": "Frigg Credentials API Schemas",
5
5
  "description": "JSON schemas for credentials API requests and responses",
6
6
  "definitions": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://friggframework.org/schemas/api-entities.json",
3
+ "$id": "https://friggframework.org/schemas/api-entities/v1.0.json",
4
4
  "title": "Frigg Entities API Schemas",
5
5
  "description": "JSON schemas for entities API requests and responses",
6
6
  "definitions": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/api-module-definition.schema.json",
3
+ "$id": "https://friggframework.org/schemas/api-module-definition/v1.0.json",
4
4
  "title": "Frigg API Module Definition",
5
5
  "description": "Schema for defining a Frigg API module including authentication, API configuration, and required methods.",
6
6
  "type": "object",
@@ -27,13 +27,6 @@
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
- },
37
30
  "requiredAuthMethods": {
38
31
  "type": "object",
39
32
  "description": "Required authentication methods and configuration",
@@ -97,22 +90,6 @@
97
90
  "type": {"type": "string", "enum": ["function"]},
98
91
  "async": {"type": "boolean", "default": true}
99
92
  }
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
- }
116
93
  }
117
94
  },
118
95
  "additionalProperties": false
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://friggframework.org/schemas/api-proxy.json",
3
+ "$id": "https://friggframework.org/schemas/api-proxy/v1.0.json",
4
4
  "title": "Frigg Proxy API Schemas",
5
5
  "description": "JSON schemas for proxy API requests and responses that forward calls to external APIs",
6
6
  "definitions": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/app-definition.schema.json",
3
+ "$id": "https://friggframework.org/schemas/app-definition/v1.0.json",
4
4
  "title": "Frigg Application Definition",
5
5
  "description": "Schema for defining a Frigg application configuration including integrations, infrastructure, security settings, and deployment options.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/core-models.schema.json",
3
+ "$id": "https://friggframework.org/schemas/core-models/v1.0.json",
4
4
  "title": "Frigg Core Data Models",
5
5
  "description": "Schema definitions for core Frigg data models including User, Entity, and Credential.",
6
6
  "definitions": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/environment-config.schema.json",
3
+ "$id": "https://friggframework.org/schemas/environment-config/v1.0.json",
4
4
  "title": "Frigg Environment Configuration",
5
5
  "description": "Schema for environment variable configuration and validation in Frigg applications.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/integration-definition.schema.json",
3
+ "$id": "https://friggframework.org/schemas/integration-definition/v1.0.json",
4
4
  "title": "Frigg Integration Definition",
5
5
  "description": "Schema for defining a Frigg integration including configuration, options, and display properties.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://schemas.friggframework.org/serverless-config.schema.json",
3
+ "$id": "https://friggframework.org/schemas/serverless-config/v1.0.json",
4
4
  "title": "Frigg Serverless Configuration",
5
5
  "description": "Schema for Serverless Framework configuration used in Frigg applications for AWS deployment.",
6
6
  "type": "object",
@@ -1,93 +0,0 @@
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
- };