← VolcanoCONTENT HISTORY

Update to Volcano

Snapshot Oct 24, 2026 · 13:36 UTC · version 0.2.26

Collection source: downloaded plugin package.

WHAT CHANGED

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