Skip to content

Latest commit

 

History

History
562 lines (441 loc) · 12.6 KB

File metadata and controls

562 lines (441 loc) · 12.6 KB

Migration Guide

This guide helps you upgrade between major versions of the AppSheet library.


Migration: v2.x → v3.0.0

Overview

Version 3.0.0 introduces breaking changes for dependency injection and testing support. The main changes are:

  • Factory injection pattern for SchemaManager and ConnectionManager
  • runAsUserEmail is now required (not optional)
  • New constructor signatures for all main classes
  • New schema introspection methods

Quick Migration

// ❌ Old (v2.x)
import { SchemaLoader, SchemaManager, AppSheetClient } from '@techdivision/appsheet';

const client = new AppSheetClient({
  appId: 'app-id',
  applicationAccessKey: 'key',
  runAsUserEmail: 'user@example.com'  // optional
});

const schema = SchemaLoader.fromYaml('./schema.yaml');
const db = new SchemaManager(schema);
const table = db.table('conn', 'tableName');  // userEmail optional

// ✅ New (v3.0.0)
import {
  SchemaLoader,
  SchemaManager,
  AppSheetClient,
  AppSheetClientFactory,
  ConnectionDefinition
} from '@techdivision/appsheet';

// Direct client usage
const connectionDef: ConnectionDefinition = {
  appId: 'app-id',
  applicationAccessKey: 'key',
  tables: { /* table definitions */ }
};
const client = new AppSheetClient(connectionDef, 'user@example.com');  // required

// Schema-based usage with factory injection
const schema = SchemaLoader.fromYaml('./schema.yaml');
const clientFactory = new AppSheetClientFactory();
const db = new SchemaManager(clientFactory, schema);
const table = db.table('conn', 'tableName', 'user@example.com');  // required

Breaking Changes

1. AppSheetClient Constructor

// ❌ Old (v2.x)
const client = new AppSheetClient({
  appId: 'app-id',
  applicationAccessKey: 'key',
  runAsUserEmail: 'user@example.com'  // optional
});

// ✅ New (v3.0.0)
const connectionDef: ConnectionDefinition = {
  appId: 'app-id',
  applicationAccessKey: 'key',
  tables: {
    users: { tableName: 'Users', keyField: 'id', fields: {...} }
  }
};
const client = new AppSheetClient(connectionDef, 'user@example.com');  // required

2. SchemaManager Constructor

// ❌ Old (v2.x)
const db = new SchemaManager(schema);

// ✅ New (v3.0.0)
const clientFactory = new AppSheetClientFactory();
const db = new SchemaManager(clientFactory, schema);

3. SchemaManager.table() - runAsUserEmail Required

// ❌ Old (v2.x)
const table = db.table('conn', 'tableName');  // optional user
const table = db.table('conn', 'tableName', 'user@example.com');

// ✅ New (v3.0.0)
const table = db.table('conn', 'tableName', 'user@example.com');  // always required

4. ConnectionManager Constructor

// ❌ Old (v2.x)
const connMgr = new ConnectionManager();
connMgr.register('name', client);
const client = connMgr.get('name', 'user@example.com');

// ✅ New (v3.0.0)
const clientFactory = new AppSheetClientFactory();
const connMgr = new ConnectionManager(clientFactory, schema);
const client = connMgr.get('name', 'user@example.com');  // both required

5. Removed Methods

Removed Alternative
AppSheetClient.getConfig() Use getTable(tableName)
ConnectionManager.register() Pass schema to constructor
ConnectionManager.remove() Create new instance
ConnectionManager.clear() Create new instance
ConnectionManager.ping() Removed
ConnectionManager.healthCheck() Removed
SchemaManager.getConnectionManager() Internal only
SchemaManager.reload() Create new instance

New Features in v3.0.0

Schema Introspection Methods

Access schema metadata directly:

// Get table definition
const tableDef = db.getTableDefinition('default', 'users');
// → { tableName: 'Users', keyField: 'id', fields: {...} }

// Get field definition
const fieldDef = db.getFieldDefinition('default', 'users', 'status');
// → { type: 'Enum', required: true, allowedValues: [...] }

