一键导入
volcano-database
Detailed guidance for browser and function data access with the Volcano query builder
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Detailed guidance for browser and function data access with the Volcano query builder
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | volcano-database |
| description | Detailed guidance for browser and function data access with the Volcano query builder |
Implement Volcano query-builder access with RLS-first authorization. All persistent data storage MUST use Volcano Databases. This skill is self-contained: the operators, mutation patterns, RLS examples, and error semantics are embedded below.
volcano.database(databaseName) BEFORE any query.select('*') unless intentional.import { VolcanoAuth } from '@volcano.dev/sdk';
const volcano = new VolcanoAuth({
apiUrl: process.env.NEXT_PUBLIC_VOLCANO_API_URL!,
anonKey: process.env.NEXT_PUBLIC_VOLCANO_ANON_KEY!,
});
volcano.database(process.env.NEXT_PUBLIC_VOLCANO_DATABASE_NAME!);
function createClient(auth?: { access_token?: string }): VolcanoAuth {
const volcano = new VolcanoAuth({
apiUrl: process.env.VOLCANO_API_URL!,
anonKey: process.env.VOLCANO_ANON_KEY!,
accessToken: auth?.access_token,
});
volcano.database(process.env.VOLCANO_DATABASE!);
return volcano;
}
Use the same request-scoped client pattern inside Functions handlers when server-side persistence is required.
const { data, error, count } = await volcano.from('posts').select('*');
const { data } = await volcano.from('posts').select('id, title, created_at');
volcano.from('t').select('*').eq('col', value); // =
volcano.from('t').select('*').neq('col', value); // !=
volcano.from('t').select('*').gt('col', n); // >
volcano.from('t').select('*').gte('col', n); // >=
volcano.from('t').select('*').lt('col', n); // <
volcano.from('t').select('*').lte('col', n); // <=
volcano.from('t').select('*').like('col', 'A%'); // case-sensitive
volcano.from('t').select('*').ilike('col', '%a%'); // case-insensitive
volcano.from('t').select('*').is('col', null); // NULL check
volcano.from('t').select('*').in('col', [a, b, c]); // IN (...)
Pattern syntax: % matches any sequence, _ matches any single character.
const { data } = await volcano
.from('products')
.select('id, name, price')
.eq('category', 'electronics')
.gte('price', 100)
.lte('price', 500)
.eq('in_stock', true);
.order('created_at', { ascending: false })
.order('category', { ascending: true })
.order('created_at', { ascending: false }); // chain for multi-column
const pageSize = 10, page = 3;
const { data, count } = await volcano
.from('posts')
.select('id, title')
.order('created_at', { ascending: false })
.limit(pageSize)
.offset((page - 1) * pageSize);
async function getPublishedPosts(category: string, page = 1) {
const pageSize = 20;
const { data, error, count } = await volcano
.from('posts')
.select('id, title, excerpt, author_name, created_at')
.eq('status', 'published')
.eq('category', category)
.is('deleted_at', null)
.order('created_at', { ascending: false })
.limit(pageSize)
.offset((page - 1) * pageSize);
if (error) throw new Error(`Failed to fetch posts: ${error.message}`);
return { posts: data, totalPages: Math.ceil((count ?? 0) / pageSize), currentPage: page };
}
const { data, error } = await volcano.insert('posts', {
title: 'My New Post',
content: 'This is the content.',
status: 'draft',
});
// data is an array; data[0] is the inserted row including any defaults.
If the table has a user_id column with default auth.uid(), the authenticated user's id is automatically associated.
const { data, error } = await volcano
.update('posts', {
title: 'Updated Title',
status: 'published',
updated_at: new Date().toISOString(),
})
.eq('id', postId);
// data[0] is the updated row. Filter is REQUIRED.
Bulk update (multiple matching rows):
await volcano
.update('posts', { status: 'archived' })
.lt('created_at', oneYearAgo)
.eq('status', 'published');
const { data, error } = await volcano.delete('posts').eq('id', postId);
// Mark deleted
await volcano.update('posts', { deleted_at: new Date().toISOString() }).eq('id', postId);
// Filter from queries
await volcano.from('posts').select('*').is('deleted_at', null);
.single()Return a single row (not an array). Errors if zero or more-than-one row matches.
// After a unique-filter select
const { data: post, error } = await volcano
.from('posts')
.select('*')
.eq('id', postId)
.single();
// data is the row, not data[0]
// After insert: get the inserted row directly
const { data: task, error } = await volcano
.insert('tasks', { title, user_id })
.select()
.single();
The query builder does NOT currently expose:
select('*, comments(*)')).When the work genuinely requires one of these, route it through a Volcano Function and use raw SQL via the request-scoped Volcano client. Consult the fallback reference if the SDK has gained these capabilities since this skill was last updated.
auth.uid() to reference the current user.-- Users can only read their own posts
CREATE POLICY "Users can read own posts"
ON posts FOR SELECT
USING (user_id = auth.uid());
-- Users can only update their own posts
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (user_id = auth.uid());
-- Users can only insert posts owned by themselves
CREATE POLICY "Users can insert own posts"
ON posts FOR INSERT
WITH CHECK (user_id = auth.uid());
-- Public-read pattern
CREATE POLICY "Published posts are public"
ON posts FOR SELECT
USING (status = 'published');
The same client query returns different rows depending on the signed-in user. Do NOT emulate authorization in client code; rely on RLS.
interface Post {
id: string;
title: string;
status: 'draft' | 'published' | 'archived';
created_at: string;
}
const { data } = await volcano.from<Post>('posts').select('*').eq('status', 'published');
// data: Post[] | null
All operations return { data, error }. Common messages:
column does not exist — invalid column name (developer error).permission denied — RLS policy blocked the operation.No active session — user not authenticated.Database name not set — forgot volcano.database(...).violates unique constraint — duplicate value on insert.violates foreign key — bad reference.violates check constraint — invalid input.When persistence belongs in a function, keep the same client pattern inside the handler:
// Client side
const { data, error } = await volcano.functions.invoke('get-dashboard-stats', { timeframe: 'last-30-days' });
// Function side: same query-builder flow with a request-scoped client
export const handler = async (event: { __volcano_auth?: { access_token?: string } }) => {
const auth = event.__volcano_auth;
if (!auth) return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) };
const volcano = createClient(auth);
const { data, error } = await volcano.from('posts').select('id, title, created_at').order('created_at', { ascending: false });
if (error) return { statusCode: 500, body: JSON.stringify({ error: error.message }) };
return { statusCode: 200, body: JSON.stringify({ posts: data ?? [] }) };
};
.limit(50) is a sensible default).error object from server handlers as { statusCode, body: JSON.stringify({ error: error.message }) }.volcano.database(name) is called before any query.event.__volcano_auth.Install or upgrade the Volcano CLI from a plugin-shipped skills environment.
Detailed guidance for authentication flows built with the Volcano SDK
Reusable error handling patterns for Volcano SDK apps: centralized error dispatcher with action enum, useApiCall React hook for loading/error/data state, retry with exponential backoff, retry with toast notifications, and cross-domain error message catalog.
Detailed guidance for server-side function invocation and orchestration with Volcano
Detailed guidance for using the Volcano SDK correctly in Next.js environments
Canonical Volcano project shape and deploy contract: the volcano/functions/ model, migrations, volcano-config.yaml, env vars, shared-code conventions, and the build/deploy workflow.