Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Stoker Platform
https://github.com/outpost-software/stoker
Admin
Stoker is a platform for building realtime internal tools with multi-tenant support, offline mode
...
Tokens:
57,531
Snippets:
560
Trust Score:
2.3
Update:
1 week ago
Context
Skills
Chat
Benchmark
78.6
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Stoker Platform Stoker is a framework for building realtime, offline-ready internal tools and enterprise applications on top of Google Cloud Platform and Firebase. It provides a declarative schema-based approach to define collections (database tables), access control policies, and admin UI components, automatically generating Firestore security rules, indexes, and a full-featured Admin UI. The platform handles complex requirements like multi-tenancy, role-based access control, offline persistence, and real-time synchronization out of the box. The framework consists of three main components: a CLI for project management and deployment, a Web SDK for client-side applications, and a Node SDK for server-side operations. Stoker apps store data in Cloud Firestore, files in Cloud Storage, and run server-side logic through Cloud Functions. The schema-driven architecture allows developers to define data models and access policies in TypeScript configuration files, which are then compiled into Firebase security rules and a React-based Admin UI with features like list views, board views, maps, calendars, and AI-powered chat. ## CLI Installation and Project Setup The Stoker CLI provides commands for initializing projects, managing deployments, and performing data operations. It requires Node.js 22+, Firebase CLI, Google Cloud CLI, and Genkit CLI as prerequisites. ```bash # Install prerequisites npm i -g firebase-tools firebase login npm i -g genkit-cli # Install Stoker CLI npm i -g @stoker-platform/cli # Create a new project mkdir my-app && cd my-app stoker init && git init && npm i && npm --prefix functions i # Add a development project (takes ~20 minutes for initial deployment) stoker add-project -n my-dev-project --development # Set the active project export GCP_PROJECT=my-dev-project && stoker set-project # Start local development with emulators stoker emulator-data npm run start # Deploy to production stoker deploy ``` ## Global Config File (src/main.ts) The global config file defines project-wide settings including user roles, app name, timezone, authentication behavior, and dashboard configuration. This file exports a `GenerateGlobalConfig` function that receives SDK type and utility functions. ```typescript // src/main.ts import type { GenerateGlobalConfig } from "@stoker-platform/types"; import { Package, Users, Inbox, Send } from "lucide-react"; export const generateConfig: GenerateGlobalConfig = (sdk, utils) => ({ // Required: Define user roles for your application roles: ["Admin", "Manager", "Staff", "Client"], // Required: App name used in page titles appName: "Project Manager", // Required: IANA timezone for date operations timezone: "America/New_York", // Authentication configuration auth: { enableMultiFactorAuth: ["Admin", "Manager"], authPersistenceType: "LOCAL", // "LOCAL" | "SESSION" | "NONE" offlinePersistenceType: (user, claims) => { return claims.role === "Admin" ? "ALL" : "WRITE"; }, signOutOnPermissionsChange: false, clearPersistenceOnSignOut: true, }, // Admin UI configuration admin: { access: ["Admin", "Manager", "Staff"], homePage: { Admin: "Projects", Manager: "Projects", Staff: "Tasks", }, dateFormat: "MM/dd/yyyy", menu: { groups: [ { title: "Work", position: 1, collections: ["Projects", "Tasks"], roles: ["Admin", "Manager", "Staff"], }, { title: "Messages", position: 2, collections: ["Inbox", "Outbox"], }, ], }, dashboard: [ { kind: "metric", collection: "Tasks", type: "count", title: "Open Tasks", constraints: [["Status", "in", ["Not Started", "In Progress"]]], roles: ["Admin", "Manager"], }, { kind: "chart", collection: "Tasks", type: "area", dateField: "Created_At", title: "Tasks Created", interval: "day", numberOfIntervals: 30, roles: ["Admin", "Manager"], }, { kind: "reminder", collection: "Tasks", columns: ["Name", "Due_Date", "Assigned_To"], title: "Overdue Tasks", constraints: [["Due_Date", "<", new Date()]], sort: { field: "Due_Date", direction: "asc" }, }, ], }, // Firebase configuration firebase: { enableEmulators: () => import.meta.env.MODE === "development", enableAnalytics: () => import.meta.env.MODE === "production", serverTimestampOptions: "estimate", logLevel: { dev: "warn", prod: "error" }, }, // Preload cache configuration (for performance optimization) preload: { async: ["Tasks", "Projects"], sync: ["Users", "Settings"], }, // Global hooks custom: { postLogin: async (user) => { console.log(`User ${user?.email} logged in`); }, postWriteError: async (operation, data, docId, context, error) => { console.error(`Write error on ${operation}:`, error); }, }, }); ``` ## Collection Files (src/collections/*.ts) Collection files define the schema, access policies, field configurations, and Admin UI settings for each data collection. Each collection maps to a Firestore collection and an Admin UI page. ```typescript // src/collections/Tasks.ts import type { GenerateSchema } from "@stoker-platform/types"; import { CheckSquare, Calendar, User } from "lucide-react"; export const generateSchema: GenerateSchema = (sdk, utils) => ({ // Collection labels labels: { collection: "Tasks", record: "Task" }, recordTitleField: "Name", // Enable write logging for audit trail enableWriteLog: true, // Soft delete configuration softDelete: { archivedField: "Archived", timestampField: "Archived_At", retentionPeriod: 90, // days }, // Full text search fields (uses Algolia for large collections) fullTextSearch: ["Name", "Description"], // Access configuration access: { operations: { read: ["Admin", "Manager", "Staff"], create: ["Admin", "Manager"], update: ["Admin", "Manager", "Staff"], delete: ["Admin"], }, // Attribute-based access control attributeRestrictions: [ { type: "Record_Owner", roles: [{ role: "Staff", assignable: true }], operations: ["Read", "Update"], }, { type: "Record_User", roles: [{ role: "Staff" }], collectionField: "Assigned_To", operations: ["Read", "Update"], }, { type: "Record_Property", roles: [ { role: "Staff", values: ["Not Started", "In Progress"] }, { role: "Manager", values: ["Not Started", "In Progress", "Review"] }, ], propertyField: "Status", operations: ["Read"], }, ], // Entity-based access control entityRestrictions: { restrictions: [ { type: "Parent", roles: [{ role: "Staff" }], collectionField: "Project", }, ], }, }, // Preload cache for performance (loads data on app startup) preloadCache: { roles: ["Admin", "Manager", "Staff"], range: { fields: ["Due_Date", "Created_At"], start: "Month", startOffsetDays: -7, selector: ["range", "week", "month"], }, }, // Field definitions fields: [ { name: "Name", type: "String", required: true, unique: true, maxlength: 200, admin: { label: "Task Name", icon: { component: CheckSquare }, }, }, { name: "Description", type: "String", admin: { textarea: true, condition: { form: true, list: false }, }, }, { name: "Status", type: "String", required: true, values: ["Not Started", "In Progress", "Review", "Completed"], admin: { badge: (record) => { const colors = { "Not Started": "bg-gray-500", "In Progress": "bg-blue-500", "Review": "bg-yellow-500", "Completed": "bg-green-500", }; return colors[record?.Status] || "bg-gray-500"; }, buttonGroup: true, }, }, { name: "Priority", type: "Number", values: [1, 2, 3], admin: { label: "Priority", radio: true, modifyDisplayValue: (record) => { const labels = { 1: "High", 2: "Medium", 3: "Low" }; return labels[record?.Priority] || "Unknown"; }, }, }, { name: "Due_Date", type: "Timestamp", required: true, sorting: { direction: "asc" }, admin: { icon: { component: Calendar }, time: true, }, }, { name: "Estimated_Hours", type: "Number", decimal: 2, min: 0, max: 1000, }, { name: "Project", type: "ManyToOne", collection: "Projects", required: true, includeFields: ["Name", "Client"], titleField: "Name", admin: { breadcrumbs: true, }, }, { name: "Assigned_To", type: "ManyToMany", collection: "Users", includeFields: ["Name", "Email"], titleField: "Name", dependencyFields: [ { field: "Name", roles: ["Admin", "Manager", "Staff"] }, { field: "Email", roles: ["Admin", "Manager"] }, ], max: 5, admin: { icon: { component: User }, }, }, { name: "Tags", type: "Array", values: ["Bug", "Feature", "Enhancement", "Documentation"], admin: { tags: ["bg-red-500", "bg-green-500", "bg-blue-500", "bg-purple-500"], }, }, { name: "Archived", type: "Boolean", restrictCreate: true, restrictUpdate: ["Admin"], }, { name: "Archived_At", type: "Timestamp", restrictCreate: true, restrictUpdate: true, }, { name: "Completion_Rate", type: "Computed", formula: (record) => { const subtasks = record?.Subtasks || []; if (subtasks.length === 0) return "N/A"; const completed = subtasks.filter((s) => s.Completed).length; return `${Math.round((completed / subtasks.length) * 100)}%`; }, }, ], // Relation lists shown on record page relationLists: [ { collection: "Comments", field: "Task" }, { collection: "Time_Entries", field: "Task" }, ], // Admin UI configuration admin: { navbarPosition: 2, titles: { collection: "Tasks", record: "Task" }, icon: CheckSquare, itemsPerPage: 25, defaultView: "list", defaultSort: { field: "Due_Date", direction: "asc" }, statusField: { field: "Status", active: ["Not Started", "In Progress", "Review"], archived: ["Completed"], }, duplicate: true, breadcrumbs: ["Project"], filters: [ { type: "select", field: "Status", style: "buttons" }, { type: "select", field: "Priority" }, { type: "relation", field: "Project" }, { type: "relation", field: "Assigned_To" }, { type: "range", field: "Due_Date", selector: ["range", "week", "month"] }, ], metrics: [ { type: "count", title: "Total Tasks" }, { type: "sum", field: "Estimated_Hours", title: "Total Hours", suffix: "h" }, { type: "chart", dateField: "Created_At", title: "Tasks Over Time", defaultRange: "30d", }, ], cards: { statusField: "Status", headerField: "Name", sections: [ { fields: ["Due_Date", "Priority"], blocks: true }, { title: "Assigned", fields: ["Assigned_To"] }, ], footerField: "Project", }, calendar: { startField: "Due_Date", endField: "Due_Date", eventTitle: (record) => record.Name, color: (record) => { const colors = { "Not Started": "#6b7280", "In Progress": "#3b82f6", "Review": "#eab308", "Completed": "#22c55e", }; return colors[record.Status] || "#6b7280"; }, unscheduled: { title: "Unscheduled" }, }, rowHighlight: [ { condition: (record) => { const dueDate = record.Due_Date?.toDate(); return dueDate && dueDate < new Date() && record.Status !== "Completed"; }, className: "bg-red-50 dark:bg-red-950", roles: ["Admin", "Manager"], }, ], }, // Collection hooks custom: { initialValue: { Status: "Not Started", Priority: 2, }, preValidate: async (operation, record, context) => { if (record.Due_Date && record.Due_Date.toDate() < new Date()) { return { valid: false, message: "Due date cannot be in the past" }; } return { valid: true }; }, preWrite: async (operation, data, docId, context) => { if (operation === "update" && data.Status === "Completed") { data.Completed_At = new Date(); } return true; }, postWrite: async (operation, data, docId, context) => { if (sdk === "node" && operation === "create") { // Send notification to assigned users const { sendMail } = await import("@stoker-platform/node-client"); for (const user of data.Assigned_To || []) { await sendMail( user.Email, `New Task Assigned: ${data.Name}`, `You have been assigned to task: ${data.Name}` ); } } }, }, }); ``` ## Web SDK - Initialization and Authentication The Web SDK provides functions for initializing Stoker apps, authenticating users, and managing application state in browser environments. It handles Firebase integration, offline persistence, and real-time updates. ```typescript import { initializeStoker, authenticateStoker, signOut, onStokerReady, onStokerSignOut, onStokerPermissionsChange, multiFactorEnroll, getCurrentUser, } from "@stoker-platform/web-client"; // Initialize Stoker app const config = await import("./config/main"); const collectionFiles = import.meta.glob("./config/collections/*", { eager: true }); const isLoggedIn = await initializeStoker( config, collectionFiles, import.meta.env, { setDialogContent: (content) => { /* show dialog */ }, setGlobalLoading: (op, id) => { /* show/hide loading */ }, } ); // Listen for app ready state const unsubscribeReady = onStokerReady(() => { console.log("Stoker is ready, user authenticated"); // App is fully initialized, safe to read/write data }); // Listen for sign out const unsubscribeSignOut = onStokerSignOut(() => { console.log("User signed out"); // Redirect to login page }); // Listen for permission changes const unsubscribePermissions = onStokerPermissionsChange(() => { console.log("User permissions changed"); // Refresh UI or reload data }); // Authenticate user with email/password try { await authenticateStoker( "user@example.com", "password123", async () => { // This function is called when MFA is required // Show OTP input and return the code return prompt("Enter your 2FA code:"); } ); } catch (error) { console.error("Authentication failed:", error); } // Get current user information const user = getCurrentUser(); console.log("Current user:", user.email); console.log("User role:", user.token.claims.role); // Enroll user in MFA await multiFactorEnroll( user, async (secret, totpUri) => { // Display QR code using totpUri // User scans with authenticator app return prompt("Enter the code from your authenticator app:"); } ); // Sign out await signOut(); // Cleanup listeners unsubscribeReady(); unsubscribeSignOut(); unsubscribePermissions(); ``` ## Web SDK - CRUD Operations The Web SDK provides functions for creating, reading, updating, and deleting records with support for relations, subcollections, pagination, and real-time subscriptions. ```typescript import { addRecord, updateRecord, deleteRecord, getOne, getSome, subscribeOne, subscribeMany, waitForPendingWrites, preloadCollection, } from "@stoker-platform/web-client"; import { where, orderBy, limit, deleteField, increment } from "firebase/firestore"; // Create a new record const newTask = await addRecord( ["Tasks"], { Name: "Complete documentation", Description: "Write comprehensive API docs", Status: "Not Started", Priority: 1, Due_Date: new Date("2024-12-31"), Project: { id: "project123", Name: "Stoker Platform" }, Assigned_To: [ { id: "user1", Name: "John Doe", Email: "john@example.com" }, ], }, undefined, // user credentials (for auth-enabled collections) undefined, // options "task123", // optional custom ID () => console.log("Validation passed") ); console.log("Created task:", newTask.id); // Create a user with authentication credentials const newUser = await addRecord( ["Users"], { Name: "Jane Smith", Email: "jane@example.com", Role: "Staff", }, { password: "SecurePass123!", passwordConfirm: "SecurePass123!", permissions: { Role: "Staff", Enabled: true, collections: { Tasks: { operations: ["Read", "Create", "Update"], recordOwner: { active: true }, }, }, }, } ); // Update a record (only provide fields to update) const updatedTask = await updateRecord( ["Tasks"], "task123", { Status: "In Progress", Estimated_Hours: increment(2), // Atomic increment } ); // Update with field deletion await updateRecord( ["Tasks"], "task123", { Description: deleteField(), // Remove field } ); // Delete a record const deletedTask = await deleteRecord(["Tasks"], "task123"); // Get a single record with relations const task = await getOne( ["Tasks"], "task123", { relations: { depth: 2, // Include relations up to 2 levels deep fields: ["Project", "Assigned_To"], // Only these relation fields }, subcollections: { collections: ["Comments", "Time_Entries"], depth: 1, limit: { number: 10, orderByField: "Created_At", orderByDirection: "desc" }, }, } ); // Get multiple records with constraints and pagination const { docs: tasks, cursor, pages } = await getSome( ["Tasks"], [ ["Status", "in", ["Not Started", "In Progress"]], ["Priority", "==", 1], ], { pagination: { number: 20, orderByField: "Due_Date", orderByDirection: "asc", }, relations: { depth: 1 }, } ); // Get next page using cursor const { docs: nextPageTasks } = await getSome( ["Tasks"], [["Status", "!=", "Completed"]], { pagination: { number: 20, orderByField: "Due_Date", orderByDirection: "asc", startAfter: cursor, }, } ); // Subscribe to a single record (real-time updates) const unsubscribeOne = await subscribeOne( ["Tasks"], "task123", (taskData) => { if (taskData) { console.log("Task updated:", taskData.Name, taskData.Status); } else { console.log("Task was deleted"); } }, (error) => console.error("Subscription error:", error), { relations: true, // Include all relations } ); // Subscribe to multiple records with constraints const { unsubscribe: unsubscribeMany } = await subscribeMany( ["Tasks"], [where("Status", "==", "In Progress")], (docs, cursor, metadata) => { console.log(`${docs.length} tasks in progress`); docs.forEach((task) => console.log(task.Name)); }, (error) => console.error("Subscription error:", error), { pagination: { number: 50, orderByField: "Due_Date", orderByDirection: "asc", }, relations: { fields: ["Assigned_To"] }, } ); // Wait for all pending writes to complete await waitForPendingWrites(); // Manually reload preload cache for a collection await preloadCollection("Tasks", [["Status", "!=", "Completed"]]); // Cleanup subscriptions unsubscribeOne(); unsubscribeMany(); ``` ## Node SDK - Server Operations The Node SDK provides server-side functionality for use in Cloud Functions, scripts, and backend services. It includes all CRUD operations plus administrative functions. ```typescript import { initializeStoker, fetchCurrentSchema, addRecord, updateRecord, deleteRecord, getOne, getSome, sendMail, sendMessage, convertTimestampToTimezone, displayDate, } from "@stoker-platform/node-client"; import { join } from "path"; // Initialize Stoker in Node environment const utils = await initializeStoker( "production", join(process.cwd(), "config", "main.js"), join(process.cwd(), "config", "collections"), true // Running in GCP environment (Cloud Functions) ); // Set the active tenant utils.setTenant("tenant123"); // Fetch current schema const schema = await fetchCurrentSchema(true); // include computed fields console.log("Collections:", Object.keys(schema.collections)); // Create a record (server-side) const newProject = await addRecord( ["Projects"], { Name: "New Website", Client: { id: "client1", Name: "Acme Corp" }, Start_Date: new Date(), Budget: 50000, }, undefined, // user credentials "admin-user-id", // userId to impersonate { noTwoWay: false, }, { source: "api" }, // context passed to hooks "project-custom-id" ); // Batch operations with transactions import { getFirestore } from "firebase-admin/firestore"; const db = getFirestore(); await db.runTransaction(async (transaction) => { const schema = await fetchCurrentSchema(); // Create multiple records in a transaction await addRecord( ["Tasks"], { Name: "Task 1", Project: { id: newProject.id } }, undefined, undefined, { providedTransaction: transaction, providedSchema: schema } ); await addRecord( ["Tasks"], { Name: "Task 2", Project: { id: newProject.id } }, undefined, undefined, { providedTransaction: transaction, providedSchema: schema } ); }); // Query records with full options const { docs: tasks, cursor, pages } = await getSome( ["Tasks"], [ ["Project", "==", "project123"], ["Status", "in", ["Not Started", "In Progress"]], ], { user: "user-id-to-impersonate", relations: { depth: 2, fields: ["Assigned_To", "Project"], }, subcollections: { collections: ["Comments"], depth: 1, constraints: [["Created_At", ">", new Date("2024-01-01")]], limit: { number: 5, orderByField: "Created_At", orderByDirection: "desc" }, }, pagination: { number: 100, orderByField: "Due_Date", orderByDirection: "asc", }, transactional: true, // Include relations in same transaction } ); // Get single record in a transaction const task = await getOne( ["Tasks"], "task123", { providedTransaction: transaction, relations: { depth: 1 }, } ); // Update with original record for better performance in bulk operations await updateRecord( ["Tasks"], task.id, { Status: "Completed", Completed_At: new Date() }, undefined, undefined, { noTwoWay: false }, undefined, task // original record prevents extra read ); // Force delete soft-deleted record await deleteRecord( ["Tasks"], "task123", undefined, { force: true } ); // Send email await sendMail( ["recipient@example.com", "another@example.com"], "Weekly Report", "Here is your weekly summary...", // plain text "<h1>Weekly Report</h1><p>Here is your weekly summary...</p>", // HTML "cc@example.com", ["bcc1@example.com", "bcc2@example.com"], "noreply@example.com", [ { filename: "report.pdf", content: pdfBuffer, contentType: "application/pdf", }, ] ); // Send SMS via Twilio await sendMessage("+1234567890", "Your verification code is: 123456"); // Date/time utilities const timestamp = task.Due_Date; const luxonDate = convertTimestampToTimezone(timestamp); const formattedDate = displayDate(timestamp); console.log("Due:", formattedDate); // Uses format from global config ``` ## Callable API (Cloud Functions) Stoker provides a callable API through Cloud Functions for external integrations. The API uses Firebase's callable functions for scalability and security. ```typescript // Using the API from any client (after Firebase Auth) import { getFunctions, httpsCallable } from "firebase/functions"; import { signInWithEmailAndPassword } from "firebase/auth"; // Step 1: Authenticate with Firebase const auth = getAuth(); await signInWithEmailAndPassword(auth, "user@example.com", "password"); // Step 2: Get functions instance const functions = getFunctions(); // Read API - Get single record const readApi = httpsCallable(functions, "stoker-readapi"); const { data: taskResult } = await readApi({ path: ["Tasks"], id: "task123", options: { relations: { depth: 1 }, }, }); console.log("Task:", taskResult.result); // Read API - Get multiple records with constraints const { data: tasksResult } = await readApi({ path: ["Tasks"], constraints: [ ["Status", "in", ["Not Started", "In Progress"]], ["Priority", "==", 1], ], options: { pagination: { number: 20, orderByField: "Due_Date" }, }, }); console.log("Tasks:", tasksResult.result); // Read API - Get subcollection records const { data: commentsResult } = await readApi({ path: ["Tasks", "task123", "Comments"], constraints: [["Created_At", ">", new Date("2024-01-01").toISOString()]], }); // Write API - Create record const writeApi = httpsCallable(functions, "stoker-writeapi"); const { data: createResult } = await writeApi({ operation: "create", path: ["Tasks"], record: { Name: "New Task via API", Status: "Not Started", Priority: 2, Due_Date: new Date().toISOString(), Project: { id: "project123" }, }, }); console.log("Created:", createResult.result.id); // Write API - Update record const { data: updateResult } = await writeApi({ operation: "update", path: ["Tasks"], id: "task123", record: { Status: "In Progress", }, }); // Write API - Delete record await writeApi({ operation: "delete", path: ["Tasks"], id: "task123", }); // Write API - Create user with credentials await writeApi({ operation: "create", path: ["Users"], record: { Name: "API User", Email: "apiuser@example.com", }, userData: { password: "SecurePass123!", permissions: { Role: "Staff", Enabled: true, }, }, }); // Search API (requires Algolia or preloadCache) const searchApi = httpsCallable(functions, "stoker-searchapi"); const { data: searchResult } = await searchApi({ collection: "Tasks", query: "documentation", hitsPerPage: 10, constraints: [["Status", "!=", "Completed"]], }); console.log("Search results:", searchResult); // Array of record IDs ``` ## CLI Commands Reference The Stoker CLI provides comprehensive commands for project management, deployment, data operations, and administration. ```bash # Project Management stoker init # Bootstrap a new Stoker project stoker add-project -n <name> [--development] # Add a GCP/Firebase project stoker delete-project -n <name> # Delete a GCP project (careful!) stoker set-project # Select active project (use with GCP_PROJECT env) stoker list-projects # List all GCP projects # Tenant Management stoker add-tenant # Add a tenant to current project stoker delete-tenant -t <tenant-id> # Delete a tenant (careful!) # Development stoker emulator-data # Copy live data to emulators stoker start # Start Firebase Emulator Suite stoker start-web-app # Start web app development server stoker build-web-app # Build web app for production stoker apply # Apply schema to local environment # Deployment stoker deploy # Deploy everything to current project stoker deploy-ttls # Deploy Firestore TTL policies stoker generate-firestore-indexes # Generate Firestore indexes stoker generate-firestore-rules # Generate security rules stoker generate-storage-rules # Generate Cloud Storage rules stoker persist-schema # Save schema to Firebase stoker lint-schema # Validate schema configuration stoker security-report # Run security analysis stoker migrate # Run database migrations # CRUD Operations (CLI) stoker add-record -c Tasks -d '{"Name":"CLI Task","Status":"Not Started"}' stoker add-record-prompt -c Tasks # Interactive record creation stoker update-record -c Tasks -i task123 -d '{"Status":"In Progress"}' stoker delete-record -c Tasks -i task123 stoker get-one -c Tasks -i task123 stoker get-some -c Tasks -q '{"Status":"Not Started"}' # User Management stoker get-user -e user@example.com # Get Firebase Auth user stoker get-user-record -e user@example.com # Get Firestore user record stoker get-user-permissions -e user@example.com stoker set-user-role -e user@example.com -r Admin stoker set-user-collection -e user@example.com -c Projects stoker set-user-document -e user@example.com -d project123 # Data Operations stoker export # Export Firestore to Cloud Storage stoker bigquery -c Tasks # Export collection to BigQuery stoker seed-data # Seed test data # Audit & Analysis stoker audit-permissions # Detect non-default role permissions stoker audit-denormalized # Check denormalized data integrity stoker audit-relations # Check relation data integrity stoker explain-preload -c Tasks # Analyze preload cache queries # Infrastructure stoker custom-domain -d example.com # Set custom domain stoker deployment --status # Check deployment status stoker maintenance --on|--off # Toggle maintenance mode stoker live-update # Trigger client refresh # Environment setup export GCP_PROJECT=my-project && stoker set-project # Set active project # Get help for any command stoker <command> --help ``` ## Environment Configuration (.env/.env) Environment files configure backend infrastructure settings for Google Cloud, Firebase services, email, SMS, and third-party integrations. ```bash # .env/.env - Main configuration file # Required: Contact Information ADMIN_EMAIL=admin@example.com ADMIN_SMS=+1234567890 # Required: Google Cloud GCP_BILLING_ACCOUNT=XXXXXX-XXXXXX-XXXXXX GCP_ORGANIZATION=organizations/123456789 GCP_FOLDER=folders/987654321 FB_GOOGLE_ANALYTICS_ACCOUNT_ID=123456789 # Required: Firestore FB_FIRESTORE_REGION=us-central1 FB_FIRESTORE_ENABLE_PITR=true FB_FIRESTORE_BACKUP_RECURRENCE=daily FB_FIRESTORE_BACKUP_RETENTION=30d # Required: Realtime Database FB_DATABASE_REGION=us-central1 # Required: Cloud Storage FB_STORAGE_REGION=us-central1 FB_STORAGE_ENABLE_VERSIONING=false FB_STORAGE_SOFT_DELETE_DURATION=30d # Required: Firebase Auth FB_AUTH_PASSWORD_POLICY={"minLength":12,"requireUppercase":true,"requireLowercase":true,"requireNumeric":true,"requireNonAlphanumeric":true} FB_AUTH_PASSWORD_POLICY_UPGRADE=false # Required: Cloud Functions FB_FUNCTIONS_REGION=us-central1 FB_FUNCTIONS_V1_REGION=us-central1 FB_FUNCTIONS_MEMORY=512MB FB_FUNCTIONS_TIMEOUT=60s FB_FUNCTIONS_MAX_INSTANCES=100 FB_FUNCTIONS_MIN_INSTANCES=0 FB_FUNCTIONS_CPU=1 FB_FUNCTIONS_CONCURRENCY=80 FB_FUNCTIONS_CONSUME_APP_CHECK_TOKEN=false # Firebase Hosting FB_HOSTING_ENABLE_CLOUD_LOGGING=true FB_HOSTING_MAX_VERSIONS=10 # App Check (Recommended) FB_ENABLE_APP_CHECK=true FB_APP_CHECK_TOKEN_TTL=3600s # AI/Genkit (Optional) FB_AI_REGION=us-central1 # Required: Mail (SMTP) MAIL_REGION=us-central1 MAIL_SENDER=My App <noreply@example.com> MAIL_SMTP_CONNECTION_URI=smtps://user@gmail.com@smtp.gmail.com:465 MAIL_SMTP_PASSWORD=xxxx-xxxx-xxxx-xxxx # SMS via Twilio (Optional) TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_PHONE_NUMBER=+1234567890 # Algolia for Full Text Search (Optional) ALGOLIA_ID=XXXXXXXXXX ALGOLIA_ADMIN_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Error Tracking (Optional) SENTRY_DSN=https://xxxxx@sentry.io/xxxxx # Calendar License (Required for calendar view) FULLCALENDAR_KEY=XXXX-XXXX-XXXX-XXXX # Custom Secrets for Cloud Functions EXTERNAL_SECRETS={"STRIPE_KEY":"sk_xxxxx","WEBHOOK_SECRET":"whsec_xxxxx"} ``` ## Summary Stoker Platform is designed for building enterprise-grade internal tools and multi-tenant SaaS applications with minimal boilerplate code. The schema-driven approach allows developers to define complex data models, access control policies, and UI configurations in TypeScript, which are then automatically compiled into secure Firebase infrastructure. Key integration patterns include: using the Web SDK for React-based client applications with real-time subscriptions and offline support, leveraging the Node SDK for server-side operations in Cloud Functions or backend scripts, and utilizing the callable API for external system integrations. The platform excels at scenarios requiring sophisticated access control (role-based, attribute-based, and entity-based restrictions), real-time collaboration features, offline-first mobile applications, and multi-tenant architectures where data isolation is critical. Common use cases include project management systems, CRM applications, field service management tools, inventory systems, and any internal tool requiring audit trails, fine-grained permissions, and the ability to work offline. The combination of declarative configuration and automatic infrastructure generation significantly reduces development time while maintaining enterprise-grade security and scalability through Google Cloud Platform.