From d71c4babb39e20c5477b762bc33b83698b6dc1f6 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 20:46:33 +0530 Subject: [PATCH 01/16] create app --- .cursor/rules/APPWRITE.mdc | 1173 ++++++++++++ .gitignore | 23 + .npmrc | 1 + .prettierignore | 9 + .prettierrc | 17 + LICENSE | 21 - README.md | 38 + eslint.config.js | 40 + package.json | 37 + pnpm-lock.yaml | 2054 +++++++++++++++++++++ pnpm-workspace.yaml | 2 + src/app.d.ts | 13 + src/app.html | 12 + src/lib/appwrite.js | 14 + src/lib/assets/favicon.svg | 1 + src/lib/languages/common/install.js | 148 ++ src/lib/languages/common/mcp.js | 55 + src/lib/languages/common/products.js | 119 ++ src/lib/languages/common/security.js | 62 + src/lib/languages/common/utils.js | 92 + src/lib/languages/dart/index.js | 26 + src/lib/languages/dart/server.js | 15 + src/lib/languages/dotnet/index.js | 3 + src/lib/languages/dotnet/server.js | 9 + src/lib/languages/dotnet/vanilla.js | 8 + src/lib/languages/go/index.js | 2 + src/lib/languages/go/server.js | 9 + src/lib/languages/index.js | 11 + src/lib/languages/js/angular.js | 9 + src/lib/languages/js/astro.js | 12 + src/lib/languages/js/index.js | 13 + src/lib/languages/js/nextjs.js | 10 + src/lib/languages/js/nodejs.js | 10 + src/lib/languages/js/nuxt.js | 13 + src/lib/languages/js/qwik.js | 10 + src/lib/languages/js/react.js | 9 + src/lib/languages/js/solid.js | 10 + src/lib/languages/js/svelte.js | 10 + src/lib/languages/js/tanstack.js | 17 + src/lib/languages/js/vanilla.js | 9 + src/lib/languages/js/vue.js | 9 + src/lib/languages/kotlin/index.js | 46 + src/lib/languages/php/index.js | 2 + src/lib/languages/php/server.js | 9 + src/lib/languages/python/flask.js | 8 + src/lib/languages/python/index.js | 3 + src/lib/languages/python/server.js | 9 + src/lib/languages/react-native/index.js | 2 + src/lib/languages/react-native/vanilla.js | 11 + src/lib/languages/ruby/index.js | 2 + src/lib/languages/ruby/server.js | 9 + src/lib/languages/swift/index.js | 36 + src/lib/rules-generator.js | 326 ++++ src/lib/utils/versions.js | 61 + src/routes/+layout.svelte | 13 + src/routes/+page.svelte | 453 +++++ static/robots.txt | 3 + svelte.config.js | 18 + tsconfig.json | 20 + vite.config.ts | 6 + 60 files changed, 5171 insertions(+), 21 deletions(-) create mode 100644 .cursor/rules/APPWRITE.mdc create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 LICENSE create mode 100644 README.md create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/lib/appwrite.js create mode 100644 src/lib/assets/favicon.svg create mode 100644 src/lib/languages/common/install.js create mode 100644 src/lib/languages/common/mcp.js create mode 100644 src/lib/languages/common/products.js create mode 100644 src/lib/languages/common/security.js create mode 100644 src/lib/languages/common/utils.js create mode 100644 src/lib/languages/dart/index.js create mode 100644 src/lib/languages/dart/server.js create mode 100644 src/lib/languages/dotnet/index.js create mode 100644 src/lib/languages/dotnet/server.js create mode 100644 src/lib/languages/dotnet/vanilla.js create mode 100644 src/lib/languages/go/index.js create mode 100644 src/lib/languages/go/server.js create mode 100644 src/lib/languages/index.js create mode 100644 src/lib/languages/js/angular.js create mode 100644 src/lib/languages/js/astro.js create mode 100644 src/lib/languages/js/index.js create mode 100644 src/lib/languages/js/nextjs.js create mode 100644 src/lib/languages/js/nodejs.js create mode 100644 src/lib/languages/js/nuxt.js create mode 100644 src/lib/languages/js/qwik.js create mode 100644 src/lib/languages/js/react.js create mode 100644 src/lib/languages/js/solid.js create mode 100644 src/lib/languages/js/svelte.js create mode 100644 src/lib/languages/js/tanstack.js create mode 100644 src/lib/languages/js/vanilla.js create mode 100644 src/lib/languages/js/vue.js create mode 100644 src/lib/languages/kotlin/index.js create mode 100644 src/lib/languages/php/index.js create mode 100644 src/lib/languages/php/server.js create mode 100644 src/lib/languages/python/flask.js create mode 100644 src/lib/languages/python/index.js create mode 100644 src/lib/languages/python/server.js create mode 100644 src/lib/languages/react-native/index.js create mode 100644 src/lib/languages/react-native/vanilla.js create mode 100644 src/lib/languages/ruby/index.js create mode 100644 src/lib/languages/ruby/server.js create mode 100644 src/lib/languages/swift/index.js create mode 100644 src/lib/rules-generator.js create mode 100644 src/lib/utils/versions.js create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.svelte create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.cursor/rules/APPWRITE.mdc b/.cursor/rules/APPWRITE.mdc new file mode 100644 index 0000000..acdc8d2 --- /dev/null +++ b/.cursor/rules/APPWRITE.mdc @@ -0,0 +1,1173 @@ +--- +description: You are an expert developer focused on building apps with Appwrite's APIs and SDKs. +alwaysApply: false +--- + +# Appwrite Development Rules + +## SDK Initialization + +### Client-Side (Web/React/Next.js) + +Always initialize the Appwrite client with proper configuration: + +```javascript +import { Client, Account, TablesDB, Storage } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') // Your Appwrite endpoint + .setProject('your-project-id'); // Your project ID + +// Initialize services +export const account = new Account(client); +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +**Best Practices:** +- Store endpoint and project ID in environment variables (`.env.local`) +- Never commit API keys or secrets to version control +- Use separate clients for different environments (dev/staging/prod) +- Initialize services once and export them as singletons + +### Server-Side (Node.js/Next.js API Routes) + +Use server SDK with API key for admin operations: + +```javascript +import { Client, TablesDB, Storage, ID } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); // Server-side only + +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +**Security:** +- API keys should NEVER be exposed to client-side code +- Use environment variables for all sensitive configuration +- API keys grant admin access - use with extreme caution + +## Authentication Patterns + +### Session Management + +Always check authentication state before making authenticated requests: + +```javascript +import { account } from './appwrite-config'; + +// Check current session +async function getCurrentUser() { + try { + return await account.get(); + } catch (error) { + // User not authenticated + return null; + } +} + +// Create session +async function login(email, password) { + try { + await account.createEmailPasswordSession(email, password); + return await account.get(); + } catch (error) { + throw new Error(`Login failed: ${error.message}`); + } +} + +// Delete session +async function logout() { + try { + await account.deleteSession('current'); + } catch (error) { + console.error('Logout error:', error); + } +} +``` + +### OAuth Providers + +When implementing OAuth, handle redirects properly: + +```javascript +// Initiate OAuth +async function loginWithOAuth(provider) { + account.createOAuth2Session( + provider, // 'google', 'github', etc. + 'https://yourapp.com/success', // Success redirect + 'https://yourapp.com/failure' // Failure redirect + ); +} + +// Handle OAuth callback (check URL params) +if (window.location.search.includes('success')) { + const user = await account.get(); + // Redirect to dashboard +} +``` + +### SSR Authentication (Server-Side Rendering) + +For SSR frameworks like Next.js, Remix, or SvelteKit, handle authentication on the server using cookies. + +#### Server-Side Client Setup + +Create a server-side client that reads cookies from requests: + +```javascript +// lib/appwrite-server.js +import { Client, Account, TablesDB } from 'appwrite'; +import { cookies } from 'next/headers'; // Next.js App Router +// or: import { parseCookies } from 'nookies'; // Next.js Pages Router + +export function createServerClient() { + const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID); + + return client; +} + +export async function getServerAccount() { + const client = createServerClient(); + const account = new Account(client); + + // Get session cookies from request + const cookieStore = await cookies(); // Next.js App Router + const sessionCookie = cookieStore.get('a_session_'); + + if (sessionCookie) { + // Set session cookie on client + client.setSession(sessionCookie.value); + } + + return account; +} + +// Alternative for Pages Router +export function getServerAccountPages(req) { + const client = createServerClient(); + const account = new Account(client); + + const cookieStore = parseCookies({ req }); + const sessionCookie = cookieStore['a_session_']; + + if (sessionCookie) { + client.setSession(sessionCookie); + } + + return account; +} +``` + +#### Next.js App Router - Server Components + +Authenticate in Server Components and pass user data to client: + +```javascript +// app/dashboard/page.js (Server Component) +import { getServerAccount } from '@/lib/appwrite-server'; +import { redirect } from 'next/navigation'; +import DashboardClient from './dashboard-client'; + +export default async function DashboardPage() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + + // User is authenticated, render dashboard + return ; + } catch (error) { + // User not authenticated, redirect to login + redirect('/login'); + } +} +``` + +#### Next.js App Router - Server Actions + +Handle authentication in Server Actions: + +```javascript +// app/actions/auth.js +'use server'; + +import { getServerAccount } from '@/lib/appwrite-server'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; + +export async function loginAction(email, password) { + const account = await getServerAccount(); + + try { + const session = await account.createEmailPasswordSession(email, password); + + // Set session cookie + const cookieStore = await cookies(); + cookieStore.set('a_session_', session.secret, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + path: '/', + }); + + redirect('/dashboard'); + } catch (error) { + return { error: error.message }; + } +} + +export async function logoutAction() { + const account = await getServerAccount(); + + try { + await account.deleteSession('current'); + + // Clear session cookie + const cookieStore = await cookies(); + cookieStore.delete('a_session_'); + + redirect('/login'); + } catch (error) { + return { error: error.message }; + } +} +``` + +#### Next.js Pages Router - getServerSideProps + +Authenticate in `getServerSideProps`: + +```javascript +// pages/dashboard.js +import { getServerAccountPages } from '@/lib/appwrite-server'; + +export async function getServerSideProps({ req, res }) { + const account = getServerAccountPages(req); + + try { + const user = await account.get(); + + return { + props: { + user: { + $id: user.$id, + email: user.email, + name: user.name, + }, + }, + }; + } catch (error) { + // Redirect to login if not authenticated + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } +} + +export default function Dashboard({ user }) { + return ( +
+

Welcome, {user.name}

+

Email: {user.email}

