
Security News
crates.io Ships Security Tab and Tightens Publishing Controls
crates.io adds a Security tab backed by RustSec advisories and narrows trusted publishing paths to reduce common CI publishing risks.
@rubriclab/chains
Advanced tools
A framework for creating recursive schemas from input/output definitions. Designed for complex structured output use cases with LLMs, runtime parsing of recursive objects, and compile time safety.
It is part of Rubric's architecture for Generative UI when used with:
bun add @rubriclab/chains
@rubriclab scope packages are not built, they are all raw typescript. If using in a next.js app, make sure to transpile.
// next.config.ts
import type { NextConfig } from 'next'
export default {
transpilePackages: ['@rubriclab/chains'],
reactStrictMode: true
} satisfies NextConfig
If using inside the monorepo (@rubric), simply add
{"@rubriclab/chains": "*"}to dependencies and then runbun i
Nodes are input/output pairs. A similar schema is used in @rubriclab/actions, @rubriclab/blocks, and for tools in @rubriclab/agents.
Inputs are Record<string, z.ZodType>
Outputs are z.ZodType
Note, only a subset of zod types are currently supported.
type SupportedZodTypes = | z.ZodString | z.ZodNumber | z.ZodBoolean | z.ZodLiteral<string> | z.ZodUndefined | z.ZodVoid | z.ZodObject<Record<string, SupportedZodTypes>> | z.ZodArray<SupportedZodTypes>
import { z } from 'zod/v4'
const stringify = {
input: {
number: z.number()
},
output: z.string()
}
const numberify = {
input: {
number: z.string()
},
output: z.number()
}
export const nodes = { stringify, numberify }
import { createChain } from '@rubriclab/chains'
const { definitions, compatabilities, drill, __Chain } = createChain(nodes)
export { definitions, compatabilities, drill }
export type Chain = typeof __Chain
export async function executeChain(chain: Chain) {
return drill(chain, key => {
return async input => {
switch (key) {
case 'stringify':
return input.number.toString()
case 'numberify':
return Number(input.string)
}
}
})
}
const chain: Chain = {
node: 'stringify',
input: {
number: {
node: 'numberify',
input: {
string: '3'
}
}
}
}
const output = await executeChain(chain)
The chains package returns definitions which are designed to make it easy to create response formats with fully featured recursion.
Zod 4 offers a new registry feature - which allows you to add metadata to types. This feature can be used with the new z.toJSONSchema feature to extract types to $defs, which allows us to do recursion within response format. This is the core unlock. Zod Source Open AI Source
import { compatabilities, definitions } from './chains'
const chainRegistry = z.registry<{ id: string }>()
// Register definitions
for (const definition of definitions) {
definition.register(chainRegistry, { id: definition.shape.node.value })
}
// Register compatabilities
for (const { shape, schema } of compatabilities) {
schema.register(chainRegistry, { id: JSON.stringify(shape) })
}
Use the response format creation util from @rubriclab/agents
import { createResponseFormat } from '@rubriclab/agents'
const responseFormat = createResponseFormat({
name: 'chain',
schema: z.object({
chain: z.union(definitions)
}),
// Pass the registry to build the recursive schema.
registry: chainRegistry
})
console.dir(responseFormat, { depth: null }) // check it out!
You can use the response format with the agents package, or pass it directly to OpenAI.
By default, strict mode is off, this means that the raw types are valid entry points for a chain.
createChain({
add: {
input: {
a: z.number(),
b: z.number()
},
output: z.number()
}
}, {
strict: false,
})
const valid = {
node: 'add',
input: {
a: {
node: 'add',
input: {
a: 1,
b: 2
}
},
b: 3
}
}
With strict mode ON, the raw types are only used for compatabilities, you can't actually pass them. In the above case, the only valid chain would be infinite:
const valid = {
node: 'add',
input: {
a: {
node: 'add',
input: {
a: {
node: 'add',
input: {
a: {
node: 'add'
...
This is useful in many LLM structured output cases, to prevent the model from hallucinating raw inputs, but you have to make sure that you have a valid entry point for each type.
createChain({
add: {
input: {
a: z.number(),
b: z.number()
},
output: z.number()
},
numberInput: {
input: {},
output: z.number()
}
}, {
strict: true,
})
const valid = {
node: 'add',
input: {
a: {
node: 'numberInput', // ex. a UI element
input: {}
},
b: {
node: 'numberInput',
input: {}
}
}
}
Sometimes, you need to push an additional compatability to the chain outside of the normal I/O chaining flow.
createChain({
pingUser: {
input: {
userId: z.uuid()
},
output: z.undefined()
}
}, {
additionalCompatabilities: [
{ type: z.uuid(), compatability: z.literal('$.USER_ID') },
]
})
const valid = {
node: 'pingUser',
input: {
userId: '$.USER_ID'
}
}
This might useful for context injection, for example, keeping sensitive values out of system prompts (and avoiding hallucinations)
It can also be combined with Strict mode to 'override' hard coded
// Crete a slot to allow the LLM to put any string here
const inputString = z.literal('input_string')
createChain({
getAccessToken: {
input: {},
output: z.string()
},
log: {
input: {
accessToken: z.string(),
message: inputString
}
}
}, {
strict: true // Set true
additionalCompatabilities: [
// allow a raw string.
{ type: inputString, compatability: z.string() },
]
})
const valid = {
node: 'log',
input: {
accessToken: {
node: 'getAccessToken',
input: {}
},
message: 'YOOOOOOO WHATS UP!!!'
}
}
const invalid = {
node: 'log',
input: {
accessToken: 'Hallucination',
message: '...'
}
}
FAQs
Unknown package
We found that @rubriclab/chains demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
crates.io adds a Security tab backed by RustSec advisories and narrows trusted publishing paths to reduce common CI publishing risks.

Research
/Security News
A Chrome extension claiming to hide Amazon ads was found secretly hijacking affiliate links, replacing creators’ tags with its own without user consent.

Security News
A surge of AI-generated vulnerability reports has pushed open source maintainers to rethink bug bounties and tighten security disclosure processes.