@laag/smithy
v1.0.3
Published
TypeScript library for interfacing with AWS Smithy models
Maintainers
Readme
@laag/smithy
A modern TypeScript library for working with AWS Smithy models. Provides comprehensive support for reading, writing, validating, and manipulating Smithy IDL documents with full type safety and code generation capabilities.
Features
- 🔧 Full TypeScript Support - Complete type definitions for Smithy 2.0 specification
- 📦 Multiple Module Formats - ESM, CommonJS, and browser bundles
- ✅ Model Validation - Built-in validation according to Smithy specification
- 🚀 High Performance - Optimized for speed and memory efficiency
- 🎯 Shape Management - Comprehensive API for working with shapes, traits, and services
- 🔌 Trait Support - Full support for standard and custom traits
- 🛠️ Code Generation - Generate TypeScript, JavaScript, and Python code from models
- 🔍 Selector Support - Query shapes using Smithy selector syntax
- 🌐 Cross-Platform - Works in Node.js, browsers, and edge environments
Installation
# Install with npm
npm install @laag/smithy @laag/core
# Install with yarn
yarn add @laag/smithy @laag/core
# Install with bun
bun add @laag/smithy @laag/coreQuick Start
ESM (Recommended)
import { Smithy } from '@laag/smithy';
// Create from JSON object
const smithy = new Smithy({
smithy: '2.0',
shapes: {
'example.weather#Weather': {
type: 'service',
version: '2006-03-01',
operations: [{ target: 'example.weather#GetWeather' }],
},
'example.weather#GetWeather': {
type: 'operation',
input: { target: 'example.weather#GetWeatherInput' },
output: { target: 'example.weather#GetWeatherOutput' },
},
},
});
console.log(smithy.version); // "2.0"
console.log(smithy.getServices().length); // 1CommonJS
const { Smithy } = require('@laag/smithy');
const smithy = new Smithy({
smithy: '2.0',
shapes: {
'example.weather#Weather': {
type: 'service',
version: '2006-03-01',
},
},
});Usage Examples
Reading an Existing Model
import { readFileSync } from 'fs';
import { Smithy } from '@laag/smithy';
// Load from JSON file
const jsonData = readFileSync('weather-service.json', 'utf8');
const smithy = new Smithy(jsonData);
console.log(`Model version: ${smithy.version}`);
console.log('Services:');
for (const service of smithy.getServices()) {
console.log(` ${service.version}`);
}
// Get all operations
const services = smithy.getServices();
for (const service of services) {
const serviceId = Object.keys(smithy.shapes.entries()).find(
([_, shape]) => shape === service
)?.[0];
if (serviceId) {
const operations = smithy.getOperations(serviceId);
console.log(`Operations for ${serviceId}:`);
for (const operation of operations) {
console.log(` ${operation.type}`);
}
}
}Output:
Model version: 2.0
Services:
2006-03-01
Operations for example.weather#Weather:
operation
operationCreating a New Model
import { Smithy } from '@laag/smithy';
import type { SmithyModel, ServiceShape, OperationShape } from '@laag/smithy';
// Create empty model
const model: SmithyModel = {
smithy: '2.0',
metadata: {
authors: ['[email protected]'],
},
shapes: {},
};
const smithy = new Smithy(model);
// Add a service
smithy.addShape('example.users#UserService', {
type: 'service',
version: '2023-01-01',
operations: [{ target: 'example.users#GetUser' }, { target: 'example.users#CreateUser' }],
});
// Add operations
smithy.addShape('example.users#GetUser', {
type: 'operation',
input: { target: 'example.users#GetUserInput' },
output: { target: 'example.users#GetUserOutput' },
traits: {
'smithy.api#readonly': {},
'smithy.api#http': {
method: 'GET',
uri: '/users/{userId}',
},
},
});
smithy.addShape('example.users#CreateUser', {
type: 'operation',
input: { target: 'example.users#CreateUserInput' },
output: { target: 'example.users#CreateUserOutput' },
traits: {
'smithy.api#http': {
method: 'POST',
uri: '/users',
},
},
});
// Add input/output structures
smithy.addShape('example.users#GetUserInput', {
type: 'structure',
members: {
userId: {
target: 'smithy.api#String',
traits: {
'smithy.api#required': {},
'smithy.api#httpLabel': {},
},
},
},
});
smithy.addShape('example.users#GetUserOutput', {
type: 'structure',
members: {
user: { target: 'example.users#User' },
},
});
smithy.addShape('example.users#User', {
type: 'structure',
members: {
id: { target: 'smithy.api#String' },
name: { target: 'smithy.api#String' },
email: { target: 'smithy.api#String' },
},
});
// Output as pretty JSON
console.log(smithy.toString(true));Working with Traits
import { Smithy } from '@laag/smithy';
const smithy = new Smithy(model);
// Check if a shape has a trait
if (smithy.hasTrait('example.users#userId', 'smithy.api#required')) {
console.log('userId is required');
}
// Get all traits for a shape
const traits = smithy.getTraits('example.users#GetUser');
if (traits) {
for (const [traitId, value] of traits) {
console.log(`Trait: ${traitId}`, value);
}
}
// Add a trait to a shape
smithy.addTrait(
'example.users#GetUser',
'smithy.api#documentation',
'Retrieves a user by their unique identifier'
);
// Get HTTP binding information
const binding = smithy.getHttpBinding('example.users#GetUser');
if (binding) {
console.log(`${binding.method} ${binding.uri}`); // "GET /users/{userId}"
}Model Validation
import { Smithy } from '@laag/smithy';
const smithy = new Smithy(model);
const validation = smithy.validate();
if (validation.valid) {
console.log('✅ Model is valid');
} else {
console.log('❌ Model has errors:');
for (const error of validation.errors) {
console.log(` ${error.path}: ${error.message}`);
}
}Shape Queries and Selectors
import { Smithy } from '@laag/smithy';
const smithy = new Smithy(model);
// Get shapes by type
const services = smithy.getShapesByType('service');
const structures = smithy.getShapesByType('structure');
const operations = smithy.getShapesByType('operation');
console.log(`Found ${services.length} services`);
console.log(`Found ${structures.length} structures`);
console.log(`Found ${operations.length} operations`);
// Use selectors to query shapes
const allShapes = smithy.select('*');
const allStructures = smithy.select('structure');
const requiredShapes = smithy.select('[trait|required]');
console.log('All structures:');
for (const match of allStructures) {
console.log(` ${match.id}: ${match.shape.type}`);
}Code Generation
Generate client code from your Smithy models:
import { Smithy } from '@laag/smithy';
const smithy = new Smithy(model);
// Generate TypeScript interfaces and client
const tsCode = smithy.generateTypeScript({
includeComments: true,
indent: 2,
});
console.log('Generated TypeScript:');
console.log(tsCode);
// Generate JavaScript classes
const jsCode = smithy.generateJavaScript({
outputFormat: 'class',
includeComments: true,
});
console.log('Generated JavaScript:');
console.log(jsCode);
// Generate Python dataclasses
const pythonCode = smithy.generatePython({
includeComments: true,
indent: 4,
});
console.log('Generated Python:');
console.log(pythonCode);Working with Services and Operations
import { Smithy } from '@laag/smithy';
const smithy = new Smithy(model);
// Get all services
const services = smithy.getServices();
for (const service of services) {
console.log(`Service version: ${service.version}`);
}
// Get a specific service
const service = smithy.getService('example.users#UserService');
if (service) {
console.log(`Service: ${service.version}`);
// Get operations for this service
const operations = smithy.getOperations('example.users#UserService');
console.log(`Operations: ${operations.length}`);
// Get resources for this service
const resources = smithy.getResources('example.users#UserService');
console.log(`Resources: ${resources.length}`);
}
// Check HTTP bindings for operations
const operations = smithy.getOperations('example.users#UserService');
for (const operation of operations) {
const operationId = Object.keys(smithy.shapes.entries()).find(
([_, shape]) => shape === operation
)?.[0];
if (operationId) {
const binding = smithy.getHttpBinding(operationId);
if (binding) {
console.log(`${binding.method} ${binding.uri}`);
}
}
}API Reference
Constructor
new Smithy(input: SmithyModel | string)Creates a new Smithy model instance. Accepts either a Smithy model object or JSON string.
Properties
version: string- Smithy version (e.g., "2.0")metadata: Record<string, unknown> | undefined- Model metadatashapes: Map<ShapeId, Shape>- All shapes in the model
Shape Operations
getShape(shapeId: ShapeId): Shape | undefined- Get a shape by IDgetShapesByType(type: ShapeType): Shape[]- Get all shapes of a specific typeaddShape(shapeId: ShapeId, shape: Shape): void- Add a shape to the modelremoveShape(shapeId: ShapeId): boolean- Remove a shape from the modelhasShape(shapeId: ShapeId): boolean- Check if a shape exists
Service Operations
getServices(): ServiceShape[]- Get all servicesgetService(serviceId: ShapeId): ServiceShape | undefined- Get a specific servicegetOperations(serviceId: ShapeId): OperationShape[]- Get operations for a servicegetResources(serviceId: ShapeId): ResourceShape[]- Get resources for a servicegetHttpBinding(operationId: ShapeId): HttpBinding | undefined- Get HTTP binding info
Trait Operations
getTraits(shapeId: ShapeId): Map<string, unknown> | undefined- Get all traits for a shapehasTrait(shapeId: ShapeId, traitId: string): boolean- Check if shape has a traitaddTrait(shapeId: ShapeId, traitId: string, value: unknown): void- Add a trait to a shape
Validation
validate(): ValidationResult- Validate the entire model
Serialization
toJSON(): SmithyModel- Convert to plain JavaScript objecttoString(pretty?: boolean): string- Convert to JSON string
Code Generation
generateTypeScript(options?: GeneratorOptions): string- Generate TypeScript codegenerateJavaScript(options?: GeneratorOptions): string- Generate JavaScript codegeneratePython(options?: GeneratorOptions): string- Generate Python code
Selectors
select(selector: string): SelectorMatch[]- Query shapes using selector syntax
TypeScript Support
This library is written in TypeScript and provides comprehensive type definitions:
import type {
SmithyModel,
Shape,
ShapeType,
ShapeId,
ServiceShape,
OperationShape,
StructureShape,
ResourceShape,
MemberShape,
HttpBinding,
GeneratorOptions,
SelectorMatch,
} from '@laag/smithy';Browser Support
The library works in modern browsers with ES2020 support:
<script type="module">
import { Smithy } from 'https://unpkg.com/@laag/smithy/dist/browser/index.js';
const smithy = new Smithy({
smithy: '2.0',
shapes: {
'example.hello#HelloService': {
type: 'service',
version: '2023-01-01',
},
},
});
</script>Examples
Check out the examples directory for more comprehensive examples:
- Basic Usage - Fundamental operations
- Code Generation - Generate client code
- Trait Usage - Working with traits
- Validation - Model validation examples
Advanced Features
Selector Syntax
The library supports basic Smithy selector syntax:
// Select all shapes
const all = smithy.select('*');
// Select by type
const structures = smithy.select('structure');
const services = smithy.select('service');
// Select by trait
const required = smithy.select('[trait|required]');
const httpOperations = smithy.select('[trait|http]');
// Select by namespace (basic support)
const exampleShapes = smithy.select('[id|namespace = example]');Generator Options
Customize code generation with options:
const options: GeneratorOptions = {
includeComments: true,
indent: 2,
outputFormat: 'class', // 'class' | 'module' | 'functional'
namespace: 'MyAPI',
};
const code = smithy.generateTypeScript(options);HTTP Bindings
Extract HTTP information from operations:
const binding = smithy.getHttpBinding('example.users#GetUser');
if (binding) {
console.log(binding.method); // "GET"
console.log(binding.uri); // "/users/{userId}"
console.log(binding.code); // 200 (if specified)
}Related Packages
- @laag/core - Core utilities and base classes
- @laag/openapi - OpenAPI/Swagger document support
- @laag/raml - RAML document support
- @laag/cli - Command-line interface
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/bschwarz/laag.git
cd laag
# Install dependencies
bun install
# Build the smithy package
cd packages/smithy
bun run build
# Run tests
bun test
# Run tests in watch mode
bun test --watchRunning Examples
# Run TypeScript examples
bun run examples/basic-usage.ts
bun run examples/code-generation.ts
# Build and test
bun run build
bun run test
bun run type-checkLicense
MIT - see LICENSE for details.
Changelog
See CHANGELOG.md for release history.
