Skip to content

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.

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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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 String
export String

@since3.10.0

String
,
age: typeof Schema.Int
age
:
import Schema
Schema
.
class Int

@since3.10.0

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 Boolean
export Boolean

@since3.10.0

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>>

@since3.10.0

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>

@since3.10.0

Record
({
key: typeof Schema.String
key
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
value: typeof Schema.Unknown
value
:
import Schema
Schema
.
class Unknown

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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
})

You can annotate schema fields with database constraints:

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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 nullable
const
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)

@since3.10.0

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>

@since3.10.0

NullOr
(
import Schema
Schema
.
class String
export String

@since3.10.0

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
),
})
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)

@since3.10.0

Struct
({
id: typeof Schema.Int
id
:
import Schema
Schema
.
class Int

@since3.10.0

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...
})
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)

@since3.10.0

Struct
({
status: typeof Schema.String
status
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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

@since3.10.0

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)),
})
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)

@since3.10.0

Struct
({
email: typeof Schema.String
email
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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.

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)

@since3.10.0

Struct
({
// Store a number as text instead of real
version: typeof Schema.Number
version
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

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 Uint8Array
export Uint8Array

A schema that transforms an array of numbers into a Uint8Array.

@since3.10.0

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')),
})

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)

@since3.10.0

Struct
({
id: typeof Schema.Int
id
:
import Schema
Schema
.
class Int

@since3.10.0

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 String
export String

@since3.10.0

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')),
})

You can specify table names in several ways:

import {
import Schema
Schema
,
import State
State
} from '@livestore/livestore'
// Using title annotation
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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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 annotation
const
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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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
})
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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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.

Effect Schema types are automatically mapped to SQLite column types:

Schema TypeSQLite TypeTypeScript Type
Schema.Stringtextstring
Schema.Numberrealnumber
Schema.Intintegernumber
Schema.Booleanintegerboolean
Schema.DatetextDate
Schema.BigInttextbigint
Complex types (Struct, Array, etc.)text (JSON encoded)Decoded type
Schema.optional(T)Nullable columnT | undefined
Schema.NullOr(T)Nullable columnT | null
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)

@since3.10.0

Struct
({
id: typeof Schema.Int
id
:
import Schema
Schema
.
class Int

@since3.10.0

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 String
export String

@since3.10.0

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 String
export String

@since3.10.0

String
,
price: typeof Schema.Number
price
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

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)

@since3.10.0

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;
}>;
}>>

@since3.10.0

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)

@since3.10.0

Struct
({
weight: typeof Schema.Number
weight
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

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)

@since3.10.0

Struct
({
width: typeof Schema.Number
width
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

Number
,
height: typeof Schema.Number
height
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

Number
,
depth: typeof Schema.Number
depth
:
import Schema
Schema
.
class Number
export Number

@since3.10.0

Number
,
}),
}),
),
isActive: typeof Schema.Boolean
isActive
:
import Schema
Schema
.
class Boolean
export Boolean

@since3.10.0

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 Date
export 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").

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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
})
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>, {}, {}>

@example

import { Schema } from "effect"
class MyClass extends Schema.Class<MyClass>("MyClass")({
someField: Schema.String
}) {
someMethod() {
return this.someField + "bar"
}
}

@since3.10.0

Class
<
class User
User
>('User')({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

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 String
export String

@since3.10.0

String
,
age: typeof Schema.Int
age
:
import Schema
Schema
.
class Int

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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
,
})
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)

@since3.10.0

Struct
({
id: typeof Schema.String
id
:
import Schema
Schema
.
class String
export String

@since3.10.0

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 String
export String

@since3.10.0

String
,
authorId: typeof Schema.String
authorId
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
,
createdAt: typeof Schema.Date
createdAt
:
import Schema
Schema
.
class Date
export 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").

@since3.10.0

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:

  1. Using explicit column definitions
  2. Using an Effect Schema (either the name property needs to be provided or the schema needs to have a title/identifier)
// Using explicit columns
const 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 annotations
import { 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 name
const 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 indexes
const 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'] },
],
})
  • Always use withPrimaryKey for 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 title or identifier to avoid repeating table names
  • Group related schemas in the same module for better organization
  • 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
  • Be mindful of complex types stored as JSON - they can impact query performance
  • Use appropriate indexes for frequently queried columns
  • Consider using withColumnType to optimize storage for specific use cases

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