or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

analytics.mdapi.mdauth.mddatastore.mdindex.mdnotifications.mdserver.mdstorage.mdutilities.md
tile.json

datastore.mddocs/

DataStore

Real-time data synchronization with offline support and conflict resolution. Provides a local-first data management solution that automatically synchronizes with the cloud when connectivity is available.

Capabilities

Core Import

import { 
  save, query, delete as del, clear, 
  start, stop 
} from "aws-amplify/datastore";

Model Operations

DataStore operations work with model instances that extend the PersistentModel interface.

/**
 * Save a model instance to DataStore
 * @param model - Model instance to save
 * @returns Promise with saved model
 */
function save<T extends PersistentModel>(model: T): Promise<T>;

/**
 * Query models from DataStore
 * @param modelConstructor - Model class constructor
 * @param criteria - Optional query criteria
 * @returns Promise with array of matching models
 */
function query<T extends PersistentModel>(
  modelConstructor: PersistentModelConstructor<T>,
  criteria?: ModelPredicate<T> | string
): Promise<T[]>;

/**
 * Delete models from DataStore
 * @param model - Model instance, class constructor, or predicate
 * @returns Promise with deleted model(s)
 */
function delete<T extends PersistentModel>(
  model: T | PersistentModelConstructor<T> | ModelPredicate<T>
): Promise<T | T[]>;

/**
 * Clear all data from DataStore
 * @returns Promise that resolves when clearing is complete
 */
function clear(): Promise<void>;

Model Definition

Models are typically generated from GraphQL schemas but can be defined manually.

interface PersistentModel {
  readonly id: string;
  readonly createdAt?: string;
  readonly updatedAt?: string;
}

interface PersistentModelConstructor<T extends PersistentModel> {
  new(init: Partial<T>): T;
  copyOf(source: T, mutator: (draft: MutableModel<T>) => void): T;
}

type MutableModel<T extends PersistentModel> = {
  -readonly [P in keyof T]: T[P];
};

Usage Examples:

import { save, query, delete as del } from "aws-amplify/datastore";

// Assuming you have generated models from a GraphQL schema
import { Todo, User } from "./models";

// Create and save a new model
const newTodo = new Todo({
  name: "Buy groceries",
  description: "Milk, bread, eggs",
  completed: false
});

const savedTodo = await save(newTodo);
console.log('Saved todo:', savedTodo.id);

// Query all todos
const allTodos = await query(Todo);
console.log('All todos:', allTodos);

// Update a model using copyOf
const updatedTodo = Todo.copyOf(savedTodo, draft => {
  draft.completed = true;
  draft.description = "Completed shopping";
});

await save(updatedTodo);

// Delete a specific model
await del(updatedTodo);

Query Predicates

Build complex queries using predicates and conditions.

type ModelPredicate<T> = (condition: ModelPredicateBuilder<T>) => ModelPredicateBuilder<T>;

interface ModelPredicateBuilder<T> {
  and(predicate: ModelPredicate<T>): ModelPredicateBuilder<T>;
  or(predicate: ModelPredicate<T>): ModelPredicateBuilder<T>;
  not(predicate: ModelPredicate<T>): ModelPredicateBuilder<T>;
  eq(value: any): ModelPredicateBuilder<T>;
  ne(value: any): ModelPredicateBuilder<T>;
  gt(value: any): ModelPredicateBuilder<T>;
  ge(value: any): ModelPredicateBuilder<T>;
  lt(value: any): ModelPredicateBuilder<T>;
  le(value: any): ModelPredicateBuilder<T>;
  contains(value: string): ModelPredicateBuilder<T>;
  notContains(value: string): ModelPredicateBuilder<T>;
  beginsWith(value: string): ModelPredicateBuilder<T>;
  between(start: any, end: any): ModelPredicateBuilder<T>;
}

Query Examples:

import { query } from "aws-amplify/datastore";
import { Todo, Priority } from "./models";

// Query with simple condition
const completedTodos = await query(Todo, todo => todo.completed.eq(true));

// Query with multiple conditions
const urgentIncompleteTodos = await query(Todo, todo => 
  todo.completed.eq(false).and(todo.priority.eq(Priority.HIGH))
);

// Query with text search
const searchResults = await query(Todo, todo => 
  todo.name.contains("grocery").or(todo.description.contains("grocery"))
);