+
+ ); +} +``` + +#### Next.js Middleware - Route Protection + +Protect routes using Next.js middleware: + +```javascript +// middleware.js (Next.js 12+) +import { NextResponse } from 'next/server'; +import { getServerAccount } from '@/lib/appwrite-server'; +import { cookies } from 'next/headers'; + +export async function middleware(request) { + const protectedPaths = ['/dashboard', '/profile', '/settings']; + const isProtectedPath = protectedPaths.some(path => + request.nextUrl.pathname.startsWith(path) + ); + + if (!isProtectedPath) { + return NextResponse.next(); + } + + try { + const account = await getServerAccount(); + await account.get(); + + // User is authenticated, allow request + return NextResponse.next(); + } catch (error) { + // User not authenticated, redirect to login + const loginUrl = new URL('/login', request.url); + loginUrl.searchParams.set('redirect', request.nextUrl.pathname); + return NextResponse.redirect(loginUrl); + } +} + +export const config = { + matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'], +}; +``` + +#### Server-Side API Routes with Authentication + +Authenticate in API routes: + +```javascript +// app/api/protected/route.js (App Router) +import { getServerAccount } from '@/lib/appwrite-server'; +import { NextResponse } from 'next/server'; + +export async function GET(request) { + try { + const account = await getServerAccount(); + const user = await account.get(); + + // User is authenticated, proceed with request + const data = await fetchProtectedData(user.$id); + + return NextResponse.json({ data }); + } catch (error) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } +} + +// pages/api/protected.js (Pages Router) +import { getServerAccountPages } from '@/lib/appwrite-server'; + +export default async function handler(req, res) { + try { + const account = getServerAccountPages(req); + const user = await account.get(); + + // User is authenticated, proceed with request + const data = await fetchProtectedData(user.$id); + + res.json({ data }); + } catch (error) { + res.status(401).json({ error: 'Unauthorized' }); + } +} +``` + +#### Cookie Management Best Practices + +1. **HttpOnly cookies** - Always set `httpOnly: true` to prevent XSS attacks +2. **Secure flag** - Use `secure: true` in production (HTTPS only) +3. **SameSite** - Use `sameSite: 'lax'` or `'strict'` for CSRF protection +4. **Cookie name** - Appwrite uses `a_session_` format +5. **Session validation** - Always validate session on server before trusting it + +#### SSR Authentication Helper + +Create a reusable authentication helper: + +```javascript +// lib/auth-server.js +import { getServerAccount } from './appwrite-server'; +import { redirect } from 'next/navigation'; + +export async function requireAuth() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + return { user, account }; + } catch (error) { + redirect('/login'); + } +} + +export async function optionalAuth() { + const account = await getServerAccount(); + + try { + const user = await account.get(); + return { user, account, isAuthenticated: true }; + } catch (error) { + return { user: null, account: null, isAuthenticated: false }; + } +} + +// Usage in Server Components +export default async function ProtectedPage() { + const { user, account } = await requireAuth(); + + // User is guaranteed to be authenticated here + return
Welcome, {user.name}
; +} +``` + +## Teams Management + +Appwrite Teams enable collaborative features and team-based permissions. Always initialize the Teams service alongside other services. + +### SDK Initialization + +Include Teams in your client setup: + +```javascript +import { Client, Account, Teams, TablesDB, Storage } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://cloud.appwrite.io/v1') + .setProject('your-project-id'); + +export const account = new Account(client); +export const teams = new Teams(client); +export const tablesDB = new TablesDB(client); +export const storage = new Storage(client); +``` + +### Creating Teams + +Create teams with proper error handling: + +```javascript +import { ID } from 'appwrite'; + +// Create a new team +async function createTeam(name) { + try { + return await teams.create(ID.unique(), name); + } catch (error) { + throw new Error(`Failed to create team: ${error.message}`); + } +} + +// Create team with custom ID +async function createTeamWithId(teamId, name) { + try { + return await teams.create(teamId, name); + } catch (error) { + if (error.code === 409) { + throw new Error('Team ID already exists'); + } + throw new Error(`Failed to create team: ${error.message}`); + } +} +``` + +### Team Membership Operations + +Manage team members with proper role assignment: + +```javascript +// Add member to team +async function addTeamMember(teamId, email, roles = ['member']) { + try { + // First, get user ID by email (requires server-side with API key) + // Or pass userId directly if you have it + return await teams.createMembership(teamId, email, roles); + } catch (error) { + if (error.code === 409) { + throw new Error('User is already a member of this team'); + } + throw new Error(`Failed to add member: ${error.message}`); + } +} + +// Add member by user ID (server-side only) +async function addTeamMemberById(teamId, userId, roles = ['member']) { + try { + return await teams.createMembership(teamId, undefined, roles, undefined, userId); + } catch (error) { + throw new Error(`Failed to add member: ${error.message}`); + } +} + +// Update member roles +async function updateMemberRoles(teamId, membershipId, roles) { + try { + return await teams.updateMembershipRoles(teamId, membershipId, roles); + } catch (error) { + throw new Error(`Failed to update roles: ${error.message}`); + } +} + +// Remove member from team +async function removeTeamMember(teamId, membershipId) { + try { + await teams.deleteMembership(teamId, membershipId); + } catch (error) { + throw new Error(`Failed to remove member: ${error.message}`); + } +} +``` + +### Listing Teams and Members + +Query teams and members efficiently: + +```javascript +// List user's teams +async function getUserTeams() { + try { + const response = await teams.list(); + return response.teams; + } catch (error) { + throw new Error(`Failed to list teams: ${error.message}`); + } +} + +// Get team details +async function getTeam(teamId) { + try { + return await teams.get(teamId); + } catch (error) { + if (error.code === 404) { + return null; + } + throw new Error(`Failed to get team: ${error.message}`); + } +} + +// List team members +async function getTeamMembers(teamId) { + try { + const response = await teams.listMemberships(teamId); + return response.memberships; + } catch (error) { + throw new Error(`Failed to list members: ${error.message}`); + } +} + +// Get specific membership +async function getMembership(teamId, membershipId) { + try { + return await teams.getMembership(teamId, membershipId); + } catch (error) { + if (error.code === 404) { + return null; + } + throw new Error(`Failed to get membership: ${error.message}`); + } +} +``` + +### Team Roles and Permissions + +Use team roles in database and storage permissions: + +```javascript +import { Permission, Role } from 'appwrite'; + +// Team-based permissions for rows +async function createTeamRow(teamId, data) { + try { + return await tablesDB.createRow( + 'database-id', + 'table-id', + ID.unique(), + data, + [ + // All team members can read + Permission.read(Role.team(teamId)), + // Only team admins can write + Permission.write(Role.team(teamId, 'admin')), + // Only team owners can delete + Permission.delete(Role.team(teamId, 'owner')) + ] + ); + } catch (error) { + throw new Error(`Failed to create row: ${error.message}`); + } +} + +// Team-based permissions for files +async function uploadTeamFile(teamId, file, bucketId) { + try { + return await storage.createFile( + bucketId, + ID.unique(), + file, + [ + Permission.read(Role.team(teamId)), + Permission.write(Role.team(teamId, 'admin')), + Permission.delete(Role.team(teamId, 'owner')) + ] + ); + } catch (error) { + throw new Error(`Failed to upload file: ${error.message}`); + } +} + +// Check if user has specific role in team +async function hasTeamRole(teamId, role) { + try { + const membership = await teams.getMembership(teamId, 'me'); // Get current user's membership + return membership.roles.includes(role); + } catch (error) { + return false; + } +} +``` + +### Team Management Operations + +Update and manage team settings: + +```javascript +// Update team name +async function updateTeamName(teamId, name) { + try { + return await teams.updateName(teamId, name); + } catch (error) { + throw new Error(`Failed to update team name: ${error.message}`); + } +} + +// Delete team +async function deleteTeam(teamId) { + try { + await teams.delete(teamId); + } catch (error) { + throw new Error(`Failed to delete team: ${error.message}`); + } +} + +// Accept team invitation +async function acceptTeamInvitation(teamId, membershipId, userId, secret) { + try { + return await teams.updateMembershipStatus( + teamId, + membershipId, + userId, + secret + ); + } catch (error) { + throw new Error(`Failed to accept invitation: ${error.message}`); + } +} +``` + +### React Hook for Teams + +Create a reusable hook for team management: + +```javascript +import { useState, useEffect } from 'react'; +import { ID } from 'appwrite'; +import { teams } from './appwrite-config'; + +function useTeams() { + const [teamsList, setTeamsList] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchTeams() { + try { + const response = await teams.list(); + setTeamsList(response.teams); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + + fetchTeams(); + }, []); + + const createTeam = async (name) => { + try { + const newTeam = await teams.create(ID.unique(), name); + setTeamsList([...teamsList, newTeam]); + return newTeam; + } catch (err) { + setError(err.message); + throw err; + } + }; + + return { teams: teamsList, loading, error, createTeam }; +} +``` + +### Server-Side Team Operations + +Handle team operations on the server with API key: + +```javascript +// lib/appwrite-server.js +import { Client, Teams } from 'appwrite'; + +const serverClient = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + +export const serverTeams = new Teams(serverClient); + +// Add user to team (server-side) +export async function addUserToTeam(teamId, userId, roles = ['member']) { + try { + return await serverTeams.createMembership( + teamId, + undefined, // email not needed when userId provided + roles, + undefined, // URL not needed for direct add + userId + ); + } catch (error) { + throw new Error(`Failed to add user to team: ${error.message}`); + } +} + +// List all teams (admin operation) +export async function listAllTeams() { + try { + const response = await serverTeams.list(); + return response.teams; + } catch (error) { + throw new Error(`Failed to list teams: ${error.message}`); + } +} +``` + +### Team-Based Access Control + +Implement team-based access control patterns: + +```javascript +import { Query } from 'appwrite'; + +// Check if user can access team resource +async function canAccessTeamResource(teamId, requiredRole = 'member') { + try { + const team = await teams.get(teamId); + const memberships = await teams.listMemberships(teamId); + + // Get current user + const user = await account.get(); + + // Find user's membership + const membership = memberships.find(m => m.userId === user.$id); + + if (!membership) { + return false; + } + + // Check role hierarchy: owner > admin > member + const roleHierarchy = { owner: 3, admin: 2, member: 1 }; + const requiredLevel = roleHierarchy[requiredRole] || 1; + const userLevel = Math.max(...membership.roles.map(r => roleHierarchy[r] || 0)); + + return userLevel >= requiredLevel; + } catch (error) { + return false; + } +} + +// Filter rows by team membership +async function getTeamRows(teamId) { + try { + // First verify user is team member + const canAccess = await canAccessTeamResource(teamId); + if (!canAccess) { + throw new Error('Access denied'); + } + + // Query rows with team permission + return await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.equal('teamId', teamId)] + ); + } catch (error) { + throw new Error(`Failed to get team rows: ${error.message}`); + } +} +``` + +### Team Best Practices + +1. **Role Management** - Use consistent role names: 'owner', 'admin', 'member' +2. **Invitations** - Always handle invitation acceptance flow properly +3. **Permissions** - Combine team roles with specific permissions (e.g., `Role.team(teamId, 'admin')`) +4. **Validation** - Always verify team membership before granting access +5. **Error Handling** - Handle 404 (team not found) and 403 (access denied) appropriately +6. **Team Limits** - Be aware of team size limits and implement pagination for large teams +7. **Cleanup** - Remove team memberships when users are deleted + +## Database Operations + +### Query Patterns + +Use Appwrite's query builder for efficient queries: + +```javascript +import { Query } from 'appwrite'; + +// Single query +const rows = await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.equal('status', 'active')] +); + +// Multiple conditions +const filtered = await tablesDB.listRows( + 'database-id', + 'table-id', + [ + Query.equal('status', 'active'), + Query.greaterThan('createdAt', '2024-01-01'), + Query.limit(10), + Query.orderDesc('createdAt') + ] +); + +// Full-text search +const searchResults = await tablesDB.listRows( + 'database-id', + 'table-id', + [Query.search('title', 'search term')] +); +``` + +### CRUD Operations + +Follow consistent patterns for create, read, update, delete: + +```javascript +import { ID } from 'appwrite'; + +// Create row +async function createRow(data) { + try { + return await tablesDB.createRow( + 'database-id', + 'table-id', + ID.unique(), + data, + [ + Permission.read(Role.user(userId)), // User can read + Permission.write(Role.user(userId)) // User can write + ] + ); + } catch (error) { + throw new Error(`Failed to create: ${error.message}`); + } +} + +// Read row +async function getRow(rowId) { + try { + return await tablesDB.getRow( + 'database-id', + 'table-id', + rowId + ); + } catch (error) { + if (error.code === 404) { + return null; + } + throw error; + } +} + +// Update row +async function updateRow(rowId, data) { + try { + return await tablesDB.updateRow( + 'database-id', + 'table-id', + rowId, + data + ); + } catch (error) { + throw new Error(`Failed to update: ${error.message}`); + } +} + +// Delete row +async function deleteRow(rowId) { + try { + await tablesDB.deleteRow( + 'database-id', + 'table-id', + rowId + ); + } catch (error) { + throw new Error(`Failed to delete: ${error.message}`); + } +} +``` + +### Permissions + +Always set appropriate permissions when creating/updating rows: + +```javascript +import { Permission, Role } from 'appwrite'; + +// User-specific row +const permissions = [ + Permission.read(Role.user(userId)), + Permission.write(Role.user(userId)), + Permission.delete(Role.user(userId)) +]; + +// Public read, authenticated write +const publicPermissions = [ + Permission.read(Role.any()), + Permission.write(Role.users()), +]; + +// Team-based permissions +const teamPermissions = [ + Permission.read(Role.team(teamId)), + Permission.write(Role.team(teamId, 'admin')) +]; +``` + +## Storage Operations + +### File Upload + +Handle file uploads with progress tracking: + +```javascript +import { ID } from 'appwrite'; + +async function uploadFile(file, bucketId) { + try { + return await storage.createFile( + bucketId, + ID.unique(), + file, + [ + Permission.read(Role.any()), // Adjust based on needs + Permission.write(Role.users()) + ], + (progress) => { + console.log(`Upload progress: ${progress.progress}%`); + } + ); + } catch (error) { + throw new Error(`Upload failed: ${error.message}`); + } +} + +// Get file preview/URL +function getFileUrl(bucketId, fileId) { + return storage.getFilePreview(bucketId, fileId); +} + +// Get file download +function getFileDownload(bucketId, fileId) { + return storage.getFileDownload(bucketId, fileId); +} +``` + +### File Management + +```javascript +// List files +async function listFiles(bucketId, queries = []) { + return await storage.listFiles(bucketId, queries); +} + +// Delete file +async function deleteFile(bucketId, fileId) { + await storage.deleteFile(bucketId, fileId); +} + +// Update file +async function updateFile(bucketId, fileId, name) { + return await storage.updateFile(bucketId, fileId, name); +} +``` + +## Error Handling + +Always implement proper error handling: + +```javascript +async function safeAppwriteCall(operation) { + try { + return await operation(); + } catch (error) { + // Handle specific error codes + switch (error.code) { + case 401: + // Unauthorized - redirect to login + window.location.href = '/login'; + break; + case 404: + // Not found + return null; + case 409: + // Conflict (e.g., duplicate email) + throw new Error('Resource already exists'); + case 429: + // Rate limited + throw new Error('Too many requests. Please try again later.'); + default: + console.error('Appwrite error:', error); + throw new Error(`Operation failed: ${error.message}`); + } + } +} + +// Usage +const user = await safeAppwriteCall(() => account.get()); +``` + +## Real-time Subscriptions + +Use real-time listeners for live updates: + +```javascript +import { RealtimeResponseEvent } from 'appwrite'; + +function subscribeToTable(databaseId, tableId, callback) { + return client.subscribe( + `databases.${databaseId}.tables.${tableId}.rows`, + (response) => { + if (response.events.includes('databases.*.tables.*.rows.*.create')) { + callback('create', response.payload); + } else if (response.events.includes('databases.*.tables.*.rows.*.update')) { + callback('update', response.payload); + } else if (response.events.includes('databases.*.tables.*.rows.*.delete')) { + callback('delete', response.payload); + } + } + ); +} + +// Usage +const unsubscribe = subscribeToTable( + 'database-id', + 'table-id', + (event, payload) => { + console.log(`${event} event:`, payload); + } +); + +// Cleanup +// unsubscribe(); +``` + +## Server-Side Functions + +When using Appwrite Functions, follow these patterns: + +```javascript +// Function handler example (Node.js) +export default async ({ req, res, log, error }) => { + try { + const { databaseId, tableId } = JSON.parse(req.body); + + // Use server SDK + const tablesDB = new TablesDB(client); + const rows = await tablesDB.listRows(databaseId, tableId); + + return res.json({ success: true, data: rows }); + } catch (err) { + error(err.message); + return res.json({ success: false, error: err.message }, 500); + } +}; +``` + +## Security Best Practices + +1. **Never expose API keys** - Use environment variables +2. **Validate permissions** - Always set appropriate row/file permissions +3. **Sanitize inputs** - Validate and sanitize user inputs before database operations +4. **Use HTTPS** - Always use HTTPS endpoints in production +5. **Rate limiting** - Implement client-side rate limiting for user-facing operations +6. **Session management** - Implement proper session expiration and refresh logic + +## Performance Optimization + +1. **Pagination** - Always use `Query.limit()` and `Query.offset()` for large datasets +2. **Indexing** - Create database indexes for frequently queried fields +3. **Caching** - Cache frequently accessed data on the client side +4. **Batch operations** - Use batch requests when possible +5. **Selective fields** - Use `Query.select()` to fetch only needed fields + +## Common Patterns + +### React Hook Example + +```javascript +import { useState, useEffect } from 'react'; +import { account } from './appwrite-config'; + +function useAuth() { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + account.get() + .then(setUser) + .catch(() => setUser(null)) + .finally(() => setLoading(false)); + }, []); + + return { user, loading }; +} +``` + +### Next.js API Route Example + +```javascript +// pages/api/data.js or app/api/data/route.js +import { tablesDB } from '@/lib/appwrite-server'; + +export default async function handler(req, res) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const rows = await tablesDB.listRows( + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_TABLE_ID + ); + res.json(rows); + } catch (error) { + res.status(500).json({ error: error.message }); + } +} +``` + +## Testing Patterns + +When writing tests, mock Appwrite SDK: + +```javascript +// Mock Appwrite in tests +jest.mock('appwrite', () => ({ + Client: jest.fn(), + Account: jest.fn(() => ({ + get: jest.fn(), + createEmailPasswordSession: jest.fn(), + })), + TablesDB: jest.fn(() => ({ + listRows: jest.fn(), + createRow: jest.fn(), + })), +})); +``` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b513262 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": [ + "prettier-plugin-svelte" + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 466695b..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Appwrite - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..75842c4 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..134b1b2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + "no-undef": 'off' } + }, + { + files: [ + '**/*.svelte', + '**/*.svelte.ts', + '**/*.svelte.js' + ], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..6cbc568 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "appwrite-cursor-rules", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ." + }, + "devDependencies": { + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.48.5", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@types/node": "^22", + "eslint": "^9.39.1", + "eslint-plugin-svelte": "^3.13.0", + "globals": "^16.5.0", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^5.43.8", + "svelte-check": "^4.3.4", + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0", + "vite": "^7.2.2" + }, + "dependencies": { + "@appwrite.io/pink": "^1.0.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3db13e6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2054 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@appwrite.io/pink': + specifier: ^1.0.0 + version: 1.0.0 + devDependencies: + '@eslint/compat': + specifier: ^1.4.0 + version: 1.4.1(eslint@9.39.1) + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.1 + '@sveltejs/adapter-auto': + specifier: ^7.0.0 + version: 7.0.0(@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))) + '@sveltejs/kit': + specifier: ^2.48.5 + version: 2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.1 + version: 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@types/node': + specifier: ^22 + version: 22.19.1 + eslint: + specifier: ^9.39.1 + version: 9.39.1 + eslint-plugin-svelte: + specifier: ^3.13.0 + version: 3.13.0(eslint@9.39.1)(svelte@5.44.0) + globals: + specifier: ^16.5.0 + version: 16.5.0 + prettier: + specifier: ^3.6.2 + version: 3.6.2 + prettier-plugin-svelte: + specifier: ^3.4.0 + version: 3.4.0(prettier@3.6.2)(svelte@5.44.0) + svelte: + specifier: ^5.43.8 + version: 5.44.0 + svelte-check: + specifier: ^4.3.4 + version: 4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.47.0 + version: 8.48.0(eslint@9.39.1)(typescript@5.9.3) + vite: + specifier: ^7.2.2 + version: 7.2.4(@types/node@22.19.1) + +packages: + + '@appwrite.io/pink-icons@1.0.0': + resolution: {integrity: sha512-+zpksP07MvOYwhx9AZDFW0pxXQNC2juKwyOQVRAwAOkN1ACSQKPlyytkI1u2ci6CQPWjJe20CzbvBBuRNXOKjA==} + + '@appwrite.io/pink@1.0.0': + resolution: {integrity: sha512-q/RFlKjtGL7yBfjYkPkHYnhPt9jSMT3zoLRlEIu+G2lt59QWjybELQW17cS8gm1ZBTcncuzoPtVd6x1vrEo5uw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.4.1': + resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@sveltejs/acorn-typescript@1.0.7': + resolution: {integrity: sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-auto@7.0.0': + resolution: {integrity: sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.49.0': + resolution: {integrity: sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.1': + resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-svelte@3.13.0: + resolution: {integrity: sha512-2ohCCQJJTNbIpQCSDSTWj+FN0OVfPmSO03lmSNT7ytqMaWF6kpT86LdzDqtm4sh7TVPl/OEWJ/d7R87bXP2Vjg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrap@2.2.0: + resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + normalize.css@8.0.1: + resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-svelte@3.4.0: + resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svelte-check@4.3.4: + resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte-eslint-parser@1.4.0: + resolution: {integrity: sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.18.3} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + svelte@5.44.0: + resolution: {integrity: sha512-R7387No2zEGw4CtYtI2rgsui6BqjFARzoZFGLiLN5OPla0Pq4Ra2WwcP/zBomP3MYalhSNvF1fzDMuU0P0zPJw==} + engines: {node: '>=18'} + + the-new-css-reset@1.11.3: + resolution: {integrity: sha512-61SB81vu9foUyEIqoU1CeqxrdlsVjJojj/CBXoG8BdvlKFsllB0Rza63DblnRqH+3uttPj3FGWo7+c9nu7MT+A==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.48.0: + resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + +snapshots: + + '@appwrite.io/pink-icons@1.0.0': {} + + '@appwrite.io/pink@1.0.0': + dependencies: + '@appwrite.io/pink-icons': 1.0.0 + normalize.css: 8.0.1 + the-new-css-reset: 1.11.3 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/compat@1.4.1(eslint@9.39.1)': + dependencies: + '@eslint/core': 0.17.0 + optionalDependencies: + eslint: 9.39.1 + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@polka/url@1.0.0-next.29': {} + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@standard-schema/spec@1.0.0': {} + + '@sveltejs/acorn-typescript@1.0.7(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))': + dependencies: + '@sveltejs/kit': 2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + + '@sveltejs/kit@2.49.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@standard-schema/spec': 1.0.0 + '@sveltejs/acorn-typescript': 1.0.7(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + '@types/cookie': 0.6.0 + acorn: 8.15.0 + cookie: 0.6.0 + devalue: 5.5.0 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.2 + sirv: 3.0.2 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + debug: 4.4.3 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@22.19.1)) + debug: 4.4.3 + deepmerge: 4.3.1 + magic-string: 0.30.21 + svelte: 5.44.0 + vite: 7.2.4(@types/node@22.19.1) + vitefu: 1.1.1(vite@7.2.4(@types/node@22.19.1)) + transitivePeerDependencies: + - supports-color + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.1': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + eslint: 9.39.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.48.0': {} + + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.48.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.3) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.48.0(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + eslint-visitor-keys: 4.2.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cookie@0.6.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + devalue@5.5.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-svelte@3.13.0(eslint@9.39.1)(svelte@5.44.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@jridgewell/sourcemap-codec': 1.5.5 + eslint: 9.39.1 + esutils: 2.0.3 + globals: 16.5.0 + known-css-properties: 0.37.0 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 7.0.1(postcss@8.5.6) + semver: 7.7.3 + svelte-eslint-parser: 1.4.0(svelte@5.44.0) + optionalDependencies: + svelte: 5.44.0 + transitivePeerDependencies: + - ts-node + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + esm-env@1.2.2: {} + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrap@2.2.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + known-css-properties@0.37.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + locate-character@3.0.0: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + normalize.css@8.0.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss-load-config@3.1.4(postcss@8.5.6): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.44.0): + dependencies: + prettier: 3.6.2 + svelte: 5.44.0 + + prettier@3.6.2: {} + + punycode@2.3.1: {} + + readdirp@4.1.2: {} + + resolve-from@4.0.0: {} + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + semver@7.7.3: {} + + set-cookie-parser@2.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.44.0 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte-eslint-parser@1.4.0(svelte@5.44.0): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.0 + optionalDependencies: + svelte: 5.44.0 + + svelte@5.44.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.7(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.5.0 + esm-env: 1.2.2 + esrap: 2.2.0 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + the-new-css-reset@1.11.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + totalist@3.0.1: {} + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@7.2.4(@types/node@22.19.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + + vitefu@1.1.1(vite@7.2.4(@types/node@22.19.1)): + optionalDependencies: + vite: 7.2.4(@types/node@22.19.1) + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yaml@1.10.2: {} + + yocto-queue@0.1.0: {} + + zimmerframe@1.1.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efc037a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..0bcd3ce --- /dev/null +++ b/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/appwrite.js b/src/lib/appwrite.js new file mode 100644 index 0000000..7b905d2 --- /dev/null +++ b/src/lib/appwrite.js @@ -0,0 +1,14 @@ +import { Client, Account } from 'appwrite'; +// @ts-ignore - SvelteKit env variables +import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public'; + +const endpoint = PUBLIC_APPWRITE_ENDPOINT || 'https://cloud.appwrite.io/v1'; +const projectId = PUBLIC_APPWRITE_PROJECT_ID || ''; + +const client = new Client() + .setEndpoint(endpoint) + .setProject(projectId); + +export const account = new Account(client); +export { client }; + diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/languages/common/install.js b/src/lib/languages/common/install.js new file mode 100644 index 0000000..e84f9d5 --- /dev/null +++ b/src/lib/languages/common/install.js @@ -0,0 +1,148 @@ +/** + * Common installation instructions by package manager/language + */ + +export const npmInstall = `npm install appwrite`; + +export const yarnInstall = `yarn add appwrite`; + +export const pnpmInstall = `pnpm add appwrite`; + +export const bunInstall = `bun add appwrite`; + +/** + * JavaScript/TypeScript installation section + * @param {string} [packageName] - Package name (default: 'appwrite') + * @param {string} [title] - Installation title (default: 'Install the Appwrite JavaScript SDK') + */ +export function jsInstall(packageName = 'appwrite', title = 'Install the Appwrite JavaScript SDK') { + return `## SDK Installation + +${title} using npm: + +\`\`\`bash +npm install ${packageName} +\`\`\` + +Or using yarn: + +\`\`\`bash +yarn add ${packageName} +\`\`\` + +Or using pnpm: + +\`\`\`bash +pnpm add ${packageName} +\`\`\` + +Or using bun: + +\`\`\`bash +bun add ${packageName} +\`\`\``; +} + +/** + * Default JavaScript installation (export as constant for backwards compatibility) + */ +export const jsInstallDefault = jsInstall(); + +/** + * Python installation section + */ +export const pythonInstall = `## SDK Installation + +Install the Appwrite Python SDK using pip: + +\`\`\`bash +pip install appwrite +\`\`\` + +Or using poetry: + +\`\`\`bash +poetry add appwrite +\`\`\``; + +/** + * PHP installation section + */ +export const phpInstall = `## SDK Installation + +Install the Appwrite PHP SDK using Composer: + +\`\`\`bash +composer require appwrite/appwrite +\`\`\``; + +/** + * Go installation section + */ +export const goInstall = `## SDK Installation + +Install the Appwrite Go SDK: + +\`\`\`bash +go get github.com/appwrite/sdk-for-go +\`\`\``; + +/** + * Ruby installation section + */ +export const rubyInstall = `## SDK Installation + +Install the Appwrite Ruby SDK using Bundler: + +\`\`\`bash +gem install appwrite +\`\`\` + +Or add to your \`Gemfile\`: + +\`\`\`ruby +gem 'appwrite' +\`\`\``; + +/** + * .NET installation section + */ +export const dotnetInstall = `## SDK Installation + +Install the Appwrite .NET SDK using NuGet: + +\`\`\`bash +dotnet add package Appwrite +\`\`\` + +Or using Package Manager: + +\`\`\`powershell +Install-Package Appwrite +\`\`\``; + +/** + * Dart/Flutter installation section (async - requires version lookup) + * @param {string} version - SDK version + * @param {boolean} isServer - Whether this is for server SDK + */ +export const dartInstall = (version, isServer = false) => { + const sdkName = isServer ? 'Dart Server SDK' : 'Flutter SDK'; + const pubCommand = isServer ? 'dart pub get' : 'flutter pub get'; + + return `## SDK Installation + +Add the Appwrite ${sdkName} to your \`pubspec.yaml\`: + +\`\`\`yaml +dependencies: + appwrite: ^${version} +\`\`\` + +Then install it: + +\`\`\`bash +${pubCommand} +\`\`\``; +}; + diff --git a/src/lib/languages/common/mcp.js b/src/lib/languages/common/mcp.js new file mode 100644 index 0000000..51b9c1a --- /dev/null +++ b/src/lib/languages/common/mcp.js @@ -0,0 +1,55 @@ +/** + * MCP Server recommendation content + * This content is included in all generated rules to recommend installing the Appwrite Docs MCP server + */ + +/** + * MCP Server recommendation section + * @returns {string} + */ +export function generateMCPRecommendation() { + return `## Recommended: Install Appwrite Docs MCP Server + +**We highly recommend installing the Appwrite Docs MCP server** to get instant access to Appwrite documentation, code examples, and best practices directly in Cursor. This enables the AI assistant to provide accurate, up-to-date Appwrite guidance and code examples. + +### Installation Steps + +1. **Open Cursor Settings** + - Go to **Cursor Settings** → **MCP** tab + - Click **Add new global MCP server** + +2. **Add the Docs Server** + - Update the \`mcp.json\` file with the following configuration: + +\`\`\`json +{ + "mcpServers": { + "appwrite-docs": { + "command": "npx", + "args": [ + "mcp-remote", + "https://mcp-for-docs.appwrite.io" + ] + } + } +} +\`\`\` + +3. **Save and Restart** + - Save the \`mcp.json\` file + - Restart Cursor if the MCP server doesn't start automatically + +### Benefits + +Once installed, you can ask questions like: +- "How do I set up real-time subscriptions in Appwrite?" +- "Show me how to authenticate users with OAuth" +- "What are the best practices for database queries?" +- "How do I implement file uploads with Appwrite Storage?" +- "Show me an example of using Appwrite Functions" + +**Note:** You can also install the Appwrite API MCP server for direct API interactions. For full setup instructions, visit: https://appwrite.io/docs/tooling/mcp/cursor + +---`; +} + diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js new file mode 100644 index 0000000..cc620dd --- /dev/null +++ b/src/lib/languages/common/products.js @@ -0,0 +1,119 @@ +/** + * Common product documentation links and descriptions + * These are reused across all SDKs and frameworks + */ + +/** + * Product documentation sections that appear in every SDK initialization + */ +export const commonProductLinks = `For TablesDB operations (create, read, update, delete rows), see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). + +For Storage operations (upload, download files), see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download).`; + +/** + * Authentication product documentation + */ +export const authProductLinks = `For detailed authentication and session management instructions, see the [Authentication Quick Start Guide](https://appwrite.io/docs/products/auth/quick-start). + +For session management, login, logout, and authentication state checking, see the [Authentication Documentation](https://appwrite.io/docs/products/auth/quick-start). + +For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). + +For server-side rendering (SSR) authentication, see the [SSR Login Guide](https://appwrite.io/docs/products/auth/server-side-rendering). + +For Teams management, team invitations, and team-based permissions, see the [Teams Documentation](https://appwrite.io/docs/products/auth/teams). + +For team invites and membership management, see the [Team Invites Guide](https://appwrite.io/docs/products/auth/team-invites). + +For multi-tenancy using Teams, see the [Multi-tenancy Guide](https://appwrite.io/docs/products/auth/multi-tenancy).`; + +/** + * Database product documentation + */ +export const databaseProductLinks = `For detailed database operations, see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). + +For querying and filtering data, see the [Queries Guide](https://appwrite.io/docs/products/databases/queries). + +For pagination, see the [Pagination Documentation](https://appwrite.io/docs/products/databases/pagination). + +For permissions and access control, see the [Permissions Guide](https://appwrite.io/docs/products/databases/permissions). + +For transactions, see the [Transactions Documentation](https://appwrite.io/docs/products/databases/transactions).`; + +/** + * Storage product documentation + */ +export const storageProductLinks = `For detailed storage operations, see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download). + +For file uploads and downloads, see the [Upload & Download Guide](https://appwrite.io/docs/products/storage/upload-download). + +For permissions and access control, see the [Storage Permissions Documentation](https://appwrite.io/docs/products/storage/permissions).`; + +/** + * Functions product documentation + */ +export const functionsProductLinks = `For detailed serverless function execution, see the [Functions Execution Documentation](https://appwrite.io/docs/products/functions/execute). + +For function domains and custom endpoints, see the [Functions Domains Guide](https://appwrite.io/docs/products/functions/domains). + +For event-driven function execution, see the [Events Documentation](https://appwrite.io/docs/advanced/platform/events). + +For scheduled function execution, see the [Scheduled Executions Guide](https://appwrite.io/docs/products/functions/execute#schedule).`; + +/** + * Messaging product documentation + */ +export const messagingProductLinks = `For detailed messaging operations, see the [Messaging Documentation](https://appwrite.io/docs/products/messaging). + +For sending push notifications, see the [Push Notifications Guide](https://appwrite.io/docs/products/messaging/send-push-notifications). + +For sending emails, see the [Email Messages Guide](https://appwrite.io/docs/products/messaging/send-email-messages). + +For sending SMS messages, see the [SMS Messages Guide](https://appwrite.io/docs/products/messaging/send-sms-messages). + +For messaging providers, see the [Providers Documentation](https://appwrite.io/docs/products/messaging/providers).`; + +/** + * Sites product documentation + */ +export const sitesProductLinks = `For detailed Sites hosting and deployment, see the [Sites Documentation](https://appwrite.io/docs/products/sites). + +For getting started with Sites, see the [Sites Quick Start Guide](https://appwrite.io/docs/products/sites/quick-start). + +For deploying from Git, see the [Deploy from Git Guide](https://appwrite.io/docs/products/sites/deploy-from-git). + +For deploying from CLI, see the [Deploy from CLI Guide](https://appwrite.io/docs/products/sites/deploy-from-cli). + +For manual deployments, see the [Manual Deployment Guide](https://appwrite.io/docs/products/sites/deploy-manually). + +For deployment management, see the [Deployments Documentation](https://appwrite.io/docs/products/sites/deployments). + +For custom domains, see the [Domains Documentation](https://appwrite.io/docs/products/sites/domains). + +For rendering strategies (static vs SSR), see the [Rendering Documentation](https://appwrite.io/docs/products/sites/rendering). + +For static site hosting, see the [Static Rendering Guide](https://appwrite.io/docs/products/sites/rendering/static). + +For server-side rendering, see the [SSR Rendering Guide](https://appwrite.io/docs/products/sites/rendering/ssr). + +For supported frameworks, see the [Frameworks Documentation](https://appwrite.io/docs/products/sites/frameworks). + +For instant rollbacks, see the [Instant Rollbacks Guide](https://appwrite.io/docs/products/sites/instant-rollbacks). + +For deployment previews, see the [Previews Documentation](https://appwrite.io/docs/products/sites/previews).`; + +/** + * Real-time product documentation + */ +export const realtimeProductLinks = `For detailed real-time subscriptions, see the [Real-time Documentation](https://appwrite.io/docs/products/realtime). + +For subscribing to database changes, see the [Database Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-databases). + +For subscribing to storage changes, see the [Storage Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-storage). + +For subscribing to account changes, see the [Account Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-account). + +For real-time channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). + +For real-time event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; + diff --git a/src/lib/languages/common/security.js b/src/lib/languages/common/security.js new file mode 100644 index 0000000..3a7f23f --- /dev/null +++ b/src/lib/languages/common/security.js @@ -0,0 +1,62 @@ +/** + * Common security best practices and guidelines + */ + +/** + * Client-side security best practices (for web frameworks) + */ +export const clientSecurity = `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Initialize services once and export as singletons`; + +/** + * Client-side security with environment variable note + * @param {string} envFile - Environment file name (e.g., '.env.local', '.env') + */ +export const clientSecurityWithEnv = (envFile = '.env.local') => `**Best Practices:** +- Store endpoint and project ID in environment variables (${envFile}) +- Never commit API keys to version control +- Initialize services once and export as singletons`; + +/** + * Server-side security best practices + */ +export const serverSecurity = `**Security:** +- API keys should NEVER be exposed to client-side code +- Use environment variables for all sensitive configuration +- API keys grant admin access - use with extreme caution`; + +/** + * Server-side security with framework-specific config note + * @param {string} configMethod - Configuration method (e.g., "configuration files or environment variables", "Rails credentials") + */ +export const serverSecurityWithConfig = (configMethod = 'environment variables') => `**Security:** +- API keys should NEVER be exposed to client-side code +- Use ${configMethod} for configuration +- API keys grant admin access - use with extreme caution`; + +/** + * Authentication-specific notes + */ +export const authNote = `**Authentication:** +- Prefer SSR auth for better security and performance`; + +/** + * Framework-specific notes + */ +export const frameworkNotes = { + angular: `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Use Angular services for dependency injection + +${authNote}`, + + nodejs: `**Best Practices:** +- Store endpoint and project ID in environment variables +- Never commit API keys to version control +- Use environment variables for configuration +- API keys grant admin access - use with extreme caution` +}; + diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js new file mode 100644 index 0000000..058d9af --- /dev/null +++ b/src/lib/languages/common/utils.js @@ -0,0 +1,92 @@ +/** + * Utility functions for composing SDK initialization templates + */ + +/** + * Creates a security/best practices section (SDK Initialization section removed) + * @param {Object} options + * @param {string} options.securityNotes - Security/best practices section + * @param {string} [options.additionalNotes] - Additional framework-specific notes + * @returns {string} + */ +export function createSecuritySection({ + securityNotes, + additionalNotes = '' +}) { + return securityNotes + (additionalNotes ? `\n\n${additionalNotes}` : ''); +} + +/** + * Creates a complete framework template by combining installation and security notes + * @param {Object} options + * @param {string} options.installation - Installation section + * @param {string} options.securityNotes - Security/best practices section + * @param {string} [options.additionalNotes] - Additional framework-specific notes + * @returns {string} + */ +export function createFrameworkTemplate({ installation, securityNotes, additionalNotes = '' }) { + const securitySection = createSecuritySection({ securityNotes, additionalNotes }); + return `${installation} + +${securitySection}`; +} + +/** + * Quick Start Guide URLs by framework + */ +export const quickStartUrls = { + // JavaScript frameworks + react: 'https://appwrite.io/docs/quick-starts/react', + nextjs: 'https://appwrite.io/docs/quick-starts/nextjs', + vue: 'https://appwrite.io/docs/quick-starts/vue', + svelte: 'https://appwrite.io/docs/quick-starts/sveltekit', + angular: 'https://appwrite.io/docs/quick-starts/angular', + astro: 'https://appwrite.io/docs/quick-starts/astro', + nuxt: 'https://appwrite.io/docs/quick-starts/nuxt', + qwik: 'https://appwrite.io/docs/quick-starts/qwik', + solid: 'https://appwrite.io/docs/quick-starts/solid', + tanstack: 'https://appwrite.io/docs/quick-starts/tanstack', + nodejs: 'https://appwrite.io/docs/quick-starts/nodejs', + vanilla: 'https://appwrite.io/docs/quick-starts/web', + + // React Native + 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', + + // Server SDKs + python: 'https://appwrite.io/docs/quick-starts/python', + php: 'https://appwrite.io/docs/quick-starts/php', + go: 'https://appwrite.io/docs/quick-starts/go', + ruby: 'https://appwrite.io/docs/quick-starts/ruby', + dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', + dart: 'https://appwrite.io/docs/quick-starts/dart', + flutter: 'https://appwrite.io/docs/quick-starts/flutter', + kotlin: 'https://appwrite.io/docs/quick-starts/kotlin' +}; + +/** + * Framework display names + */ +export const frameworkNames = { + react: 'React', + nextjs: 'Next.js', + vue: 'Vue', + svelte: 'SvelteKit', + angular: 'Angular', + astro: 'Astro', + nuxt: 'Nuxt', + qwik: 'Qwik', + solid: 'Solid', + tanstack: 'TanStack', + nodejs: 'Node.js', + vanilla: 'Web', + 'react-native': 'React Native', + python: 'Python', + php: 'PHP', + go: 'Go', + ruby: 'Ruby', + dotnet: '.NET', + dart: 'Dart', + flutter: 'Flutter', + kotlin: 'Kotlin' +}; + diff --git a/src/lib/languages/dart/index.js b/src/lib/languages/dart/index.js new file mode 100644 index 0000000..8b82657 --- /dev/null +++ b/src/lib/languages/dart/index.js @@ -0,0 +1,26 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { dartInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +/** + * Gets the Flutter SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator for Flutter + * @returns {Promise} + */ +export const flutter = async () => { + const version = await getSDKVersion('client-flutter'); + const installation = dartInstall(version, false); + return createFrameworkTemplate({ installation, securityNotes: '' }); +}; + +/** + * Export vanilla as an alias to flutter for backwards compatibility + * @returns {Promise} + */ +export const vanilla = flutter; + +/** + * Export server from server.js + */ +export { server } from './server.js'; + diff --git a/src/lib/languages/dart/server.js b/src/lib/languages/dart/server.js new file mode 100644 index 0000000..a00c1f3 --- /dev/null +++ b/src/lib/languages/dart/server.js @@ -0,0 +1,15 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { dartInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +/** + * Gets the Dart Server SDK installation template with the latest version from Appwrite's API + * @returns {Promise} + */ +export const server = async () => { + const version = await getSDKVersion('server-dart'); + const installation = dartInstall(version, true); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); +}; + diff --git a/src/lib/languages/dotnet/index.js b/src/lib/languages/dotnet/index.js new file mode 100644 index 0000000..fac460d --- /dev/null +++ b/src/lib/languages/dotnet/index.js @@ -0,0 +1,3 @@ +export { server } from './server.js'; +export { vanilla } from './vanilla.js'; + diff --git a/src/lib/languages/dotnet/server.js b/src/lib/languages/dotnet/server.js new file mode 100644 index 0000000..d009aca --- /dev/null +++ b/src/lib/languages/dotnet/server.js @@ -0,0 +1,9 @@ +import { dotnetInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurityWithConfig } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: dotnetInstall, + securityNotes: serverSecurityWithConfig('configuration files or environment variables') +}); + diff --git a/src/lib/languages/dotnet/vanilla.js b/src/lib/languages/dotnet/vanilla.js new file mode 100644 index 0000000..4b481e2 --- /dev/null +++ b/src/lib/languages/dotnet/vanilla.js @@ -0,0 +1,8 @@ +import { dotnetInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +export const vanilla = createFrameworkTemplate({ + installation: dotnetInstall, + securityNotes: '' +}); + diff --git a/src/lib/languages/go/index.js b/src/lib/languages/go/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/go/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/go/server.js b/src/lib/languages/go/server.js new file mode 100644 index 0000000..1c6dfb7 --- /dev/null +++ b/src/lib/languages/go/server.js @@ -0,0 +1,9 @@ +import { goInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: goInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/index.js b/src/lib/languages/index.js new file mode 100644 index 0000000..c2eee88 --- /dev/null +++ b/src/lib/languages/index.js @@ -0,0 +1,11 @@ +export * as js from './js/index.js'; +export * as python from './python/index.js'; +export * as php from './php/index.js'; +export * as go from './go/index.js'; +export * as dart from './dart/index.js'; +export * as swift from './swift/index.js'; +export * as kotlin from './kotlin/index.js'; +export * as reactNative from './react-native/index.js'; +export * as ruby from './ruby/index.js'; +export * as dotnet from './dotnet/index.js'; + diff --git a/src/lib/languages/js/angular.js b/src/lib/languages/js/angular.js new file mode 100644 index 0000000..ba24fad --- /dev/null +++ b/src/lib/languages/js/angular.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { frameworkNotes } from '../common/security.js'; + +export const angular = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: frameworkNotes.angular +}); + diff --git a/src/lib/languages/js/astro.js b/src/lib/languages/js/astro.js new file mode 100644 index 0000000..3726df4 --- /dev/null +++ b/src/lib/languages/js/astro.js @@ -0,0 +1,12 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const astro = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: `${clientSecurityWithEnv('.env')} + +- Use separate clients for client-side and server-side operations`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/index.js b/src/lib/languages/js/index.js new file mode 100644 index 0000000..ce81dcd --- /dev/null +++ b/src/lib/languages/js/index.js @@ -0,0 +1,13 @@ +export { react } from './react.js'; +export { nextjs } from './nextjs.js'; +export { svelte } from './svelte.js'; +export { nodejs } from './nodejs.js'; +export { vanilla } from './vanilla.js'; +export { vue } from './vue.js'; +export { angular } from './angular.js'; +export { astro } from './astro.js'; +export { nuxt } from './nuxt.js'; +export { qwik } from './qwik.js'; +export { solid } from './solid.js'; +export { tanstack } from './tanstack.js'; + diff --git a/src/lib/languages/js/nextjs.js b/src/lib/languages/js/nextjs.js new file mode 100644 index 0000000..a418c40 --- /dev/null +++ b/src/lib/languages/js/nextjs.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity, authNote } from '../common/security.js'; + +export const nextjs = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: serverSecurity, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/nodejs.js b/src/lib/languages/js/nodejs.js new file mode 100644 index 0000000..5176ec4 --- /dev/null +++ b/src/lib/languages/js/nodejs.js @@ -0,0 +1,10 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const nodejs = createFrameworkTemplate({ + installation: jsInstall('node-appwrite', 'Install the Appwrite Node.js SDK'), + securityNotes: `${serverSecurity} +- Never log or expose API keys in error messages` +}); + diff --git a/src/lib/languages/js/nuxt.js b/src/lib/languages/js/nuxt.js new file mode 100644 index 0000000..3ad76a1 --- /dev/null +++ b/src/lib/languages/js/nuxt.js @@ -0,0 +1,13 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { authNote } from '../common/security.js'; + +export const nuxt = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: `**Best Practices:** +- Store endpoint and project ID in \`nuxt.config.ts\` +- Never commit API keys to version control +- Use Nuxt plugins for client-side initialization`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/qwik.js b/src/lib/languages/js/qwik.js new file mode 100644 index 0000000..005ec60 --- /dev/null +++ b/src/lib/languages/js/qwik.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity, authNote } from '../common/security.js'; + +export const qwik = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/react.js b/src/lib/languages/js/react.js new file mode 100644 index 0000000..ea92424 --- /dev/null +++ b/src/lib/languages/js/react.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +export const react = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity +}); + diff --git a/src/lib/languages/js/solid.js b/src/lib/languages/js/solid.js new file mode 100644 index 0000000..006df4e --- /dev/null +++ b/src/lib/languages/js/solid.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const solid = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env'), + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/svelte.js b/src/lib/languages/js/svelte.js new file mode 100644 index 0000000..673df65 --- /dev/null +++ b/src/lib/languages/js/svelte.js @@ -0,0 +1,10 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; + +export const svelte = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env'), + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/tanstack.js b/src/lib/languages/js/tanstack.js new file mode 100644 index 0000000..68569d8 --- /dev/null +++ b/src/lib/languages/js/tanstack.js @@ -0,0 +1,17 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { authNote } from '../common/security.js'; + +export const tanstack = createFrameworkTemplate({ + installation: jsInstall('appwrite @tanstack/react-query', 'Install the Appwrite JavaScript SDK and TanStack Query'), + securityNotes: `For TanStack Query integration examples and best practices, refer to the [TanStack Query Documentation](https://tanstack.com/query/latest). + +**Best Practices:** +- Use query keys consistently for cache management +- Invalidate queries after mutations to keep data fresh +- Use \`enabled\` option to conditionally fetch data +- Store endpoint and project ID in environment variables +- Never commit API keys to version control`, + additionalNotes: authNote +}); + diff --git a/src/lib/languages/js/vanilla.js b/src/lib/languages/js/vanilla.js new file mode 100644 index 0000000..9cf3bd1 --- /dev/null +++ b/src/lib/languages/js/vanilla.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +export const vanilla = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurity.replace('- Initialize services once and export as singletons', '').trim() +}); + diff --git a/src/lib/languages/js/vue.js b/src/lib/languages/js/vue.js new file mode 100644 index 0000000..c8d5d8d --- /dev/null +++ b/src/lib/languages/js/vue.js @@ -0,0 +1,9 @@ +import { jsInstallDefault as jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurityWithEnv } from '../common/security.js'; + +export const vue = createFrameworkTemplate({ + installation: jsInstall, + securityNotes: clientSecurityWithEnv('.env') +}); + diff --git a/src/lib/languages/kotlin/index.js b/src/lib/languages/kotlin/index.js new file mode 100644 index 0000000..9b0a111 --- /dev/null +++ b/src/lib/languages/kotlin/index.js @@ -0,0 +1,46 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +/** + * Generates the Kotlin SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Kotlin SDK to your \`build.gradle.kts\`: + +**Recommended: Specify exact version for stability** + +\`\`\`kotlin +dependencies { + implementation("io.appwrite:sdk-for-kotlin:${version}") +} +\`\`\` + +Or for Maven, add to \`pom.xml\`: + +**Recommended: Specify exact version** + +\`\`\`xml + + io.appwrite + sdk-for-kotlin + ${version} + +\`\`\` +`; +} + +/** + * Gets the Kotlin SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('server-kotlin'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); +}; diff --git a/src/lib/languages/php/index.js b/src/lib/languages/php/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/php/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/php/server.js b/src/lib/languages/php/server.js new file mode 100644 index 0000000..182f357 --- /dev/null +++ b/src/lib/languages/php/server.js @@ -0,0 +1,9 @@ +import { phpInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: phpInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/python/flask.js b/src/lib/languages/python/flask.js new file mode 100644 index 0000000..9425800 --- /dev/null +++ b/src/lib/languages/python/flask.js @@ -0,0 +1,8 @@ +import { createSecuritySection } from '../common/utils.js'; + +export const flask = createSecuritySection({ + securityNotes: `**Best Practices:** +- Use environment variables for configuration +- Never commit API keys to version control` +}); + diff --git a/src/lib/languages/python/index.js b/src/lib/languages/python/index.js new file mode 100644 index 0000000..3ab6ffe --- /dev/null +++ b/src/lib/languages/python/index.js @@ -0,0 +1,3 @@ +export { flask } from './flask.js'; +export { server } from './server.js'; + diff --git a/src/lib/languages/python/server.js b/src/lib/languages/python/server.js new file mode 100644 index 0000000..0ceba5b --- /dev/null +++ b/src/lib/languages/python/server.js @@ -0,0 +1,9 @@ +import { pythonInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: pythonInstall, + securityNotes: serverSecurity +}); + diff --git a/src/lib/languages/react-native/index.js b/src/lib/languages/react-native/index.js new file mode 100644 index 0000000..57da198 --- /dev/null +++ b/src/lib/languages/react-native/index.js @@ -0,0 +1,2 @@ +export { vanilla } from './vanilla.js'; + diff --git a/src/lib/languages/react-native/vanilla.js b/src/lib/languages/react-native/vanilla.js new file mode 100644 index 0000000..f125743 --- /dev/null +++ b/src/lib/languages/react-native/vanilla.js @@ -0,0 +1,11 @@ +import { jsInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +export const vanilla = createFrameworkTemplate({ + installation: jsInstall('react-native-appwrite', 'Install the Appwrite React Native SDK'), + securityNotes: `**Best Practices:** +- Store endpoint and project ID in environment variables or config files +- Never commit API keys to version control +- Initialize services once and export as singletons` +}); + diff --git a/src/lib/languages/ruby/index.js b/src/lib/languages/ruby/index.js new file mode 100644 index 0000000..5a95a78 --- /dev/null +++ b/src/lib/languages/ruby/index.js @@ -0,0 +1,2 @@ +export { server } from './server.js'; + diff --git a/src/lib/languages/ruby/server.js b/src/lib/languages/ruby/server.js new file mode 100644 index 0000000..f45ed75 --- /dev/null +++ b/src/lib/languages/ruby/server.js @@ -0,0 +1,9 @@ +import { rubyInstall } from '../common/install.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurityWithConfig } from '../common/security.js'; + +export const server = createFrameworkTemplate({ + installation: rubyInstall, + securityNotes: serverSecurityWithConfig('environment variables or Rails credentials') +}); + diff --git a/src/lib/languages/swift/index.js b/src/lib/languages/swift/index.js new file mode 100644 index 0000000..8bcce6a --- /dev/null +++ b/src/lib/languages/swift/index.js @@ -0,0 +1,36 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; + +/** + * Generates the Swift SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Swift SDK to your \`Package.swift\`: + +\`\`\`swift +dependencies: [ + .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") +] +\`\`\` + +Or add it via Xcode: +1. File → Add Packages... +2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +3. Select version: \`${version}\` or later`; +} + +/** + * Gets the Swift SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-apple'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: '' }); +}; + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js new file mode 100644 index 0000000..2d91620 --- /dev/null +++ b/src/lib/rules-generator.js @@ -0,0 +1,326 @@ +// Rules generator for different Appwrite SDKs and frameworks +import * as codeExamples from './languages/index.js'; +import { generateMCPRecommendation } from './languages/common/mcp.js'; + +/** @typedef {Object} SDKConfig + * @property {string} name + * @property {string[]} frameworks + * @property {string} importSyntax + * @property {string} exportSyntax + * @property {string} asyncSyntax + */ + +/** @typedef {Object} GeneratorConfig + * @property {string} sdk + * @property {string} framework + * @property {string[]} features + */ + +/** @type {Record} */ +export const SDK_OPTIONS = { + javascript: { + name: 'JavaScript/TypeScript', + frameworks: ['nextjs', 'react', 'vue', 'svelte', 'angular', 'astro', 'nuxt', 'qwik', 'solid', 'tanstack', 'nodejs', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'export', + asyncSyntax: 'async/await' + }, + 'react-native': { + name: 'React Native', + frameworks: ['react-native', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'export', + asyncSyntax: 'async/await' + }, + python: { + name: 'Python', + frameworks: ['flask', 'django', 'fastapi', 'server'], + importSyntax: 'from', + exportSyntax: 'def', + asyncSyntax: 'async def' + }, + flutter: { + name: 'Flutter/Dart', + frameworks: ['flutter', 'server'], + importSyntax: 'import', + exportSyntax: 'class', + asyncSyntax: 'Future' + }, + swift: { + name: 'Swift', + frameworks: ['ios', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'async' + }, + kotlin: { + name: 'Kotlin', + frameworks: ['android', 'vanilla'], + importSyntax: 'import', + exportSyntax: 'fun', + asyncSyntax: 'suspend' + }, + php: { + name: 'PHP', + frameworks: ['laravel', 'symfony', 'server'], + importSyntax: 'use', + exportSyntax: 'function', + asyncSyntax: 'async' + }, + go: { + name: 'Go', + frameworks: ['gin', 'fiber', 'server'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'goroutine' + }, + ruby: { + name: 'Ruby', + frameworks: ['rails', 'server'], + importSyntax: 'require', + exportSyntax: 'def', + asyncSyntax: 'async' + }, + dotnet: { + name: '.NET', + frameworks: ['aspnet', 'server', 'vanilla'], + importSyntax: 'using', + exportSyntax: 'public', + asyncSyntax: 'async Task' + } +}; + +/** + * @param {GeneratorConfig} config + * @returns {Promise} + */ +export async function generateRules(config) { + const { sdk, framework, features } = config; + const sdkInfo = SDK_OPTIONS[sdk]; + + const sdkInit = await generateSDKInitialization(sdk, framework); + + // Generate all sections in parallel + const sections = await Promise.all([ + features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), + features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), + features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), + features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), + features.includes('messaging') ? generateMessagingSection(sdk, framework) : Promise.resolve(''), + features.includes('sites') ? generateSitesSection(sdk, framework) : Promise.resolve(''), + features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') + ]); + + let rules = `--- +description: You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. +alwaysApply: false +--- + +# Appwrite Development Rules + +${generateMCPRecommendation()} + +${sdkInit} +${sections.join('\n\n')} +`; + + return rules; +} + + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateSDKInitialization(sdk, framework) { + /** @type {Record Promise)>>} */ + const templates = { + javascript: codeExamples.js, + 'react-native': codeExamples.reactNative, + python: codeExamples.python, + php: codeExamples.php, + go: codeExamples.go, + flutter: codeExamples.dart, + swift: codeExamples.swift, + kotlin: codeExamples.kotlin, + ruby: codeExamples.ruby, + dotnet: codeExamples.dotnet + }; + + const sdkTemplates = templates[sdk]; + if (sdkTemplates && sdkTemplates[framework]) { + const template = sdkTemplates[framework]; + // Check if it's an async function + if (typeof template === 'function') { + return await template(); + } + return template; + } + + // Fallback to vanilla if available + if (sdkTemplates && sdkTemplates.vanilla) { + const template = sdkTemplates.vanilla; + // Check if it's an async function + if (typeof template === 'function') { + return await template(); + } + return template; + } + + // Final fallback + return `## SDK Initialization + +Configure your Appwrite client for ${SDK_OPTIONS[sdk]?.name || sdk}.`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateAuthSection(sdk, framework) { + const { authProductLinks } = await import('./languages/common/products.js'); + return `## Authentication & Teams + +${authProductLinks} + +### Best Practices for Authentication & Teams + +- **Session Security**: Always use HttpOnly cookies for session storage in SSR applications +- **API Keys**: Never expose API keys to client-side code - use environment variables +- **Session Validation**: Always validate sessions on the server before trusting them +- **Team Permissions**: Use team roles for granular access control in multi-tenant applications +- **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs +- **Password Security**: Use strong password requirements and consider implementing MFA +- **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateDatabaseSection(sdk, framework) { + const { databaseProductLinks } = await import('./languages/common/products.js'); + return `## Database Operations + +${databaseProductLinks} + +### Best Practices for Databases + +- **Permissions**: Always set appropriate permissions at table and row levels +- **Query Optimization**: Use indexes for frequently queried fields to improve performance +- **Data Validation**: Validate data before creating or updating rows +- **Transactions**: Use transactions for operations that must succeed or fail together +- **Pagination**: Always implement pagination for large datasets to improve performance +- **Type Safety**: Use type-safe models when available in your SDK for better code quality`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateStorageSection(sdk, framework) { + const { storageProductLinks } = await import('./languages/common/products.js'); + return `## Storage Operations + +${storageProductLinks} + +### Best Practices for Storage + +- **File Size Limits**: Set appropriate file size limits to prevent abuse +- **File Types**: Validate file types before upload to ensure security +- **Permissions**: Set proper permissions on buckets and files to control access +- **Cleanup**: Implement cleanup strategies for unused or temporary files +- **Virus Scanning**: Consider implementing virus scanning for uploaded files`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateFunctionsSection(sdk, framework) { + const { functionsProductLinks } = await import('./languages/common/products.js'); + return `## Functions + +${functionsProductLinks} + +### Best Practices for Functions + +- **Error Handling**: Implement comprehensive error handling in your functions +- **Timeouts**: Be aware of function execution timeouts and optimize accordingly +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Logging**: Implement proper logging for debugging and monitoring +- **Security**: Validate all inputs and never trust user-provided data +- **Resource Limits**: Be mindful of memory and CPU limits for function executions`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateMessagingSection(sdk, framework) { + const { messagingProductLinks } = await import('./languages/common/products.js'); + return `## Messaging + +${messagingProductLinks} + +### Best Practices for Messaging + +- **Provider Selection**: Choose the right messaging provider based on your needs (FCM, APNS, Mailgun, Twilio, etc.) +- **Message Content**: Keep push notifications concise and actionable +- **Scheduling**: Use scheduled messages for better user engagement timing +- **Personalization**: Personalize messages to increase engagement +- **Rate Limiting**: Be mindful of rate limits when sending bulk messages +- **Error Handling**: Implement retry logic for failed message deliveries`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateSitesSection(sdk, framework) { + const { sitesProductLinks } = await import('./languages/common/products.js'); + return `## Sites + +${sitesProductLinks} + +### Best Practices for Sites + +- **Environment Variables**: Use environment variables for configuration, not hardcoded values +- **Custom Domains**: Configure custom domains for production sites for better branding +- **Rendering Strategy**: Choose between static and SSR based on your content needs and SEO requirements +- **Deployment Strategy**: Use Git deployments for automatic builds on commits +- **Rollback Plan**: Keep previous deployments ready for instant rollbacks if needed`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generateRealtimeSection(sdk, framework) { + const { realtimeProductLinks } = await import('./languages/common/products.js'); + return `## Real-time Subscriptions + +${realtimeProductLinks} + +### Best Practices for Real-time Subscriptions + +- **Connection Management**: Always unsubscribe from channels when components unmount or pages are closed to prevent memory leaks +- **Error Handling**: Implement reconnection logic for dropped connections and handle network errors gracefully +- **Event Filtering**: Filter events on the client side to only process relevant updates for better performance +- **Channel Selection**: Subscribe only to the specific channels you need to minimize bandwidth and improve performance +- **Payload Validation**: Always validate payload data before processing to ensure data integrity +- **Rate Limiting**: Be mindful of the number of subscriptions and events to avoid overwhelming the client +- **State Synchronization**: Use real-time updates to keep local state in sync with server state, but handle conflicts appropriately +- **Authentication**: Ensure proper authentication is in place before subscribing to protected channels +- **Testing**: Test real-time functionality with network interruptions and reconnection scenarios +- **Cleanup**: Store unsubscribe functions and call them in cleanup hooks (useEffect cleanup, componentWillUnmount, etc.)`; +} + diff --git a/src/lib/utils/versions.js b/src/lib/utils/versions.js new file mode 100644 index 0000000..9771863 --- /dev/null +++ b/src/lib/utils/versions.js @@ -0,0 +1,61 @@ +/** + * Fetches the latest SDK versions from Appwrite's versions API + * @returns {Promise>} + */ +export async function fetchSDKVersions() { + try { + const response = await fetch('https://cloud.appwrite.io/versions'); + if (!response.ok) { + throw new Error(`Failed to fetch versions: ${response.statusText}`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching SDK versions:', error); + // Return fallback versions if API is unavailable + return { + 'server-kotlin': '13.0.0', + 'client-flutter': '20.3.2', + 'client-apple': '13.4.0', + 'client-android': '11.3.0', + 'client-react-native': '0.18.0', + 'server-nodejs': '26.0.0', + 'server-php': '20.0.0', + 'server-python': '15.0.0', + 'server-ruby': '19.4.0', + 'server-go': 'v0.15.0', + 'server-dotnet': '0.23.0', + 'server-dart': '20.0.1', + 'server-swift': '14.0.0', + 'client-web': '21.4.0' + }; + } +} + +/** + * Gets the version for a specific SDK + * @param {string} sdkKey - The SDK key (e.g., 'server-kotlin', 'client-flutter') + * @returns {Promise} + */ +export async function getSDKVersion(sdkKey) { + const versions = await fetchSDKVersions(); + return versions[sdkKey] || 'latest'; +} + +/** + * Version cache to avoid multiple API calls + * @type {Record | null} + */ +let versionCache = null; + +/** + * Gets cached or fetches SDK versions + * @returns {Promise>} + */ +export async function getCachedVersions() { + if (versionCache) { + return versionCache; + } + versionCache = await fetchSDKVersions(); + return versionCache; +} + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..5cdf9e2 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,13 @@ + + + + + + + +{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..5ade608 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,453 @@ + + + + Appwrite Rules Generator + + +
+ + +
+ {#if previewVisible && generatedRules} +
+
+

Generated Rules

+
+ + +
+
+
+
{generatedRules}
+
+
+ {:else} +
+
+

Select your options and click "Generate Rules" to see the output here.

+
+
+ {/if} +
+
+ + + + diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..1295460 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); From 7df4cecdc96c3ea9448956f0556927203ef3eb00 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:01:12 +0530 Subject: [PATCH 02/16] remove initial draft rules file --- .cursor/rules/APPWRITE.mdc | 1173 ------------------------------------ 1 file changed, 1173 deletions(-) delete mode 100644 .cursor/rules/APPWRITE.mdc diff --git a/.cursor/rules/APPWRITE.mdc b/.cursor/rules/APPWRITE.mdc deleted file mode 100644 index acdc8d2..0000000 --- a/.cursor/rules/APPWRITE.mdc +++ /dev/null @@ -1,1173 +0,0 @@ ---- -description: You are an expert developer focused on building apps with Appwrite's APIs and SDKs. -alwaysApply: false ---- - -# Appwrite Development Rules - -## SDK Initialization - -### Client-Side (Web/React/Next.js) - -Always initialize the Appwrite client with proper configuration: - -```javascript -import { Client, Account, TablesDB, Storage } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') // Your Appwrite endpoint - .setProject('your-project-id'); // Your project ID - -// Initialize services -export const account = new Account(client); -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -**Best Practices:** -- Store endpoint and project ID in environment variables (`.env.local`) -- Never commit API keys or secrets to version control -- Use separate clients for different environments (dev/staging/prod) -- Initialize services once and export them as singletons - -### Server-Side (Node.js/Next.js API Routes) - -Use server SDK with API key for admin operations: - -```javascript -import { Client, TablesDB, Storage, ID } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') - .setProject(process.env.APPWRITE_PROJECT_ID) - .setKey(process.env.APPWRITE_API_KEY); // Server-side only - -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -**Security:** -- API keys should NEVER be exposed to client-side code -- Use environment variables for all sensitive configuration -- API keys grant admin access - use with extreme caution - -## Authentication Patterns - -### Session Management - -Always check authentication state before making authenticated requests: - -```javascript -import { account } from './appwrite-config'; - -// Check current session -async function getCurrentUser() { - try { - return await account.get(); - } catch (error) { - // User not authenticated - return null; - } -} - -// Create session -async function login(email, password) { - try { - await account.createEmailPasswordSession(email, password); - return await account.get(); - } catch (error) { - throw new Error(`Login failed: ${error.message}`); - } -} - -// Delete session -async function logout() { - try { - await account.deleteSession('current'); - } catch (error) { - console.error('Logout error:', error); - } -} -``` - -### OAuth Providers - -When implementing OAuth, handle redirects properly: - -```javascript -// Initiate OAuth -async function loginWithOAuth(provider) { - account.createOAuth2Session( - provider, // 'google', 'github', etc. - 'https://yourapp.com/success', // Success redirect - 'https://yourapp.com/failure' // Failure redirect - ); -} - -// Handle OAuth callback (check URL params) -if (window.location.search.includes('success')) { - const user = await account.get(); - // Redirect to dashboard -} -``` - -### SSR Authentication (Server-Side Rendering) - -For SSR frameworks like Next.js, Remix, or SvelteKit, handle authentication on the server using cookies. - -#### Server-Side Client Setup - -Create a server-side client that reads cookies from requests: - -```javascript -// lib/appwrite-server.js -import { Client, Account, TablesDB } from 'appwrite'; -import { cookies } from 'next/headers'; // Next.js App Router -// or: import { parseCookies } from 'nookies'; // Next.js Pages Router - -export function createServerClient() { - const client = new Client() - .setEndpoint(process.env.APPWRITE_ENDPOINT) - .setProject(process.env.APPWRITE_PROJECT_ID); - - return client; -} - -export async function getServerAccount() { - const client = createServerClient(); - const account = new Account(client); - - // Get session cookies from request - const cookieStore = await cookies(); // Next.js App Router - const sessionCookie = cookieStore.get('a_session_'); - - if (sessionCookie) { - // Set session cookie on client - client.setSession(sessionCookie.value); - } - - return account; -} - -// Alternative for Pages Router -export function getServerAccountPages(req) { - const client = createServerClient(); - const account = new Account(client); - - const cookieStore = parseCookies({ req }); - const sessionCookie = cookieStore['a_session_']; - - if (sessionCookie) { - client.setSession(sessionCookie); - } - - return account; -} -``` - -#### Next.js App Router - Server Components - -Authenticate in Server Components and pass user data to client: - -```javascript -// app/dashboard/page.js (Server Component) -import { getServerAccount } from '@/lib/appwrite-server'; -import { redirect } from 'next/navigation'; -import DashboardClient from './dashboard-client'; - -export default async function DashboardPage() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - - // User is authenticated, render dashboard - return ; - } catch (error) { - // User not authenticated, redirect to login - redirect('/login'); - } -} -``` - -#### Next.js App Router - Server Actions - -Handle authentication in Server Actions: - -```javascript -// app/actions/auth.js -'use server'; - -import { getServerAccount } from '@/lib/appwrite-server'; -import { cookies } from 'next/headers'; -import { redirect } from 'next/navigation'; - -export async function loginAction(email, password) { - const account = await getServerAccount(); - - try { - const session = await account.createEmailPasswordSession(email, password); - - // Set session cookie - const cookieStore = await cookies(); - cookieStore.set('a_session_', session.secret, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - }); - - redirect('/dashboard'); - } catch (error) { - return { error: error.message }; - } -} - -export async function logoutAction() { - const account = await getServerAccount(); - - try { - await account.deleteSession('current'); - - // Clear session cookie - const cookieStore = await cookies(); - cookieStore.delete('a_session_'); - - redirect('/login'); - } catch (error) { - return { error: error.message }; - } -} -``` - -#### Next.js Pages Router - getServerSideProps - -Authenticate in `getServerSideProps`: - -```javascript -// pages/dashboard.js -import { getServerAccountPages } from '@/lib/appwrite-server'; - -export async function getServerSideProps({ req, res }) { - const account = getServerAccountPages(req); - - try { - const user = await account.get(); - - return { - props: { - user: { - $id: user.$id, - email: user.email, - name: user.name, - }, - }, - }; - } catch (error) { - // Redirect to login if not authenticated - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } -} - -export default function Dashboard({ user }) { - return ( -
-

