Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

* Added programmatic context schema discovery to `@finos/fdc3-context` (re-exported from `@finos/fdc3`), exposing `getContextTypes()`, `getContextSchema()`, `getAllContextSchemas()`, `getContextSchemaMetadata()` and `hasContextSchema()` so that applications and tooling can enumerate the standardized context types and retrieve their JSON Schemas at runtime. The registry is generated from the existing context schema files (the single source of truth) via a new `schemagen` build step, keeping it automatically in sync.
* Added standalone Workbench examples for the FDC3 2.2 `fdc3.action`, `fdc3.fileAttachment`, `fdc3.message`, `fdc3.orderList`, `fdc3.tradeList`, and `fdc3.timeRange` context types. ([#1949](https://github.com/finos/FDC3/pull/1949))
* Added advanced conformance tests (`fdc3.intentListenerConflict`) covering intent listener conflicts, verifying that `addIntentListener`/`addIntentListenerWithContext` reject with `ResolveError.IntentListenerConflict` for conflicting listeners (unfiltered, or overlapping context types) and allow non-overlapping filtered listeners, listeners for different intents, and re-adding after `unsubscribe()`. Added the corresponding test definitions to the "Avoiding Adding Multiple Intent Listeners" section of the Intents conformance docs.
* Added a classification field to Instrument context type ([#1665](https://github.com/finos/FDC3/pull/1665))
Expand Down
29 changes: 29 additions & 0 deletions packages/fdc3-context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

This folder contains Typescript interfaces, in ContextTypes.ts generated from the context JSONSchema (https://json-schema.org/) files via quicktype (https://quicktype.io/).

## Programmatic context schema discovery

In addition to the generated TypeScript interfaces, this package exposes the standardized context JSON Schemas at runtime so that tooling (resolvers, validators, form/UI generators, agents bridging FDC3 to other protocols, etc.) can enumerate the context types and retrieve their schemas without a Desktop Agent connection:

```TypeScript
import {
getContextTypes,
getContextSchema,
getAllContextSchemas,
getContextSchemaMetadata,
hasContextSchema,
} from '@finos/fdc3-context';

getContextTypes();
// ["fdc3.action", "fdc3.chart", "fdc3.contact", ...]

const schema = getContextSchema('fdc3.instrument'); // JSON Schema (draft-07), or undefined
getContextSchemaMetadata('fdc3.instrument');
// { type: 'fdc3.instrument', title: 'Instrument', description: '...', id: '...', examples: [...] }

hasContextSchema('fdc3.instrument'); // true
```

The registry is generated (into `generated/context/ContextSchemas.ts`) from the schema files in `schemas/context` by `generateContextSchemas.cjs`, so it stays in sync with the source-of-truth schemas. The abstract base context type (`fdc3.context`) is excluded, as it is not a concrete context type. All returned values are defensive copies.

## Generated TypeScript interfaces

The `ContextTypes.ts` file contains TypeScript interfaces generated from the context JSONSchema files via quicktype.

Please note that these definitions are provided to help developers working in TypeScript to produce valid context objects - but should not be considered the 'source of truth' for context definitions (instead look to the schemas and documentation). Source files may also be generated for us in other languages supported by quicktype.

It is not always possible to perfectly replicate a type/interface defined in JSONSchema via TypeScript. Hence, in the event of any disagreement between the definitions, the JSONSchema should be assumed to be correct, rather than the Typescript. For example, JSONSchema may define optional fields on an object + a restriction on the type of additional properties (via `"additionalProperties": { "type": "string"}`), which will result in an index signature `[property: string]: string;` in generated TypeScript. That signature, is incompatible with with an optional properties (including string properties) as they have type `string | undefined`. A similar problem may occur in JSON Schema if the schema is used to create a subtype via composition as `additionalProperties` is not aware of teh subschema's definitions. Both issues can be worked around by using `unevaluatedProperties` in the schema, which will defer to the declared type of the optional property in the subschema, but is also currently ignored by quicktype - resulting in a type that will compile, but doesn't restrict the type of optional property values as defined in the schema.
Expand Down
112 changes: 112 additions & 0 deletions packages/fdc3-context/generateContextSchemas.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* SPDX-License-Identifier: Apache-2.0
* Copyright FINOS FDC3 contributors - see NOTICE file
*/

/**
* Generates a runtime-accessible registry of the standardized FDC3 context
* JSON Schemas. The JSON Schema files in `schemas/context` are the single
* source of truth for context definitions, but historically they were only
* available at build time (to generate `ContextTypes.ts`) and were not shipped
* in the published package. This script inlines each schema into a generated
* TypeScript module so that applications and tooling can discover the set of
* standardized context types and retrieve their schemas programmatically.
*
* Usage: node generateContextSchemas.cjs <schemasDir> <outputFile>
*/

const fs = require('fs');
const path = require('path');

const args = process.argv.slice(2);
const schemasDir = args[0] || path.join('schemas', 'context');
const outputFile = args[1] || path.join('generated', 'context', 'ContextSchemas.ts');

/**
* Recursively locate the `type` const that identifies a context type, e.g.
* `{ properties: { type: { const: 'fdc3.instrument' } } }`. Standardized
* context schemas declare this inside an `allOf` entry. The abstract base
* (`context.schema.json`) declares `type` as a plain string with no const and
* is therefore intentionally excluded from the registry.
*/
function findContextTypeId(node) {
if (Array.isArray(node)) {
for (const item of node) {
const found = findContextTypeId(item);
if (found) return found;
}
return undefined;
}
if (node && typeof node === 'object') {
const typeConst = node.properties && node.properties.type && node.properties.type.const;
if (typeof typeConst === 'string') {
return typeConst;
}
for (const key of Object.keys(node)) {
const found = findContextTypeId(node[key]);
if (found) return found;
}
}
return undefined;
}

const files = fs
.readdirSync(schemasDir)
.filter(f => f.endsWith('.schema.json'))
.sort();

/** @type {Record<string, unknown>} */
const registry = {};
const skipped = [];

for (const file of files) {
const raw = fs.readFileSync(path.join(schemasDir, file), 'utf-8');
const schema = JSON.parse(raw);
const typeId = findContextTypeId(schema);
if (!typeId) {
skipped.push(file);
continue;
}
if (registry[typeId]) {
throw new Error(`Duplicate context type id "${typeId}" found in ${file}`);
}
registry[typeId] = schema;
}

const orderedIds = Object.keys(registry).sort();
const ordered = {};
for (const id of orderedIds) {
ordered[id] = registry[id];
}

const header = `/**
* SPDX-License-Identifier: Apache-2.0
* Copyright FINOS FDC3 contributors - see NOTICE file
*/

/* eslint-disable */
// THIS IS A GENERATED FILE - DO NOT EDIT.
// Regenerate with \`npm run generate\` (see generateContextSchemas.cjs).
// Source of truth: packages/fdc3-context/schemas/context/*.schema.json

/**
* The JSON Schema definitions for every standardized FDC3 context type, keyed
* by their context \`type\` identifier (e.g. \`"fdc3.instrument"\`). Prefer the
* accessor helpers exported from the package root (\`getContextSchema\`,
* \`getContextTypes\`, etc.) over reading this object directly.
*/
`;

const body = `export const contextSchemas: Record<string, Record<string, unknown>> = ${JSON.stringify(
ordered,
null,
2
)};\n`;

fs.mkdirSync(path.dirname(outputFile), { recursive: true });
fs.writeFileSync(outputFile, header + body, 'utf-8');

console.log(
`Wrote ${orderedIds.length} context schemas to ${outputFile}` +
(skipped.length ? ` (skipped non-typed schemas: ${skipped.join(', ')})` : '')
);
Loading
Loading