Migrate from TypeScript
MetaScript is designed for gradual adoption. You can migrate file-by-file while keeping full interop with TypeScript.
Migration Strategy
Phase 1: Side-by-Side
Run MetaScript alongside TypeScript:
my-project/
├── src/
│ ├── index.ts # TypeScript entry
│ ├── utils.ts # Existing TypeScript
│ └── new-feature.ms # New MetaScript code
├── tsconfig.json
└── build.msPhase 2: Gradual Conversion
Convert files one at a time, starting with leaf modules:
1. Utilities and helpers (no dependencies)
2. Data models and types
3. Business logic
4. Entry pointsPhase 3: Full Migration
Once stable, remove TypeScript tooling.
Configuration
Dual Build Setup
// build.ms
export const package = {
name: "my-app",
version: "1.0.0",
backends: ["js"],
};
export const deps = {};
export const deps_js = {
// Your npm dependencies
};// tsconfig.json
{
"compilerOptions": {
"paths": {
"@ms/*": ["./dist/*.js"]
}
}
}Package.json Scripts
{
"scripts": {
"build:ms": "msc build",
"build:ts": "tsc",
"build": "npm run build:ms && npm run build:ts",
"dev": "concurrently \"msc watch\" \"tsc --watch\""
}
}Syntax Differences
Imports
// TypeScript
import { User } from "./models/user"
import type { UserConfig } from "./types"
// MetaScript - same syntax!
import { User } from "./models/user"
import type { UserConfig } from "./types"Classes
// TypeScript
class User {
constructor(
public name: string,
public email: string
) {}
greet(): string {
return `Hello, ${this.name}!`
}
}
// MetaScript - cleaner syntax
class User {
name: string
email: string
greet(): string {
return `Hello, ${this.name}!`
}
}Type Annotations
// TypeScript
function process(items: string[]): number {
return items.length
}
// MetaScript - identical
function process(items: string[]): number {
return items.length
}Decorators
// TypeScript (experimental)
@Entity()
class User {
@Column()
name: string
}
// MetaScript - native support
@derive(Entity)
class User {
@column
name: string
}Calling TypeScript from MetaScript
Import TypeScript Modules
// src/feature.ms
import { existingUtil } from "./utils" // TypeScript file
import { lodash } from "lodash" // npm package
const result = existingUtil(data)Type Declarations
Create .d.ms files for untyped JavaScript:
// src/types/legacy.d.ms
declare module "legacy-lib" {
export function doThing(input: string): number
export class OldService {
process(data: unknown): Promise<void>
}
}Calling MetaScript from TypeScript
Generated Types
MetaScript generates .d.ts files automatically:
// dist/new-feature.d.ts (auto-generated)
export declare class NewFeature {
process(data: string): Result<Data, Error>
}Import in TypeScript
// src/app.ts
import { NewFeature } from "@ms/new-feature"
const feature = new NewFeature()
const result = feature.process(data)Converting Files
Step 1: Rename and Fix Syntax
# Rename file
mv src/utils.ts src/utils.ms
# Check for errors
msc check src/utils.msStep 2: Add Type Annotations
// Before (TypeScript inference)
const users = []
users.push({ name: "Alice" })
// After (MetaScript explicit)
const users: User[] = []
users.push(new User(name: "Alice"))Step 3: Replace Patterns
| TypeScript | MetaScript |
|---|---|
any | Remove or add proper type |
as Type | Type guards or .as<Type>() |
! (non-null) | Optional chaining or guards |
enum | type union |
namespace | Modules |
Step 4: Add Derive Macros
// Before
class User {
name: string
email: string
equals(other: User): boolean {
return this.name === other.name && this.email === other.email
}
clone(): User {
return new User(this.name, this.email)
}
}
// After
@derive(Eq, Clone)
class User {
name: string
email: string
}Common Issues
any Types
// TypeScript
function process(data: any): any {
return data.value
}
// MetaScript - use generics or unions
function process<T>(data: { value: T }): T {
return data.value
}
// Or for truly dynamic data
function process(data: unknown): Result<Value, ParseError> {
return Value.parse(data)
}Type Assertions
// TypeScript
const user = response as User
// MetaScript - runtime validation
const user = User.parse(response) // Returns Result<User, Error>
// Or type guard
if (isUser(response)) {
// response is User here
}Optional Properties
// TypeScript
interface Config {
host: string
port?: number
}
// MetaScript
class Config {
host: string
port: number | null = null
}Testing During Migration
Run Both Test Suites
{
"scripts": {
"test:ts": "jest --testMatch '**/*.test.ts'",
"test:ms": "msc test",
"test": "npm run test:ts && npm run test:ms"
}
}Integration Tests
Write integration tests that exercise both TypeScript and MetaScript code together.
Next Steps
- Quick Start - Learn MetaScript basics
- Real Types - Understand the type system
- Compile-Time Power - Use @derive and @comptime