Welcome, {user.name}

-

Email: {user.email}

-
- ); -} -``` - -#### Next.js Middleware - Route Protection - -Protect routes using Next.js middleware: - -```javascript -// middleware.js (Next.js 12+) -import { NextResponse } from 'next/server'; -import { getServerAccount } from '@/lib/appwrite-server'; -import { cookies } from 'next/headers'; - -export async function middleware(request) { - const protectedPaths = ['/dashboard', '/profile', '/settings']; - const isProtectedPath = protectedPaths.some(path => - request.nextUrl.pathname.startsWith(path) - ); - - if (!isProtectedPath) { - return NextResponse.next(); - } - - try { - const account = await getServerAccount(); - await account.get(); - - // User is authenticated, allow request - return NextResponse.next(); - } catch (error) { - // User not authenticated, redirect to login - const loginUrl = new URL('/login', request.url); - loginUrl.searchParams.set('redirect', request.nextUrl.pathname); - return NextResponse.redirect(loginUrl); - } -} - -export const config = { - matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'], -}; -``` - -#### Server-Side API Routes with Authentication - -Authenticate in API routes: - -```javascript -// app/api/protected/route.js (App Router) -import { getServerAccount } from '@/lib/appwrite-server'; -import { NextResponse } from 'next/server'; - -export async function GET(request) { - try { - const account = await getServerAccount(); - const user = await account.get(); - - // User is authenticated, proceed with request - const data = await fetchProtectedData(user.$id); - - return NextResponse.json({ data }); - } catch (error) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } -} - -// pages/api/protected.js (Pages Router) -import { getServerAccountPages } from '@/lib/appwrite-server'; - -export default async function handler(req, res) { - try { - const account = getServerAccountPages(req); - const user = await account.get(); - - // User is authenticated, proceed with request - const data = await fetchProtectedData(user.$id); - - res.json({ data }); - } catch (error) { - res.status(401).json({ error: 'Unauthorized' }); - } -} -``` - -#### Cookie Management Best Practices - -1. **HttpOnly cookies** - Always set `httpOnly: true` to prevent XSS attacks -2. **Secure flag** - Use `secure: true` in production (HTTPS only) -3. **SameSite** - Use `sameSite: 'lax'` or `'strict'` for CSRF protection -4. **Cookie name** - Appwrite uses `a_session_` format -5. **Session validation** - Always validate session on server before trusting it - -#### SSR Authentication Helper - -Create a reusable authentication helper: - -```javascript -// lib/auth-server.js -import { getServerAccount } from './appwrite-server'; -import { redirect } from 'next/navigation'; - -export async function requireAuth() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - return { user, account }; - } catch (error) { - redirect('/login'); - } -} - -export async function optionalAuth() { - const account = await getServerAccount(); - - try { - const user = await account.get(); - return { user, account, isAuthenticated: true }; - } catch (error) { - return { user: null, account: null, isAuthenticated: false }; - } -} - -// Usage in Server Components -export default async function ProtectedPage() { - const { user, account } = await requireAuth(); - - // User is guaranteed to be authenticated here - return
Welcome, {user.name}
; -} -``` - -## Teams Management - -Appwrite Teams enable collaborative features and team-based permissions. Always initialize the Teams service alongside other services. - -### SDK Initialization - -Include Teams in your client setup: - -```javascript -import { Client, Account, Teams, TablesDB, Storage } from 'appwrite'; - -const client = new Client() - .setEndpoint('https://cloud.appwrite.io/v1') - .setProject('your-project-id'); - -export const account = new Account(client); -export const teams = new Teams(client); -export const tablesDB = new TablesDB(client); -export const storage = new Storage(client); -``` - -### Creating Teams - -Create teams with proper error handling: - -```javascript -import { ID } from 'appwrite'; - -// Create a new team -async function createTeam(name) { - try { - return await teams.create(ID.unique(), name); - } catch (error) { - throw new Error(`Failed to create team: ${error.message}`); - } -} - -// Create team with custom ID -async function createTeamWithId(teamId, name) { - try { - return await teams.create(teamId, name); - } catch (error) { - if (error.code === 409) { - throw new Error('Team ID already exists'); - } - throw new Error(`Failed to create team: ${error.message}`); - } -} -``` - -### Team Membership Operations - -Manage team members with proper role assignment: - -```javascript -// Add member to team -async function addTeamMember(teamId, email, roles = ['member']) { - try { - // First, get user ID by email (requires server-side with API key) - // Or pass userId directly if you have it - return await teams.createMembership(teamId, email, roles); - } catch (error) { - if (error.code === 409) { - throw new Error('User is already a member of this team'); - } - throw new Error(`Failed to add member: ${error.message}`); - } -} - -// Add member by user ID (server-side only) -async function addTeamMemberById(teamId, userId, roles = ['member']) { - try { - return await teams.createMembership(teamId, undefined, roles, undefined, userId); - } catch (error) { - throw new Error(`Failed to add member: ${error.message}`); - } -} - -// Update member roles -async function updateMemberRoles(teamId, membershipId, roles) { - try { - return await teams.updateMembershipRoles(teamId, membershipId, roles); - } catch (error) { - throw new Error(`Failed to update roles: ${error.message}`); - } -} - -// Remove member from team -async function removeTeamMember(teamId, membershipId) { - try { - await teams.deleteMembership(teamId, membershipId); - } catch (error) { - throw new Error(`Failed to remove member: ${error.message}`); - } -} -``` - -### Listing Teams and Members - -Query teams and members efficiently: - -```javascript -// List user's teams -async function getUserTeams() { - try { - const response = await teams.list(); - return response.teams; - } catch (error) { - throw new Error(`Failed to list teams: ${error.message}`); - } -} - -// Get team details -async function getTeam(teamId) { - try { - return await teams.get(teamId); - } catch (error) { - if (error.code === 404) { - return null; - } - throw new Error(`Failed to get team: ${error.message}`); - } -} - -// List team members -async function getTeamMembers(teamId) { - try { - const response = await teams.listMemberships(teamId); - return response.memberships; - } catch (error) { - throw new Error(`Failed to list members: ${error.message}`); - } -} - -// Get specific membership -async function getMembership(teamId, membershipId) { - try { - return await teams.getMembership(teamId, membershipId); - } catch (error) { - if (error.code === 404) { - return null; - } - throw new Error(`Failed to get membership: ${error.message}`); - } -} -``` - -### Team Roles and Permissions - -Use team roles in database and storage permissions: - -```javascript -import { Permission, Role } from 'appwrite'; - -// Team-based permissions for rows -async function createTeamRow(teamId, data) { - try { - return await tablesDB.createRow( - 'database-id', - 'table-id', - ID.unique(), - data, - [ - // All team members can read - Permission.read(Role.team(teamId)), - // Only team admins can write - Permission.write(Role.team(teamId, 'admin')), - // Only team owners can delete - Permission.delete(Role.team(teamId, 'owner')) - ] - ); - } catch (error) { - throw new Error(`Failed to create row: ${error.message}`); - } -} - -// Team-based permissions for files -async function uploadTeamFile(teamId, file, bucketId) { - try { - return await storage.createFile( - bucketId, - ID.unique(), - file, - [ - Permission.read(Role.team(teamId)), - Permission.write(Role.team(teamId, 'admin')), - Permission.delete(Role.team(teamId, 'owner')) - ] - ); - } catch (error) { - throw new Error(`Failed to upload file: ${error.message}`); - } -} - -// Check if user has specific role in team -async function hasTeamRole(teamId, role) { - try { - const membership = await teams.getMembership(teamId, 'me'); // Get current user's membership - return membership.roles.includes(role); - } catch (error) { - return false; - } -} -``` - -### Team Management Operations - -Update and manage team settings: - -```javascript -// Update team name -async function updateTeamName(teamId, name) { - try { - return await teams.updateName(teamId, name); - } catch (error) { - throw new Error(`Failed to update team name: ${error.message}`); - } -} - -// Delete team -async function deleteTeam(teamId) { - try { - await teams.delete(teamId); - } catch (error) { - throw new Error(`Failed to delete team: ${error.message}`); - } -} - -// Accept team invitation -async function acceptTeamInvitation(teamId, membershipId, userId, secret) { - try { - return await teams.updateMembershipStatus( - teamId, - membershipId, - userId, - secret - ); - } catch (error) { - throw new Error(`Failed to accept invitation: ${error.message}`); - } -} -``` - -### React Hook for Teams - -Create a reusable hook for team management: - -```javascript -import { useState, useEffect } from 'react'; -import { ID } from 'appwrite'; -import { teams } from './appwrite-config'; - -function useTeams() { - const [teamsList, setTeamsList] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - async function fetchTeams() { - try { - const response = await teams.list(); - setTeamsList(response.teams); - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - } - - fetchTeams(); - }, []); - - const createTeam = async (name) => { - try { - const newTeam = await teams.create(ID.unique(), name); - setTeamsList([...teamsList, newTeam]); - return newTeam; - } catch (err) { - setError(err.message); - throw err; - } - }; - - return { teams: teamsList, loading, error, createTeam }; -} -``` - -### Server-Side Team Operations - -Handle team operations on the server with API key: - -```javascript -// lib/appwrite-server.js -import { Client, Teams } from 'appwrite'; - -const serverClient = new Client() - .setEndpoint(process.env.APPWRITE_ENDPOINT) - .setProject(process.env.APPWRITE_PROJECT_ID) - .setKey(process.env.APPWRITE_API_KEY); - -export const serverTeams = new Teams(serverClient); - -// Add user to team (server-side) -export async function addUserToTeam(teamId, userId, roles = ['member']) { - try { - return await serverTeams.createMembership( - teamId, - undefined, // email not needed when userId provided - roles, - undefined, // URL not needed for direct add - userId - ); - } catch (error) { - throw new Error(`Failed to add user to team: ${error.message}`); - } -} - -// List all teams (admin operation) -export async function listAllTeams() { - try { - const response = await serverTeams.list(); - return response.teams; - } catch (error) { - throw new Error(`Failed to list teams: ${error.message}`); - } -} -``` - -### Team-Based Access Control - -Implement team-based access control patterns: - -```javascript -import { Query } from 'appwrite'; - -// Check if user can access team resource -async function canAccessTeamResource(teamId, requiredRole = 'member') { - try { - const team = await teams.get(teamId); - const memberships = await teams.listMemberships(teamId); - - // Get current user - const user = await account.get(); - - // Find user's membership - const membership = memberships.find(m => m.userId === user.$id); - - if (!membership) { - return false; - } - - // Check role hierarchy: owner > admin > member - const roleHierarchy = { owner: 3, admin: 2, member: 1 }; - const requiredLevel = roleHierarchy[requiredRole] || 1; - const userLevel = Math.max(...membership.roles.map(r => roleHierarchy[r] || 0)); - - return userLevel >= requiredLevel; - } catch (error) { - return false; - } -} - -// Filter rows by team membership -async function getTeamRows(teamId) { - try { - // First verify user is team member - const canAccess = await canAccessTeamResource(teamId); - if (!canAccess) { - throw new Error('Access denied'); - } - - // Query rows with team permission - return await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.equal('teamId', teamId)] - ); - } catch (error) { - throw new Error(`Failed to get team rows: ${error.message}`); - } -} -``` - -### Team Best Practices - -1. **Role Management** - Use consistent role names: 'owner', 'admin', 'member' -2. **Invitations** - Always handle invitation acceptance flow properly -3. **Permissions** - Combine team roles with specific permissions (e.g., `Role.team(teamId, 'admin')`) -4. **Validation** - Always verify team membership before granting access -5. **Error Handling** - Handle 404 (team not found) and 403 (access denied) appropriately -6. **Team Limits** - Be aware of team size limits and implement pagination for large teams -7. **Cleanup** - Remove team memberships when users are deleted - -## Database Operations - -### Query Patterns - -Use Appwrite's query builder for efficient queries: - -```javascript -import { Query } from 'appwrite'; - -// Single query -const rows = await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.equal('status', 'active')] -); - -// Multiple conditions -const filtered = await tablesDB.listRows( - 'database-id', - 'table-id', - [ - Query.equal('status', 'active'), - Query.greaterThan('createdAt', '2024-01-01'), - Query.limit(10), - Query.orderDesc('createdAt') - ] -); - -// Full-text search -const searchResults = await tablesDB.listRows( - 'database-id', - 'table-id', - [Query.search('title', 'search term')] -); -``` - -### CRUD Operations - -Follow consistent patterns for create, read, update, delete: - -```javascript -import { ID } from 'appwrite'; - -// Create row -async function createRow(data) { - try { - return await tablesDB.createRow( - 'database-id', - 'table-id', - ID.unique(), - data, - [ - Permission.read(Role.user(userId)), // User can read - Permission.write(Role.user(userId)) // User can write - ] - ); - } catch (error) { - throw new Error(`Failed to create: ${error.message}`); - } -} - -// Read row -async function getRow(rowId) { - try { - return await tablesDB.getRow( - 'database-id', - 'table-id', - rowId - ); - } catch (error) { - if (error.code === 404) { - return null; - } - throw error; - } -} - -// Update row -async function updateRow(rowId, data) { - try { - return await tablesDB.updateRow( - 'database-id', - 'table-id', - rowId, - data - ); - } catch (error) { - throw new Error(`Failed to update: ${error.message}`); - } -} - -// Delete row -async function deleteRow(rowId) { - try { - await tablesDB.deleteRow( - 'database-id', - 'table-id', - rowId - ); - } catch (error) { - throw new Error(`Failed to delete: ${error.message}`); - } -} -``` - -### Permissions - -Always set appropriate permissions when creating/updating rows: - -```javascript -import { Permission, Role } from 'appwrite'; - -// User-specific row -const permissions = [ - Permission.read(Role.user(userId)), - Permission.write(Role.user(userId)), - Permission.delete(Role.user(userId)) -]; - -// Public read, authenticated write -const publicPermissions = [ - Permission.read(Role.any()), - Permission.write(Role.users()), -]; - -// Team-based permissions -const teamPermissions = [ - Permission.read(Role.team(teamId)), - Permission.write(Role.team(teamId, 'admin')) -]; -``` - -## Storage Operations - -### File Upload - -Handle file uploads with progress tracking: - -```javascript -import { ID } from 'appwrite'; - -async function uploadFile(file, bucketId) { - try { - return await storage.createFile( - bucketId, - ID.unique(), - file, - [ - Permission.read(Role.any()), // Adjust based on needs - Permission.write(Role.users()) - ], - (progress) => { - console.log(`Upload progress: ${progress.progress}%`); - } - ); - } catch (error) { - throw new Error(`Upload failed: ${error.message}`); - } -} - -// Get file preview/URL -function getFileUrl(bucketId, fileId) { - return storage.getFilePreview(bucketId, fileId); -} - -// Get file download -function getFileDownload(bucketId, fileId) { - return storage.getFileDownload(bucketId, fileId); -} -``` - -### File Management - -```javascript -// List files -async function listFiles(bucketId, queries = []) { - return await storage.listFiles(bucketId, queries); -} - -// Delete file -async function deleteFile(bucketId, fileId) { - await storage.deleteFile(bucketId, fileId); -} - -// Update file -async function updateFile(bucketId, fileId, name) { - return await storage.updateFile(bucketId, fileId, name); -} -``` - -## Error Handling - -Always implement proper error handling: - -```javascript -async function safeAppwriteCall(operation) { - try { - return await operation(); - } catch (error) { - // Handle specific error codes - switch (error.code) { - case 401: - // Unauthorized - redirect to login - window.location.href = '/login'; - break; - case 404: - // Not found - return null; - case 409: - // Conflict (e.g., duplicate email) - throw new Error('Resource already exists'); - case 429: - // Rate limited - throw new Error('Too many requests. Please try again later.'); - default: - console.error('Appwrite error:', error); - throw new Error(`Operation failed: ${error.message}`); - } - } -} - -// Usage -const user = await safeAppwriteCall(() => account.get()); -``` - -## Real-time Subscriptions - -Use real-time listeners for live updates: - -```javascript -import { RealtimeResponseEvent } from 'appwrite'; - -function subscribeToTable(databaseId, tableId, callback) { - return client.subscribe( - `databases.${databaseId}.tables.${tableId}.rows`, - (response) => { - if (response.events.includes('databases.*.tables.*.rows.*.create')) { - callback('create', response.payload); - } else if (response.events.includes('databases.*.tables.*.rows.*.update')) { - callback('update', response.payload); - } else if (response.events.includes('databases.*.tables.*.rows.*.delete')) { - callback('delete', response.payload); - } - } - ); -} - -// Usage -const unsubscribe = subscribeToTable( - 'database-id', - 'table-id', - (event, payload) => { - console.log(`${event} event:`, payload); - } -); - -// Cleanup -// unsubscribe(); -``` - -## Server-Side Functions - -When using Appwrite Functions, follow these patterns: - -```javascript -// Function handler example (Node.js) -export default async ({ req, res, log, error }) => { - try { - const { databaseId, tableId } = JSON.parse(req.body); - - // Use server SDK - const tablesDB = new TablesDB(client); - const rows = await tablesDB.listRows(databaseId, tableId); - - return res.json({ success: true, data: rows }); - } catch (err) { - error(err.message); - return res.json({ success: false, error: err.message }, 500); - } -}; -``` - -## Security Best Practices - -1. **Never expose API keys** - Use environment variables -2. **Validate permissions** - Always set appropriate row/file permissions -3. **Sanitize inputs** - Validate and sanitize user inputs before database operations -4. **Use HTTPS** - Always use HTTPS endpoints in production -5. **Rate limiting** - Implement client-side rate limiting for user-facing operations -6. **Session management** - Implement proper session expiration and refresh logic - -## Performance Optimization - -1. **Pagination** - Always use `Query.limit()` and `Query.offset()` for large datasets -2. **Indexing** - Create database indexes for frequently queried fields -3. **Caching** - Cache frequently accessed data on the client side -4. **Batch operations** - Use batch requests when possible -5. **Selective fields** - Use `Query.select()` to fetch only needed fields - -## Common Patterns - -### React Hook Example - -```javascript -import { useState, useEffect } from 'react'; -import { account } from './appwrite-config'; - -function useAuth() { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - account.get() - .then(setUser) - .catch(() => setUser(null)) - .finally(() => setLoading(false)); - }, []); - - return { user, loading }; -} -``` - -### Next.js API Route Example - -```javascript -// pages/api/data.js or app/api/data/route.js -import { tablesDB } from '@/lib/appwrite-server'; - -export default async function handler(req, res) { - if (req.method !== 'GET') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - try { - const rows = await tablesDB.listRows( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_TABLE_ID - ); - res.json(rows); - } catch (error) { - res.status(500).json({ error: error.message }); - } -} -``` - -## Testing Patterns - -When writing tests, mock Appwrite SDK: - -```javascript -// Mock Appwrite in tests -jest.mock('appwrite', () => ({ - Client: jest.fn(), - Account: jest.fn(() => ({ - get: jest.fn(), - createEmailPasswordSession: jest.fn(), - })), - TablesDB: jest.fn(() => ({ - listRows: jest.fn(), - createRow: jest.fn(), - })), -})); -``` From 4181a43631fd380f912b76033a21d1ac3f07a148 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:01:38 +0530 Subject: [PATCH 03/16] Add multitenancy guideline to rules generator --- src/lib/rules-generator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 2d91620..fe3f8d9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -191,6 +191,7 @@ ${authProductLinks} - **API Keys**: Never expose API keys to client-side code - use environment variables - **Session Validation**: Always validate sessions on the server before trusting them - **Team Permissions**: Use team roles for granular access control in multi-tenant applications +- **Multitenancy**: Use team-based permissions when a user requires multitenancy to properly isolate data and resources between tenants - **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs - **Password Security**: Use strong password requirements and consider implementing MFA - **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; From ad127804f438ad45056abf41e1cb1b81bb1f3d42 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:18:51 +0530 Subject: [PATCH 04/16] Add Apple and Android SDK support to rules generator; update installation templates and quick start URLs --- src/lib/languages/android/index.js | 47 ++++++++++++++++++++++++++++++ src/lib/languages/apple/index.js | 37 +++++++++++++++++++++++ src/lib/languages/common/utils.js | 12 ++++++-- src/lib/languages/index.js | 2 ++ src/lib/languages/swift/index.js | 11 +++---- src/lib/rules-generator.js | 20 +++++++++++-- 6 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 src/lib/languages/android/index.js create mode 100644 src/lib/languages/apple/index.js diff --git a/src/lib/languages/android/index.js b/src/lib/languages/android/index.js new file mode 100644 index 0000000..bee6839 --- /dev/null +++ b/src/lib/languages/android/index.js @@ -0,0 +1,47 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +/** + * Generates the Android SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Android SDK to your \`build.gradle.kts\`: + +**Recommended: Specify exact version for stability** + +\`\`\`kotlin +dependencies { + implementation("io.appwrite:sdk-for-android:${version}") +} +\`\`\` + +Or for Maven, add to \`pom.xml\`: + +**Recommended: Specify exact version** + +\`\`\`xml + + io.appwrite + sdk-for-android + ${version} + +\`\`\` +`; +} + +/** + * Gets the Android SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-android'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); +}; + diff --git a/src/lib/languages/apple/index.js b/src/lib/languages/apple/index.js new file mode 100644 index 0000000..d477e88 --- /dev/null +++ b/src/lib/languages/apple/index.js @@ -0,0 +1,37 @@ +import { getSDKVersion } from '$lib/utils/versions.js'; +import { createFrameworkTemplate } from '../common/utils.js'; +import { clientSecurity } from '../common/security.js'; + +/** + * Generates the Apple SDK installation template with the latest version + * @param {string} version - The SDK version to use + * @returns {string} + */ +function generateInstallationTemplate(version) { + return `## SDK Installation + +Add the Appwrite Apple SDK to your \`Package.swift\`: + +\`\`\`swift +dependencies: [ + .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") +] +\`\`\` + +Or add it via Xcode: +1. File → Add Packages... +2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +3. Select version: \`${version}\` or later`; +} + +/** + * Gets the Apple SDK installation template with the latest version from Appwrite's API + * This is the main export used by the rules generator + * @returns {Promise} + */ +export const vanilla = async () => { + const version = await getSDKVersion('client-apple'); + const installation = generateInstallationTemplate(version); + return createFrameworkTemplate({ installation, securityNotes: clientSecurity }); +}; + diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js index 058d9af..fcf1f16 100644 --- a/src/lib/languages/common/utils.js +++ b/src/lib/languages/common/utils.js @@ -52,6 +52,10 @@ export const quickStartUrls = { // React Native 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', + // Mobile Client SDKs + apple: 'https://appwrite.io/docs/quick-starts/apple', + android: 'https://appwrite.io/docs/quick-starts/android', + // Server SDKs python: 'https://appwrite.io/docs/quick-starts/python', php: 'https://appwrite.io/docs/quick-starts/php', @@ -60,7 +64,8 @@ export const quickStartUrls = { dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', dart: 'https://appwrite.io/docs/quick-starts/dart', flutter: 'https://appwrite.io/docs/quick-starts/flutter', - kotlin: 'https://appwrite.io/docs/quick-starts/kotlin' + kotlin: 'https://appwrite.io/docs/quick-starts/kotlin', + swift: 'https://appwrite.io/docs/quick-starts/swift' }; /** @@ -80,6 +85,8 @@ export const frameworkNames = { nodejs: 'Node.js', vanilla: 'Web', 'react-native': 'React Native', + apple: 'Apple', + android: 'Android', python: 'Python', php: 'PHP', go: 'Go', @@ -87,6 +94,7 @@ export const frameworkNames = { dotnet: '.NET', dart: 'Dart', flutter: 'Flutter', - kotlin: 'Kotlin' + kotlin: 'Kotlin', + swift: 'Swift' }; diff --git a/src/lib/languages/index.js b/src/lib/languages/index.js index c2eee88..060ee08 100644 --- a/src/lib/languages/index.js +++ b/src/lib/languages/index.js @@ -8,4 +8,6 @@ export * as kotlin from './kotlin/index.js'; export * as reactNative from './react-native/index.js'; export * as ruby from './ruby/index.js'; export * as dotnet from './dotnet/index.js'; +export * as apple from './apple/index.js'; +export * as android from './android/index.js'; diff --git a/src/lib/languages/swift/index.js b/src/lib/languages/swift/index.js index 8bcce6a..4e33efc 100644 --- a/src/lib/languages/swift/index.js +++ b/src/lib/languages/swift/index.js @@ -1,5 +1,6 @@ import { getSDKVersion } from '$lib/utils/versions.js'; import { createFrameworkTemplate } from '../common/utils.js'; +import { serverSecurity } from '../common/security.js'; /** * Generates the Swift SDK installation template with the latest version @@ -9,17 +10,17 @@ import { createFrameworkTemplate } from '../common/utils.js'; function generateInstallationTemplate(version) { return `## SDK Installation -Add the Appwrite Swift SDK to your \`Package.swift\`: +Add the Appwrite Swift Server SDK to your \`Package.swift\`: \`\`\`swift dependencies: [ - .package(url: "https://github.com/appwrite/sdk-for-apple", from: "${version}") + .package(url: "https://github.com/appwrite/sdk-for-swift", from: "${version}") ] \`\`\` Or add it via Xcode: 1. File → Add Packages... -2. Enter: \`https://github.com/appwrite/sdk-for-apple\` +2. Enter: \`https://github.com/appwrite/sdk-for-swift\` 3. Select version: \`${version}\` or later`; } @@ -29,8 +30,8 @@ Or add it via Xcode: * @returns {Promise} */ export const vanilla = async () => { - const version = await getSDKVersion('client-apple'); + const version = await getSDKVersion('server-swift'); const installation = generateInstallationTemplate(version); - return createFrameworkTemplate({ installation, securityNotes: '' }); + return createFrameworkTemplate({ installation, securityNotes: serverSecurity }); }; diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index fe3f8d9..122aea0 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -46,16 +46,30 @@ export const SDK_OPTIONS = { exportSyntax: 'class', asyncSyntax: 'Future' }, + apple: { + name: 'Apple', + frameworks: ['vanilla'], + importSyntax: 'import', + exportSyntax: 'func', + asyncSyntax: 'async' + }, + android: { + name: 'Android', + frameworks: ['vanilla'], + importSyntax: 'import', + exportSyntax: 'fun', + asyncSyntax: 'suspend' + }, swift: { name: 'Swift', - frameworks: ['ios', 'vanilla'], + frameworks: ['server', 'vanilla'], importSyntax: 'import', exportSyntax: 'func', asyncSyntax: 'async' }, kotlin: { name: 'Kotlin', - frameworks: ['android', 'vanilla'], + frameworks: ['server', 'vanilla'], importSyntax: 'import', exportSyntax: 'fun', asyncSyntax: 'suspend' @@ -142,6 +156,8 @@ async function generateSDKInitialization(sdk, framework) { php: codeExamples.php, go: codeExamples.go, flutter: codeExamples.dart, + apple: codeExamples.apple, + android: codeExamples.android, swift: codeExamples.swift, kotlin: codeExamples.kotlin, ruby: codeExamples.ruby, From 50a18294191e3b2be0f71617835423a938772b81 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 21:18:58 +0530 Subject: [PATCH 05/16] Update Dart SDK installation instructions and enhance Qwik security notes with server-side guidelines --- src/lib/languages/common/install.js | 3 ++- src/lib/languages/js/qwik.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/languages/common/install.js b/src/lib/languages/common/install.js index e84f9d5..ec1d33d 100644 --- a/src/lib/languages/common/install.js +++ b/src/lib/languages/common/install.js @@ -129,6 +129,7 @@ Install-Package Appwrite export const dartInstall = (version, isServer = false) => { const sdkName = isServer ? 'Dart Server SDK' : 'Flutter SDK'; const pubCommand = isServer ? 'dart pub get' : 'flutter pub get'; + const packageName = isServer ? 'dart_appwrite' : 'appwrite'; return `## SDK Installation @@ -136,7 +137,7 @@ Add the Appwrite ${sdkName} to your \`pubspec.yaml\`: \`\`\`yaml dependencies: - appwrite: ^${version} + ${packageName}: ^${version} \`\`\` Then install it: diff --git a/src/lib/languages/js/qwik.js b/src/lib/languages/js/qwik.js index 005ec60..4da9355 100644 --- a/src/lib/languages/js/qwik.js +++ b/src/lib/languages/js/qwik.js @@ -1,10 +1,17 @@ import { jsInstallDefault as jsInstall } from '../common/install.js'; import { createFrameworkTemplate } from '../common/utils.js'; -import { clientSecurity, authNote } from '../common/security.js'; +import { clientSecurityWithEnv, authNote } from '../common/security.js'; export const qwik = createFrameworkTemplate({ installation: jsInstall, - securityNotes: clientSecurity, + securityNotes: `${clientSecurityWithEnv('.env')} + +**Server-Side Security (Critical for Qwik):** +- API keys should NEVER be exposed to client-side code +- Separate client and server code: protect API keys on server, never expose them to client +- Use route loaders (\`routeLoader$\`) or server endpoints (\`server$\`) for secret operations +- For operations requiring API keys, use server actions or route loaders that run only on the server +- Client-side code should only use public endpoint and project ID`, additionalNotes: authNote }); From 8684766c1c313e6acfc96ea71fb0e2f1422e7325 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 22:39:49 +0530 Subject: [PATCH 06/16] Change "real-time" to "realtime" --- src/lib/languages/common/mcp.js | 2 +- src/lib/languages/common/products.js | 8 ++++---- src/lib/rules-generator.js | 8 ++++---- src/routes/+page.svelte | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/languages/common/mcp.js b/src/lib/languages/common/mcp.js index 51b9c1a..556aa87 100644 --- a/src/lib/languages/common/mcp.js +++ b/src/lib/languages/common/mcp.js @@ -42,7 +42,7 @@ export function generateMCPRecommendation() { ### Benefits Once installed, you can ask questions like: -- "How do I set up real-time subscriptions in Appwrite?" +- "How do I set up realtime subscriptions in Appwrite?" - "Show me how to authenticate users with OAuth" - "What are the best practices for database queries?" - "How do I implement file uploads with Appwrite Storage?" diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index cc620dd..4077324 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -103,9 +103,9 @@ For instant rollbacks, see the [Instant Rollbacks Guide](https://appwrite.io/doc For deployment previews, see the [Previews Documentation](https://appwrite.io/docs/products/sites/previews).`; /** - * Real-time product documentation + * Realtime product documentation */ -export const realtimeProductLinks = `For detailed real-time subscriptions, see the [Real-time Documentation](https://appwrite.io/docs/products/realtime). +export const realtimeProductLinks = `For detailed subscriptions, see the [Realtime Documentation](https://appwrite.io/docs/products/realtime). For subscribing to database changes, see the [Database Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-databases). @@ -113,7 +113,7 @@ For subscribing to storage changes, see the [Storage Subscriptions Guide](https: For subscribing to account changes, see the [Account Subscriptions Guide](https://appwrite.io/docs/products/realtime/subscribe-to-account). -For real-time channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). +For realtime channels and events, see the [Channels Documentation](https://appwrite.io/docs/products/realtime/channels). -For real-time event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; +For realtime event types and payloads, see the [Events Documentation](https://appwrite.io/docs/products/realtime/events).`; diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 122aea0..af353f9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -323,11 +323,11 @@ ${sitesProductLinks} */ async function generateRealtimeSection(sdk, framework) { const { realtimeProductLinks } = await import('./languages/common/products.js'); - return `## Real-time Subscriptions + return `## Realtime Subscriptions ${realtimeProductLinks} -### Best Practices for Real-time Subscriptions +### Best Practices for Realtime Subscriptions - **Connection Management**: Always unsubscribe from channels when components unmount or pages are closed to prevent memory leaks - **Error Handling**: Implement reconnection logic for dropped connections and handle network errors gracefully @@ -335,9 +335,9 @@ ${realtimeProductLinks} - **Channel Selection**: Subscribe only to the specific channels you need to minimize bandwidth and improve performance - **Payload Validation**: Always validate payload data before processing to ensure data integrity - **Rate Limiting**: Be mindful of the number of subscriptions and events to avoid overwhelming the client -- **State Synchronization**: Use real-time updates to keep local state in sync with server state, but handle conflicts appropriately +- **State Synchronization**: Use realtime updates to keep local state in sync with server state, but handle conflicts appropriately - **Authentication**: Ensure proper authentication is in place before subscribing to protected channels -- **Testing**: Test real-time functionality with network interruptions and reconnection scenarios +- **Testing**: Test realtime functionality with network interruptions and reconnection scenarios - **Cleanup**: Store unsubscribe functions and call them in cleanup hooks (useEffect cleanup, componentWillUnmount, etc.)`; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5ade608..2175202 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,7 +15,7 @@ { id: 'functions', label: 'Functions' }, { id: 'messaging', label: 'Messaging' }, { id: 'sites', label: 'Sites' }, - { id: 'realtime', label: 'Real-time' } + { id: 'realtime', label: 'Realtime' } ]; function updateFrameworks() { From 90b50052675ddc72af1885f40f15e59d3c030328 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Thu, 27 Nov 2025 22:56:05 +0530 Subject: [PATCH 07/16] update favicon to appwrite logo --- src/lib/assets/favicon.svg | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg index cc5dc66..bd847aa 100644 --- a/src/lib/assets/favicon.svg +++ b/src/lib/assets/favicon.svg @@ -1 +1,8 @@ -svelte-logo \ No newline at end of file + + + + \ No newline at end of file From 0d0510285bdf21736e265f54c25f74f69db08717 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 01:46:35 +0530 Subject: [PATCH 08/16] Add MCP section to rules generator and update package.json with Next.js generation script; include MCP in framework options --- package.json | 3 +- scripts/generate-nextjs-rules.js | 32 ++ scripts/lib-loader.js | 22 ++ src/lib/rules-generator.js | 510 +++++++++++++++++++++++++++++-- src/routes/+page.svelte | 3 +- 5 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 scripts/generate-nextjs-rules.js create mode 100644 scripts/lib-loader.js diff --git a/package.json b/package.json index 6cbc568..665662b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", - "lint": "prettier --check . && eslint ." + "lint": "prettier --check . && eslint .", + "generate:nextjs": "node --loader ./scripts/lib-loader.js scripts/generate-nextjs-rules.js" }, "devDependencies": { "@eslint/compat": "^1.4.0", diff --git a/scripts/generate-nextjs-rules.js b/scripts/generate-nextjs-rules.js new file mode 100644 index 0000000..1a4b7cb --- /dev/null +++ b/scripts/generate-nextjs-rules.js @@ -0,0 +1,32 @@ +import { generateRules } from '../src/lib/rules-generator.js'; +import { writeFile } from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +async function generateNextJSRules() { + try { + console.log('Generating Next.js rules with all products enabled...'); + + const rules = await generateRules({ + sdk: 'javascript', + framework: 'nextjs', + features: ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime'], + includeMCP: true + }); + + const outputPath = join(__dirname, '..', 'APPWRITE-javascript-nextjs.mdc'); + await writeFile(outputPath, rules, 'utf-8'); + + console.log(`Rules generated successfully!`); + console.log(`Output file: ${outputPath}`); + } catch (error) { + console.error('Error generating rules:', error); + process.exit(1); + } +} + +generateNextJSRules(); + diff --git a/scripts/lib-loader.js b/scripts/lib-loader.js new file mode 100644 index 0000000..22b6fe9 --- /dev/null +++ b/scripts/lib-loader.js @@ -0,0 +1,22 @@ +// Custom Node.js loader to resolve $lib aliases +import { pathToFileURL } from 'node:url'; +import { resolve as resolvePath } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = resolvePath(__dirname, '..'); + +export async function resolve(specifier, context, nextResolve) { + if (specifier.startsWith('$lib/')) { + const relativePath = specifier.replace('$lib/', ''); + const absolutePath = resolvePath(projectRoot, 'src', 'lib', relativePath); + const fileUrl = pathToFileURL(absolutePath).href; + return { + url: fileUrl, + format: 'module', + shortCircuit: true + }; + } + return nextResolve(specifier, context); +} + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index af353f9..c81b805 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -14,6 +14,7 @@ import { generateMCPRecommendation } from './languages/common/mcp.js'; * @property {string} sdk * @property {string} framework * @property {string[]} features + * @property {boolean} [includeMCP] - Whether to include MCP recommendation section */ /** @type {Record} */ @@ -109,14 +110,16 @@ export const SDK_OPTIONS = { * @returns {Promise} */ export async function generateRules(config) { - const { sdk, framework, features } = config; + const { sdk, framework, features, includeMCP = false } = config; const sdkInfo = SDK_OPTIONS[sdk]; const sdkInit = await generateSDKInitialization(sdk, framework); // Generate all sections in parallel + // Permissions section is mandatory for all products const sections = await Promise.all([ features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), + generatePermissionsSection(sdk, framework), features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), @@ -125,6 +128,8 @@ export async function generateRules(config) { features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') ]); + const mcpSection = includeMCP ? `${generateMCPRecommendation()}\n\n` : ''; + let rules = `--- description: You are an expert developer focused on building apps with Appwrite's ${sdkInfo?.name || sdk} SDK. alwaysApply: false @@ -132,9 +137,7 @@ alwaysApply: false # Appwrite Development Rules -${generateMCPRecommendation()} - -${sdkInit} +${mcpSection}${sdkInit} ${sections.join('\n\n')} `; @@ -206,11 +209,409 @@ ${authProductLinks} - **Session Security**: Always use HttpOnly cookies for session storage in SSR applications - **API Keys**: Never expose API keys to client-side code - use environment variables - **Session Validation**: Always validate sessions on the server before trusting them -- **Team Permissions**: Use team roles for granular access control in multi-tenant applications -- **Multitenancy**: Use team-based permissions when a user requires multitenancy to properly isolate data and resources between tenants +- **Team-Based Architecture**: ALWAYS prefer team/member-based roles over user-specific roles for any application requiring shared access or multi-tenancy +- **Multi-Tenant Applications**: Use teams as the primary mechanism for tenant isolation and resource sharing - **OAuth Redirects**: Handle OAuth redirects properly with success and failure URLs - **Password Security**: Use strong password requirements and consider implementing MFA -- **Session Expiry**: Configure appropriate session expiry times based on your security requirements`; +- **Session Expiry**: Configure appropriate session expiry times based on your security requirements + +### Team & Member Management Fundamentals + +When building applications that involve multiple users or tenants: + +1. **Always Start with Teams**: For any feature requiring shared access, create a team first, then add members with roles +2. **Role-Based Access**: Assign roles (e.g., "owner", "admin", "member", "viewer") to team members rather than setting individual user permissions +3. **Team Isolation**: Use teams as the boundary for data isolation in multi-tenant applications +4. **Member Invitations**: Implement team invitation workflows for onboarding new members +5. **Role Management**: Build role management UIs that allow team owners/admins to manage member roles dynamically`; +} + +/** + * @param {string} sdk + * @param {string} framework + * @returns {Promise} + */ +async function generatePermissionsSection(sdk, framework) { + const { authProductLinks } = await import('./languages/common/products.js'); + return `## Permissions & Multi-Tenancy + +This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. + +### Why Multi-Tenancy Matters + +Multi-tenancy allows a single application instance to serve multiple isolated groups of users (tenants) while maintaining complete data isolation and security. Almost every modern SaaS application requires multi-tenancy to scale efficiently. + +### Team/Member Roles vs User-Specific Roles: The Critical Distinction + +**ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: + +#### Avoid: User-Specific Permissions +\`\`\` +// DON'T do this for multi-tenant apps +create(collectionId, data, [ + Permission.read(Role.user(userId1)), + Permission.write(Role.user(userId1)) +]) +\`\`\` + +**Problems with user-specific permissions:** +- Hard to scale when users need to share resources +- Difficult to add/remove access without updating every document +- No way to represent organizational hierarchies +- Poor support for collaborative features +- Maintenance nightmare as teams grow + +#### Prefer: Team/Member-Based Roles +\`\`\` +// DO this for multi-tenant apps +create(collectionId, data, [ + Permission.read(Role.team(teamId, "owner")), + Permission.read(Role.team(teamId, "admin")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "owner")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) +]) +\`\`\` + +**Benefits of team/member-based roles:** +- Automatic access for all team members based on their role +- Easy to add/remove members without touching documents +- Scales naturally as teams grow +- Supports organizational hierarchies and complex permissions +- Industry-standard pattern for SaaS applications + +### Building Multi-Tenant Applications from Scratch + +#### Step 1: Create Teams Structure + +Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace, etc.). + +**Creating a team:** +\`\`\` +import { Teams } from 'appwrite'; + +// Create a team when a new tenant/organization signs up +const team = await teams.create( + teamId, // Unique team ID (can be auto-generated) + teamName, // Display name + roles // Array of role strings: ['owner', 'admin', 'member'] +); +\`\`\` + +#### Step 2: Define Custom Roles + +Create roles that match your application's permission model. Common roles: +- **owner**: Full control, can manage team settings and members +- **admin**: Can manage resources and most settings, but not team membership +- **member**: Can create/edit resources, but with limited permissions +- **viewer**: Read-only access + +**Creating custom roles (Server-side only):** +\`\`\` +import { Teams } from 'appwrite'; + +// Define roles when creating the team (optional, defaults exist) +// Or create via Appwrite Console or Server SDK +// Roles are created per team, allowing different permission models per tenant +\`\`\` + +#### Step 3: Member Management from Scratch + +Member management is the foundation of multi-tenant applications. Here's how to build it: + +**A. Invite Members to Teams** + +\`\`\` +import { Teams } from 'appwrite'; + +// Send team invitation (email-based) +const invite = await teams.createMembership( + teamId, + email, // Email of user to invite + roles, // Array of role strings: ['admin', 'member'] + url // Invitation redirect URL +); + +// Or invite by user ID (if user already exists) +const membership = await teams.createMembership( + teamId, + userId, + roles +); +\`\`\` + +**B. List Team Members** + +\`\`\` +import { Teams } from 'appwrite'; + +// Get all members of a team +const memberships = await teams.listMemberships(teamId); + +// Access member data +memberships.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); // Array of role strings + console.log(membership.userName); + console.log(membership.userEmail); +}); +\`\`\` + +**C. Update Member Roles** + +\`\`\` +import { Teams } from 'appwrite'; + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembershipRoles( + teamId, + membershipId, + ['admin', 'member'] // New roles array +); +\`\`\` + +**D. Remove Members** + +\`\`\` +import { Teams } from 'appwrite'; + +// Remove a member from a team +await teams.deleteMembership(teamId, membershipId); +\`\`\` + +**E. Get Current User's Teams** + +\`\`\` +import { Teams } from 'appwrite'; + +// List all teams the current user belongs to +const teams = await teams.list(); + +teams.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +}); +\`\`\` + +**F. Get Current User's Role in a Team** + +\`\`\` +import { Teams } from 'appwrite'; + +// Get membership details for current user in a specific team +const memberships = await teams.listMemberships(teamId); + +const userMembership = memberships.memberships.find( + m => m.userId === currentUserId +); + +if (userMembership) { + console.log(userMembership.roles); // ['owner', 'admin', etc.] + const hasAdminRole = userMembership.roles.includes('admin'); +} +\`\`\` + +#### Step 4: Apply Permissions in Collections + +When creating documents in multi-tenant applications, always use team roles: + +**Database Collections:** + +\`\`\` +import { TablesDB, Permission, Role } from 'appwrite'; + +// Create document with team-based permissions +await tablesdb.createRow( + databaseId, + tableId, + documentId, + { + title: 'My Document', + teamId: teamId, // Always store teamId for querying + // ... other fields + }, + [ + // Owners and admins can do everything + Permission.read(Role.team(teamId, "owner")), + Permission.read(Role.team(teamId, "admin")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "owner")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")), + Permission.delete(Role.team(teamId, "admin")) + ] +); +\`\`\` + +**Collection-Level Permissions:** + +When creating collections, set default permissions: + +\`\`\` +import { TablesDB, Permission, Role } from 'appwrite'; + +// Create collection with team-based permissions +await tablesdb.createTable( + databaseId, + tableId, + tableName, + [ + // Collection permissions + Permission.create(Role.team(teamId, "member")), + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) + ] +); +\`\`\` + +#### Step 5: Query with Team Isolation + +Always filter queries by teamId to ensure data isolation: + +\`\`\` +import { TablesDB, Query } from 'appwrite'; + +// ALWAYS filter by teamId to ensure tenant isolation +const documents = await tablesdb.listDocuments( + databaseId, + tableId, + [ + Query.equal('teamId', teamId), // Critical: filter by team + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +); +\`\`\` + +#### Step 6: Storage Permissions + +Apply the same team-based permission pattern to storage: + +\`\`\` +import { Storage, Permission, Role } from 'appwrite'; + +// Create file with team-based permissions +await storage.createFile( + bucketId, + fileId, + fileInput, + [ + Permission.read(Role.team(teamId, "member")), + Permission.update(Role.team(teamId, "admin")), + Permission.delete(Role.team(teamId, "owner")) + ] +); +\`\`\` + +### Complete Member Management Implementation Pattern + +Here's a complete pattern for building member management UI and logic: + +**1. Team Creation Flow:** +\`\`\` +// When user creates account/organization +const team = await teams.create(uniqueId(), 'Company Name'); +// Make creator an owner +await teams.createMembership(team.$id, userId, ['owner']); +\`\`\` + +**2. Invite Flow:** +\`\`\` +// Owner/admin invites new member +const invite = await teams.createMembership( + teamId, + email, + ['member'], // Default role + 'https://yourapp.com/accept-invite' // Redirect after accepting +); +// User receives email, clicks link, accepts invitation +\`\`\` + +**3. Member List UI:** +\`\`\` +// Display all team members with their roles +const memberships = await teams.listMemberships(teamId); +// Show list with role badges and action buttons +\`\`\` + +**4. Role Change:** +\`\`\` +// Admin/owner changes member role +await teams.updateMembershipRoles(teamId, membershipId, ['admin']); +\`\`\` + +**5. Member Removal:** +\`\`\` +// Remove member (with confirmation) +await teams.deleteMembership(teamId, membershipId); +\`\`\` + +### Permission Best Practices + +1. **Always Store teamId**: Every document/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation + +2. **Default Deny**: Don't grant permissions unless explicitly needed. Use minimal permission sets. + +3. **Role Hierarchy**: Design your roles to reflect natural hierarchies (owner > admin > member > viewer) + +4. **Permission Consistency**: Use the same permission pattern across database, storage, and other resources + +5. **Server-Side Validation**: Always validate team membership on the server side, even if client has permissions + +6. **Query Isolation**: Always include \`teamId\` in queries to prevent cross-tenant data leaks + +7. **Role Checks**: Before allowing sensitive operations, check the user's role in the team: + \`\`\` + const membership = await getCurrentUserMembership(teamId); + if (!membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); + } + \`\`\` + +8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions + +9. **Document-Level Permissions**: For fine-grained control, set permissions on individual documents while still using team roles + +10. **Audit Trail**: Log permission changes and team membership changes for security auditing + +### Common Multi-Tenancy Patterns + +**Pattern 1: Workspace-Based (e.g., Notion, Slack)** +- Each workspace is a team +- Users can belong to multiple teams +- Resources belong to one team +- Perfect for: Collaboration tools, project management + +**Pattern 2: Organization-Based (e.g., GitHub, GitLab)** +- Each organization is a team +- Resources belong to organization +- Members have roles within organization +- Perfect for: Enterprise SaaS, developer tools + +**Pattern 3: Project-Based (e.g., Linear, Asana)** +- Each project is a team +- Resources scoped to project +- Members invited per project +- Perfect for: Project management, task tracking + +### Debugging Permission Issues + +When permissions aren't working: + +1. **Check Team Membership**: Verify user is actually a member of the team +2. **Verify Roles**: Ensure user has the required role (check \`membership.roles\`) +3. **Check Permission Strings**: Verify permission strings match exactly (case-sensitive) +4. **Query Filters**: Ensure \`teamId\` filters are applied correctly +5. **Server vs Client**: Some operations require server SDK (like creating custom roles) +6. **Session Context**: Permissions are evaluated in the context of the current session + +### Additional Resources + +${authProductLinks} + +For comprehensive permission patterns and examples, always refer to the official Appwrite documentation on Teams, Multi-tenancy, and Permissions.`; } /** @@ -226,12 +627,17 @@ ${databaseProductLinks} ### Best Practices for Databases -- **Permissions**: Always set appropriate permissions at table and row levels -- **Query Optimization**: Use indexes for frequently queried fields to improve performance -- **Data Validation**: Validate data before creating or updating rows -- **Transactions**: Use transactions for operations that must succeed or fail together -- **Pagination**: Always implement pagination for large datasets to improve performance -- **Type Safety**: Use type-safe models when available in your SDK for better code quality`; +- **SDK Usage**: Always use \`TablesDB\` instead of \`Databases\` in the SDKs +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications +- **Tenant Isolation**: Always include \`teamId\` fields in your documents and filter queries by \`teamId\` to ensure complete data isolation between tenants +- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all collections. Use Role.team() for all permission checks +- **Query Security**: Every multi-tenant query MUST include a \`teamId\` filter to prevent cross-tenant data access +- **Collection Permissions**: Set collection-level permissions using team roles, then override at document level when needed +- **Query Optimization**: Use indexes for frequently queried fields, especially on \`teamId\` and commonly filtered fields +- **Data Validation**: Validate data before creating or updating rows, including team membership validation +- **Transactions**: Use transactions for operations that must succeed or fail together, ensuring atomicity across tenant boundaries +- **Pagination**: Always implement pagination for large datasets to improve performance and reduce response sizes +- **Type Safety**: Use type-safe models when available in your SDK for better code quality and fewer runtime errors`; } /** @@ -247,11 +653,60 @@ ${storageProductLinks} ### Best Practices for Storage -- **File Size Limits**: Set appropriate file size limits to prevent abuse -- **File Types**: Validate file types before upload to ensure security -- **Permissions**: Set proper permissions on buckets and files to control access -- **Cleanup**: Implement cleanup strategies for unused or temporary files -- **Virus Scanning**: Consider implementing virus scanning for uploaded files`; +- **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for storage permissions (see Permissions & Multi-Tenancy section above). Apply Role.team() permissions to buckets and files for proper tenant isolation +- **Bucket Organization**: Consider organizing files by team/tenant using folder structures or bucket naming conventions for easier management +- **Tenant Isolation**: When querying files, always filter by metadata (e.g., \`teamId\`) to ensure users only access files from their teams +- **File Size Limits**: Set appropriate file size limits to prevent abuse and manage costs +- **File Types**: Validate file types before upload to ensure security and prevent malicious uploads +- **Permission Patterns**: Use team roles (owner, admin, member, viewer) consistently for bucket and file permissions, matching your database permission model +- **Cleanup**: Implement cleanup strategies for unused or temporary files, especially when teams are deleted +- **Virus Scanning**: Consider implementing virus scanning for uploaded files to protect all tenants +- **Access Control**: Validate team membership before allowing file uploads/downloads, even if permissions are set correctly`; +} + +/** + * Maps SDK names to their corresponding template paths in the Appwrite templates repository + * @param {string} sdk + * @returns {string|null} Template path or null if no template available + */ +function getFunctionTemplatePath(sdk) { + /** @type {Record} */ + const templateMap = { + javascript: 'node/starter', + 'react-native': 'node/starter', + python: 'python/starter', + php: 'php/starter', + go: 'go/starter', + flutter: 'dart/starter', + swift: 'swift/starter', + kotlin: 'kotlin/starter', + ruby: 'ruby/starter', + dotnet: 'dotnet/starter' + }; + return templateMap[sdk] || null; +} + +/** + * Generates template links section for functions + * @param {string} sdk + * @returns {string} + */ +function generateFunctionTemplateLinks(sdk) { + const templatePath = getFunctionTemplatePath(sdk); + if (!templatePath) { + return ''; + } + + const templateUrl = `https://github.com/appwrite/templates/tree/main/${templatePath}`; + const templatesBaseUrl = 'https://github.com/appwrite/templates'; + + return `### Starter Templates + +For getting started with Appwrite Functions, use the official starter template for your runtime: + +- **${SDK_OPTIONS[sdk]?.name || sdk} Starter**: [View Template](${templateUrl}) + +For more templates and examples, see the [Appwrite Templates Repository](${templatesBaseUrl}).`; } /** @@ -261,10 +716,26 @@ ${storageProductLinks} */ async function generateFunctionsSection(sdk, framework) { const { functionsProductLinks } = await import('./languages/common/products.js'); + const templateLinks = generateFunctionTemplateLinks(sdk); + return `## Functions ${functionsProductLinks} +${templateLinks} + +### When to Use Starter Templates + +**ALWAYS use starter templates from the [Appwrite Templates Repository](https://github.com/appwrite/templates) when building functions for:** + +- **Scheduled Tasks**: Functions that run on a schedule (cron jobs, periodic cleanup, etc.) +- **Event-Driven Tasks**: Functions triggered by Appwrite events (database changes, storage uploads, user events, etc.) +- **Background Processing**: Long-running or resource-intensive operations +- **Integration Functions**: Functions that integrate with third-party services (APIs, webhooks, etc.) +- **Complex Functions**: Any function that requires specific runtime configuration or dependencies + +**Why use templates?** Starter templates provide the correct project structure, dependencies, and configuration needed for functions to build and execute successfully. They ensure proper handling of environment variables, logging, error handling, and Appwrite SDK initialization. + ### Best Practices for Functions - **Error Handling**: Implement comprehensive error handling in your functions @@ -272,7 +743,8 @@ ${functionsProductLinks} - **Environment Variables**: Use environment variables for configuration, not hardcoded values - **Logging**: Implement proper logging for debugging and monitoring - **Security**: Validate all inputs and never trust user-provided data -- **Resource Limits**: Be mindful of memory and CPU limits for function executions`; +- **Resource Limits**: Be mindful of memory and CPU limits for function executions +- **Template Usage**: Start with official templates for scheduled and event-driven functions to ensure proper setup`; } /** diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2175202..fc01412 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,7 +15,8 @@ { id: 'functions', label: 'Functions' }, { id: 'messaging', label: 'Messaging' }, { id: 'sites', label: 'Sites' }, - { id: 'realtime', label: 'Realtime' } + { id: 'realtime', label: 'Realtime' }, + { id: 'mcp', label: 'MCP' } ]; function updateFrameworks() { From d2ef717e1f75e4f2d49ae47e9e5e07c87792d69a Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 01:51:41 +0530 Subject: [PATCH 09/16] Enhance README with detailed features and usage instructions for Appwrite Cursor Rules Generator; add .mdc file extension to .gitignore --- .gitignore | 4 ++ README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 3b462cb..bc116d1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Rules + +*.mdc \ No newline at end of file diff --git a/README.md b/README.md index 75842c4..72eb616 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,132 @@ -# sv +# Appwrite Cursor Rules Generator -Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). +A web application for generating comprehensive Cursor rules (`.mdc` files) for Appwrite development across multiple SDKs and frameworks. This tool helps developers create customized development rules that include best practices, code examples, and guidance for building applications with Appwrite. -## Creating a project +## Features -If you're seeing this, you've probably already done this step. Congrats! +- **Multi-SDK Support**: Generate rules for JavaScript/TypeScript, Python, PHP, Go, Flutter/Dart, Apple, Android, Swift, Kotlin, Ruby, .NET, and React Native +- **Framework-Specific Rules**: Get tailored rules for popular frameworks like Next.js, React, Vue, Svelte, Angular, Astro, Nuxt, Qwik, Solid, and more +- **Feature Selection**: Choose which Appwrite features to include: + - Authentication & Teams + - Database Operations + - Storage Operations + - Functions + - Messaging + - Sites + - Realtime Subscriptions + - MCP (Model Context Protocol) recommendations +- **Export Options**: Copy to clipboard or download as `.mdc` file +- **Best Practices**: Generated rules include comprehensive best practices, multi-tenancy patterns, and security guidelines -```sh -# create a new project in the current directory -npx sv create +## Supported SDKs and Frameworks -# create a new project in my-app -npx sv create my-app -``` +| SDK | Frameworks | +|-----|------------| +| JavaScript/TypeScript | Next.js, React, Vue, Svelte, Angular, Astro, Nuxt, Qwik, Solid, TanStack, Node.js, Vanilla | +| React Native | React Native, Vanilla | +| Python | Flask, Django, FastAPI, Server | +| Flutter/Dart | Flutter, Server | +| Apple | Vanilla | +| Android | Vanilla | +| Swift | Server, Vanilla | +| Kotlin | Server, Vanilla | +| PHP | Laravel, Symfony, Server | +| Go | Gin, Fiber, Server | +| Ruby | Rails, Server | +| .NET | ASP.NET, Server, Vanilla | + +## Getting Started + +### Prerequisites -## Developing +- Node.js 18+ +- pnpm -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +### Installation + +1. Clone the repository: +```bash +git clone +cd appwrite-cursor-rules +``` -```sh -npm run dev +2. Install dependencies: +```bash +pnpm install +``` -# or start the server and open the app in a new browser tab -npm run dev -- --open +3. Start the development server: +```bash +pnpm dev ``` -## Building +4. Open your browser and navigate to `http://localhost:5173` (or the port shown in the terminal) + +## Usage + +1. **Select SDK**: Choose your preferred Appwrite SDK from the dropdown +2. **Select Framework**: Pick the framework you're using (options depend on the selected SDK) +3. **Choose Features**: Check the boxes for the Appwrite features you want to include in your rules +4. **Generate Rules**: Click the "Generate Rules" button +5. **Export**: Copy the rules to your clipboard or download as a `.mdc` file + +The generated rules file can be used in Cursor IDE to provide AI-assisted development guidance specific to your Appwrite setup. + +## Development -To create a production version of your app: +### Available Scripts -```sh -npm run build +- `pnpm dev` - Start development server +- `pnpm build` - Build for production +- `pnpm preview` - Preview production build +- `pnpm check` - Run Svelte type checking +- `pnpm lint` - Run ESLint and Prettier +- `pnpm format` - Format code with Prettier +- `pnpm generate:nextjs` - Generate Next.js rules file (example script) + +### Project Structure + +``` +appwrite-cursor-rules/ +├── src/ +│ ├── lib/ +│ │ ├── languages/ # SDK and framework-specific code examples +│ │ │ ├── js/ # JavaScript/TypeScript frameworks +│ │ │ ├── python/ # Python frameworks +│ │ │ ├── common/ # Shared rules (products, MCP, etc.) +│ │ │ └── ... # Other SDKs +│ │ ├── rules-generator.js # Main rules generation logic +│ │ └── utils/ # Utility functions +│ └── routes/ +│ ├── +page.svelte # Main application page +│ └── +layout.svelte # Layout component +├── scripts/ +│ ├── generate-nextjs-rules.js # Example script for generating rules +│ └── lib-loader.js # Module loader for scripts +└── static/ # Static assets ``` -You can preview the production build with `npm run preview`. +### Adding New SDKs or Frameworks + +1. Create a new file in `src/lib/languages/[sdk-name]/index.js` (or add to existing SDK directory) +2. Export framework-specific initialization code +3. Add the SDK configuration to `SDK_OPTIONS` in `src/lib/rules-generator.js` +4. Export the SDK module in `src/lib/languages/index.js` + +### Adding New Features + +1. Create a new section generator function in `src/lib/rules-generator.js` (e.g., `generateNewFeatureSection`) +2. Add the feature to the features array in `src/routes/+page.svelte` +3. Include the feature in the `generateRules` function's Promise.all array + +## Generated Rules Format + +The generated rules follow the Cursor `.mdc` format and include: + +- **Frontmatter**: Metadata about the rules (description, alwaysApply flag) +- **SDK Initialization**: Framework-specific code examples for setting up Appwrite +- **Feature Sections**: Best practices and guidance for selected features +- **Multi-Tenancy Guide**: Comprehensive guide on using teams and permissions +- **Product Links**: Links to official Appwrite documentation + -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. From db0f222216b873c8a4e1bba04a8f3d20ef33d620 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 28 Nov 2025 02:47:50 +0530 Subject: [PATCH 10/16] Integrate permission examples into rules generator documentation; enhance code snippets for multi-tenancy best practices and member management flows. --- .../languages/common/permissions-examples.js | 4889 +++++++++++++++++ src/lib/rules-generator.js | 260 +- 2 files changed, 4958 insertions(+), 191 deletions(-) create mode 100644 src/lib/languages/common/permissions-examples.js diff --git a/src/lib/languages/common/permissions-examples.js b/src/lib/languages/common/permissions-examples.js new file mode 100644 index 0000000..0661d5e --- /dev/null +++ b/src/lib/languages/common/permissions-examples.js @@ -0,0 +1,4889 @@ +/** + * Language-specific permission examples for Appwrite + * All examples are validated against official Appwrite documentation + */ + +export const permissionExamples = { + javascript: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import { TablesDB, Permission, Role } from 'appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +});`, + preferTeamPermissions: `// DO this for multi-tenant apps +import { TablesDB, Permission, Role } from 'appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + createTeam: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Create a team when a new tenant/organization signs up +const team = await teams.create({ + teamId: '', // Unique team ID (can be auto-generated) + name: '', // Display name + roles: ['owner', 'admin', 'member'] // Optional: Array of role strings +});`, + createMembershipEmail: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Send team invitation (email-based) +const invite = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' // Invitation redirect URL +});`, + createMembershipUserId: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Or invite by user ID (if user already exists) +const membership = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + userId: '' +});`, + listMemberships: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Get all members of a team +const response = await teams.listMemberships({ + teamId: '' +}); + +// Access member data +response.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); // Array of role strings + console.log(membership.userName); + console.log(membership.userEmail); +});`, + updateMembership: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin', 'member'] // New roles array +});`, + deleteMembership: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Remove a member from a team +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + listTeams: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// List all teams the current user belongs to +const response = await teams.list(); + +response.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +});`, + getUserRole: `import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +// Get membership details for current user in a specific team +const response = await teams.listMemberships({ + teamId: '' +}); + +const userMembership = response.memberships.find( + m => m.userId === '' +); + +if (userMembership) { + console.log(userMembership.roles); // ['owner', 'admin', etc.] + const hasAdminRole = userMembership.roles.includes('admin'); +}`, + createRow: `import { Client, TablesDB, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +// Create document with team-based permissions +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { + title: 'My Document', + teamId: '', // Always store teamId for querying + // ... other fields + }, + permissions: [ + // Owners and admins can do everything + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +});`, + createTable: `import { Client, TablesDB, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +const tablesDB = new TablesDB(client); + +// Create table with team-based permissions +await tablesDB.createTable({ + databaseId: '', + tableId: '', + name: '', + permissions: [ + // Collection permissions + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + listRows: `import { Client, TablesDB, Query } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +const response = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +});`, + createFile: `import { Client, Storage, Permission, Role } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const storage = new Storage(client); + +// Create file with team-based permissions +await storage.createFile({ + bucketId: '', + fileId: '', + file: fileInput, // File object from input + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + teamCreationFlow: `// When user creates account/organization +import { Client, Teams, ID } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: ID.unique(), + name: 'Company Name' +}); + +// Make creator an owner +await teams.createMembership({ + teamId: team.$id, + roles: ['owner'], + userId: '' +});`, + inviteFlow: `// Owner/admin invites new member +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' // Redirect after accepting +}); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin'] +});`, + memberRemoval: `// Remove member (with confirmation) +import { Client, Teams } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + roleCheck: `const response = await teams.listMemberships({ + teamId: '' +}); + +const membership = response.memberships.find( + m => m.userId === '' +); + +if (!membership || !membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +}` + }, + 'react-native': { + // React Native uses the same syntax as JavaScript + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import { TablesDB, Permission, Role } from 'react-native-appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +});`, + preferTeamPermissions: `// DO this for multi-tenant apps +import { TablesDB, Permission, Role } from 'react-native-appwrite'; + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { title: 'My Document' }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + createTeam: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] +});`, + createMembershipEmail: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +});`, + createMembershipUserId: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const membership = await teams.createMembership({ + teamId: '', + roles: ['admin', 'member'], + userId: '' +});`, + listMemberships: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); + +response.memberships.forEach(membership => { + console.log(membership.userId); + console.log(membership.roles); + console.log(membership.userName); + console.log(membership.userEmail); +});`, + updateMembership: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +});`, + deleteMembership: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + listTeams: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.list(); + +response.teams.forEach(team => { + console.log(team.$id); + console.log(team.name); +});`, + getUserRole: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +}); + +const userMembership = response.memberships.find( + m => m.userId === '' +); + +if (userMembership) { + console.log(userMembership.roles); + const hasAdminRole = userMembership.roles.includes('admin'); +}`, + createRow: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.createRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { + title: 'My Document', + teamId: '', + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +});`, + createTable: `import { Client, TablesDB, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.createTable({ + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + listRows: `import { Client, TablesDB, Query } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +const response = await tablesDB.listRows({ + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), + Query.orderDesc('$createdAt'), + Query.limit(25) + ] +});`, + createFile: `import { Client, Storage, Permission, Role } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const storage = new Storage(client); + +await storage.createFile({ + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +});`, + teamCreationFlow: `import { Client, Teams, ID } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const team = await teams.create({ + teamId: ID.unique(), + name: 'Company Name' +}); + +await teams.createMembership({ + teamId: team.$id, + roles: ['owner'], + userId: '' +});`, + inviteFlow: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const invite = await teams.createMembership({ + teamId: '', + roles: ['member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +});`, + memberListUI: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +const response = await teams.listMemberships({ + teamId: '' +});`, + roleChange: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.updateMembership({ + teamId: '', + membershipId: '', + roles: ['admin'] +});`, + memberRemoval: `import { Client, Teams } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const teams = new Teams(client); + +await teams.deleteMembership({ + teamId: '', + membershipId: '' +});`, + roleCheck: `const response = await teams.listMemberships({ + teamId: '' +}); + +const membership = response.memberships.find( + m => m.userId === '' +); + +if (!membership || !membership.roles.includes('admin')) { + throw new Error('Insufficient permissions'); +}` + }, + python: { + avoidUserPermissions: `# DON'T do this for multi-tenant apps +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={'title': 'My Document'}, + permissions=[ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +)`, + preferTeamPermissions: `# DO this for multi-tenant apps +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={'title': 'My Document'}, + permissions=[ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + createTeam: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Create a team when a new tenant/organization signs up +team = teams.create( + team_id='', + name='', + roles=['owner', 'admin', 'member'] # optional +)`, + createMembershipEmail: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Send team invitation (email-based) +invite = teams.create_membership( + team_id='', + roles=['admin', 'member'], + email='user@example.com', + url='https://yourapp.com/accept-invite' +)`, + createMembershipUserId: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') # Server SDK + +teams = Teams(client) + +# Or invite by user ID (if user already exists) +membership = teams.create_membership( + team_id='', + roles=['admin', 'member'], + user_id='' +)`, + listMemberships: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Get all members of a team +response = teams.list_memberships(team_id='') + +# Access member data +for membership in response['memberships']: + print(membership['userId']) + print(membership['roles']) # Array of role strings + print(membership['userName']) + print(membership['userEmail'])`, + updateMembership: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Update a member's roles (only team owners/admins can do this) +teams.update_membership( + team_id='', + membership_id='', + roles=['admin', 'member'] +)`, + deleteMembership: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Remove a member from a team +teams.delete_membership( + team_id='', + membership_id='' +)`, + listTeams: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# List all teams the current user belongs to +response = teams.list() + +for team in response['teams']: + print(team['$id']) + print(team['name'])`, + getUserRole: `from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +# Get membership details for current user in a specific team +response = teams.list_memberships(team_id='') + +user_membership = next( + (m for m in response['memberships'] if m['userId'] == ''), + None +) + +if user_membership: + print(user_membership['roles']) # ['owner', 'admin', etc.] + has_admin_role = 'admin' in user_membership['roles']`, + createRow: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +# Create document with team-based permissions +tables_db.create_row( + database_id='', + table_id='', + row_id='', + data={ + 'title': 'My Document', + 'teamId': '', # Always store teamId for querying + }, + permissions=[ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +)`, + createTable: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') # Server SDK requires API key + +tables_db = TablesDB(client) + +# Create table with team-based permissions +tables_db.create_table( + database_id='', + table_id='', + name='', + permissions=[ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + listRows: `from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +tables_db = TablesDB(client) + +# ALWAYS filter by teamId to ensure tenant isolation +response = tables_db.list_rows( + database_id='', + table_id='', + queries=[ + Query.equal('teamId', ''), # Critical: filter by team + Query.order_desc('$createdAt'), + Query.limit(25) + ] +)`, + createFile: `from appwrite.client import Client +from appwrite.services.storage import Storage +from appwrite.models import Permission, Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +storage = Storage(client) + +# Create file with team-based permissions +storage.create_file( + bucket_id='', + file_id='', + file=file_input, + permissions=[ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + teamCreationFlow: `# When user creates account/organization +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +team = teams.create( + team_id=ID.unique(), + name='Company Name' +) + +# Make creator an owner +teams.create_membership( + team_id=team['$id'], + roles=['owner'], + user_id='' +)`, + inviteFlow: `# Owner/admin invites new member +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +invite = teams.create_membership( + team_id='', + roles=['member'], # Default role + email='user@example.com', + url='https://yourapp.com/accept-invite' +) +# User receives email, clicks link, accepts invitation`, + memberListUI: `# Display all team members with their roles +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +response = teams.list_memberships(team_id='') +# Show list with role badges and action buttons`, + roleChange: `# Admin/owner changes member role +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +teams.update_membership( + team_id='', + membership_id='', + roles=['admin'] +)`, + memberRemoval: `# Remove member (with confirmation) +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_session('') + +teams = Teams(client) + +teams.delete_membership( + team_id='', + membership_id='' +)`, + roleCheck: `response = teams.list_memberships(team_id='') + +user_membership = next( + (m for m in response['memberships'] if m['userId'] == ''), + None +) + +if not user_membership or 'admin' not in user_membership['roles']: + raise Exception('Insufficient permissions')` + }, + php: { + avoidUserPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: ['title' => 'My Document'], + permissions: [ + Permission::read(Role::user('')), + Permission::write(Role::user('')) + ] +);`, + preferTeamPermissions: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: ['title' => 'My Document'], + permissions: [ + Permission::read(Role::team('', 'owner')), + Permission::read(Role::team('', 'admin')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'owner')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + createTeam: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Create a team when a new tenant/organization signs up +$team = $teams->create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Send team invitation (email-based) +$invite = $teams->createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); // Server SDK + +$teams = new Teams($client); + +// Or invite by user ID (if user already exists) +$membership = $teams->createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Get all members of a team +$response = $teams->listMemberships(teamId: ''); + +// Access member data +foreach ($response['memberships'] as $membership) { + echo $membership['userId']; + echo implode(', ', $membership['roles']); // Array of role strings + echo $membership['userName']; + echo $membership['userEmail']; +}`, + updateMembership: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Update a member's roles (only team owners/admins can do this) +$teams->updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Remove a member from a team +$teams->deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// List all teams the current user belongs to +$response = $teams->list(); + +foreach ($response['teams'] as $team) { + echo $team['$id']; + echo $team['name']; +}`, + getUserRole: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +// Get membership details for current user in a specific team +$response = $teams->listMemberships(teamId: ''); + +$userMembership = array_filter( + $response['memberships'], + fn($m) => $m['userId'] === '' +); + +if (!empty($userMembership)) { + $membership = reset($userMembership); + echo implode(', ', $membership['roles']); // ['owner', 'admin', etc.] + $hasAdminRole = in_array('admin', $membership['roles']); +}`, + createRow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +// Create document with team-based permissions +$tablesDB->createRow( + databaseId: '', + tableId: '', + rowId: '', + data: [ + 'title' => 'My Document', + 'teamId' => '', // Always store teamId for querying + ], + permissions: [ + Permission::read(Role::team('', 'owner')), + Permission::read(Role::team('', 'admin')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'owner')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')), + Permission::delete(Role::team('', 'admin')) + ] +);`, + createTable: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); // Server SDK requires API key + +$tablesDB = new TablesDB($client); + +// Create table with team-based permissions +$tablesDB->createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission::create(Role::team('', 'member')), + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + listRows: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$tablesDB = new TablesDB($client); + +// ALWAYS filter by teamId to ensure tenant isolation +$response = $tablesDB->listRows( + databaseId: '', + tableId: '', + queries: [ + Query::equal('teamId', ''), // Critical: filter by team + Query::orderDesc('$createdAt'), + Query::limit(25) + ] +);`, + createFile: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$storage = new Storage($client); + +// Create file with team-based permissions +$storage->createFile( + bucketId: '', + fileId: '', + file: $fileInput, + permissions: [ + Permission::read(Role::team('', 'member')), + Permission::update(Role::team('', 'admin')), + Permission::delete(Role::team('', 'owner')) + ] +);`, + teamCreationFlow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$team = $teams->create( + teamId: ID::unique(), + name: 'Company Name' +); + +// Make creator an owner +$teams->createMembership( + teamId: $team['$id'], + roles: ['owner'], + userId: '' +);`, + inviteFlow: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$invite = $teams->createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$response = $teams->listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$teams->updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setSession(''); + +$teams = new Teams($client); + +$teams->deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `$response = $teams->listMemberships(teamId: ''); + +$userMembership = array_filter( + $response['memberships'], + fn($m) => $m['userId'] === '' +); + +if (empty($userMembership) || !in_array('admin', reset($userMembership)['roles'])) { + throw new Exception('Insufficient permissions'); +}` + }, + go: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + }, + []interface{}{ + models.PermissionRead(models.RoleUser("")), + models.PermissionWrite(models.RoleUser("")), + }, +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + }, + []interface{}{ + models.PermissionRead(models.RoleTeam("", "owner")), + models.PermissionRead(models.RoleTeam("", "admin")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "owner")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }, +)`, + createTeam: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Create a team when a new tenant/organization signs up +service.Create( + "", + "", + teams.WithCreateRoles([]interface{}{"owner", "admin", "member"}), +)`, + createMembershipEmail: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Send team invitation (email-based) +service.CreateMembership( + "", + []interface{}{"admin", "member"}, + teams.WithCreateMembershipEmail("user@example.com"), + teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), +)`, + createMembershipUserId: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), // Server SDK +) + +service := teams.New(client) + +// Or invite by user ID (if user already exists) +service.CreateMembership( + "", + []interface{}{"admin", "member"}, + teams.WithCreateMembershipUserId(""), +)`, + listMemberships: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Get all members of a team +response, _ := service.ListMemberships("") + +// Access member data +for _, membership := range response.Memberships { + fmt.Println(membership.UserId) + fmt.Println(membership.Roles) // Array of role strings + fmt.Println(membership.UserName) + fmt.Println(membership.UserEmail) +}`, + updateMembership: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Update a member's roles (only team owners/admins can do this) +service.UpdateMembership( + "", + "", + []interface{}{"admin", "member"}, +)`, + deleteMembership: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Remove a member from a team +service.DeleteMembership( + "", + "", +)`, + listTeams: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// List all teams the current user belongs to +response, _ := service.List() + +for _, team := range response.Teams { + fmt.Println(team.Id) + fmt.Println(team.Name) +}`, + getUserRole: `package main + +import ( + "fmt" + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +// Get membership details for current user in a specific team +response, _ := service.ListMemberships("") + +var userMembership *teams.Membership +for _, membership := range response.Memberships { + if membership.UserId == "" { + userMembership = &membership + break + } +} + +if userMembership != nil { + fmt.Println(userMembership.Roles) // ['owner', 'admin', etc.] + hasAdminRole := false + for _, role := range userMembership.Roles { + if role == "admin" { + hasAdminRole = true + break + } + } +}`, + createRow: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +// Create document with team-based permissions +tablesDB.CreateRow( + "", + "", + "", + map[string]interface{}{ + "title": "My Document", + "teamId": "", // Always store teamId for querying + }, + []interface{}{ + models.PermissionRead(models.RoleTeam("", "owner")), + models.PermissionRead(models.RoleTeam("", "admin")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "owner")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + models.PermissionDelete(models.RoleTeam("", "admin")), + }, +)`, + createTable: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithKey(""), // Server SDK requires API key +) + +tablesDB := tablesdb.New(client) + +// Create table with team-based permissions +tablesDB.CreateTable( + "", + "", + "", + tablesdb.WithCreateTablePermissions([]interface{}{ + models.PermissionCreate(models.RoleTeam("", "member")), + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }), +)`, + listRows: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/tablesdb" + "github.com/appwrite/sdk-for-go/query" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +tablesDB := tablesdb.New(client) + +// ALWAYS filter by teamId to ensure tenant isolation +tablesDB.ListRows( + "", + "", + tablesdb.WithListRowsQueries([]interface{}{ + query.Equal("teamId", ""), // Critical: filter by team + query.OrderDesc("$createdAt"), + query.Limit(25), + }), +)`, + createFile: `package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/storage" + "github.com/appwrite/sdk-for-go/models" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +storageService := storage.New(client) + +// Create file with team-based permissions +storageService.CreateFile( + "", + "", + fileInput, + storage.WithCreateFilePermissions([]interface{}{ + models.PermissionRead(models.RoleTeam("", "member")), + models.PermissionUpdate(models.RoleTeam("", "admin")), + models.PermissionDelete(models.RoleTeam("", "owner")), + }), +)`, + teamCreationFlow: `// When user creates account/organization +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" + "github.com/appwrite/sdk-for-go/id" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +team, _ := service.Create( + id.Unique(), + "Company Name", +) + +// Make creator an owner +service.CreateMembership( + team.Id, + []interface{}{"owner"}, + teams.WithCreateMembershipUserId(""), +)`, + inviteFlow: `// Owner/admin invites new member +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.CreateMembership( + "", + []interface{}{"member"}, // Default role + teams.WithCreateMembershipEmail("user@example.com"), + teams.WithCreateMembershipUrl("https://yourapp.com/accept-invite"), +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +response, _ := service.ListMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.UpdateMembership( + "", + "", + []interface{}{"admin"}, +)`, + memberRemoval: `// Remove member (with confirmation) +package main + +import ( + "github.com/appwrite/sdk-for-go/client" + "github.com/appwrite/sdk-for-go/teams" +) + +client := client.New( + client.WithEndpoint("https://.cloud.appwrite.io/v1"), + client.WithProject(""), + client.WithSession(""), +) + +service := teams.New(client) + +service.DeleteMembership( + "", + "", +)`, + roleCheck: `response, _ := service.ListMemberships("") + +var userMembership *teams.Membership +for _, membership := range response.Memberships { + if membership.UserId == "" { + userMembership = &membership + break + } +} + +hasAdminRole := false +if userMembership != nil { + for _, role := range userMembership.Roles { + if role == "admin" { + hasAdminRole = true + break + } + } +} + +if !hasAdminRole { + panic("Insufficient permissions") +}` + }, + ruby: { + avoidUserPermissions: `# DON'T do this for multi-tenant apps +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: {'title' => 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +)`, + preferTeamPermissions: `# DO this for multi-tenant apps +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: {'title' => 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + createTeam: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Create a team when a new tenant/organization signs up +team = teams.create( + team_id: '', + name: '', + roles: ['owner', 'admin', 'member'] # optional +)`, + createMembershipEmail: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Send team invitation (email-based) +invite = teams.create_membership( + team_id: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +)`, + createMembershipUserId: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') # Server SDK + +teams = Teams.new(client) + +# Or invite by user ID (if user already exists) +membership = teams.create_membership( + team_id: '', + roles: ['admin', 'member'], + user_id: '' +)`, + listMemberships: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Get all members of a team +response = teams.list_memberships(team_id: '') + +# Access member data +response['memberships'].each do |membership| + puts membership['userId'] + puts membership['roles'] # Array of role strings + puts membership['userName'] + puts membership['userEmail'] +end`, + updateMembership: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Update a member's roles (only team owners/admins can do this) +teams.update_membership( + team_id: '', + membership_id: '', + roles: ['admin', 'member'] +)`, + deleteMembership: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Remove a member from a team +teams.delete_membership( + team_id: '', + membership_id: '' +)`, + listTeams: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# List all teams the current user belongs to +response = teams.list + +response['teams'].each do |team| + puts team['$id'] + puts team['name'] +end`, + getUserRole: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +# Get membership details for current user in a specific team +response = teams.list_memberships(team_id: '') + +user_membership = response['memberships'].find do |m| + m['userId'] == '' +end + +if user_membership + puts user_membership['roles'] # ['owner', 'admin', etc.] + has_admin_role = user_membership['roles'].include?('admin') +end`, + createRow: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +# Create document with team-based permissions +tables_db.create_row( + database_id: '', + table_id: '', + row_id: '', + data: { + 'title' => 'My Document', + 'teamId' => '', # Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +)`, + createTable: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') # Server SDK requires API key + +tables_db = TablesDB.new(client) + +# Create table with team-based permissions +tables_db.create_table( + database_id: '', + table_id: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + listRows: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +tables_db = TablesDB.new(client) + +# ALWAYS filter by teamId to ensure tenant isolation +response = tables_db.list_rows( + database_id: '', + table_id: '', + queries: [ + Query.equal('teamId', ''), # Critical: filter by team + Query.order_desc('$createdAt'), + Query.limit(25) + ] +)`, + createFile: `require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +storage = Storage.new(client) + +# Create file with team-based permissions +storage.create_file( + bucket_id: '', + file_id: '', + file: file_input, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +)`, + teamCreationFlow: `# When user creates account/organization +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +team = teams.create( + team_id: ID.unique, + name: 'Company Name' +) + +# Make creator an owner +teams.create_membership( + team_id: team['$id'], + roles: ['owner'], + user_id: '' +)`, + inviteFlow: `# Owner/admin invites new member +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +invite = teams.create_membership( + team_id: '', + roles: ['member'], # Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +) +# User receives email, clicks link, accepts invitation`, + memberListUI: `# Display all team members with their roles +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +response = teams.list_memberships(team_id: '') +# Show list with role badges and action buttons`, + roleChange: `# Admin/owner changes member role +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +teams.update_membership( + team_id: '', + membership_id: '', + roles: ['admin'] +)`, + memberRemoval: `# Remove member (with confirmation) +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_session('') + +teams = Teams.new(client) + +teams.delete_membership( + team_id: '', + membership_id: '' +)`, + roleCheck: `response = teams.list_memberships(team_id: '') + +user_membership = response['memberships'].find do |m| + m['userId'] == '' +end + +if !user_membership || !user_membership['roles'].include?('admin') + raise 'Insufficient permissions' +end` + }, + dotnet: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { title = "My Document" }, + permissions: new List { + Permission.Read(Role.User("")), + Permission.Write(Role.User("")) + } +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { title = "My Document" }, + permissions: new List { + Permission.Read(Role.Team("", "owner")), + Permission.Read(Role.Team("", "admin")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "owner")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + createTeam: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Create a team when a new tenant/organization signs up +Team team = await teams.Create( + teamId: "", + name: "", + roles: new List { "owner", "admin", "member" } // optional +);`, + createMembershipEmail: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Send team invitation (email-based) +Membership invite = await teams.CreateMembership( + teamId: "", + roles: new List { "admin", "member" }, + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +);`, + createMembershipUserId: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); // Server SDK + +Teams teams = new Teams(client); + +// Or invite by user ID (if user already exists) +Membership membership = await teams.CreateMembership( + teamId: "", + roles: new List { "admin", "member" }, + userId: "" +);`, + listMemberships: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Get all members of a team +MembershipList response = await teams.ListMemberships(teamId: ""); + +// Access member data +foreach (var membership in response.Memberships) { + Console.WriteLine(membership.UserId); + Console.WriteLine(string.Join(", ", membership.Roles)); // Array of role strings + Console.WriteLine(membership.UserName); + Console.WriteLine(membership.UserEmail); +}`, + updateMembership: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.UpdateMembership( + teamId: "", + membershipId: "", + roles: new List { "admin", "member" } +);`, + deleteMembership: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Remove a member from a team +await teams.DeleteMembership( + teamId: "", + membershipId: "" +);`, + listTeams: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// List all teams the current user belongs to +TeamList response = await teams.List(); + +foreach (var team in response.Teams) { + Console.WriteLine(team.Id); + Console.WriteLine(team.Name); +}`, + getUserRole: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using System.Linq; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +// Get membership details for current user in a specific team +MembershipList response = await teams.ListMemberships(teamId: ""); + +var userMembership = response.Memberships.FirstOrDefault( + m => m.UserId == "" +); + +if (userMembership != null) { + Console.WriteLine(string.Join(", ", userMembership.Roles)); // ['owner', 'admin', etc.] + bool hasAdminRole = userMembership.Roles.Contains("admin"); +}`, + createRow: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +// Create document with team-based permissions +await tablesDB.CreateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new { + title = "My Document", + teamId = "", // Always store teamId for querying + }, + permissions: new List { + Permission.Read(Role.Team("", "owner")), + Permission.Read(Role.Team("", "admin")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "owner")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")), + Permission.Delete(Role.Team("", "admin")) + } +);`, + createTable: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); // Server SDK requires API key + +TablesDB tablesDB = new TablesDB(client); + +// Create table with team-based permissions +await tablesDB.CreateTable( + databaseId: "", + tableId: "", + name: "", + permissions: new List { + Permission.Create(Role.Team("", "member")), + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + listRows: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Query; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +TablesDB tablesDB = new TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +RowList response = await tablesDB.ListRows( + databaseId: "", + tableId: "", + queries: new List { + Query.Equal("teamId", ""), // Critical: filter by team + Query.OrderDesc("$createdAt"), + Query.Limit(25) + } +);`, + createFile: `using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Storage storage = new Storage(client); + +// Create file with team-based permissions +await storage.CreateFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: new List { + Permission.Read(Role.Team("", "member")), + Permission.Update(Role.Team("", "admin")), + Permission.Delete(Role.Team("", "owner")) + } +);`, + teamCreationFlow: `// When user creates account/organization +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.ID; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +Team team = await teams.Create( + teamId: ID.Unique(), + name: "Company Name" +); + +// Make creator an owner +await teams.CreateMembership( + teamId: team.Id, + roles: new List { "owner" }, + userId: "" +);`, + inviteFlow: `// Owner/admin invites new member +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +Membership invite = await teams.CreateMembership( + teamId: "", + roles: new List { "member" }, // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +MembershipList response = await teams.ListMemberships(teamId: ""); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +await teams.UpdateMembership( + teamId: "", + membershipId: "", + roles: new List { "admin" } +);`, + memberRemoval: `// Remove member (with confirmation) +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetSession(""); + +Teams teams = new Teams(client); + +await teams.DeleteMembership( + teamId: "", + membershipId: "" +);`, + roleCheck: `MembershipList response = await teams.ListMemberships(teamId: ""); + +var userMembership = response.Memberships.FirstOrDefault( + m => m.UserId == "" +); + +if (userMembership == null || !userMembership.Roles.Contains("admin")) { + throw new Exception("Insufficient permissions"); +}` + }, + swift: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ] +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + createTeam: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Create a team when a new tenant/organization signs up +let team = try await teams.create( + teamId: "", + name: "", + roles: ["owner", "admin", "member"] // optional +)`, + createMembershipEmail: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Send team invitation (email-based) +let invite = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +)`, + createMembershipUserId: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK + +let teams = Teams(client) + +// Or invite by user ID (if user already exists) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + userId: "" +)`, + listMemberships: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Get all members of a team +let response = try await teams.listMemberships(teamId: "") + +// Access member data +for membership in response.memberships { + print(membership.userId) + print(membership.roles) // Array of role strings + print(membership.userName ?? "") + print(membership.userEmail ?? "") +}`, + updateMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin", "member"] +)`, + deleteMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Remove a member from a team +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + listTeams: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// List all teams the current user belongs to +let response = try await teams.list() + +for team in response.teams { + print(team.id) + print(team.name) +}`, + getUserRole: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +// Get membership details for current user in a specific team +let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if let membership = userMembership { + print(membership.roles) // ['owner', 'admin', etc.] + let hasAdminRole = membership.roles.contains("admin") +}`, + createRow: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +// Create document with team-based permissions +try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: [ + "title": "My Document", + "teamId": "", // Always store teamId for querying + ], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ] +)`, + createTable: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +let tablesDB = TablesDB(client) + +// Create table with team-based permissions +try await tablesDB.createTable( + databaseId: "", + tableId: "", + name: "", + permissions: [ + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + listRows: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +let response = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ] +)`, + createFile: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let storage = Storage(client) + +// Create file with team-based permissions +try await storage.createFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: [ + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + teamCreationFlow: `// When user creates account/organization +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let team = try await teams.create( + teamId: ID.unique(), + name: "Company Name" +) + +// Make creator an owner +try await teams.createMembership( + teamId: team.id, + roles: ["owner"], + userId: "" +)`, + inviteFlow: `// Owner/admin invites new member +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let invite = try await teams.createMembership( + teamId: "", + roles: ["member"], // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +let response = try await teams.listMemberships(teamId: "") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin"] +)`, + memberRemoval: `// Remove member (with confirmation) +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +let teams = Teams(client) + +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + roleCheck: `let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if userMembership == nil || !userMembership!.roles.contains("admin") { + throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) +}` + }, + kotlin: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ) +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + createTeam: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Create a team when a new tenant/organization signs up +teams.create( + "", // teamId + "", // name + listOf("owner", "admin", "member") // roles (optional) +)`, + createMembershipEmail: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Send team invitation (email-based) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + "user@example.com", // email + null, // userId (optional) + null, // phone (optional) + "https://yourapp.com/accept-invite", // url + null // name (optional) +)`, + createMembershipUserId: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK + +val teams = Teams(client) + +// Or invite by user ID (if user already exists) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + listMemberships: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Get all members of a team +val response = teams.listMemberships( + "", // teamId + listOf(), // queries (optional) + null // search (optional) +) + +// Access member data +response.memberships.forEach { membership -> + println(membership.userId) + println(membership.roles) // Array of role strings + println(membership.userName) + println(membership.userEmail) +}`, + updateMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin", "member") // roles +)`, + deleteMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Remove a member from a team +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + listTeams: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// List all teams the current user belongs to +val response = teams.list( + listOf(), // queries (optional) + null // search (optional) +) + +response.teams.forEach { team -> + println(team.id) + println(team.name) +}`, + getUserRole: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +// Get membership details for current user in a specific team +val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership != null) { + println(userMembership.roles) // ['owner', 'admin', etc.] + val hasAdminRole = userMembership.roles.contains("admin") +}`, + createRow: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +// Create document with team-based permissions +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf( + "title" to "My Document", + "teamId" to "", // Always store teamId for querying + ), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ) +)`, + createTable: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +val tablesDB = TablesDB(client) + +// Create table with team-based permissions +tablesDB.createTable( + databaseId = "", + tableId = "", + name = "", + permissions = listOf( + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + listRows: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.Query + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +val response = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ) +)`, + createFile: `import io.appwrite.Client +import io.appwrite.services.Storage +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val storage = Storage(client) + +// Create file with team-based permissions +storage.createFile( + bucketId = "", + fileId = "", + file = fileInput, + permissions = listOf( + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + teamCreationFlow: `// When user creates account/organization +import io.appwrite.Client +import io.appwrite.services.Teams +import io.appwrite.ID + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +val team = teams.create( + ID.unique(), + "Company Name" +) + +// Make creator an owner +teams.createMembership( + team.id, // teamId + listOf("owner"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + inviteFlow: `// Owner/admin invites new member +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.createMembership( + "", // teamId + listOf("member"), // Default role + "user@example.com", // email + null, // userId + null, // phone + "https://yourapp.com/accept-invite", // url + null // name +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +val response = teams.listMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin") // roles +)`, + memberRemoval: `// Remove member (with confirmation) +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setSession("") + +val teams = Teams(client) + +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + roleCheck: `val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership == null || !userMembership.roles.contains("admin")) { + throw Exception("Insufficient permissions") +}` + }, + apple: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ] +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: ["title": "My Document"], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + createTeam: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Create a team when a new tenant/organization signs up +let team = try await teams.create( + teamId: "", + name: "", + roles: ["owner", "admin", "member"] // optional +)`, + createMembershipEmail: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Send team invitation (email-based) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +)`, + createMembershipUserId: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Or invite by user ID (if user already exists) +let membership = try await teams.createMembership( + teamId: "", + roles: ["admin", "member"], + userId: "" +)`, + listMemberships: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Get all members of a team +let response = try await teams.listMemberships( + teamId: "" +) + +// Access member data +for membership in response.memberships { + print(membership.userId) + print(membership.roles) // Array of role strings + print(membership.userName ?? "") + print(membership.userEmail ?? "") +}`, + updateMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +let membership = try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin", "member"] +)`, + deleteMembership: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Remove a member from a team +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + listTeams: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// List all teams the current user belongs to +let response = try await teams.list() + +for team in response.teams { + print(team.id) + print(team.name) +}`, + getUserRole: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +// Get membership details for current user in a specific team +let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if let membership = userMembership { + print(membership.roles) // ['owner', 'admin', etc.] + let hasAdminRole = membership.roles.contains("admin") +}`, + createRow: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +// Create document with team-based permissions +let row = try await tablesDB.createRow( + databaseId: "", + tableId: "", + rowId: "", + data: [ + "title": "My Document", + "teamId": "", // Always store teamId for querying + ], + permissions: [ + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ] +)`, + createTable: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +let tablesDB = TablesDB(client) + +// Create table with team-based permissions +let table = try await tablesDB.createTable( + databaseId: "", + tableId: "", + name: "", + permissions: [ + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + listRows: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +let response = try await tablesDB.listRows( + databaseId: "", + tableId: "", + queries: [ + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ] +)`, + createFile: `import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let storage = Storage(client) + +// Create file with team-based permissions +let file = try await storage.createFile( + bucketId: "", + fileId: "", + file: fileInput, + permissions: [ + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ] +)`, + teamCreationFlow: `// When user creates account/organization +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let team = try await teams.create( + teamId: ID.unique(), + name: "Company Name" +) + +// Make creator an owner +try await teams.createMembership( + teamId: team.id, + roles: ["owner"], + userId: "" +)`, + inviteFlow: `// Owner/admin invites new member +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let invite = try await teams.createMembership( + teamId: "", + roles: ["member"], // Default role + email: "user@example.com", + url: "https://yourapp.com/accept-invite" +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let response = try await teams.listMemberships(teamId: "") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +let membership = try await teams.updateMembership( + teamId: "", + membershipId: "", + roles: ["admin"] +)`, + memberRemoval: `// Remove member (with confirmation) +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let teams = Teams(client) + +try await teams.deleteMembership( + teamId: "", + membershipId: "" +)`, + roleCheck: `let response = try await teams.listMemberships(teamId: "") + +let userMembership = response.memberships.first { $0.userId == "" } + +if userMembership == nil || !userMembership!.roles.contains("admin") { + throw NSError(domain: "Appwrite", code: 403, userInfo: [NSLocalizedDescriptionKey: "Insufficient permissions"]) +}` + }, + android: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.user("")), + Permission.write(Role.user("")) + ) +)`, + preferTeamPermissions: `// DO this for multi-tenant apps +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf("title" to "My Document"), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + createTeam: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Create a team when a new tenant/organization signs up +teams.create( + "", // teamId + "", // name + listOf("owner", "admin", "member") // roles (optional) +)`, + createMembershipEmail: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Send team invitation (email-based) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + "user@example.com", // email + null, // userId (optional) + null, // phone (optional) + "https://yourapp.com/accept-invite", // url + null // name (optional) +)`, + createMembershipUserId: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Or invite by user ID (if user already exists) +teams.createMembership( + "", // teamId + listOf("admin", "member"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + listMemberships: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Get all members of a team +val response = teams.listMemberships( + "", // teamId + listOf(), // queries (optional) + null // search (optional) +) + +// Access member data +response.memberships.forEach { membership -> + Log.d("Appwrite", membership.userId) + Log.d("Appwrite", membership.roles.toString()) // Array of role strings + Log.d("Appwrite", membership.userName ?: "") + Log.d("Appwrite", membership.userEmail ?: "") +}`, + updateMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Update a member's roles (only team owners/admins can do this) +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin", "member") // roles +)`, + deleteMembership: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Remove a member from a team +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + listTeams: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// List all teams the current user belongs to +val response = teams.list( + listOf(), // queries (optional) + null // search (optional) +) + +response.teams.forEach { team -> + Log.d("Appwrite", team.id) + Log.d("Appwrite", team.name) +}`, + getUserRole: `import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +// Get membership details for current user in a specific team +val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership != null) { + Log.d("Appwrite", userMembership.roles.toString()) // ['owner', 'admin', etc.] + val hasAdminRole = userMembership.roles.contains("admin") +}`, + createRow: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +// Create document with team-based permissions +tablesDB.createRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf( + "title" to "My Document", + "teamId" to "", // Always store teamId for querying + ), + permissions = listOf( + Permission.read(Role.team("", "owner")), + Permission.read(Role.team("", "admin")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "owner")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")), + Permission.delete(Role.team("", "admin")) + ) +)`, + createTable: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") // Server SDK requires API key + +val tablesDB = TablesDB(client) + +// Create table with team-based permissions +tablesDB.createTable( + databaseId = "", + tableId = "", + name = "", + permissions = listOf( + Permission.create(Role.team("", "member")), + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + listRows: `import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.Query + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val tablesDB = TablesDB(client) + +// ALWAYS filter by teamId to ensure tenant isolation +val response = tablesDB.listRows( + databaseId = "", + tableId = "", + queries = listOf( + Query.equal("teamId", ""), // Critical: filter by team + Query.orderDesc("$createdAt"), + Query.limit(25) + ) +)`, + createFile: `import io.appwrite.Client +import io.appwrite.services.Storage +import io.appwrite.models.Permission +import io.appwrite.models.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val storage = Storage(client) + +// Create file with team-based permissions +storage.createFile( + bucketId = "", + fileId = "", + file = fileInput, + permissions = listOf( + Permission.read(Role.team("", "member")), + Permission.update(Role.team("", "admin")), + Permission.delete(Role.team("", "owner")) + ) +)`, + teamCreationFlow: `// When user creates account/organization +import io.appwrite.Client +import io.appwrite.services.Teams +import io.appwrite.ID + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +val team = teams.create( + ID.unique(), + "Company Name" +) + +// Make creator an owner +teams.createMembership( + team.id, // teamId + listOf("owner"), // roles + null, // email + "", // userId + null, // phone + null, // url + null // name +)`, + inviteFlow: `// Owner/admin invites new member +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.createMembership( + "", // teamId + listOf("member"), // Default role + "user@example.com", // email + null, // userId + null, // phone + "https://yourapp.com/accept-invite", // url + null // name +) +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +val response = teams.listMemberships("") +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.updateMembership( + "", // teamId + "", // membershipId + listOf("admin") // roles +)`, + memberRemoval: `// Remove member (with confirmation) +import io.appwrite.Client +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val teams = Teams(client) + +teams.deleteMembership( + "", // teamId + "" // membershipId +)`, + roleCheck: `val response = teams.listMemberships("") + +val userMembership = response.memberships.find { + it.userId == "" +} + +if (userMembership == null || !userMembership.roles.contains("admin")) { + throw Exception("Insufficient permissions") +}` + }, + flutter: { + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + createTeam: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Create a team when a new tenant/organization signs up +final team = await teams.create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Send team invitation (email-based) +final invite = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Or invite by user ID (if user already exists) +final membership = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get all members of a team +final response = await teams.listMemberships(teamId: ''); + +// Access member data +for (final membership in response.memberships) { + print(membership.userId); + print(membership.roles); // Array of role strings + print(membership.userName); + print(membership.userEmail); +}`, + updateMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Remove a member from a team +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// List all teams the current user belongs to +final response = await teams.list(); + +for (final team in response.teams) { + print(team.id); + print(team.name); +}`, + getUserRole: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get membership details for current user in a specific team +final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership != null) { + print(userMembership.roles); // ['owner', 'admin', etc.] + final hasAdminRole = userMembership.roles.contains('admin'); +}`, + createRow: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// Create document with team-based permissions +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: { + 'title': 'My Document', + 'teamId': '', // Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +);`, + createTable: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +final tablesDB = TablesDB(client); + +// Create table with team-based permissions +final table = await tablesDB.createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + listRows: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +final response = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('\$createdAt'), + Query.limit(25) + ] +);`, + createFile: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final storage = Storage(client); + +// Create file with team-based permissions +final file = await storage.createFile( + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + teamCreationFlow: `// When user creates account/organization +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final team = await teams.create( + teamId: ID.unique(), + name: 'Company Name' +); + +// Make creator an owner +await teams.createMembership( + teamId: team.id, + roles: ['owner'], + userId: '' +);`, + inviteFlow: `// Owner/admin invites new member +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final invite = await teams.createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final response = await teams.listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `// Remove member (with confirmation) +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership == null || !userMembership.roles.contains('admin')) { + throw Exception('Insufficient permissions'); +}` + }, + dart: { + // Dart uses the same syntax as Flutter + avoidUserPermissions: `// DON'T do this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.user('')), + Permission.write(Role.user('')) + ] +);`, + preferTeamPermissions: `// DO this for multi-tenant apps +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: {'title': 'My Document'}, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + createTeam: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Create a team when a new tenant/organization signs up +final team = await teams.create( + teamId: '', + name: '', + roles: ['owner', 'admin', 'member'] // optional +);`, + createMembershipEmail: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Send team invitation (email-based) +final invite = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +);`, + createMembershipUserId: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Or invite by user ID (if user already exists) +final membership = await teams.createMembership( + teamId: '', + roles: ['admin', 'member'], + userId: '' +);`, + listMemberships: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get all members of a team +final response = await teams.listMemberships(teamId: ''); + +// Access member data +for (final membership in response.memberships) { + print(membership.userId); + print(membership.roles); // Array of role strings + print(membership.userName); + print(membership.userEmail); +}`, + updateMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Update a member's roles (only team owners/admins can do this) +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin', 'member'] +);`, + deleteMembership: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Remove a member from a team +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + listTeams: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// List all teams the current user belongs to +final response = await teams.list(); + +for (final team in response.teams) { + print(team.id); + print(team.name); +}`, + getUserRole: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +// Get membership details for current user in a specific team +final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership != null) { + print(userMembership.roles); // ['owner', 'admin', etc.] + final hasAdminRole = userMembership.roles.contains('admin'); +}`, + createRow: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// Create document with team-based permissions +final row = await tablesDB.createRow( + databaseId: '', + tableId: '', + rowId: '', + data: { + 'title': 'My Document', + 'teamId': '', // Always store teamId for querying + }, + permissions: [ + Permission.read(Role.team('', 'owner')), + Permission.read(Role.team('', 'admin')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'owner')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')), + Permission.delete(Role.team('', 'admin')) + ] +);`, + createTable: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); // Server SDK requires API key + +final tablesDB = TablesDB(client); + +// Create table with team-based permissions +final table = await tablesDB.createTable( + databaseId: '', + tableId: '', + name: '', + permissions: [ + Permission.create(Role.team('', 'member')), + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + listRows: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final tablesDB = TablesDB(client); + +// ALWAYS filter by teamId to ensure tenant isolation +final response = await tablesDB.listRows( + databaseId: '', + tableId: '', + queries: [ + Query.equal('teamId', ''), // Critical: filter by team + Query.orderDesc('\$createdAt'), + Query.limit(25) + ] +);`, + createFile: `import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final storage = Storage(client); + +// Create file with team-based permissions +final file = await storage.createFile( + bucketId: '', + fileId: '', + file: fileInput, + permissions: [ + Permission.read(Role.team('', 'member')), + Permission.update(Role.team('', 'admin')), + Permission.delete(Role.team('', 'owner')) + ] +);`, + teamCreationFlow: `// When user creates account/organization +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final team = await teams.create( + teamId: ID.unique(), + name: 'Company Name' +); + +// Make creator an owner +await teams.createMembership( + teamId: team.id, + roles: ['owner'], + userId: '' +);`, + inviteFlow: `// Owner/admin invites new member +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final invite = await teams.createMembership( + teamId: '', + roles: ['member'], // Default role + email: 'user@example.com', + url: 'https://yourapp.com/accept-invite' +); +// User receives email, clicks link, accepts invitation`, + memberListUI: `// Display all team members with their roles +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +final response = await teams.listMemberships(teamId: ''); +// Show list with role badges and action buttons`, + roleChange: `// Admin/owner changes member role +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.updateMembership( + teamId: '', + membershipId: '', + roles: ['admin'] +);`, + memberRemoval: `// Remove member (with confirmation) +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final teams = Teams(client); + +await teams.deleteMembership( + teamId: '', + membershipId: '' +);`, + roleCheck: `final response = await teams.listMemberships(teamId: ''); + +final userMembership = response.memberships.firstWhere( + (m) => m.userId == '', + orElse: () => null, +); + +if (userMembership == null || !userMembership.roles.contains('admin')) { + throw Exception('Insufficient permissions'); +}` + } +}; + +/** + * Get permission examples for a specific SDK + * @param {string} sdk - The SDK name (javascript, python, php, go, etc.) + * @returns {Object} Permission examples for the SDK + */ +export function getPermissionExamples(sdk) { + // Map SDK names to their examples + /** @type {Record} */ + const sdkMap = { + javascript: permissionExamples.javascript, + 'react-native': permissionExamples['react-native'], + python: permissionExamples.python, + php: permissionExamples.php, + go: permissionExamples.go, + ruby: permissionExamples.ruby, + dotnet: permissionExamples.dotnet, + swift: permissionExamples.swift, + kotlin: permissionExamples.kotlin, + apple: permissionExamples.apple, + android: permissionExamples.android, + flutter: permissionExamples.flutter, + dart: permissionExamples.dart + }; + + return sdkMap[sdk] || permissionExamples.javascript; // Default to JavaScript +} + diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index c81b805..30d8d17 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -233,6 +233,9 @@ When building applications that involve multiple users or tenants: */ async function generatePermissionsSection(sdk, framework) { const { authProductLinks } = await import('./languages/common/products.js'); + const { getPermissionExamples } = await import('./languages/common/permissions-examples.js'); + const examples = getPermissionExamples(sdk); + return `## Permissions & Multi-Tenancy This section is CRITICAL for building secure, scalable applications with Appwrite. Multi-tenancy is one of the most important architectural patterns in modern applications, and Appwrite's team-based permission system is designed specifically for this. @@ -246,12 +249,8 @@ Multi-tenancy allows a single application instance to serve multiple isolated gr **ALWAYS PREFER TEAM/MEMBER-BASED ROLES over user-specific roles.** This is a fundamental architectural decision: #### Avoid: User-Specific Permissions -\`\`\` -// DON'T do this for multi-tenant apps -create(collectionId, data, [ - Permission.read(Role.user(userId1)), - Permission.write(Role.user(userId1)) -]) +\`\`\`${getLanguageFromSdk(sdk)} +${examples.avoidUserPermissions} \`\`\` **Problems with user-specific permissions:** @@ -262,16 +261,8 @@ create(collectionId, data, [ - Maintenance nightmare as teams grow #### Prefer: Team/Member-Based Roles -\`\`\` -// DO this for multi-tenant apps -create(collectionId, data, [ - Permission.read(Role.team(teamId, "owner")), - Permission.read(Role.team(teamId, "admin")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "owner")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) -]) +\`\`\`${getLanguageFromSdk(sdk)} +${examples.preferTeamPermissions} \`\`\` **Benefits of team/member-based roles:** @@ -288,15 +279,8 @@ create(collectionId, data, [ Teams in Appwrite represent tenants. Each team should map to a business entity (company, organization, workspace, etc.). **Creating a team:** -\`\`\` -import { Teams } from 'appwrite'; - -// Create a team when a new tenant/organization signs up -const team = await teams.create( - teamId, // Unique team ID (can be auto-generated) - teamName, // Display name - roles // Array of role strings: ['owner', 'admin', 'member'] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createTeam} \`\`\` #### Step 2: Define Custom Roles @@ -308,9 +292,7 @@ Create roles that match your application's permission model. Common roles: - **viewer**: Read-only access **Creating custom roles (Server-side only):** -\`\`\` -import { Teams } from 'appwrite'; - +\`\`\`${getLanguageFromSdk(sdk)} // Define roles when creating the team (optional, defaults exist) // Or create via Appwrite Console or Server SDK // Roles are created per team, allowing different permission models per tenant @@ -322,94 +304,42 @@ Member management is the foundation of multi-tenant applications. Here's how to **A. Invite Members to Teams** +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createMembershipEmail} \`\`\` -import { Teams } from 'appwrite'; - -// Send team invitation (email-based) -const invite = await teams.createMembership( - teamId, - email, // Email of user to invite - roles, // Array of role strings: ['admin', 'member'] - url // Invitation redirect URL -); - -// Or invite by user ID (if user already exists) -const membership = await teams.createMembership( - teamId, - userId, - roles -); + +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createMembershipUserId} \`\`\` **B. List Team Members** -\`\`\` -import { Teams } from 'appwrite'; - -// Get all members of a team -const memberships = await teams.listMemberships(teamId); - -// Access member data -memberships.memberships.forEach(membership => { - console.log(membership.userId); - console.log(membership.roles); // Array of role strings - console.log(membership.userName); - console.log(membership.userEmail); -}); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listMemberships} \`\`\` **C. Update Member Roles** -\`\`\` -import { Teams } from 'appwrite'; - -// Update a member's roles (only team owners/admins can do this) -await teams.updateMembershipRoles( - teamId, - membershipId, - ['admin', 'member'] // New roles array -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.updateMembership} \`\`\` **D. Remove Members** -\`\`\` -import { Teams } from 'appwrite'; - -// Remove a member from a team -await teams.deleteMembership(teamId, membershipId); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.deleteMembership} \`\`\` **E. Get Current User's Teams** -\`\`\` -import { Teams } from 'appwrite'; - -// List all teams the current user belongs to -const teams = await teams.list(); - -teams.teams.forEach(team => { - console.log(team.$id); - console.log(team.name); -}); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listTeams} \`\`\` **F. Get Current User's Role in a Team** -\`\`\` -import { Teams } from 'appwrite'; - -// Get membership details for current user in a specific team -const memberships = await teams.listMemberships(teamId); - -const userMembership = memberships.memberships.find( - m => m.userId === currentUserId -); - -if (userMembership) { - console.log(userMembership.roles); // ['owner', 'admin', etc.] - const hasAdminRole = userMembership.roles.includes('admin'); -} +\`\`\`${getLanguageFromSdk(sdk)} +${examples.getUserRole} \`\`\` #### Step 4: Apply Permissions in Collections @@ -418,91 +348,32 @@ When creating documents in multi-tenant applications, always use team roles: **Database Collections:** -\`\`\` -import { TablesDB, Permission, Role } from 'appwrite'; - -// Create document with team-based permissions -await tablesdb.createRow( - databaseId, - tableId, - documentId, - { - title: 'My Document', - teamId: teamId, // Always store teamId for querying - // ... other fields - }, - [ - // Owners and admins can do everything - Permission.read(Role.team(teamId, "owner")), - Permission.read(Role.team(teamId, "admin")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "owner")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")), - Permission.delete(Role.team(teamId, "admin")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createRow} \`\`\` **Collection-Level Permissions:** When creating collections, set default permissions: -\`\`\` -import { TablesDB, Permission, Role } from 'appwrite'; - -// Create collection with team-based permissions -await tablesdb.createTable( - databaseId, - tableId, - tableName, - [ - // Collection permissions - Permission.create(Role.team(teamId, "member")), - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createTable} \`\`\` #### Step 5: Query with Team Isolation Always filter queries by teamId to ensure data isolation: -\`\`\` -import { TablesDB, Query } from 'appwrite'; - -// ALWAYS filter by teamId to ensure tenant isolation -const documents = await tablesdb.listDocuments( - databaseId, - tableId, - [ - Query.equal('teamId', teamId), // Critical: filter by team - Query.orderDesc('$createdAt'), - Query.limit(25) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.listRows} \`\`\` #### Step 6: Storage Permissions Apply the same team-based permission pattern to storage: -\`\`\` -import { Storage, Permission, Role } from 'appwrite'; - -// Create file with team-based permissions -await storage.createFile( - bucketId, - fileId, - fileInput, - [ - Permission.read(Role.team(teamId, "member")), - Permission.update(Role.team(teamId, "admin")), - Permission.delete(Role.team(teamId, "owner")) - ] -); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.createFile} \`\`\` ### Complete Member Management Implementation Pattern @@ -510,42 +381,28 @@ await storage.createFile( Here's a complete pattern for building member management UI and logic: **1. Team Creation Flow:** -\`\`\` -// When user creates account/organization -const team = await teams.create(uniqueId(), 'Company Name'); -// Make creator an owner -await teams.createMembership(team.$id, userId, ['owner']); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.teamCreationFlow} \`\`\` **2. Invite Flow:** -\`\`\` -// Owner/admin invites new member -const invite = await teams.createMembership( - teamId, - email, - ['member'], // Default role - 'https://yourapp.com/accept-invite' // Redirect after accepting -); -// User receives email, clicks link, accepts invitation +\`\`\`${getLanguageFromSdk(sdk)} +${examples.inviteFlow} \`\`\` **3. Member List UI:** -\`\`\` -// Display all team members with their roles -const memberships = await teams.listMemberships(teamId); -// Show list with role badges and action buttons +\`\`\`${getLanguageFromSdk(sdk)} +${examples.memberListUI} \`\`\` **4. Role Change:** -\`\`\` -// Admin/owner changes member role -await teams.updateMembershipRoles(teamId, membershipId, ['admin']); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.roleChange} \`\`\` **5. Member Removal:** -\`\`\` -// Remove member (with confirmation) -await teams.deleteMembership(teamId, membershipId); +\`\`\`${getLanguageFromSdk(sdk)} +${examples.memberRemoval} \`\`\` ### Permission Best Practices @@ -563,11 +420,8 @@ await teams.deleteMembership(teamId, membershipId); 6. **Query Isolation**: Always include \`teamId\` in queries to prevent cross-tenant data leaks 7. **Role Checks**: Before allowing sensitive operations, check the user's role in the team: - \`\`\` - const membership = await getCurrentUserMembership(teamId); - if (!membership.roles.includes('admin')) { - throw new Error('Insufficient permissions'); - } + \`\`\`${getLanguageFromSdk(sdk)} +${examples.roleCheck} \`\`\` 8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions @@ -614,6 +468,30 @@ ${authProductLinks} For comprehensive permission patterns and examples, always refer to the official Appwrite documentation on Teams, Multi-tenancy, and Permissions.`; } +/** + * Get language identifier for code blocks based on SDK + * @param {string} sdk + * @returns {string} + */ +function getLanguageFromSdk(sdk) { + const languageMap = { + javascript: 'javascript', + 'react-native': 'javascript', + python: 'python', + php: 'php', + go: 'go', + flutter: 'dart', + dart: 'dart', + apple: 'swift', + android: 'kotlin', + swift: 'swift', + kotlin: 'kotlin', + ruby: 'ruby', + dotnet: 'csharp' + }; + return languageMap[sdk] || 'javascript'; +} + /** * @param {string} sdk * @param {string} framework From 5d7e36750dbd33c84df514b6349e9982634ef87f Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:42:38 +0530 Subject: [PATCH 11/16] Add API endpoints for SDK listing and rule generation --- README.md | 93 +++++++++++++++++++++++++++++++++ src/routes/api/rules/+server.js | 85 ++++++++++++++++++++++++++++++ src/routes/api/sdks/+server.js | 36 +++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/routes/api/rules/+server.js create mode 100644 src/routes/api/sdks/+server.js diff --git a/README.md b/README.md index 72eb616..3e07947 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,94 @@ pnpm dev The generated rules file can be used in Cursor IDE to provide AI-assisted development guidance specific to your Appwrite setup. +## API Endpoints + +The application exposes REST API endpoints for programmatic access to rule generation. + +### Get Available SDKs and Frameworks + +**GET** `/api/sdks` + +Returns a list of all available SDKs, their frameworks, and available features. + +**Response:** +```json +{ + "sdks": [ + { + "id": "javascript", + "name": "JavaScript/TypeScript", + "frameworks": ["nextjs", "react", "vue", ...], + "importSyntax": "import", + "exportSyntax": "export", + "asyncSyntax": "async/await" + }, + ... + ], + "availableFeatures": ["auth", "database", "storage", "functions", "messaging", "sites", "realtime"] +} +``` + +### Generate Rules + +**GET** `/api/rules` + +Generate rules using query parameters. + +**Query Parameters:** +- `sdk` (required): SDK identifier (e.g., `javascript`, `python`, `go`) +- `framework` (required): Framework identifier (e.g., `nextjs`, `react`, `flask`) +- `features` (optional): Comma-separated list of features (default: `auth`) + - Available: `auth`, `database`, `storage`, `functions`, `messaging`, `sites`, `realtime` + - Special: `all` - includes all available features +- `mcp` (optional): Include MCP recommendations (`true` or `false`, default: `false`) +- `format` (optional): Response format (`text` or `json`, default: `text`) + +**Example:** +```bash +# Get rules as markdown text +curl "http://localhost:5173/api/rules?sdk=javascript&framework=nextjs&features=auth,database&format=text" + +# Get all features +curl "http://localhost:5173/api/rules?sdk=javascript&framework=nextjs&features=all" + +# Get rules as JSON +curl "http://localhost:5173/api/rules?sdk=python&framework=flask&features=auth,storage&format=json" +``` + +**Response (format=text):** +- Content-Type: `text/markdown; charset=utf-8` +- Returns the generated rules as markdown text +- Includes `Content-Disposition` header for file download + +**Response (format=json):** +```json +{ + "sdk": "javascript", + "framework": "nextjs", + "features": ["auth", "database"], + "includeMCP": false, + "rules": "---\ndescription: You are an expert developer...\n---\n\n# Appwrite Development Rules\n..." +} +``` + +**Error Responses:** +- `400 Bad Request`: Invalid SDK or framework +- `500 Internal Server Error`: Server error during rule generation + +**Example Usage:** + +```javascript +// Fetch rules using fetch API +const response = await fetch('/api/rules?sdk=javascript&framework=nextjs&features=auth,database'); +const rules = await response.text(); + +// Get all features as JSON +const response = await fetch('/api/rules?sdk=javascript&framework=nextjs&features=all&format=json'); +const data = await response.json(); +console.log(data.rules); +``` + ## Development ### Available Scripts @@ -98,6 +186,11 @@ appwrite-cursor-rules/ │ │ ├── rules-generator.js # Main rules generation logic │ │ └── utils/ # Utility functions │ └── routes/ +│ ├── api/ +│ │ ├── rules/ +│ │ │ └── +server.js # API endpoint for generating rules +│ │ └── sdks/ +│ │ └── +server.js # API endpoint for listing SDKs │ ├── +page.svelte # Main application page │ └── +layout.svelte # Layout component ├── scripts/ diff --git a/src/routes/api/rules/+server.js b/src/routes/api/rules/+server.js new file mode 100644 index 0000000..65f8a03 --- /dev/null +++ b/src/routes/api/rules/+server.js @@ -0,0 +1,85 @@ +import { generateRules, SDK_OPTIONS } from '$lib/rules-generator.js'; +import { json, text } from '@sveltejs/kit'; + +// All available features +const ALL_FEATURES = ['auth', 'database', 'storage', 'functions', 'messaging', 'sites', 'realtime']; + +/** + * Normalize features array - replace 'all' with all available features + * @param {string[]} features + * @returns {string[]} + */ +function normalizeFeatures(features) { + if (features.includes('all')) { + return ALL_FEATURES; + } + return features; +} + +/** + * @param {{ url: URL }} event + */ +export async function GET({ url }) { + try { + const sdk = url.searchParams.get('sdk') || 'javascript'; + const framework = url.searchParams.get('framework') || 'nextjs'; + const featuresParam = url.searchParams.get('features'); + const includeMCP = url.searchParams.get('mcp') === 'true'; + const format = url.searchParams.get('format') || 'text'; // 'text' or 'json' + + // Validate SDK + if (!SDK_OPTIONS[sdk]) { + return json( + { error: `Invalid SDK: ${sdk}. Available SDKs: ${Object.keys(SDK_OPTIONS).join(', ')}` }, + { status: 400 } + ); + } + + // Validate framework + const sdkInfo = SDK_OPTIONS[sdk]; + if (!sdkInfo.frameworks.includes(framework)) { + return json( + { + error: `Invalid framework: ${framework} for SDK: ${sdk}. Available frameworks: ${sdkInfo.frameworks.join(', ')}` + }, + { status: 400 } + ); + } + + // Parse and normalize features + const features = normalizeFeatures( + featuresParam ? featuresParam.split(',').filter(Boolean) : ['auth'] + ); + + // Generate rules + const rules = await generateRules({ + sdk, + framework, + features, + includeMCP + }); + + // Return based on format + if (format === 'json') { + return json({ + sdk, + framework, + features, + includeMCP, + rules + }); + } + + return text(rules, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Content-Disposition': `attachment; filename="APPWRITE-${sdk}-${framework}.mdc"` + } + }); + } catch (error) { + console.error('Error generating rules:', error); + const errorMessage = error instanceof Error ? error.message : 'Failed to generate rules'; + return json({ error: errorMessage }, { status: 500 }); + } +} + diff --git a/src/routes/api/sdks/+server.js b/src/routes/api/sdks/+server.js new file mode 100644 index 0000000..744ddcc --- /dev/null +++ b/src/routes/api/sdks/+server.js @@ -0,0 +1,36 @@ +import { SDK_OPTIONS } from '$lib/rules-generator.js'; +import { json } from '@sveltejs/kit'; + +/** + * @param {Request} request + */ +export async function GET() { + try { + const sdks = Object.entries(SDK_OPTIONS).map(([key, value]) => ({ + id: key, + name: value.name, + frameworks: value.frameworks, + importSyntax: value.importSyntax, + exportSyntax: value.exportSyntax, + asyncSyntax: value.asyncSyntax + })); + + return json({ + sdks, + availableFeatures: [ + 'auth', + 'database', + 'storage', + 'functions', + 'messaging', + 'sites', + 'realtime', + 'all' + ] + }); + } catch (error) { + console.error('Error fetching SDKs:', error); + return json({ error: error.message || 'Failed to fetch SDKs' }, { status: 500 }); + } +} + From 9c347b542ee6bdf3d91d5e4a43d02ededc755636 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:54:09 +0530 Subject: [PATCH 12/16] Replace "document" with "row" and "collection" with "table" --- .../languages/common/permissions-examples.js | 104 +++++++++--------- src/lib/rules-generator.js | 24 ++-- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/lib/languages/common/permissions-examples.js b/src/lib/languages/common/permissions-examples.js index 0661d5e..6ff4a62 100644 --- a/src/lib/languages/common/permissions-examples.js +++ b/src/lib/languages/common/permissions-examples.js @@ -12,7 +12,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -25,7 +25,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -169,13 +169,13 @@ const client = new Client() const tablesDB = new TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', data: { - title: 'My Document', + title: 'My Row', teamId: '', // Always store teamId for querying // ... other fields }, @@ -205,7 +205,7 @@ await tablesDB.createTable({ tableId: '', name: '', permissions: [ - // Collection permissions + // Table permissions Permission.create(Role.team('', 'member')), Permission.read(Role.team('', 'member')), Permission.update(Role.team('', 'admin')), @@ -346,7 +346,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -359,7 +359,7 @@ await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', - data: { title: 'My Document' }, + data: { title: 'My Row' }, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -499,7 +499,7 @@ await tablesDB.createRow({ tableId: '', rowId: '', data: { - title: 'My Document', + title: 'My Row', teamId: '', }, permissions: [ @@ -664,7 +664,7 @@ tables_db.create_row( database_id='', table_id='', row_id='', - data={'title': 'My Document'}, + data={'title': 'My Row'}, permissions=[ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -686,7 +686,7 @@ tables_db.create_row( database_id='', table_id='', row_id='', - data={'title': 'My Document'}, + data={'title': 'My Row'}, permissions=[ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -843,13 +843,13 @@ client.set_session('') tables_db = TablesDB(client) -# Create document with team-based permissions +# Create row with team-based permissions tables_db.create_row( database_id='', table_id='', row_id='', data={ - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', # Always store teamId for querying }, permissions=[ @@ -1042,7 +1042,7 @@ $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', - data: ['title' => 'My Document'], + data: ['title' => 'My Row'], permissions: [ Permission::read(Role::user('')), Permission::write(Role::user('')) @@ -1066,7 +1066,7 @@ $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', - data: ['title' => 'My Document'], + data: ['title' => 'My Row'], permissions: [ Permission::read(Role::team('', 'owner')), Permission::read(Role::team('', 'admin')), @@ -1237,13 +1237,13 @@ $client = (new Client()) $tablesDB = new TablesDB($client); -// Create document with team-based permissions +// Create row with team-based permissions $tablesDB->createRow( databaseId: '', tableId: '', rowId: '', data: [ - 'title' => 'My Document', + 'title' => 'My Row', 'teamId' => '', // Always store teamId for querying ], permissions: [ @@ -1451,7 +1451,7 @@ tablesDB.CreateRow( "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", }, []interface{}{ models.PermissionRead(models.RoleUser("")), @@ -1480,7 +1480,7 @@ tablesDB.CreateRow( "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", }, []interface{}{ models.PermissionRead(models.RoleTeam("", "owner")), @@ -1698,13 +1698,13 @@ client := client.New( tablesDB := tablesdb.New(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.CreateRow( "", "", "", map[string]interface{}{ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying }, []interface{}{ @@ -1949,7 +1949,7 @@ tables_db.create_row( database_id: '', table_id: '', row_id: '', - data: {'title' => 'My Document'}, + data: {'title' => 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -1971,7 +1971,7 @@ tables_db.create_row( database_id: '', table_id: '', row_id: '', - data: {'title' => 'My Document'}, + data: {'title' => 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -2138,13 +2138,13 @@ client = Client.new tables_db = TablesDB.new(client) -# Create document with team-based permissions +# Create row with team-based permissions tables_db.create_row( database_id: '', table_id: '', row_id: '', data: { - 'title' => 'My Document', + 'title' => 'My Row', 'teamId' => '', # Always store teamId for querying }, permissions: [ @@ -2339,7 +2339,7 @@ await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", - data: new { title = "My Document" }, + data: new { title = "My Row" }, permissions: new List { Permission.Read(Role.User("")), Permission.Write(Role.User("")) @@ -2361,7 +2361,7 @@ await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", - data: new { title = "My Document" }, + data: new { title = "My Row" }, permissions: new List { Permission.Read(Role.Team("", "owner")), Permission.Read(Role.Team("", "admin")), @@ -2529,13 +2529,13 @@ Client client = new Client() TablesDB tablesDB = new TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions await tablesDB.CreateRow( databaseId: "", tableId: "", rowId: "", data: new { - title = "My Document", + title = "My Row", teamId = "", // Always store teamId for querying }, permissions: new List { @@ -2730,7 +2730,7 @@ try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -2750,7 +2750,7 @@ try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -2897,13 +2897,13 @@ let client = Client() let tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", data: [ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying ], permissions: [ @@ -3081,7 +3081,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3104,7 +3104,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -3278,13 +3278,13 @@ val client = Client() val tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.createRow( databaseId = "", tableId = "", rowId = "", data = mapOf( - "title" to "My Document", + "title" to "My Row", "teamId" to "", // Always store teamId for querying ), permissions = listOf( @@ -3481,7 +3481,7 @@ let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3500,7 +3500,7 @@ let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", - data: ["title": "My Document"], + data: ["title": "My Row"], permissions: [ Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -3640,13 +3640,13 @@ let client = Client() let tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", data: [ - "title": "My Document", + "title": "My Row", "teamId": "", // Always store teamId for querying ], permissions: [ @@ -3816,7 +3816,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.user("")), Permission.write(Role.user("")) @@ -3838,7 +3838,7 @@ tablesDB.createRow( databaseId = "", tableId = "", rowId = "", - data = mapOf("title" to "My Document"), + data = mapOf("title" to "My Row"), permissions = listOf( Permission.read(Role.team("", "owner")), Permission.read(Role.team("", "admin")), @@ -4003,13 +4003,13 @@ val client = Client(context) val tablesDB = TablesDB(client) -// Create document with team-based permissions +// Create row with team-based permissions tablesDB.createRow( databaseId = "", tableId = "", rowId = "", data = mapOf( - "title" to "My Document", + "title" to "My Row", "teamId" to "", // Always store teamId for querying ), permissions = listOf( @@ -4199,7 +4199,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -4218,7 +4218,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -4359,13 +4359,13 @@ final client = Client() final tablesDB = TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', data: { - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', // Always store teamId for querying }, permissions: [ @@ -4536,7 +4536,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.user('')), Permission.write(Role.user('')) @@ -4555,7 +4555,7 @@ final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', - data: {'title': 'My Document'}, + data: {'title': 'My Row'}, permissions: [ Permission.read(Role.team('', 'owner')), Permission.read(Role.team('', 'admin')), @@ -4696,13 +4696,13 @@ final client = Client() final tablesDB = TablesDB(client); -// Create document with team-based permissions +// Create row with team-based permissions final row = await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', data: { - 'title': 'My Document', + 'title': 'My Row', 'teamId': '', // Always store teamId for querying }, permissions: [ diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 30d8d17..6d14ce9 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -255,7 +255,7 @@ ${examples.avoidUserPermissions} **Problems with user-specific permissions:** - Hard to scale when users need to share resources -- Difficult to add/remove access without updating every document +- Difficult to add/remove access without updating every row - No way to represent organizational hierarchies - Poor support for collaborative features - Maintenance nightmare as teams grow @@ -267,7 +267,7 @@ ${examples.preferTeamPermissions} **Benefits of team/member-based roles:** - Automatic access for all team members based on their role -- Easy to add/remove members without touching documents +- Easy to add/remove members without touching rows - Scales naturally as teams grow - Supports organizational hierarchies and complex permissions - Industry-standard pattern for SaaS applications @@ -342,19 +342,19 @@ ${examples.listTeams} ${examples.getUserRole} \`\`\` -#### Step 4: Apply Permissions in Collections +#### Step 4: Apply Permissions in Tables -When creating documents in multi-tenant applications, always use team roles: +When creating rows in multi-tenant applications, always use team roles: -**Database Collections:** +**Database Tables:** \`\`\`${getLanguageFromSdk(sdk)} ${examples.createRow} \`\`\` -**Collection-Level Permissions:** +**Table-Level Permissions:** -When creating collections, set default permissions: +When creating tables, set default permissions: \`\`\`${getLanguageFromSdk(sdk)} ${examples.createTable} @@ -407,7 +407,7 @@ ${examples.memberRemoval} ### Permission Best Practices -1. **Always Store teamId**: Every document/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation +1. **Always Store teamId**: Every row/resource in a multi-tenant app should have a \`teamId\` field for filtering and isolation 2. **Default Deny**: Don't grant permissions unless explicitly needed. Use minimal permission sets. @@ -426,7 +426,7 @@ ${examples.roleCheck} 8. **Permission Inheritance**: Consider if child resources should inherit parent team permissions -9. **Document-Level Permissions**: For fine-grained control, set permissions on individual documents while still using team roles +9. **Row-Level Permissions**: For fine-grained control, set permissions on individual rows while still using team roles 10. **Audit Trail**: Log permission changes and team membership changes for security auditing @@ -507,10 +507,10 @@ ${databaseProductLinks} - **SDK Usage**: Always use \`TablesDB\` instead of \`Databases\` in the SDKs - **Permissions & Multi-Tenancy**: ALWAYS use team/member-based roles for permissions (see Permissions & Multi-Tenancy section above). Never use user-specific permissions in multi-tenant applications -- **Tenant Isolation**: Always include \`teamId\` fields in your documents and filter queries by \`teamId\` to ensure complete data isolation between tenants -- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all collections. Use Role.team() for all permission checks +- **Tenant Isolation**: Always include \`teamId\` fields in your rows and filter queries by \`teamId\` to ensure complete data isolation between tenants +- **Permission Patterns**: Apply team roles (owner, admin, member, viewer) consistently across all tables. Use Role.team() for all permission checks - **Query Security**: Every multi-tenant query MUST include a \`teamId\` filter to prevent cross-tenant data access -- **Collection Permissions**: Set collection-level permissions using team roles, then override at document level when needed +- **Table Permissions**: Set table-level permissions using team roles, then override at row level when needed - **Query Optimization**: Use indexes for frequently queried fields, especially on \`teamId\` and commonly filtered fields - **Data Validation**: Validate data before creating or updating rows, including team membership validation - **Transactions**: Use transactions for operations that must succeed or fail together, ensuring atomicity across tenant boundaries From 404442e793c33d5c76511251f4bba060f48d4573 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 00:59:49 +0530 Subject: [PATCH 13/16] Remove outdated common product links --- src/lib/languages/common/products.js | 7 ------- src/lib/languages/common/utils.js | 10 ++++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index 4077324..0329140 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -3,13 +3,6 @@ * These are reused across all SDKs and frameworks */ -/** - * Product documentation sections that appear in every SDK initialization - */ -export const commonProductLinks = `For TablesDB operations (create, read, update, delete rows), see the [Rows Documentation](https://appwrite.io/docs/products/databases/rows). - -For Storage operations (upload, download files), see the [Storage Documentation](https://appwrite.io/docs/products/storage/upload-download).`; - /** * Authentication product documentation */ diff --git a/src/lib/languages/common/utils.js b/src/lib/languages/common/utils.js index fcf1f16..6be628b 100644 --- a/src/lib/languages/common/utils.js +++ b/src/lib/languages/common/utils.js @@ -49,12 +49,11 @@ export const quickStartUrls = { nodejs: 'https://appwrite.io/docs/quick-starts/nodejs', vanilla: 'https://appwrite.io/docs/quick-starts/web', - // React Native - 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', - // Mobile Client SDKs apple: 'https://appwrite.io/docs/quick-starts/apple', android: 'https://appwrite.io/docs/quick-starts/android', + flutter: 'https://appwrite.io/docs/quick-starts/flutter', + 'react-native': 'https://appwrite.io/docs/quick-starts/react-native', // Server SDKs python: 'https://appwrite.io/docs/quick-starts/python', @@ -63,7 +62,6 @@ export const quickStartUrls = { ruby: 'https://appwrite.io/docs/quick-starts/ruby', dotnet: 'https://appwrite.io/docs/quick-starts/dotnet', dart: 'https://appwrite.io/docs/quick-starts/dart', - flutter: 'https://appwrite.io/docs/quick-starts/flutter', kotlin: 'https://appwrite.io/docs/quick-starts/kotlin', swift: 'https://appwrite.io/docs/quick-starts/swift' }; @@ -84,16 +82,16 @@ export const frameworkNames = { tanstack: 'TanStack', nodejs: 'Node.js', vanilla: 'Web', - 'react-native': 'React Native', apple: 'Apple', android: 'Android', + flutter: 'Flutter', + 'react-native': 'React Native', python: 'Python', php: 'PHP', go: 'Go', ruby: 'Ruby', dotnet: '.NET', dart: 'Dart', - flutter: 'Flutter', kotlin: 'Kotlin', swift: 'Swift' }; From 1b2f159f3381fef5802cfcaf0fe92f3f4c822e40 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:01:49 +0530 Subject: [PATCH 14/16] Remove redundant authentication documentation link from common product links --- src/lib/languages/common/products.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/languages/common/products.js b/src/lib/languages/common/products.js index 0329140..e01a13e 100644 --- a/src/lib/languages/common/products.js +++ b/src/lib/languages/common/products.js @@ -8,8 +8,6 @@ */ export const authProductLinks = `For detailed authentication and session management instructions, see the [Authentication Quick Start Guide](https://appwrite.io/docs/products/auth/quick-start). -For session management, login, logout, and authentication state checking, see the [Authentication Documentation](https://appwrite.io/docs/products/auth/quick-start). - For OAuth providers and social authentication, see the [OAuth2 Documentation](https://appwrite.io/docs/products/auth/oauth2). For server-side rendering (SSR) authentication, see the [SSR Login Guide](https://appwrite.io/docs/products/auth/server-side-rendering). From 3a5ceb02eb44a59fc127b6908128dfc7bc455393 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:09:41 +0530 Subject: [PATCH 15/16] Remove unnecessary JSDoc comment from SDK server route --- src/routes/api/sdks/+server.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/routes/api/sdks/+server.js b/src/routes/api/sdks/+server.js index 744ddcc..ce9dd19 100644 --- a/src/routes/api/sdks/+server.js +++ b/src/routes/api/sdks/+server.js @@ -1,9 +1,6 @@ import { SDK_OPTIONS } from '$lib/rules-generator.js'; import { json } from '@sveltejs/kit'; -/** - * @param {Request} request - */ export async function GET() { try { const sdks = Object.entries(SDK_OPTIONS).map(([key, value]) => ({ From 2cfc254f6af383d796b8122a2a5567e1b2aa3abd Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sat, 29 Nov 2025 01:09:54 +0530 Subject: [PATCH 16/16] Refactor rules generator functions to remove unnecessary parameters --- src/lib/rules-generator.js | 46 +++++++++++++------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/lib/rules-generator.js b/src/lib/rules-generator.js index 6d14ce9..49b50c1 100644 --- a/src/lib/rules-generator.js +++ b/src/lib/rules-generator.js @@ -118,14 +118,14 @@ export async function generateRules(config) { // Generate all sections in parallel // Permissions section is mandatory for all products const sections = await Promise.all([ - features.includes('auth') ? generateAuthSection(sdk, framework) : Promise.resolve(''), - generatePermissionsSection(sdk, framework), - features.includes('database') ? generateDatabaseSection(sdk, framework) : Promise.resolve(''), - features.includes('storage') ? generateStorageSection(sdk, framework) : Promise.resolve(''), - features.includes('functions') ? generateFunctionsSection(sdk, framework) : Promise.resolve(''), - features.includes('messaging') ? generateMessagingSection(sdk, framework) : Promise.resolve(''), - features.includes('sites') ? generateSitesSection(sdk, framework) : Promise.resolve(''), - features.includes('realtime') ? generateRealtimeSection(sdk, framework) : Promise.resolve('') + features.includes('auth') ? generateAuthSection() : Promise.resolve(''), + generatePermissionsSection(sdk), + features.includes('database') ? generateDatabaseSection() : Promise.resolve(''), + features.includes('storage') ? generateStorageSection() : Promise.resolve(''), + features.includes('functions') ? generateFunctionsSection(sdk) : Promise.resolve(''), + features.includes('messaging') ? generateMessagingSection() : Promise.resolve(''), + features.includes('sites') ? generateSitesSection() : Promise.resolve(''), + features.includes('realtime') ? generateRealtimeSection() : Promise.resolve('') ]); const mcpSection = includeMCP ? `${generateMCPRecommendation()}\n\n` : ''; @@ -194,11 +194,9 @@ Configure your Appwrite client for ${SDK_OPTIONS[sdk]?.name || sdk}.`; } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateAuthSection(sdk, framework) { +async function generateAuthSection() { const { authProductLinks } = await import('./languages/common/products.js'); return `## Authentication & Teams @@ -228,10 +226,9 @@ When building applications that involve multiple users or tenants: /** * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generatePermissionsSection(sdk, framework) { +async function generatePermissionsSection(sdk) { const { authProductLinks } = await import('./languages/common/products.js'); const { getPermissionExamples } = await import('./languages/common/permissions-examples.js'); const examples = getPermissionExamples(sdk); @@ -493,11 +490,9 @@ function getLanguageFromSdk(sdk) { } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateDatabaseSection(sdk, framework) { +async function generateDatabaseSection() { const { databaseProductLinks } = await import('./languages/common/products.js'); return `## Database Operations @@ -519,11 +514,9 @@ ${databaseProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateStorageSection(sdk, framework) { +async function generateStorageSection() { const { storageProductLinks } = await import('./languages/common/products.js'); return `## Storage Operations @@ -589,10 +582,9 @@ For more templates and examples, see the [Appwrite Templates Repository](${templ /** * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateFunctionsSection(sdk, framework) { +async function generateFunctionsSection(sdk) { const { functionsProductLinks } = await import('./languages/common/products.js'); const templateLinks = generateFunctionTemplateLinks(sdk); @@ -626,11 +618,9 @@ ${templateLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateMessagingSection(sdk, framework) { +async function generateMessagingSection() { const { messagingProductLinks } = await import('./languages/common/products.js'); return `## Messaging @@ -647,11 +637,9 @@ ${messagingProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateSitesSection(sdk, framework) { +async function generateSitesSection() { const { sitesProductLinks } = await import('./languages/common/products.js'); return `## Sites @@ -667,11 +655,9 @@ ${sitesProductLinks} } /** - * @param {string} sdk - * @param {string} framework * @returns {Promise} */ -async function generateRealtimeSection(sdk, framework) { +async function generateRealtimeSection() { const { realtimeProductLinks } = await import('./languages/common/products.js'); return `## Realtime Subscriptions