// Get allowed values for Enum fields
const values = db.getAllowedValues('default', 'users', 'status');
// → ['Active', 'Inactive', 'Pending']

Testing with Mock Factory

import { MockAppSheetClientFactory, SchemaManager } from '@techdivision/appsheet';

// Test setup - no real API calls
const mockFactory = new MockAppSheetClientFactory();
const db = new SchemaManager(mockFactory, schema);

// Test your code
const table = db.table('conn', 'users', 'test@example.com');
await table.add([{ id: '1', name: 'Test' }]);
const users = await table.findAll();  // Returns in-memory data

Migration: v1.x → v2.0.0

This guide helps you upgrade from version 1.x to 2.0.0 of the AppSheet library.

Overview

Version 2.0.0 introduces breaking changes to provide better type safety and validation through AppSheet-specific field types. The old generic TypeScript types (string, number, etc.) have been replaced with 27 AppSheet field types that match the actual AppSheet column types.

What's Changed

1. Field Type System

Old (v1.x): Generic TypeScript types

type FieldType = 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object';

New (v2.0.0): AppSheet-specific types

type AppSheetFieldType =
  | 'Text' | 'Email' | 'URL' | 'Phone'
  | 'Number' | 'Decimal' | 'Percent' | 'Price'
  | 'Date' | 'DateTime' | 'Time' | 'Duration'
  | 'YesNo' | 'Enum' | 'EnumList'
  // ... and 12 more types

2. Schema Format

Old (v1.x): Mixed formats allowed

fields:
  email: string              # Shorthand string
  age: number                # Shorthand string
  status:                    # Full object
    type: string
    enum: ["Active", "Inactive"]

New (v2.0.0): Only full object format

fields:
  email:
    type: Email
    required: true
  age:
    type: Number
    required: false
  status:
    type: Enum
    required: true
    allowedValues: ["Active", "Inactive"]

3. Property Renames

Old Property New Property Notes
enum allowedValues More descriptive name

Migration Steps

Step 1: Update Schema Files

Generic Types → AppSheet Types

# Before (v1.x)
fields:
  name: string
  email: string
  age: number
  active: boolean
  createdAt: date
  tags: array

# After (v2.0.0)
fields:
  name:
    type: Text
    required: false
  email:
    type: Email
    required: true
  age:
    type: Number
    required: false
  active:
    type: YesNo
    required: false
  createdAt:
    type: DateTime
    required: false
  tags:
    type: EnumList
    required: false

Type Mapping Reference

Old Type New Type(s) Notes
string Text, Email, URL, Phone, Address, Name Choose based on data
number Number, Decimal, Price, Percent Choose based on usage
boolean YesNo Accepts boolean or "Yes"/"No" strings
date Date, DateTime, Time, Duration Choose based on precision
array EnumList, RefList Choose based on usage
object (various) Depends on context

Step 2: Update Enum Definitions

# Before (v1.x)
status:
  type: string
  enum: ["Active", "Inactive", "Pending"]

# After (v2.0.0)
status:
  type: Enum
  required: true
  allowedValues: ["Active", "Inactive", "Pending"]

Step 3: Use CLI to Generate New Schema

The easiest way to migrate is to use the CLI inspect command:

# Generate new schema from existing AppSheet app
npx appsheet inspect \
  --app-id YOUR_APP_ID \
  --key YOUR_ACCESS_KEY \
  --output schema.yaml

# The CLI will auto-detect field types including:
# - Email addresses
# - URLs
# - Phone numbers
# - Enum fields (with extracted allowedValues)
# - EnumList fields
# - Dates, DateTimes, Percentages, etc.

Step 4: Review Auto-Generated Schema

The CLI uses smart heuristics but may need manual adjustments:

# CLI might detect this as Text
website:
  type: Text

# But you should change it to URL for validation
website:
  type: URL
  required: false

Step 5: Test Your Schema

# Validate schema structure
npx appsheet validate --schema schema.yaml

# Run your application tests
npm test

Common Migration Patterns

Pattern 1: Simple String Fields

# Before
name: string

# After - Choose appropriate type
name:
  type: Text        # Generic text
