@syncropel/projections
v0.8.0
Published
TypeScript schema + validators for the Syncropel Rendering Protocol (SRP v0.8) — declarative UI documents for query-driven and AI-generated interfaces. v0.8 adds five visual-atom nodes (glyph, status-dot, pulse, link, code) on top of the v0.7 grammar (39
Downloads
1,622
Maintainers
Readme
@syncropel/projections
TypeScript schema and runtime validators for the Syncropel Rendering Protocol (SRP) — declarative UI documents composed from a constrained palette of block-level primitives, designed for query-driven and AI-generated interfaces.
import { validateSRP, type SRPDocument } from "@syncropel/projections";
const doc: SRPDocument = {
srp: "0.1",
root: {
type: "column",
props: { gap: "md" },
children: [
{ type: "heading", props: { level: 1, text: "Session summary" } },
{
type: "grid",
props: { cols: 3, gap: "sm" },
children: [
{ type: "stat", props: { label: "Tracks played", value: 47 } },
{ type: "stat", props: { label: "Artists", value: 14, delta: 3 } },
{ type: "stat", props: { label: "Transitions", value: 12 } },
],
},
],
},
};
const result = validateSRP(doc);
if (!result.valid) {
for (const err of result.errors) {
console.warn(`${err.path}: ${err.message}`);
}
}Features
- 19 block-level primitives — containers, record-rendering, data display, interactive, feedback. Closed palette; growth happens through reviewed spec proposals, not consumer-side overrides.
- TypeScript schema — every primitive has a typed node interface with discriminated-union dispatching.
- Runtime validators —
validateSRP()walks a document and returns structured errors (JSON-pointer-style paths + machine-readable codes) for anything that doesn't conform. - Markdown subset parser — inline
**bold**,*italic*,`code`,[link](url),~~strike~~in text-valued props, rendered via the corresponding typography atoms. - Zero runtime dependencies — uses the platform only.
- Universal runtime — Node 18+, Deno, Bun, Cloudflare Workers, modern browsers.
Install
npm install @syncropel/projectionsShips compiled JavaScript + TypeScript declarations. ESM only.
What's in the box
Schema types
The 25 node types are exported as a discriminated union keyed on type:
| Category | Nodes |
|---|---|
| Containers | column, row, grid, card, divider |
| Record rendering | record-line, record-line-list, chip |
| Data display | heading, text, stat, key-value |
| Interactive | button, icon-button, copy-button, select |
| Feedback | empty-state, error-state, skeleton |
| Interactive containers + inputs (v0.2) | tabs, data-table, board, text-input, form |
| Choice control (v0.3) | segmented |
Every node has a matching TypeScript interface (e.g., ButtonNode, StatNode). Import the ones you need, or use SRPNode for the full union.
Validators
validateSRP(doc) accepts an unknown and returns:
type ValidationResult =
| { valid: true }
| { valid: false; errors: ValidationError[] };
interface ValidationError {
path: string; // JSON-pointer-style path
message: string; // human-readable
code: ValidationErrorCode; // machine-readable
}Error codes: SRP_VERSION_INVALID, ROOT_MISSING, NODE_NOT_OBJECT, NODE_TYPE_MISSING, NODE_TYPE_UNKNOWN, PROPS_MISSING_REQUIRED, PROPS_INVALID_VALUE, CHILDREN_NOT_ARRAY, CHILDREN_NOT_ALLOWED, BIND_MISSING_REQUIRED, BIND_INVALID_SHAPE.
Use isSRPDocument(x) or isSRPNode(x) as type guards in adapter code that receives untrusted JSON.
Markdown subset
Text-valued props (text, label, message, etc.) accept a narrow markdown subset for inline formatting. Parse it with parseInline(text) to get an AST you can render:
import { parseInline, type InlineNode } from "@syncropel/projections";
const nodes = parseInline("Visit **example.com** or read the [docs](/docs).");
// → [
// { kind: "text", text: "Visit " },
// { kind: "bold", children: [{ kind: "text", text: "example.com" }] },
// { kind: "text", text: " or read the " },
// { kind: "link", url: "/docs", children: [{ kind: "text", text: "docs" }] },
// { kind: "text", text: "." }
// ]Supported constructs: **bold**, *italic*, `code`, [label](url), ~~strike~~. Unbalanced delimiters render as literal text (no silent drops). Block-level markdown (headings, lists, tables, etc.) is NOT parsed — use SRP block-level nodes for structural content.
Strip formatting for aria-labels / plain-text search with stripFormatting(nodes).
Who uses this
- Adapter authors — emit projection documents as record bodies; the schema guarantees your document renders across every Syncropel surface.
- Renderer authors —
@syncropel/reactconsumes this package for its prop types; future renderers (Vue, Svelte, server-side HTML, etc.) will too. - Backend servers — validate projection documents before storing them, without pulling in a React runtime.
Related packages
- @syncropel/sdk — emit + query records (TypeScript SDK for the Syncropel protocol)
- @syncropel/react — React renderer for SRP documents
- @syncropel/extensions — iframe postMessage runtime for Tier-2 extensions (SAP protocol)
See CHANGELOG.md for release notes.
