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.
import {
save, query, delete as del, clear,
start, stop
} from "aws-amplify/datastore";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>;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);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()))
);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();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();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
}
);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);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;
}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);
}
});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);
}// 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;
}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);
}
}clear() when appropriate