Preparing for Basketry 0.2 Release Candidate
Basketry 0.2 is now available as a Release Candidate, marking a major evolution in how Basketry defines and works with its Intermediate Representation (IR). This release formalizes the IR in JSON Schema, providing a machine-readable contract that enables consistent tooling across languages and environments. By taking a schema-first approach, Basketry can now use its own pipelines to generate TypeScript definitions, documentation, and language-agnostic representations directly from the IR specification—an important step toward a truly polyglot ecosystem.
For most users, moving to 0.2 requires only a single configuration change. For plugin authors and other advanced users, this release introduces breaking changes in the IR, updated conventions, and new capabilities that may require updates to your components.
You can view the full Basketry IR Specification v0.2 for more details.
If you maintain Basketry plugins—open-source or proprietary—now is the time to upgrade to the Release Candidate, test your changes, and report any issues before 0.2 becomes the latest release. While 0.1.x components will continue to work indefinitely, they will no longer receive new features or fixes once 0.2 is promoted to latest.
For Typical Users
If you use Basketry by creating a configuration file and installing generators or rules from npm, the move to 0.2 will be simple: update your configuration so that source and output paths are relative to the config file itself, rather than to the directory where you run npx basketry
.
What to change
Before (0.1.x) — paths are relative to the current working directory:
{
"source": "./src/some-project/domain.oas3.json",
"output": "./src/some-project/generated"
... other config ...
}
Running this from the project root works as expected, but running it from a subdirectory (or with the config in a different location) changes how paths are resolved.
After (0.2.x) — paths are relative to the config file:
{
"source": "domain.oas3.json",
"output": "generated"
... other config ...
}
Now, regardless of where you run the CLI from, Basketry will resolve paths based on the location of basketry.config.json
.
Why This Matters
Previously, the output of npx basketry
could vary depending on your current working directory:
-
Before: Running from
~/
vs~/src/some-project
might produce different results or fail to find the spec file. -
After: You can run
npx basketry --config path/to/basketry.config.json
from anywhere—whether in your project root, a scripts folder, or even a completely different working directory—and the output will be consistent and deterministic.
In short: update your paths to be relative to the config file, and you’re good to go.
For Plugin Authors
This section is for maintainers of Basketry components—such as generators, rules, and other plugins—whether open-source or proprietary. If your work consumes Basketry’s Intermediate Representation (IR) or produces output based on it, the 0.2 release will likely require changes to your code.
The IR in 0.2 has been redefined and formalized in JSON Schema, providing a precise, machine-readable contract for every node in the model. While this unlocks new capabilities and consistency across languages, it also introduces structural and naming changes that will break existing code written against the 0.1.x IR.
Updating your components during the Release Candidate period ensures they will be compatible when 0.2 becomes the latest release. The changes described below are intended as a practical reference to guide you through the migration.
Member Values
- Member value type information is now a
value
property that is aMemberValue
node. isOptional
andisNullable
are directly defined onMemberValue
(therequired
rule is deprecated).isPrimitive
is deprecated in favor ofvalue.kind
.
Previously, properties, parameters, and return values had an "is a" relationship with a value type. This has been replaced with a "has a" relationship, where the value type is a member of the parent node. Information about property names, descriptions, etc can still be found directly on the parent node. Information about the value type is now found on a new value
property that is a MemberValue
node.
// Before
const typeName = property.typeName;
// After
const typeName = property.value.typeName;
Previously, a member was determined to be required if it had a required
rule. Now, a member is required by default and is only optional if it has an isOptional
value set. Addtiionally, member values how support isNullable
without needing to explicitly define a union with null
.
// Before
if (property.rules.some((r) => r.id === "required")) {
/* ... */
}
// After
if (!property.value.isOptional) {
/* ... */
}
Previously, member values types were descriminated by the isPrimitive
value. Now, they are discriminated on the kind
property of a MemberValue
node.
// Before
if (member.isPrimitive) {
/* ... */
}
// After
if (member.value.kind === "PrimitiveValue" /* or 'ComplexValue' */) {
/* ... */
}
Rules
- The following rules have been removed:
required
,constant
, andstring-enum
- Rule casing has changed to PascalCase from kebab-case. (eg.
StringMaxLength
instead ofstring-max-length
)
To determine of a member is required, check the isOptional
property of the MemberValue
node (see above).
Constant values are defined on the MemberValue
node in the constant
property. This value was added in a previous version of the IR. How that the constant
rules is being removed, the constant
property is now the single source of truth for constant values.
// Before
const constant = member.rules.find((r) => r.id === "constant")?.value;
// After
const constant = member.value.constant?.value;
All enums are defined in at the top level of the IR in the enums
collection and are referenced in a ComplexValue
member value with the typeName
property set to the enum name.
// Example
if (member.value.kind === "ComplexValue") {
const typeName = member.value.typeName;
const e = service.enums.find((e) => e.name.value === typeName.value);
}
Unions
- The collection of unions on the IR is now poplulated by
SimpleUnion
andDiscriminatedUnion
nodes. - A
disjunctionKind
is added toSimpleUnion
that differentiates betweenanyOf
andoneOf
semantics.
Previously, all unions where Union
nodes with an optional discriminator
property. Now, there are two types of unions discriminated by kind
: SimpleUnion
and DiscriminatedUnion
.
// Before
const isDiscriminated = !!union.discriminator;
// After
const isDiscriminated = union.kind === "DiscriminatedUnion";
In 0.2, simple unions include a new disjunctionKind
property, which indicates whether the union is inclusive
or exclusive
. An inclusive disjunction maps to anyOf
semantics in SDLs like OpenAPI or JSON Schema, meaning a value may satisfy multiple member types at once. An exclusive
disjunction maps to oneOf
semantics, meaning a value must match exactly one member type. This property removes the need to infer union semantics from the source SDL and makes intent explicit in the IR. Note that not all languages support a clear distinction between inclusive and exclusive disjunctions in their union semantics, so this property is optional and defaults to inclusive
.
Scalars → Literals
- The generic
Scalar<T>
type has been removed from the IR. - Instead of a single scalar type, 0.2 defines explicit literal types such as
NumberLiteral
,StringLiteral
,NonEmptyStringLiteral
,NonNegativeIntegerLiteral
, etc.
A "literal" type is a value that maps to a literal expression in the source SDL. This allows tooling to source map back to a specific offset and length in the original source file. Previously, the generic Scalar<T>
represented all of these values. However, because JSON Schema (how used to to define the IR) does not support literal types, the IR had to be updated to support them. Additionally, this allows for Basketry implementation in lanaguages that themselves do not support generics.
The more precice literal types now also support more precise validation. For example, NonEmptyStringLiteral
will now validate that the string is not empty, and NonNegativeIntegerLiteral
will validate that the number is not negative. Each literal type has a kind
property that indicates the type of literal it is.
Return Values
- The
ReturnValue
type has been renamedReturnValue
Member.returnType
is now renamedMember.returns
.
ReturnType<Type>
is a built-in utility type in TypeScript that extracts the return type of a function type. It is part of TypeScript's standard library and provides a way to infer and reuse the type returned by a function without explicitly defining it again. This type name conflicted with Basketry's ReturnType
type creating confusion when it was not explicitly imported from baksetry
. In 0.2, it has been renamed to ReturnValue
to avoid the conflict.
In conjunction with type name change, Member.returnType
is now renamed Member.returns
.
// Before
const returnType = member.returnType; // ReturnType
// After
const returns = member.returns; // ReturnValue
Protocols
- The
Interface.protocols
property is now optional - Some HTTP protocol types and properties have been renamed:
HttpPath
→HttpRoute
,HttpParameter.in
→HttpParameter.location
,HttpPath.path
→HttpRoute.pattern
These changes bring protocol naming in line with common terminology and improve clarity when mapping IR data to HTTP and other transport layers.
Kinds
- Nearly every node in the IR now includes a kind property that identifies its exact type.
- The kind property provides a reliable entry point for implementing visitor-pattern–based rules or generators, reducing the need for manual type checks.
Source Paths
- The
sourcePath
property is nowsourcePaths
, an array of file references - Encoded locations now include an index of the corresponding source path
With sourcePaths
now supporting multiple file references, Basketry can accurately represent services defined across several source files. Component authors don’t need to manage these files directly—the IR still presents a unified service model—but you now have full visibility into where each node originated. Each node’s loc
includes an encoded sourceIndex
that points to the correct entry in sourcePaths
, making it easy to trace definitions back to their source file for debugging, documentation, or error reporting.
// Example
import { decodeRange } from "basketry";
const { sourceIndex, range } = decodeRange(node.loc);
const sourcePath = service.sourcePaths[sourceIndex];
Next Steps
Basketry 0.2 introduces foundational changes to the IR that will enable more consistent tooling, better cross-language support, and greater flexibility for complex service definitions. While 0.1.x components will continue to function, they will no longer receive new features or fixes once 0.2 becomes the latest release.
If you maintain a Basketry plugin, now is the time to:
- Upgrade to the 0.2 Release Candidate in your development environment.
- Update your component code based on the changes outlined in this guide.
- Test your components against your existing Basketry pipelines.
- Report any issues via GitHub or join the Basketry Discord for discussion and support.
- Attend weekly office hours (weekends on Discord) if you’d like real-time help or to review migration details.
By preparing now, you’ll ensure your components are ready when 0.2 is promoted to latest and can take full advantage of the new IR capabilities without disruption to your users.
Basketry 0.2 is a major step toward a more flexible, language-agnostic ecosystem—but it’s only the beginning. To see where we’re headed next, including features and improvements planned beyond 0.2, check out the Basketry roadmap. Your early feedback and participation will help shape the future of the project.
This article was written by a human. Editing and proofreading were performed with the assistance of one or more large language models.