← VolcanoCONTENT HISTORY
WHAT CHANGED
Update to Volcano
Snapshot Oct 24, 2026 · 13:36 UTC · version 0.2.26
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Volcano and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Canonical TypeScript type definitions for the Volcano SDK: User, Session, AuthResponse, QueryBuilder, StorageObject, Realtime and Durable types, Function invocation generics, OAuth providers, middleware types, and utility types.",
"included_files": [],
"name": "volcano-typescript",
"skill_md_contents": "---\nname: volcano-typescript\ndescription: \"Canonical TypeScript type definitions for the Volcano SDK: User, Session, AuthResponse, QueryBuilder, StorageObject, Realtime and Durable types, Function invocation generics, OAuth providers, middleware types, and utility types.\"\n---\n# Volcano TypeScript Types Skill\n\n## Role\nProvide 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.\n\n## When to use\n- Defining application data models that will go through `volcano.from<T>(...)` or `insert<T>(...)`.\n- Reading or destructuring auth responses (`signIn`, `signUp`, `getUser`, `getSessions`).\n- Typing realtime event handlers (`PostgresChange`, `PresenceState`).\n- Typing function invocations with `invoke<P, R>(...)`.\n- Typing durable handlers, contexts, and execution results.\n- Writing middleware helpers that consume `getUser(token)` or `refreshToken(...)`.\n\n## Importing\n```ts\nimport { VolcanoAuth } from '@volcano.dev/sdk';\nimport type {\n User,\n Session,\n AuthResponse,\n UserResponse,\n SessionResponse,\n VolcanoAuthConfig,\n} from '@volcano.dev/sdk';\n```\n\n## Core Types\n\n### Configuration\n```ts\ninterface VolcanoAuthConfig {\n /** API URL (defaults to https://api.volcano.dev) */\n apiUrl?: string;\n /** Project anon key (required) */\n anonKey: string;\n /** Access token for server-side use */\n accessToken?: string;\n /** Refresh token for server-side use */\n refreshToken?: string;\n}\n```\n\n### User\n```ts\ninterface User {\n id: string;\n email: string;\n user_metadata?: Record<string, JsonValue>;\n created_at: string;\n updated_at: string;\n}\n```\nThe `id` is the Volcano auth user_id; use it as the foreign key in your `users` application table.\n\n### Session\n```ts\ninterface Session {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n}\n```\n\n### Auth response shapes\n```ts\ninterface AuthResponse {\n user: User | null;\n session: Session | null;\n error: Error | null;\n}\n\ninterface UserResponse {\n user: User | null;\n error: Error | null;\n}\n\ninterface SessionResponse {\n session: Session | null;\n error: Error | null;\n}\n```\nReturned from `signUp`, `signIn`, `getUser`, `refreshSession`, etc. Always check `error` before consuming `user`/`session` — they are `null` when `error` is set.\n\n## Database Types\n\n### Typed query results with generics\n```ts\ninterface Post {\n id: string;\n title: string;\n content: string;\n status: 'draft' | 'published' | 'archived';\n user_id: string;\n created_at: string;\n}\n\nconst { data, error } = await volcano\n .from<Post>('posts')\n .select('*')\n .eq('status', 'published');\n\nif (data) {\n data.forEach(post => {\n // TypeScript knows post.title is string\n // and post.status is the union literal type\n });\n}\n```\n\n### Insert / update with generics\n```ts\nconst { data, error } = await volcano\n .insert<Post>('posts', {\n title: 'New Post',\n content: 'Content here',\n status: 'draft',\n });\n\nconst { data: updated } = await volcano\n .update<Post>('posts', { status: 'published' })\n .eq('id', postId);\n```\nTypeScript validates the partial shape against `Post`.\n\n### QueryBuilder<T> contract\n```ts\ninterface QueryBuilder<T> {\n select(columns: string): QueryBuilder<T>;\n eq(column: string, value: FilterValue): QueryBuilder<T>;\n neq(column: string, value: FilterValue): QueryBuilder<T>;\n gt(column: string, value: FilterValue): QueryBuilder<T>;\n gte(column: string, value: FilterValue): QueryBuilder<T>;\n lt(column: string, value: FilterValue): QueryBuilder<T>;\n lte(column: string, value: FilterValue): QueryBuilder<T>;\n like(column: string, pattern: string): QueryBuilder<T>;\n ilike(column: string, pattern: string): QueryBuilder<T>;\n is(column: string, value: null): QueryBuilder<T>;\n in(column: string, values: FilterValue[]): QueryBuilder<T>;\n order(column: string, options?: { ascending?: boolean }): QueryBuilder<T>;\n limit(count: number): QueryBuilder<T>;\n offset(count: number): QueryBuilder<T>;\n execute(): Promise<QueryResult<T>>;\n}\n\ninterface QueryResult<T> {\n data: T[] | null;\n error: Error | null;\n count?: number;\n}\n```\nThe chain auto-resolves on `await` — you rarely need to call `.execute()` explicitly. `count` populates only when the query/result mode supports it.\n\n## Storage Types\n```ts\ninterface StorageObject {\n id: string;\n bucket_id: string;\n name: string;\n owner_id?: string;\n is_public: boolean;\n size: number;\n mime_type: string;\n etag?: string;\n metadata?: Record<string, JsonValue>;\n created_at: string;\n updated_at: string;\n public_url?: string;\n}\n\ninterface StorageUploadOptions {\n contentType?: string;\n}\n\ninterface StorageUploadResponse {\n data: StorageObject | null;\n error: Error | null;\n}\n\ninterface StorageDownloadResponse {\n data: Blob | null; // Blob in browser; convert with Buffer.from(await blob.arrayBuffer()) in Node\n error: Error | null;\n}\n\ninterface StorageListResponse {\n data: StorageObject[] | null;\n error: Error | null;\n nextCursor: string | null; // null on the final page\n}\n```\n\n### Usage\n```ts\nconst storage = volcano.storage.from('avatars');\n\nconst { data, error }: StorageUploadResponse = await storage.upload(\n 'user/avatar.jpg',\n file,\n { contentType: 'image/jpeg' }\n);\n\nconst { data: blob }: StorageDownloadResponse = await storage.download('user/avatar.jpg');\n\nconst { data: files, nextCursor }: StorageListResponse = await storage.list('user/', {\n limit: 100,\n});\n```\n\n## Realtime Types\nImport realtime types from the realtime module:\n```ts\nimport { VolcanoRealtime, RealtimeChannel } from '@volcano.dev/sdk/realtime';\nimport type {\n PostgresChange,\n PresenceState,\n ConnectContext,\n DisconnectContext,\n ErrorContext,\n RealtimeConfig,\n ChannelOptions,\n WebSocketConstructor,\n} from '@volcano.dev/sdk/realtime';\n```\n\n### Realtime configuration\n```ts\ninterface RealtimeConfig {\n apiUrl: string;\n anonKey: string;\n accessToken?: string;\n getToken?: () => Promise<string>;\n volcanoClient?: VolcanoAuth;\n fetchConfig?: FetchConfig;\n webSocket?: WebSocketConstructor;\n}\n\ninterface ChannelOptions {\n type?: 'broadcast' | 'presence' | 'postgres';\n autoFetch?: boolean;\n fetchBatchWindowMs?: number;\n fetchMaxBatchSize?: number;\n}\n```\n\n### Event contexts\n```ts\ninterface ConnectContext {\n client?: string;\n latency?: number;\n}\n\ninterface DisconnectContext {\n code?: number;\n reason?: string;\n reconnect?: boolean;\n}\n\ninterface ErrorContext {\n error?: Error;\n message?: string;\n code?: number;\n}\n```\n\n### Postgres changes\n```ts\ninterface PostgresChange {\n table: string;\n schema: string;\n type: 'INSERT' | 'UPDATE' | 'DELETE';\n record?: Record<string, unknown>; // present on INSERT/UPDATE\n old_record?: Record<string, unknown>; // present on UPDATE/DELETE\n columns?: string[]; // changed columns, UPDATE only\n timestamp: string;\n}\n```\n\n### Typed postgres listeners\n```ts\ninterface Message {\n id: string;\n content: string;\n user_id: string;\n created_at: string;\n}\n\nchannel.onPostgresChanges('INSERT', 'public', 'messages', (change: PostgresChange) => {\n const message = change.record as Message;\n // ...\n});\n```\nThe callback's `change.record` is `Record<string, unknown>`; cast to your row type at the boundary.\n\n### Presence\n```ts\ninterface PresenceState {\n [clientId: string]: Record<string, unknown>;\n}\n\ninterface UserPresence {\n user_id: string;\n username: string;\n status: 'online' | 'away' | 'busy';\n}\n\nchannel.onPresenceSync((state: PresenceState) => {\n const users = Object.values(state) as UserPresence[];\n // ...\n});\n```\n\n## Functions Types\n\n### Typed invocation with payload + response generics\n```ts\ninterface InvokeParams {\n userId: string;\n action: 'fetch' | 'update' | 'delete';\n}\n\ninterface InvokeResult {\n success: boolean;\n data?: Record<string, unknown>;\n message?: string;\n}\n\nconst { data, error } = await volcano.functions.invoke<InvokeParams, InvokeResult>(\n 'process-user',\n { userId: '123', action: 'fetch' }\n);\n\nif (data) {\n // TypeScript knows data.success is boolean, data.message is string | undefined\n}\n```\nThe full return tuple from `invoke<P, R>(...)` is `{ data: R | null; status: number; headers: Record<string, string>; version: string; error: Error | null }`.\n\n## Durable Function Types\n\nAuthoring types come from the durable entry point:\n\n```ts\nimport { durable, type DurableContext, type DurableHandler } from '@volcano.dev/sdk/durable';\n\ninterface OrderInput {\n order_id: number;\n}\n\ninterface OrderResult {\n order_id: number;\n outcome: 'shipped' | 'refunded';\n}\n\nconst run: DurableHandler<OrderInput, OrderResult> = async (input, ctx) => {\n const order = await ctx.step('load', () => loadOrder(input.order_id));\n await ctx.wait('settle', '30s');\n return { order_id: order.id, outcome: 'shipped' };\n};\n\nexport const handler = durable(run);\n```\n\n`DurableContext`, `StepOptions`, `WaitUntilOptions`, `BatchOptions`,\n`BatchResult`, `Retry`, and `DurableDuration` describe authoring. Execution\nclient types come from the main entry point:\n\n```ts\nimport type { DurableExecution, DurableExecutionStatus } from '@volcano.dev/sdk';\n\nconst status: DurableExecutionStatus = 'running';\nconst { data } = await volcano.durable.list(projectId, 'order-pipeline', { status });\nconst execution: DurableExecution | undefined = data?.data[0];\n```\n\n`DurableExecution` uses the API's snake-case fields. Its `result` is `unknown`,\nso narrow it before use.\n\n## OAuth Types\n```ts\ntype OAuthProviderName = 'google' | 'github' | 'microsoft' | 'apple';\n\ninterface OAuthProvider {\n provider: OAuthProviderName;\n linked_at: string;\n updated_at: string;\n}\n\ninterface OAuthAPIParams {\n endpoint: string;\n method?: string;\n body?: JsonValue;\n}\n```\n\n```ts\nvolcano.auth.signInWithOAuth('google'); // OK\nvolcano.auth.signInWithOAuth('invalid'); // Compile error: not in OAuthProviderName\n\nconst { providers } = await volcano.auth.getLinkedOAuthProviders();\nproviders?.forEach((p: OAuthProvider) => { /* ... */ });\n```\n\n## Session Management Types\n```ts\ninterface AuthSession {\n id: string;\n user_id: string;\n provider: 'email' | 'google' | 'github' | 'microsoft' | 'apple' | 'anonymous';\n user_agent?: string;\n ip_address?: string;\n last_ip_address?: string;\n expires_at: string;\n last_activity_at?: string;\n session_started_at?: string;\n is_active: boolean;\n is_current: boolean;\n created_at: string;\n updated_at: string;\n}\n\ninterface SessionsResponse {\n sessions: AuthSession[] | null;\n total: number;\n page: number;\n limit: number;\n total_pages: number;\n error: Error | null;\n}\n```\nReturned from `volcano.auth.getSessions({ page, limit })`. `is_current` flags the calling client's own session.\n\n## Middleware Types\n```ts\nimport type {\n ServerClientConfig,\n ServerClient,\n User,\n GetUserResult,\n RefreshTokenResult,\n} from '@volcano.dev/sdk/next/middleware';\n\ninterface ServerClientConfig {\n anonKey: string;\n apiUrl?: string;\n accessToken?: string;\n}\n\ninterface ServerClient {\n getUser(accessToken: string): Promise<GetUserResult>;\n refreshToken(refreshToken: string): Promise<RefreshTokenResult>;\n}\n\ninterface GetUserResult {\n user: User | null;\n error: Error | null;\n}\n\ninterface RefreshTokenResult {\n accessToken: string | null;\n refreshToken: string | null;\n error: Error | null;\n}\n```\n\n## Utility Types\n\n### JsonValue\nRecursive JSON-serializable type. Used throughout the SDK for `user_metadata`, OAuth bodies, function payloads, and realtime broadcast payloads:\n```ts\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n```\n\n### FilterValue\nValues accepted by query filter operators (`eq`/`neq`/`gt`/etc.):\n```ts\ntype FilterValue = string | number | boolean | null | Date;\n```\n\n### UserMetadata\nConvenience alias when typing user_metadata records:\n```ts\ntype UserMetadata = Record<string, JsonValue>;\n```\n\n## Best Practices\n\n### Define your data models once\nCentralize 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.\n```ts\n// web/types/models.ts, or your existing shared application types path\nexport interface User {\n id: string;\n email: string;\n display_name?: string;\n created_at: string;\n}\n\nexport interface Post {\n id: string;\n user_id: string;\n title: string;\n content: string;\n published: boolean;\n created_at: string;\n}\n```\n\n### Use type guards for narrowing on union return types\n```ts\nfunction isError<T>(result: { data: T | null; error: Error | null }): result is { data: null; error: Error } {\n return result.error !== null;\n}\n\nconst result = await volcano.from('posts').select('*');\nif (isError(result)) {\n console.error(result.error.message);\n return;\n}\n// result.data is non-null here\n```\n\n### Handle null on success-side too\nEven 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:\n```ts\nconst { data, error } = await volcano.update('posts', { ... }).eq('id', id);\nif (error) return handleError(error);\nif (!data || data.length === 0) return notFound();\nconst updated = data[0];\n```\n\n### Type your realtime callbacks at the boundary\nThe SDK gives you `Record<string, unknown>`; cast to your row type once at the entry of the handler:\n```ts\nchannel.onPostgresChanges('INSERT', 'public', 'posts', (change) => {\n const post = change.record as Post;\n setPosts(prev => [post, ...prev]);\n});\n```\n\n### Use strict mode\n`tsconfig.json` should have `\"strict\": true` (the volcano-standard scaffold sets this by default). It surfaces null/undefined mistakes at compile time:\n```json\n{\n \"compilerOptions\": {\n \"strict\": true,\n \"noUncheckedIndexedAccess\": true\n }\n}\n```\n`noUncheckedIndexedAccess` is optional but catches subtle bugs with `data[0]` after a query.\n\n## Quick Reference — what type do I need?\n\n| Domain | Most useful type | Where it appears |\n|---|---|---|\n| Auth | `User`, `Session`, `AuthResponse` | `signIn`/`signUp`/`getUser` returns |\n| Database | `QueryResult<T>`, `FilterValue` | `from<T>(...).select(...)` chain |\n| Storage | `StorageObject`, `StorageUploadResponse`, `StorageListResponse` | `storage.from(...)` operations |\n| Realtime | `PostgresChange`, `PresenceState`, `ConnectContext` | Channel callbacks |\n| Functions | `invoke<P, R>(...)` generic params | Both ends of an invocation |\n| Durable | `DurableHandler`, `DurableContext`, `DurableExecution` | Durable authoring and execution clients |\n| OAuth | `OAuthProviderName`, `OAuthProvider` | Provider name validation |\n| Sessions | `AuthSession`, `SessionsResponse` | Multi-device session UI |\n| Middleware | `ServerClient`, `GetUserResult` | Next.js middleware/route handlers |\n| Utility | `JsonValue`, `FilterValue`, `UserMetadata` | Anywhere user-supplied JSON-shaped data flows |\n\n## Verification Checklist\n- `tsconfig.json` has `\"strict\": true`.\n- All `data` consumption is guarded by an `if (error)` (or equivalent) check first.\n- Application data models are centralized (one source of truth) and imported, not redeclared.\n- `volcano.from<T>(...)`, `volcano.insert<T>(...)`, `volcano.update<T>(...)` use generic parameters where they apply.\n- Realtime callbacks cast `change.record` to a typed row type at the entry boundary.\n- Function invocations use `invoke<Params, Result>(...)` generics, not `invoke(...)`.\n- Durable handlers use `DurableHandler<Input, Result>` when their input and result are known.\n- OAuth provider names are typed as `OAuthProviderName`, not `string`.\n\n## Companion Skills\n- `volcano-sdk` — top-level orientation and mandatory usage.\n- `volcano-platform` — project shape, Volcano Functions runtime contract, build pipeline.\n- Domain and interface skills (`volcano-auth`, `volcano-database`, `volcano-functions`, `volcano-durable`, `volcano-storage`, `volcano-realtime`, `volcano-nextjs`, `volcano-uiux`) — pair with this skill when implementing.\n"
}SHA-256 of public snapshot: afd9aad2b16b16f8131c4a6377b256db465defa747dd32550ab5670174472c3c