Pre-AlphaMetaScript is in early design phase. The compiler is not yet available.Join Discord for updates
MetaScript

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.ms

Phase 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 points

Phase 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.ms

Step 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

TypeScriptMetaScript
anyRemove or add proper type
as TypeType guards or .as<Type>()
! (non-null)Optional chaining or guards
enumtype union
namespaceModules

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