# or
email:
  type: Email       # Email with validation
# or
website:
  type: URL         # URL with validation

Pattern 2: Enum Fields

# Before
priority:
  type: string
  enum: ["Low", "Medium", "High"]

# After
priority:
  type: Enum
  required: true
  allowedValues: ["Low", "Medium", "High"]

Pattern 3: Multi-Select Fields

# Before
skills:
  type: array

# After
skills:
  type: EnumList
  required: false
  allowedValues: ["JavaScript", "TypeScript", "Python"]

Pattern 4: Numeric Fields

# Before
quantity: number
price: number
discount: number

# After - Choose appropriate numeric type
quantity:
  type: Number      # Integer or decimal
  required: true
price:
  type: Price       # Currency value
  required: true
discount:
  type: Percent     # 0.00 to 1.00
  required: false

Validation Changes

Version 2.0.0 adds comprehensive validation:

Email Validation

// Now validates email format
await table.add([{ email: 'invalid' }]);
// ❌ Error: must be a valid email address

URL Validation

// Now validates URL format
await table.add([{ website: 'not-a-url' }]);
// ❌ Error: must be a valid URL

Enum Validation

// Now validates against allowedValues
await table.add([{ status: 'Unknown' }]);
// ❌ Error: must be one of: Active, Inactive, Pending

Percent Range Validation

// Now validates range (0.00 to 1.00)
await table.add([{ discount: 1.5 }]);
// ❌ Error: must be between 0.00 and 1.00

Automated Migration Tool

For large schemas, consider this migration script:

import * as fs from 'fs';
import * as yaml from 'yaml';

const oldSchema = yaml.parse(fs.readFileSync('old-schema.yaml', 'utf-8'));

// Type mapping
const typeMap = {
  'string': 'Text',
  'number': 'Number',
  'boolean': 'YesNo',
  'date': 'Date',
  'array': 'EnumList',
  'object': 'Text',
};

function migrateField(oldField: any) {
  if (typeof oldField === 'string') {
    // Shorthand format
    return {
      type: typeMap[oldField] || 'Text',
      required: false,
    };
  }

  // Full object format
  const newField: any = {
    type: typeMap[oldField.type] || 'Text',
    required: oldField.required || false,
  };

  // Rename enum → allowedValues
  if (oldField.enum) {
    newField.type = 'Enum';
    newField.allowedValues = oldField.enum;
  }

  if (oldField.description) {
    newField.description = oldField.description;
  }

  return newField;
}

// Migrate all tables
for (const [connName, conn] of Object.entries(oldSchema.connections)) {
  for (const [tableName, table] of Object.entries((conn as any).tables)) {
    const newFields: any = {};
    for (const [fieldName, fieldDef] of Object.entries((table as any).fields)) {
      newFields[fieldName] = migrateField(fieldDef);
    }
    (table as any).fields = newFields;
  }
}

fs.writeFileSync('migrated-schema.yaml', yaml.stringify(oldSchema));
console.log('✓ Schema migrated to v2.0.0 format');
console.log('⚠ Please review and adjust field types manually');

Troubleshooting

Error: "Type 'string' is not assignable to type 'AppSheetFieldType'"

Cause: Using old generic type in schema

Solution: Replace with AppSheet type:

# ❌ Old
email: string

# ✅ New
email:
  type: Email

Error: "Field 'enum' does not exist on type 'FieldDefinition'"

Cause: Using old enum property

Solution: Rename to allowedValues:

# ❌ Old
status:
  type: string
  enum: ["Active"]

# ✅ New
status:
  type: Enum
  allowedValues: ["Active"]

Error: "Shorthand format no longer supported"

Cause: Using shorthand string format

Solution: Use full object format:

# ❌ Old
name: string

# ✅ New
name:
  type: Text
  required: false

Getting Help

Summary

  1. ✅ Replace generic types with AppSheet types
  2. ✅ Convert all fields to full object format
  3. ✅ Rename enumallowedValues
  4. ✅ Use CLI to auto-generate new schema
  5. ✅ Review and test thoroughly

Estimated Migration Time: 15-30 minutes per schema file (depending on size)