// Query with date range
const recentTodos = await query(Todo, todo => 
  todo.createdAt.gt("2023-01-01").and(todo.createdAt.lt("2023-12-31"))
);

// Complex nested query
const complexQuery = await query(Todo, todo => 
  todo.completed.eq(false)
    .and(todo.priority.eq(Priority.HIGH))
    .and(todo.dueDate.lt(new Date().toISOString()))
);

Real-time Subscriptions

Subscribe to model changes for real-time updates.

/**
 * Observe model changes in real-time
 * @param modelConstructor - Model class to observe
 * @param criteria - Optional criteria for filtering observations
 * @returns Observable stream of model changes
 */
function observe<T extends PersistentModel>(
  modelConstructor?: PersistentModelConstructor<T>,
  criteria?: string
): Observable<SubscriptionMessage<T>>;

interface SubscriptionMessage<T> {
  opType: 'INSERT' | 'UPDATE' | 'DELETE';
  element: T;
  model: string;
  condition?: ModelPredicate<T>;
}

interface Observable<T> {
  subscribe(observer: {
    next?: (value: T) => void;
    error?: (error: any) => void;
    complete?: () => void;
  }): { unsubscribe(): void };
}

Subscription Examples:

import { observe } from "aws-amplify/datastore";
import { Todo } from "./models";

// Observe all Todo changes
const todoSubscription = observe(Todo).subscribe({
  next: ({ opType, element }) => {
    switch (opType) {
      case 'INSERT':
        console.log('New todo created:', element);
        break;
      case 'UPDATE':
        console.log('Todo updated:', element);
        break;
      case 'DELETE':
        console.log('Todo deleted:', element);
        break;
    }
  },
  error: (error) => {
    console.error('Subscription error:', error);
  }
});

// Observe specific model instance
const specificTodoSubscription = observe(Todo, todo.id).subscribe({
  next: ({ opType, element }) => {
    console.log(`Todo ${element.id} ${opType.toLowerCase()}:`, element);
  }
});

// Unsubscribe when done
todoSubscription.unsubscribe();
specificTodoSubscription.unsubscribe();

DataStore Lifecycle

Control DataStore synchronization and lifecycle.

/**
 * Start DataStore and begin synchronization
 * @returns Promise that resolves when DataStore is ready
 */
function start(): Promise<void>;

/**
 * Stop DataStore synchronization
 * @returns Promise that resolves when DataStore is stopped
 */
function stop(): Promise<void>;

Lifecycle Examples:

import { start, stop, clear } from "aws-amplify/datastore";

// Start DataStore (usually done automatically)
await start();
console.log('DataStore started');

// Stop DataStore (pauses sync)
await stop();
console.log('DataStore stopped');

// Clear all local data
await clear();
console.log('DataStore cleared');

// Restart after clearing
await start();

Pagination

Handle large datasets with pagination.

interface Page<T> {
  items: T[];
  nextToken?: string;
}

// Pagination is handled through query limits and tokens
function query<T extends PersistentModel>(
  modelConstructor: PersistentModelConstructor<T>,
  criteria?: ModelPredicate<T>,
  paginationInput?: {
    page?: number;
    limit?: number;
  }
): Promise<T[]>;

Pagination Example:

import { query } from "aws-amplify/datastore";
import { Todo } from "./models";

// Query with pagination
const firstPage = await query(Todo, undefined, {
  page: 0,
  limit: 20
});

const secondPage = await query(Todo, undefined, {
  page: 1, 
  limit: 20
});

// Or use manual pagination with sorting
const sortedTodos = await query(Todo, todo => 
  todo.createdAt.gt("2023-01-01"), {
    limit: 50
  }
);

Relationships

Handle model relationships and associations.

// Example models with relationships
class User extends Model {
  public readonly id: string;
  public readonly name: string;
  public readonly email: string;
  public readonly todos?: Todo[];
}

class Todo extends Model {
  public readonly id: string;
  public readonly name: string;
  public readonly userID: string;
  public readonly user?: User;
}

// Query with relationships
const userWithTodos = await query(User, user => user.id.eq("user123"));
// Note: Related data is automatically populated based on schema relationships

// Create models with relationships
const user = new User({
  name: "John Doe",
  email: "john@example.com"
});

const savedUser = await save(user);

