| name | volcano-typescript |
| description | Canonical TypeScript type definitions for the Volcano SDK: User, Session, AuthResponse, QueryBuilder, StorageObject, Realtime types, Function invocation generics, OAuth providers, middleware types, and utility types. |
Volcano TypeScript Types Skill
Role
Provide the canonical TypeScript type definitions for every Volcano SDK surface. Use this skill whenever a task needs a precise type — including return shapes for auth/db/storage/realtime/functions, query builder generics, and middleware contracts. The SDK ships these types alongside the runtime, so import type { ... } from '@volcano.dev/sdk' works at compile time without any extra package.
When to use
- Defining application data models that will go through
volcano.from<T>(...) or insert<T>(...).
- Reading or destructuring auth responses (
signIn, signUp, getUser, getSessions).
- Typing realtime event handlers (
PostgresChange, PresenceState).
- Typing function invocations with
invoke<P, R>(...).
- Writing middleware helpers that consume
getUser(token) or refreshToken(...).
Importing
import { VolcanoAuth } from '@volcano.dev/sdk';
import type {
User,
Session,
AuthResponse,
UserResponse,
SessionResponse,
VolcanoAuthConfig,
} from '@volcano.dev/sdk';
Core Types
Configuration
interface VolcanoAuthConfig {
apiUrl?: string;
anonKey: string;
accessToken?: string;
refreshToken?: string;
}
User
interface User {
id: string;
email: string;
user_metadata?: Record<string, JsonValue>;
created_at: string;
updated_at: string;
}
The id is the Volcano auth user_id; use it as the foreign key in your users application table.
Session
interface Session {
access_token: string;
refresh_token: string;
expires_in: number;
}
Auth response shapes
interface AuthResponse {
user: User | null;
session: Session | null;
error: Error | null;
}
interface UserResponse {
user: User | null;
error: Error | null;
}
interface SessionResponse {
session: Session | null;
error: Error | null;
}
Returned from signUp, signIn, getUser, refreshSession, etc. Always check error before consuming user/session — they are null when error is set.
Database Types
Typed query results with generics
interface Post {
id: string;
title: string;
content: string;
status: 'draft' | 'published' | 'archived';
user_id: string;
created_at: string;
}
const { data, error } = await volcano
.from<Post>('posts')
.select('*')
.eq('status', 'published');
if (data) {
data.forEach(post => {
});
}
Insert / update with generics
const { data, error } = await volcano
.insert<Post>('posts', {
title: 'New Post',
content: 'Content here',
status: 'draft',
});
const { data: updated } = await volcano
.update<Post>('posts', { status: 'published' })
.eq('id', postId);
TypeScript validates the partial shape against Post.
QueryBuilder contract
interface QueryBuilder<T> {
select(columns: string): QueryBuilder<T>;
eq(column: string, value: FilterValue): QueryBuilder<T>;
neq(column: string, value: FilterValue): QueryBuilder<T>;
gt(column: string, value: FilterValue): QueryBuilder<T>;
gte(column: string, value: FilterValue): QueryBuilder<T>;
lt(column: string, value: FilterValue): QueryBuilder<T>;
lte(column: string, value: FilterValue): QueryBuilder<T>;
like(column: string, pattern: string): QueryBuilder<T>;
ilike(column: string, pattern: string): QueryBuilder<T>;
is(column: string, value: null): QueryBuilder<T>;
in(column: string, values: FilterValue[]): QueryBuilder<T>;
order(column: string, options?: { ascending?: boolean }): QueryBuilder<T>;
limit(count: number): QueryBuilder<T>;
offset(count: number): QueryBuilder<T>;
execute(): Promise<QueryResult<T>>;
}
interface QueryResult<T> {
data: T[] | null;
error: Error | null;
count?: number;
}
The chain auto-resolves on await — you rarely need to call .execute() explicitly. count populates only when the query/result mode supports it.
Storage Types
interface StorageObject {
id: string;
bucket_id: string;
name: string;
owner_id?: string;
is_public: boolean;
size: number;
mime_type: string;
etag?: string;
metadata?: Record<string, JsonValue>;
created_at: string;
updated_at: string;
public_url?: string;
}
interface StorageUploadOptions {
contentType?: string;
}
interface StorageUploadResponse {
data: StorageObject | null;
error: Error | null;
}
interface StorageDownloadResponse {
data: Blob | null;
error: Error | null;
}
interface StorageListResponse {
data: StorageObject[] | null;
error: Error | null;
nextCursor: string | null;
}
Usage
const storage = volcano.storage.from('avatars');
const { data, error }: StorageUploadResponse = await storage.upload(
'user/avatar.jpg',
file,
{ contentType: 'image/jpeg' }
);
const { data: blob }: StorageDownloadResponse = await storage.download('user/avatar.jpg');
const { data: files, nextCursor }: StorageListResponse = await storage.list('user/', {
limit: 100,
});
Realtime Types
Import realtime types from the realtime module:
import { VolcanoRealtime, RealtimeChannel } from '@volcano.dev/sdk/realtime';
import type {
PostgresChange,
PresenceState,
ConnectContext,
DisconnectContext,
ErrorContext,
RealtimeConfig,
ChannelOptions,
WebSocketConstructor,
} from '@volcano.dev/sdk/realtime';
Realtime configuration
interface RealtimeConfig {
apiUrl: string;
anonKey: string;
accessToken?: string;
getToken?: () => Promise<string>;
volcanoClient?: VolcanoAuth;
fetchConfig?: FetchConfig;
webSocket?: WebSocketConstructor;
}
interface ChannelOptions {
type?: 'broadcast' | 'presence' | 'postgres';
autoFetch?: boolean;
fetchBatchWindowMs?: number;
fetchMaxBatchSize?: number;
}
Event contexts
interface ConnectContext {
client?: string;
latency?: number;
}
interface DisconnectContext {
code?: number;
reason?: string;
reconnect?: boolean;
}
interface ErrorContext {
error?: Error;
message?: string;
code?: number;
}
Postgres changes
interface PostgresChange {
table: string;
schema: string;
type: 'INSERT' | 'UPDATE' | 'DELETE';
record?: Record<string, unknown>;
old_record?: Record<string, unknown>;
columns?: string[];
timestamp: string;
}
Typed postgres listeners
interface Message {
id: string;
content: string;
user_id: string;
created_at: string;
}
channel.onPostgresChanges('INSERT', 'public', 'messages', (change: PostgresChange) => {
const message = change.record as Message;
});
The callback's change.record is Record<string, unknown>; cast to your row type at the boundary.
Presence
interface PresenceState {
[clientId: string]: Record<string, unknown>;
}
interface UserPresence {
user_id: string;
username: string;
status: 'online' | 'away' | 'busy';
}
channel.onPresenceSync((state: PresenceState) => {
const users = Object.values(state) as UserPresence[];
});
Functions Types
Typed invocation with payload + response generics
interface InvokeParams {
userId: string;
action: 'fetch' | 'update' | 'delete';
}
interface InvokeResult {
success: boolean;
data?: Record<string, unknown>;
message?: string;
}
const { data, error } = await volcano.functions.invoke<InvokeParams, InvokeResult>(
'process-user',
{ userId: '123', action: 'fetch' }
);
if (data) {
}
The full return tuple from invoke<P, R>(...) is { data: R | null; status: number; headers: Record<string, string>; version: string; error: Error | null }.
OAuth Types
type OAuthProviderName = 'google' | 'github' | 'microsoft' | 'apple';
interface OAuthProvider {
provider: OAuthProviderName;
linked_at: string;
updated_at: string;
}
interface OAuthAPIParams {
endpoint: string;
method?: string;
body?: JsonValue;
}
volcano.auth.signInWithOAuth('google');
volcano.auth.signInWithOAuth('invalid');
const { providers } = await volcano.auth.getLinkedOAuthProviders();
providers?.forEach((p: OAuthProvider) => { });
Session Management Types
interface AuthSession {
id: string;
user_id: string;
provider: 'email' | 'google' | 'github' | 'microsoft' | 'apple' | 'anonymous';
user_agent?: string;
ip_address?: string;
last_ip_address?: string;
expires_at: string;
last_activity_at?: string;
session_started_at?: string;
is_active: boolean;
is_current: boolean;
created_at: string;
updated_at: string;
}
interface SessionsResponse {
sessions: AuthSession[] | null;
total: number;
page: number;
limit: number;
total_pages: number;
error: Error | null;
}
Returned from volcano.auth.getSessions({ page, limit }). is_current flags the calling client's own session.
Middleware Types
import type {
ServerClientConfig,
ServerClient,
User,
GetUserResult,
RefreshTokenResult,
} from '@volcano.dev/sdk/next/middleware';
interface ServerClientConfig {
anonKey: string;
apiUrl?: string;
accessToken?: string;
}
interface ServerClient {
getUser(accessToken: string): Promise<GetUserResult>;
refreshToken(refreshToken: string): Promise<RefreshTokenResult>;
}
interface GetUserResult {
user: User | null;
error: Error | null;
}
interface RefreshTokenResult {
accessToken: string | null;
refreshToken: string | null;
error: Error | null;
}
Utility Types
JsonValue
Recursive JSON-serializable type. Used throughout the SDK for user_metadata, OAuth bodies, function payloads, and realtime broadcast payloads:
type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
FilterValue
Values accepted by query filter operators (eq/neq/gt/etc.):
type FilterValue = string | number | boolean | null | Date;
UserMetadata
Convenience alias when typing user_metadata records:
type UserMetadata = Record<string, JsonValue>;
Best Practices
Define your data models once
Centralize entity types in one application-owned shared model file and import them everywhere they're used. In the canonical web/ frontend this lives under web/types/ (e.g. web/types/models.ts); in a non-web app, use that app's existing shared types path instead of creating web/ just for types.
export interface User {
id: string;
email: string;
display_name?: string;
created_at: string;
}
export interface Post {
id: string;
user_id: string;
title: string;
content: string;
published: boolean;
created_at: string;
}
Use type guards for narrowing on union return types
function isError<T>(result: { data: T | null; error: Error | null }): result is { data: null; error: Error } {
return result.error !== null;
}
const result = await volcano.from('posts').select('*');
if (isError(result)) {
console.error(result.error.message);
return;
}
Handle null on success-side too
Even with error null, data can be empty (e.g., select with no matches returns [], not null; but update/delete returning no rows returns [] or null depending on the call). Always handle both:
const { data, error } = await volcano.update('posts', { ... }).eq('id', id);
if (error) return handleError(error);
if (!data || data.length === 0) return notFound();
const updated = data[0];
Type your realtime callbacks at the boundary
The SDK gives you Record<string, unknown>; cast to your row type once at the entry of the handler:
channel.onPostgresChanges('INSERT', 'public', 'posts', (change) => {
const post = change.record as Post;
setPosts(prev => [post, ...prev]);
});
Use strict mode
tsconfig.json should have "strict": true (the volcano-standard scaffold sets this by default). It surfaces null/undefined mistakes at compile time:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}
noUncheckedIndexedAccess is optional but catches subtle bugs with data[0] after a query.
Quick Reference — what type do I need?
| Domain | Most useful type | Where it appears |
|---|
| Auth | User, Session, AuthResponse | signIn/signUp/getUser returns |
| Database | QueryResult<T>, FilterValue | from<T>(...).select(...) chain |
| Storage | StorageObject, StorageUploadResponse, StorageListResponse | storage.from(...) operations |
| Realtime | PostgresChange, PresenceState, ConnectContext | Channel callbacks |
| Functions | invoke<P, R>(...) generic params | Both ends of an invocation |
| OAuth | OAuthProviderName, OAuthProvider | Provider name validation |
| Sessions | AuthSession, SessionsResponse | Multi-device session UI |
| Middleware | ServerClient, GetUserResult | Next.js middleware/route handlers |
| Utility | JsonValue, FilterValue, UserMetadata | Anywhere user-supplied JSON-shaped data flows |
Verification Checklist
tsconfig.json has "strict": true.
- All
data consumption is guarded by an if (error) (or equivalent) check first.
- Application data models are centralized (one source of truth) and imported, not redeclared.
volcano.from<T>(...), volcano.insert<T>(...), volcano.update<T>(...) use generic parameters where they apply.
- Realtime callbacks cast
change.record to a typed row type at the entry boundary.
- Function invocations use
invoke<Params, Result>(...) generics, not invoke(...).
- OAuth provider names are typed as
OAuthProviderName, not string.
Companion Skills
volcano_sdk — top-level orientation and mandatory usage.
volcano_platform — project shape, Volcano Functions runtime contract, build pipeline.
- Domain skills (
volcano_auth, volcano_database, volcano_functions, volcano_storage, volcano_realtime, volcano_nextjs) — pair with this skill when implementing.