Runtime Guides

Package-source copy · Generated into the site so runtime docs are searchable and linkable.

Migration Guide (TypeScript)

This guide helps migrate from raw AWS SDK v3 DynamoDB usage to @theory-cloud/tabletheory-ts.

Migration: raw PutItemdb.create

Problem

Hand-authored PutItemCommand payloads are verbose and drift-prone across services.

Solution

Define a model once and use typed helpers.

// ✅ CORRECT: TableTheory model + create
const db = new TheorydbClient(ddb).register(User);
await db.create('User', { PK: 'U#1', SK: 'PROFILE' }, { ifNotExists: true });

Migration: manual LastEvaluatedKey → opaque cursor

Problem

Exposing raw DynamoDB keys couples clients to your schema.

Solution

Use the opaque cursor returned by page() and pass it back to the next request.

Schema migration (autoMigrate)

This mirrors the Go migration guide schema-migration story: move data from a v1 table shape to a v2 shape, optionally backing up the source first and transforming each item on the way. It is reproducible against DynamoDB Local — no AWS account required.

autoMigrate(ddb, sourceModel, options) ensures the target table exists and, when dataCopy is set, scans the source table and writes each item into the target (through an optional transform). Item-level transforms mirror the Go helpers:

  • renameField(oldName, newName)
  • addField(name, value)
  • removeField(name)
  • copyAllFields()
  • chainTransforms(...transforms) composes them left to right

Walkthrough

import {
  addField,
  autoMigrate,
  chainTransforms,
  defineModel,
  ensureTable,
  renameField,
} from '@theory-cloud/tabletheory-ts';
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';

const ddb = new DynamoDBClient({
  region: process.env.AWS_REGION ?? 'us-east-1',
  endpoint: process.env.DYNAMODB_ENDPOINT ?? 'http://localhost:8000',
  credentials: { accessKeyId: 'dummy', secretAccessKey: 'dummy' },
});

const userV1 = defineModel({
  name: 'UserV1',
  table: { name: 'users_migration_v1' },
  keys: {
    partition: { attribute: 'PK', type: 'S' },
    sort: { attribute: 'SK', type: 'S' },
  },
  attributes: [
    { attribute: 'PK', type: 'S', roles: ['pk'] },
    { attribute: 'SK', type: 'S', roles: ['sk'] },
    { attribute: 'name', type: 'S' },
  ],
});

const userV2 = defineModel({
  name: 'UserV2',
  table: { name: 'users_migration_v2' },
  keys: {
    partition: { attribute: 'PK', type: 'S' },
    sort: { attribute: 'SK', type: 'S' },
  },
  attributes: [
    { attribute: 'PK', type: 'S', roles: ['pk'] },
    { attribute: 'SK', type: 'S', roles: ['sk'] },
    { attribute: 'displayName', type: 'S' },
    { attribute: 'status', type: 'S' },
  ],
});

// Seed the v1 table.
await ensureTable(ddb, userV1);
await ddb.send(
  new PutItemCommand({
    TableName: userV1.tableName,
    Item: { PK: { S: 'USER#1' }, SK: { S: 'v1' }, name: { S: 'Ada' } },
  }),
);

// Back up v1, then migrate v1 -> v2: rename `name` -> `displayName` and add `status`.
await autoMigrate(ddb, userV1, {
  targetModel: userV2,
  dataCopy: true,
  backupTable: 'users_migration_backup',
  transform: chainTransforms(
    renameField('name', 'displayName'),
    addField('status', { S: 'active' }),
  ),
});
// users_migration_v2 now holds { PK, SK, displayName: 'Ada', status: 'active' };
// users_migration_backup holds the original { PK, SK, name: 'Ada' }.

A runnable version of this walkthrough lives in ts/test/integration/schema-migration.test.ts and runs on the integration lane against DynamoDB Local.