const userTodo = new Todo({
  name: "User's todo",
  userID: savedUser.id
});

await save(userTodo);

Conflict Resolution

DataStore automatically handles conflicts when multiple clients modify the same data.

// Conflict resolution is handled automatically, but you can observe conflicts
interface ConflictResolutionStrategy {
  strategy: 'OPTIMISTIC_CONCURRENCY' | 'LAMBDA' | 'CUSTOM';
  resolver?: (conflicts: ConflictData[]) => ConflictResolution[];
}

interface ConflictData {
  modelName: string;
  localModel: PersistentModel;
  remoteModel: PersistentModel;
}

interface ConflictResolution {
  strategy: 'RETRY' | 'APPLY_REMOTE' | 'APPLY_LOCAL';
  model?: PersistentModel;
}

Offline Capabilities

DataStore works offline and syncs when connectivity is restored.

// All operations work offline
const offlineTodo = new Todo({
  name: "Offline todo",
  description: "Created without internet"
});

// This saves locally and will sync when online
await save(offlineTodo);

// Queries work from local cache
const localTodos = await query(Todo);

// Subscriptions continue to work with local changes
const localSubscription = observe(Todo).subscribe({
  next: ({ opType, element }) => {
    console.log('Local change:', opType, element);
  }
});

Model Validation

Validate models before saving.

// Models can include validation logic
class Todo extends Model {
  public readonly id: string;
  public readonly name: string;
  public readonly dueDate?: string;
  
  static validate(todo: Todo): ValidationResult {
    const errors: string[] = [];
    
    if (!todo.name || todo.name.trim().length === 0) {
      errors.push('Name is required');
    }
    
    if (todo.dueDate && new Date(todo.dueDate) < new Date()) {
      errors.push('Due date cannot be in the past');
    }
    
    return {
      isValid: errors.length === 0,
      errors
    };
  }
}

// Validate before saving
const todo = new Todo({
  name: "",
  dueDate: "2020-01-01"
});

const validation = Todo.validate(todo);
if (!validation.isValid) {
  console.log('Validation errors:', validation.errors);
} else {
  await save(todo);
}

Types

// Core DataStore types
interface Model {
  readonly id: string;
  readonly createdAt?: string;
  readonly updatedAt?: string;
}

type PersistentModel = Model;

// Schema definition
interface ModelSchema {
  name: string;
  fields: Record<string, ModelField>;
  syncable?: boolean;
  pluralName?: string;
  attributes?: ModelAttribute[];
}

interface ModelField {
  name: string;
  isArray: boolean;
  type: string;
  isRequired?: boolean;
  attributes?: FieldAttribute[];
}

// Error types
class DataStoreError extends Error {
  name: 'DataStoreError';
  message: string;
  cause?: Error;
}

class DataStoreValidationError extends DataStoreError {
  name: 'DataStoreValidationError';
  modelName: string;
  fieldName?: string;
}

Error Handling

DataStore operations can encounter various errors:

import { save, DataStoreError } from "aws-amplify/datastore";
import { Todo } from "./models";

try {
  const todo = new Todo({
    name: "Test todo"
  });
  
  await save(todo);
} catch (error) {
  if (error instanceof DataStoreError) {
    switch (error.name) {
      case 'DataStoreValidationError':
        console.log('Validation failed:', error.message);
        break;
      case 'DataStoreConflictError':
        console.log('Conflict detected:', error.message);
        break;
      case 'DataStoreNetworkError':
        console.log('Network error:', error.message);
        break;
      default:
        console.log('DataStore error:', error.message);
        break;
    }
  } else {
    console.log('Unexpected error:', error);
  }
}

Best Practices

Model Design

  • Keep models focused and avoid overly complex relationships
  • Use appropriate field types for your data
  • Include validation logic in model definitions
  • Design for offline-first scenarios

Performance

  • Use predicates to filter queries efficiently
  • Implement pagination for large datasets
  • Unsubscribe from observations when components unmount
  • Consider query frequency and cache appropriately

Synchronization

  • Design conflict resolution strategies for your use case
  • Handle network connectivity changes gracefully
  • Test offline scenarios thoroughly
  • Monitor sync status and provide user feedback

Data Management

  • Regularly clean up old data using clear() when appropriate
  • Use meaningful model names and field names
  • Implement proper error handling for all operations
  • Consider data privacy and security requirements