@ossy/schema
v1.0.1
Published
Schema id grammar, validation engine, and component id helpers
Readme
@ossy/schema
Canonical id grammar, component resolution, and the shared Schema validation engine for the Ossy platform. Isomorphic (Node + browser), no I/O — the same helpers run at build time, on the server, and in the browser.
Core concepts
Ossy uses stable slash-separated ids for every persisted type and UI projection:
| Kind | Pattern | Example |
|------|---------|---------|
| Resource schema | @{provider}/{feature}/schema/{concept} | @ossy/booking/schema/service |
| View component | @{provider}/{feature}/view/{concept}[/projection] | @ossy/booking/view/service/row |
| Form component | @{provider}/{feature}/form/{concept} | @ossy/booking/form/service |
| Input widget | @{provider}/{feature}/input/{type} or @ossy/design-system/input/{type} | @ossy/booking/input/location |
| Action | @{provider}/{feature}/actions/{intent} | @ossy/booking/actions/create |
| Task | @{provider}/{feature}/tasks/{intent} | @ossy/booking/tasks/create |
| Flow | @{provider}/{feature}/flows/{name} | @ossy/authentication/flows/sign-up |
| Email | @{provider}/{feature}/emails/{name} | @ossy/authentication/emails/signed-up |
Resource documents declare fields[] for content only in *.schema.js. Platform envelope fields (resourceId, name, access, …) are not part of the schema definition.
Installation
npm install @ossy/schemaSchema ids
import {
schemaIdFromParts,
isCanonicalSchemaId,
parseSchemaId,
viewComponentId,
formComponentId,
schemaIdFromFormId,
} from '@ossy/schema'
schemaIdFromParts('ossy', 'booking', 'service')
// => '@ossy/booking/schema/service'
viewComponentId('@ossy/booking/schema/service', 'row')
// => '@ossy/booking/view/service/row'
formComponentId('@ossy/booking/schema/service')
// => '@ossy/booking/form/service'API reference — ids
| Export | Description |
|---|---|
| parseSchemaId(id) | Parse provider, feature, concept from a canonical schema id |
| schemaIdFromParts(provider, feature, concept) | Build a canonical schema id |
| isCanonicalSchemaId(id) | Test @{provider}/{feature}/schema/{concept} |
| viewComponentId(schemaId, projection?) | Derive view component id |
| formComponentId(schemaId) | Derive form component id |
| schemaIdFromFormId(formId) | Reverse …/form/… → …/schema/… |
| designSystemInputId(fieldType) | @ossy/design-system/input/{type} |
| featureInputId(schemaId, fieldType) | @ossy/{feature}/input/{type} from schema id |
| mimeViewId(mime, projection?) | MIME-based view id for file resources |
Action and task ids
Actions are caller-facing intents (POST /actions, MCP). Each invoke action has a primary task at the same intent with the tasks kind segment:
import {
actionIdFromParts,
capabilityIdFromPackage,
taskIdFromActionId,
actionIdFromTaskId,
isPrimaryTaskForAction,
isActionId,
isTaskId,
} from '@ossy/schema'
actionIdFromParts('ossy', 'booking', 'create')
// => '@ossy/booking/actions/create'
capabilityIdFromPackage('@ossy/booking', 'actions', 'create')
// => '@ossy/booking/actions/create'
taskIdFromActionId('@ossy/booking/actions/create')
// => '@ossy/booking/tasks/create'
isPrimaryTaskForAction('@ossy/booking/actions/create', '@ossy/booking/tasks/create')
// => trueComponent resolution
Resolve the fallback chain when loading a view for a resource or MIME type:
import {
resolveViewComponentChain,
resolveInputComponentChain,
resolveMimeViewChain,
resolveFormComponentId,
isSchemaDocumentType,
isMimeType,
} from '@ossy/schema'
resolveViewComponentChain('@ossy/booking/schema/service', 'row')
// => ['@ossy/booking/view/service/row', '@ossy/design-system/view/row']
resolveInputComponentChain('@ossy/booking/schema/service', 'text')
// => ['@ossy/booking/input/text', '@ossy/design-system/input/text']
resolveFormComponentId('@ossy/booking/schema/service')
// => '@ossy/booking/form/service'Explicit projection (e.g. 'row') never falls back to the base detail view — only to @ossy/design-system/view/{projection}.
Input field chains
resolveInputComponentChain implements ADR 0006 §5 (Edit): feature-scoped field widgets first, then design-system defaults. Unlike view resolution, invalid or missing schemaId does not throw — the chain is design-system-only so generic forms still render.
| schemaId | Chain for text |
|---|---|
| @ossy/booking/schema/service | @ossy/booking/input/text → @ossy/design-system/input/text |
| null / invalid | @ossy/design-system/input/text |
@ossy/design-system Fields calls useInputSlot(schemaId, type), which walks this chain in ComponentSlotsProvider. Pass schemaId on Form so the feature tier is consulted. Built-in controls are pre-registered via DEFAULT_FORM_FIELD_SLOTS under design-system ids (App merges them at bootstrap). Register feature overrides as *.component.jsx with metadata.id @ossy/{feature}/input/{type}.
Schema engine
Schema.of(manifest) validates schema definitions, document content, capability metadata, and produces faker mocks for flows and forms.
import { Schema, CAPABILITY_SCHEMA_ID, validateContent, contentValidator } from '@ossy/schema'
const engine = Schema.of(manifest, {
actionIds: manifest.actions.map(a => a.id),
taskIds: manifest.tasks.map(t => t.id),
strictRefs: true,
})
// Validate a schema definition
engine.validate(serviceSchema).ok // true
// Validate document content
const result = engine.validate(serviceSchema, { name: 'Consultation', duration: 60 })
if (!result.ok) console.log(result.errors)
// Faker mock for flow runner / tests
const mock = engine.mock(serviceSchema)
// Adapter for design-system Form
const validate = contentValidator(engine, serviceSchema)Arity
engine.validate(schema)— validate a schema definitionengine.validate(schema, data)— validate content against fields
Capability metadata
Built-in platform capability schemas live under @ossy/platform/schema/…:
| Constant | Describes |
|---|---|
| CAPABILITY_SCHEMA_ID.TASK_META | export const metadata from *.task.js |
| CAPABILITY_SCHEMA_ID.ACTION_META | export const metadata from *.action.js |
| CAPABILITY_SCHEMA_ID.TASK_TRIGGER | Normalized task trigger |
| CAPABILITY_SCHEMA_ID.PAGE_META | Page metadata |
| CAPABILITY_SCHEMA_ID.COMPONENT_CAPABILITY | Manifest component entry |
Use strictRefs: true at build time so task triggers reference schema ids present in the manifest.
Subpath imports: @ossy/schema/capability-schemas, @ossy/schema/ids, @ossy/schema/resolve, @ossy/schema/capability-ids.
Task catalog
Extract automation topology from task metadata for the manifest task catalog:
import { extractTaskCatalogEntry, buildTaskCatalogEdges } from '@ossy/schema'
const entry = extractTaskCatalogEntry({
metadata: taskModule.metadata,
package: '@ossy/media-tasks',
entry: '/static/…',
actionIdsInManifest: new Set(manifest.actions.map(a => a.id)),
})
const edges = buildTaskCatalogEdges(catalog)
// => [{ from, to, kind, label? }, …]Action scopes
Token scope matching for POST /actions:
import { normalizeScopes, isActionAllowedByScopes, assertActionScoped } from '@ossy/schema'
assertActionScoped('@ossy/booking/actions/create', ['@ossy/booking/actions/*'])Where it is used
| Consumer | Role |
|---|---|
| @ossy/app | Manifest build — schema validation, task catalog, capability schemas |
| @ossy/platform | Schema boot, action invoke (taskIdFromActionId), task triggers, scope checks |
| @ossy/resources | Content validation, useSchemaEngine, generic forms |
| @ossy/design-system | useInputSlot / Fields field resolution via resolveInputComponentChain |
| @ossy/sdk-react / feature packages | contentValidator on forms |
| @ossy/platform/test/flow-runner | Schema.mock for declarative flow form steps |
Design constraints
- Canonical ids —
event.type,resource.type, and*.schema.jsidmust all match. - Content only —
fields[]describes domain payload; envelope is platform-owned. - Provider namespace — first-party packages use
@ossy/{feature}/…; third-party packages use their own provider (@acme/crm/actions/create). - Primary task pairing — invoke actions resolve
taskIdFromActionId(action); task-only async work uses@{provider}/{feature}/tasks/{intent}without a matching action. - Token scopes — glob patterns match full ids (e.g.
@ossy/booking/actions/*);*grants all actions. - Locale-stable UI — prefer
data-*markers and component ids over visible copy in tests and projections.
Authoring
New document types use *.schema.js. Default export: { id, name, fields[] } with a canonical id.
