Declarations¶
Top-level declarations define transforms, types, shared memory, and functions.
Output declarations¶
OUTPUT sends data through an optional adapter and a template block that describes the resulting structure.
Transform declarations¶
transformDecl ::= annotation* TRANSFORM IDENTIFIER? transformSig? transformOptions? block
transformSig ::= "(" transformParamList? ")" "->" typeExpr
transformOptions ::= OPTIONS "{" optionEntry* "}"
Any. Use _/_? as placeholders for Any/Any?.
Contract inference routing:
- No signature: input/output contracts are fully inferred.
- Signature with concrete output type: explicit contract conversion is used.
- Signature with wildcard output (_, _?, or alias resolving to any): input type seeds flow inference and output stays inferred.
Example signatures:
TRANSFORM X(input: _) -> _ { ... }
TRANSFORM Enrich(user: { id: string, email?: string }) -> { user: string } { ... }
OPTIONS is optional and keeps per-transform configuration close to the transform:
TRANSFORM Normalize OPTIONS {
mode: buffer
input: { adapter: kafka("topic") }
output: { adapter: json() }
shared: [ cache SINGLE, tokens MANY ]
} { ... }
Shared declarations¶
Shared memory exposes mutable storage that can beSINGLE or MANY valued.
Function declarations¶
Functions define reusable computations, taking an optional parameter list and either an expression or block body.Type declarations¶
Type definitions describe enums, unions, list types, placeholders, and record schemas with required or optional fields.TYPE User = { id: string, name: string, email?: string }
TYPE Tags = [string]
TYPE UniqueTags = set<string>
TYPE Status = enum { Active, Suspended, Deleted }
TYPE Flexible = _?
Constraints¶
- Declarations are top-level; use
LET/SETinside blocks for local state. SHAREDresources must be declared before first use.TYPEaliases define shapes but do not perform runtime validation unless the host or tests enforce it.
Versioning and compatibility¶
When you evolve a schema, prefer explicit versioned TYPE names so older contracts can coexist with new definitions.
TYPE Order_v1 = { id: string, status: enum { Open, Closed } }
TYPE Order_v2 = { id: string, status: enum { Open, Closed, Cancelled }, note?: string }
Use the CLI to compare two type definitions or JSON Schema documents:
bl contract-diff schemas/order-v1.json schemas/order-v2.json
bl contract-diff types/order.bl types/order-new.bl --type Order
The CLI classifies changes as: - Breaking changes: removed fields, optional fields that become required, type narrowing. - Non-breaking changes: added optional fields, required fields that become optional, type widening.