SQLite State Schema (Effect Schema)
LiveStore supports defining SQLite tables using Effect Schema with annotations for database constraints. This approach provides strong type safety, composability, and automatic type mapping from TypeScript to SQLite.
Note: This approach will become the default once Effect Schema v4 is released. See livestore#382 for details.
For the traditional column-based approach, see SQLite State Schema.
Basic Usage
Section titled “Basic Usage”Define tables using Effect Schema with database constraint annotations:
import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const UserSchema: Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>
UserSchema = import Schema
Schema.function Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>(fields: { id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}): Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), email: typeof Schema.String
email: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique), name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String, age: typeof Schema.Int
age: import Schema
Schema.class Int
Int.Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault(0)), isActive: typeof Schema.Boolean
isActive: import Schema
Schema.class Booleanexport Boolean
Boolean.Pipeable.pipe<typeof Schema.Boolean, typeof Schema.Boolean>(this: typeof Schema.Boolean, ab: (_: typeof Schema.Boolean) => typeof Schema.Boolean): typeof Schema.Boolean (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault(true)), metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>
metadata: import Schema
Schema.const optional: <Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>(self: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>) => Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>
optional( import Schema
Schema.const Record: <typeof Schema.String, typeof Schema.Unknown>(options: { readonly key: typeof Schema.String; readonly value: typeof Schema.Unknown;}) => Schema.Record$<typeof Schema.String, typeof Schema.Unknown>
Record({ key: typeof Schema.String
key: import Schema
Schema.class Stringexport String
String, value: typeof Schema.Unknown
value: import Schema
Schema.class Unknown
Unknown, }), ),}).Struct<{ id: typeof String$; email: typeof String$; name: typeof String$; age: typeof Int; isActive: typeof Boolean$; metadata: optional<Record$<typeof String$, typeof Unknown>>; }>.annotations(annotations: Schema.Annotations.Schema<{ readonly id: string; readonly email: string; readonly name: string; readonly age: number; readonly isActive: boolean; readonly metadata?: { readonly [x: string]: unknown; } | undefined;}, readonly []>): Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>
Merges a set of new annotations with existing ones, potentially overwriting
any duplicates.
annotations({ Annotations.Doc<A>.title?: string
title: 'users' })
export const const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly email: string; readonly name: string; readonly age: number; readonly isActive: boolean; readonly metadata?: { readonly [x: string]: unknown; } | undefined;}, { readonly id: string; readonly email: string; readonly name: string; readonly age: number; readonly isActive: boolean; readonly metadata?: { readonly [x: string]: unknown; } | undefined;}, Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>>, State.SQLite.TableOptions, Schema.Schema<...>>
userTable = import State
State.import SQLite
SQLite.function table<Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>, Partial<{ indexes: Index[];}>>(args: { schema: Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>; }>;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ schema: Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>
schema: const UserSchema: Schema.Struct<{ id: typeof Schema.String; email: typeof Schema.String; name: typeof Schema.String; age: typeof Schema.Int; isActive: typeof Schema.Boolean; metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;}>
UserSchema })Schema Annotations
Section titled “Schema Annotations”You can annotate schema fields with database constraints:
Primary Keys
Section titled “Primary Keys”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ id: typeof Schema.String;}>
_schema = import Schema
Schema.function Struct<{ id: typeof Schema.String;}>(fields: { id: typeof Schema.String;}): Schema.Struct<{ id: typeof Schema.String;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), // Other fields...})Important: Primary key columns cannot be nullable. This will throw an error:
import { import Schema
Schema, import State
State } from '@livestore/livestore'
// ❌ This will throw an error at runtime because primary keys cannot be nullableconst const _badSchema: Schema.Struct<{ id: Schema.NullOr<typeof Schema.String>;}>
_badSchema = import Schema
Schema.function Struct<{ id: Schema.NullOr<typeof Schema.String>;}>(fields: { id: Schema.NullOr<typeof Schema.String>;}): Schema.Struct<{ id: Schema.NullOr<typeof Schema.String>;}> (+1 overload)
Struct({ id: Schema.NullOr<typeof Schema.String>
id: import Schema
Schema.const NullOr: <typeof Schema.String>(self: typeof Schema.String) => Schema.NullOr<typeof Schema.String>
NullOr(import Schema
Schema.class Stringexport String
String).Pipeable.pipe<Schema.NullOr<typeof Schema.String>, Schema.NullOr<typeof Schema.String>>(this: Schema.NullOr<typeof Schema.String>, ab: (_: Schema.NullOr<typeof Schema.String>) => Schema.NullOr<typeof Schema.String>): Schema.NullOr<typeof Schema.String> (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey),})Auto-Increment
Section titled “Auto-Increment”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ id: typeof Schema.Int;}>
_schema = import Schema
Schema.function Struct<{ id: typeof Schema.Int;}>(fields: { id: typeof Schema.Int;}): Schema.Struct<{ id: typeof Schema.Int;}> (+1 overload)
Struct({ id: typeof Schema.Int
id: import Schema
Schema.class Int
Int.Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey).Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withAutoIncrement: <T extends Schema.Schema.All>(schema: T) => T
Adds an auto-increment annotation to a schema.
withAutoIncrement), // Other fields...})Default Values
Section titled “Default Values”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ status: typeof Schema.String; createdAt: typeof Schema.String; count: typeof Schema.Int;}>
_schema = import Schema
Schema.function Struct<{ status: typeof Schema.String; createdAt: typeof Schema.String; count: typeof Schema.Int;}>(fields: { status: typeof Schema.String; createdAt: typeof Schema.String; count: typeof Schema.Int;}): Schema.Struct<{ status: typeof Schema.String; createdAt: typeof Schema.String; count: typeof Schema.Int;}> (+1 overload)
Struct({ status: typeof Schema.String
status: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault('active')), createdAt: typeof Schema.String
createdAt: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault('CURRENT_TIMESTAMP')), count: typeof Schema.Int
count: import Schema
Schema.class Int
Int.Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault(0)),})Unique Constraints
Section titled “Unique Constraints”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ email: typeof Schema.String; username: typeof Schema.String;}>
_schema = import Schema
Schema.function Struct<{ email: typeof Schema.String; username: typeof Schema.String;}>(fields: { email: typeof Schema.String; username: typeof Schema.String;}): Schema.Struct<{ email: typeof Schema.String; username: typeof Schema.String;}> (+1 overload)
Struct({ email: typeof Schema.String
email: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique), username: typeof Schema.String
username: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique),})Unique annotations automatically create unique indexes.
Custom Column Types
Section titled “Custom Column Types”Override the automatically inferred SQLite column type:
import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ version: typeof Schema.Number; data: typeof Schema.Uint8Array;}>
_schema = import Schema
Schema.function Struct<{ version: typeof Schema.Number; data: typeof Schema.Uint8Array;}>(fields: { version: typeof Schema.Number; data: typeof Schema.Uint8Array;}): Schema.Struct<{ version: typeof Schema.Number; data: typeof Schema.Uint8Array;}> (+1 overload)
Struct({ // Store a number as text instead of real version: typeof Schema.Number
version: import Schema
Schema.class Numberexport Number
Number.Pipeable.pipe<typeof Schema.Number, typeof Schema.Number>(this: typeof Schema.Number, ab: (_: typeof Schema.Number) => typeof Schema.Number): typeof Schema.Number (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withColumnType: (type: FieldColumnType) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withColumnType('text')), // Store binary data as blob data: typeof Schema.Uint8Array
data: import Schema
Schema.class Uint8Arrayexport Uint8Array
A schema that transforms an array of numbers into a Uint8Array.
Uint8Array.Pipeable.pipe<typeof Schema.Uint8Array, typeof Schema.Uint8Array>(this: typeof Schema.Uint8Array, ab: (_: typeof Schema.Uint8Array) => typeof Schema.Uint8Array): typeof Schema.Uint8Array (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withColumnType: (type: FieldColumnType) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withColumnType('blob')),})Combining Annotations
Section titled “Combining Annotations”Annotations can be chained together:
import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const _schema: Schema.Struct<{ id: typeof Schema.Int; email: typeof Schema.String;}>
_schema = import Schema
Schema.function Struct<{ id: typeof Schema.Int; email: typeof Schema.String;}>(fields: { id: typeof Schema.Int; email: typeof Schema.String;}): Schema.Struct<{ id: typeof Schema.Int; email: typeof Schema.String;}> (+1 overload)
Struct({ id: typeof Schema.Int
id: import Schema
Schema.class Int
Int.Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey).Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withAutoIncrement: <T extends Schema.Schema.All>(schema: T) => T
Adds an auto-increment annotation to a schema.
withAutoIncrement), email: typeof Schema.String
email: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique).Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withColumnType: (type: FieldColumnType) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withColumnType('text')),})Table Naming
Section titled “Table Naming”You can specify table names in several ways:
Using Schema Annotations
Section titled “Using Schema Annotations”import { import Schema
Schema, import State
State } from '@livestore/livestore'
// Using title annotationconst const UserSchema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
UserSchema = import Schema
Schema.function Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>(fields: { id: typeof Schema.String; name: typeof Schema.String;}): Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String,}).Struct<{ id: typeof String$; name: typeof String$; }>.annotations(annotations: Schema.Annotations.Schema<{ readonly id: string; readonly name: string;}, readonly []>): Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
Merges a set of new annotations with existing ones, potentially overwriting
any duplicates.
annotations({ Annotations.Doc<{ readonly id: string; readonly name: string; }>.title?: string
title: 'users' })
export const const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<{ readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, never>>
userTable = import State
State.import SQLite
SQLite.function table<Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>, Partial<{ indexes: Index[];}>>(args: { schema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; }>;} & Partial<Partial<{ indexes: Index[];}>>): State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<...>> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ schema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
schema: const UserSchema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
UserSchema })
// Using identifier annotationconst const PostSchema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>
PostSchema = import Schema
Schema.function Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>(fields: { id: typeof Schema.String; title: typeof Schema.String;}): Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), title: typeof Schema.String
title: import Schema
Schema.class Stringexport String
String,}).Struct<{ id: typeof String$; title: typeof String$; }>.annotations(annotations: Schema.Annotations.Schema<{ readonly id: string; readonly title: string;}, readonly []>): Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>
Merges a set of new annotations with existing ones, potentially overwriting
any duplicates.
annotations({ Annotations.Schema<A, TypeParameters extends ReadonlyArray<any> = readonly []>.identifier?: string
identifier: 'posts' })
export const const postTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly title: string;}, { readonly id: string; readonly title: string;}, Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<{ readonly id: string; readonly title: string;}, { readonly id: string; readonly title: string;}, never>>
postTable = import State
State.import SQLite
SQLite.function table<Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>, Partial<{ indexes: Index[];}>>(args: { schema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; }>;} & Partial<Partial<{ indexes: Index[];}>>): State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly title: string;}, { readonly id: string; readonly title: string;}, Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<...>> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ schema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>
schema: const PostSchema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String;}>
PostSchema })Explicit Name
Section titled “Explicit Name”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const UserSchema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
UserSchema = import Schema
Schema.function Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>(fields: { id: typeof Schema.String; name: typeof Schema.String;}): Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String,})
export const const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<"users", { readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<{ readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, never>>
userTable = import State
State.import SQLite
SQLite.function table<"users", Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>, Partial<{ indexes: Index[];}>>(args: { name: "users"; schema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String; }>;} & Partial<Partial<{ indexes: Index[];}>>): State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<"users", { readonly id: string; readonly name: string;}, { readonly id: string; readonly name: string;}, Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>>, State.SQLite.TableOptions, Schema.Schema<...>> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "users"
name: 'users', schema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
schema: const UserSchema: Schema.Struct<{ id: typeof Schema.String; name: typeof Schema.String;}>
UserSchema,})Note: Title annotation takes precedence over identifier annotation.
Type Mapping
Section titled “Type Mapping”Effect Schema types are automatically mapped to SQLite column types:
| Schema Type | SQLite Type | TypeScript Type |
|---|---|---|
Schema.String | text | string |
Schema.Number | real | number |
Schema.Int | integer | number |
Schema.Boolean | integer | boolean |
Schema.Date | text | Date |
Schema.BigInt | text | bigint |
| Complex types (Struct, Array, etc.) | text (JSON encoded) | Decoded type |
Schema.optional(T) | Nullable column | T | undefined |
Schema.NullOr(T) | Nullable column | T | null |
Advanced Examples
Section titled “Advanced Examples”Complex Schema with Multiple Constraints
Section titled “Complex Schema with Multiple Constraints”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const ProductSchema: Schema.Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>
ProductSchema = import Schema
Schema.function Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>(fields: { id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}): Schema.Struct<...> (+1 overload)
Struct({ id: typeof Schema.Int
id: import Schema
Schema.class Int
Int.Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey).Pipeable.pipe<typeof Schema.Int, typeof Schema.Int>(this: typeof Schema.Int, ab: (_: typeof Schema.Int) => typeof Schema.Int): typeof Schema.Int (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withAutoIncrement: <T extends Schema.Schema.All>(schema: T) => T
Adds an auto-increment annotation to a schema.
withAutoIncrement), sku: typeof Schema.String
sku: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique), name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String, price: typeof Schema.Number
price: import Schema
Schema.class Numberexport Number
Number.Pipeable.pipe<typeof Schema.Number, typeof Schema.Number>(this: typeof Schema.Number, ab: (_: typeof Schema.Number) => typeof Schema.Number): typeof Schema.Number (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault(0)), category: Schema.Literal<["electronics", "clothing", "books"]>
category: import Schema
Schema.function Literal<["electronics", "clothing", "books"]>(literals_0: "electronics", literals_1: "clothing", literals_2: "books"): Schema.Literal<["electronics", "clothing", "books"]> (+2 overloads)
Literal('electronics', 'clothing', 'books'), metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}>>
metadata: import Schema
Schema.const optional: <Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}>>(self: Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}>) => Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}>>
optional( import Schema
Schema.function Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}>(fields: { weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}): Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>;}> (+1 overload)
Struct({ weight: typeof Schema.Number
weight: import Schema
Schema.class Numberexport Number
Number, dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number;}>
dimensions: import Schema
Schema.function Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number;}>(fields: { width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number;}): Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number;}> (+1 overload)
Struct({ width: typeof Schema.Number
width: import Schema
Schema.class Numberexport Number
Number, height: typeof Schema.Number
height: import Schema
Schema.class Numberexport Number
Number, depth: typeof Schema.Number
depth: import Schema
Schema.class Numberexport Number
Number, }), }), ), isActive: typeof Schema.Boolean
isActive: import Schema
Schema.class Booleanexport Boolean
Boolean.Pipeable.pipe<typeof Schema.Boolean, typeof Schema.Boolean>(this: typeof Schema.Boolean, ab: (_: typeof Schema.Boolean) => typeof Schema.Boolean): typeof Schema.Boolean (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault(true)), createdAt: typeof Schema.Date
createdAt: import Schema
Schema.class Dateexport Date
This schema converts a string into a Date object using the new Date
constructor. It ensures that only valid date strings are accepted,
rejecting any strings that would result in an invalid date, such as new Date("Invalid Date").
Date.Pipeable.pipe<typeof Schema.Date, typeof Schema.Date>(this: typeof Schema.Date, ab: (_: typeof Schema.Date) => typeof Schema.Date): typeof Schema.Date (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withDefault: (value: unknown) => <T extends Schema.Schema.All>(schema: T) => T (+1 overload)
withDefault('CURRENT_TIMESTAMP')),}).Struct<{ id: typeof Int; sku: typeof String$; name: typeof String$; price: typeof Number$; category: Literal<["electronics", "clothing", "books"]>; metadata: optional<Struct<...>>; isActive: typeof Boolean$; createdAt: typeof Date$; }>.annotations(annotations: Schema.Annotations.Schema<{ readonly id: number; readonly sku: string; readonly name: string; readonly price: number; readonly category: "electronics" | "clothing" | "books"; readonly metadata?: { readonly weight: number; readonly dimensions: { readonly width: number; readonly height: number; readonly depth: number; }; } | undefined; readonly isActive: boolean; readonly createdAt: Date;}, readonly []>): Schema.Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<...>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>
Merges a set of new annotations with existing ones, potentially overwriting
any duplicates.
annotations({ Annotations.Doc<A>.title?: string
title: 'products' })
export const const productTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: number; readonly sku: string; readonly name: string; readonly price: number; readonly category: "electronics" | "clothing" | "books"; readonly metadata?: { readonly weight: number; readonly dimensions: { readonly width: number; readonly height: number; readonly depth: number; }; } | undefined; readonly isActive: boolean; readonly createdAt: Date;}, { readonly id: number; readonly sku: string; readonly name: string; readonly price: number; readonly category: "electronics" | "clothing" | "books"; readonly isActive: boolean; readonly createdAt: string; readonly metadata?: { ...; } | undefined;}, Schema.Struct<...>>, State.SQLite.TableOptions, Schema.Schema<...>>
productTable = import State
State.import SQLite
SQLite.function table<Schema.Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>, Partial<{ indexes: Index[];}>>(args: { ...;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ schema: Schema.Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>
schema: const ProductSchema: Schema.Struct<{ id: typeof Schema.Int; sku: typeof Schema.String; name: typeof Schema.String; price: typeof Schema.Number; category: Schema.Literal<["electronics", "clothing", "books"]>; metadata: Schema.optional<Schema.Struct<{ weight: typeof Schema.Number; dimensions: Schema.Struct<{ width: typeof Schema.Number; height: typeof Schema.Number; depth: typeof Schema.Number; }>; }>>; isActive: typeof Schema.Boolean; createdAt: typeof Schema.Date;}>
ProductSchema })Working with Schema.Class
Section titled “Working with Schema.Class”import { import Schema
Schema, import State
State } from '@livestore/livestore'
class class User
User extends import Schema
Schema.const Class: <User>(identifier: string) => <Fields>(fieldsOr: Fields | HasFields<Fields>, annotations?: ClassAnnotations<User, { [K in keyof Schema.Struct<Fields extends Schema.Struct.Fields>.Type<Fields>]: Schema.Struct.Type<Fields>[K]; }> | undefined) => Schema.Class<User, Fields, Schema.Struct.Encoded<Fields>, Schema.Schema<in out A, in out I = A, out R = never>.Context<Fields[keyof Fields]>, Schema.Struct.Constructor<Fields>, {}, {}>
Class<class User
User>('User')({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), email: typeof Schema.String
email: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withUnique: <T extends Schema.Schema.All>(schema: T) => T
Adds a unique constraint annotation to a schema.
withUnique), name: typeof Schema.String
name: import Schema
Schema.class Stringexport String
String, age: typeof Schema.Int
age: import Schema
Schema.class Int
Int,}) {}
export const const userTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<"users", User, { readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, typeof User>, State.SQLite.TableOptions, Schema.Schema<{ readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, { readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, never>>
userTable = import State
State.import SQLite
SQLite.function table<"users", typeof User, Partial<{ indexes: Index[];}>>(args: { name: "users"; schema: typeof User;} & Partial<Partial<{ indexes: Index[];}>>): State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<"users", User, { readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, typeof User>, State.SQLite.TableOptions, Schema.Schema<{ readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, { readonly name: string; readonly age: number; readonly id: string; readonly email: string;}, never>> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ name: "users"
name: 'users', schema: typeof User
schema: class User
User,})Custom Indexes
Section titled “Custom Indexes”import { import Schema
Schema, import State
State } from '@livestore/livestore'
const const PostSchema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>
PostSchema = import Schema
Schema.function Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>(fields: { id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}): Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}> (+1 overload)
Struct({ id: typeof Schema.String
id: import Schema
Schema.class Stringexport String
String.Pipeable.pipe<typeof Schema.String, typeof Schema.String>(this: typeof Schema.String, ab: (_: typeof Schema.String) => typeof Schema.String): typeof Schema.String (+21 overloads)
pipe(import State
State.import SQLite
SQLite.const withPrimaryKey: <T extends Schema.Schema.All>(schema: T) => T
Adds a primary key annotation to a schema.
withPrimaryKey), title: typeof Schema.String
title: import Schema
Schema.class Stringexport String
String, authorId: typeof Schema.String
authorId: import Schema
Schema.class Stringexport String
String, createdAt: typeof Schema.Date
createdAt: import Schema
Schema.class Dateexport Date
This schema converts a string into a Date object using the new Date
constructor. It ensures that only valid date strings are accepted,
rejecting any strings that would result in an invalid date, such as new Date("Invalid Date").
Date,}).Struct<{ id: typeof String$; title: typeof String$; authorId: typeof String$; createdAt: typeof Date$; }>.annotations(annotations: Schema.Annotations.Schema<{ readonly id: string; readonly title: string; readonly authorId: string; readonly createdAt: Date;}, readonly []>): Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>
Merges a set of new annotations with existing ones, potentially overwriting
any duplicates.
annotations({ Annotations.Doc<{ readonly id: string; readonly title: string; readonly authorId: string; readonly createdAt: Date; }>.title?: string
title: 'posts' })
export const const postTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForSchemaInput<string, { readonly id: string; readonly title: string; readonly authorId: string; readonly createdAt: Date;}, { readonly id: string; readonly title: string; readonly authorId: string; readonly createdAt: string;}, Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>>, State.SQLite.TableOptions, Schema.Schema<{ readonly id: string; readonly title: string; readonly authorId: string; readonly createdAt: Date;}, { ...;}, never>>
postTable = import State
State.import SQLite
SQLite.function table<Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>, { readonly schema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date; }>; readonly indexes: [{ readonly name: "idx_posts_author"; readonly columns: readonly ["authorId"]; }, { readonly name: "idx_posts_created"; readonly columns: readonly ["createdAt"]; }];}>(args: { schema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date; }>;} & Partial<...>): State.SQLite.TableDef<...> (+2 overloads)
Creates a SQLite table definition from columns or an Effect Schema.
This function supports two main ways to define a table:
- Using explicit column definitions
- Using an Effect Schema (either the
name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columnsconst usersTable = State.SQLite.table({ name: 'users', columns: { id: State.SQLite.text({ primaryKey: true }), name: State.SQLite.text({ nullable: false }), email: State.SQLite.text({ nullable: false }), age: State.SQLite.integer({ nullable: true }), },})
// Using Effect Schema with annotationsimport { Schema } from '@livestore/utils/effect'
const UserSchema = Schema.Struct({ id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement), email: Schema.String.pipe(State.SQLite.withUnique), name: Schema.String, active: Schema.Boolean.pipe(State.SQLite.withDefault(true)), createdAt: Schema.optional(Schema.Date),})
// Option 1: With explicit nameconst usersTable = State.SQLite.table({ name: 'users', schema: UserSchema,})
// Option 2: With name from schema annotation (title or identifier)const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })const usersTable2 = State.SQLite.table({ schema: AnnotatedUserSchema,})
// Adding indexesconst PostSchema = Schema.Struct({ id: Schema.String.pipe(State.SQLite.withPrimaryKey), title: Schema.String, authorId: Schema.String, createdAt: Schema.Date,}).annotations({ identifier: 'posts' })
const postsTable = State.SQLite.table({ schema: PostSchema, indexes: [ { name: 'idx_posts_author', columns: ['authorId'] }, { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false }, ],})
table({ schema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>
schema: const PostSchema: Schema.Struct<{ id: typeof Schema.String; title: typeof Schema.String; authorId: typeof Schema.String; createdAt: typeof Schema.Date;}>
PostSchema, indexes?: [{ readonly name: "idx_posts_author"; readonly columns: readonly ["authorId"];}, { readonly name: "idx_posts_created"; readonly columns: readonly ["createdAt"];}]
indexes: [ { name: "idx_posts_author"
name: 'idx_posts_author', columns: readonly ["authorId"]
columns: ['authorId'] }, { name: "idx_posts_created"
name: 'idx_posts_created', columns: readonly ["createdAt"]
columns: ['createdAt'] }, ],})Best Practices
Section titled “Best Practices”Schema Design
Section titled “Schema Design”- Always use
withPrimaryKeyfor primary key columns - never combine it with nullable types - Use
Schema.optional()for truly optional fields that can be undefined - Use
Schema.NullOr()for fields that can explicitly be set to null - Leverage schema annotations like
titleoridentifierto avoid repeating table names - Group related schemas in the same module for better organization
Type Safety
Section titled “Type Safety”- Let TypeScript infer table types rather than explicitly typing them
- Use Effect Schema’s refinements and transformations for data validation
- Prefer Effect Schema’s built-in types (
Schema.Int,Schema.Date) over generic types where appropriate
Performance
Section titled “Performance”- Be mindful of complex types stored as JSON - they can impact query performance
- Use appropriate indexes for frequently queried columns
- Consider using
withColumnTypeto optimize storage for specific use cases
When to Use This Approach
Section titled “When to Use This Approach”Use Effect Schema-based tables when:
- You already have Effect Schema definitions to reuse
- You prefer Effect Schema’s composability and transformations
- Your schemas are shared across different parts of your application
- You want automatic type mapping and strong type safety
- You plan to migrate to Effect Schema v4 when it becomes available
Consider column-based tables when:
- You need precise control over SQLite column types
- You’re migrating from existing SQLite schemas
- You prefer explicit column configuration
- You’re not already using Effect Schema extensively in your project