Package Exports
- basebase-js
- basebase-js/auth
Readme
BaseBase JavaScript SDK
An SDK for BaseBase server interactions, patterned after the Firebase/Firestore SDK. Makes it easy to build web applications that use BaseBase as a data store and user authentication service.
⚠️ Early Development
This SDK is currently in early development (0.1.x). The API may change between versions. Please use with caution in production environments.
🚀 Features
- No initialization required - Set environment variable and start using immediately
- Firebase-like API - Drop-in replacement for Firebase/Firestore
- Simple user authentication - SMS-based auth with JWT tokens
- Real-time data storage and retrieval - CRUD operations with collections and documents
- Advanced queries - User where, orderBy, limit constraints with server-side structured queries
- BaseBase tasks - Define, call and schedule BaseBase server-side tasks with parameters and error handling
- TypeScript support - Full type safety and IntelliSense
- Cross-platform - Works in browsers and Node.js environments
📦 Installation
npm install basebase-jsOr with yarn:
yarn add basebase-js🛠 Quick Start
1. Authentication First
import { requestCode, verifyCode } from "basebase-js";
// Request SMS verification code
const response = await requestCode("john_doe", "+1234567890");
// Verify the code and get JWT token
const authResult = await verifyCode(
"+1234567890",
"123456",
"bb_your_api_key_here"
);
console.log("User:", authResult.user);
console.log("Token:", authResult.token);
// Token is automatically stored - now you can use the database!2. Start Using Database - No Setup Required!
Browser (after authentication):
import { db, doc, getDoc, collection, getDocs, addDoc } from "basebase-js";
// Get a single document - no setup needed after auth!
const userRef = doc(db, "myproject/users/user123");
const userSnap = await getDoc(userRef);
if (userSnap.exists) {
console.log("User data:", userSnap.data());
}
// Get all documents in a collection
const usersRef = collection(db, "myproject/users");
const snapshot = await getDocs(usersRef);
snapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});
// Add a new document
const newUserRef = await addDoc(collection(db, "myproject/user_profiles"), {
name: "Jane Doe",
email: "jane@example.com",
preferences: { theme: "dark", notifications: true },
});
console.log("New user ID:", newUserRef.id);Server/Node.js (with token):
import { getDatabase, doc, getDoc, collection, addDoc } from "basebase-js";
// Create database instance with JWT token
const db = getDatabase("your_jwt_token_here");
// Use exactly the same API as browser
const userRef = doc(db, "myproject/users/user123");
const userSnap = await getDoc(userRef);
if (userSnap.exists) {
console.log("User data:", userSnap.data());
}3. Document Operations
import {
db,
doc,
collection,
getDoc,
getDocs,
addDoc,
setDoc,
updateDoc,
deleteDoc,
} from "basebase-js";
// Get a single document
const userRef = doc(db, "myproject/users/user123");
const userSnap = await getDoc(userRef);
if (userSnap.exists) {
console.log("User data:", userSnap.data());
}
// Get all documents in a collection
const usersRef = collection(db, "myproject/users");
const snapshot = await getDocs(usersRef);
snapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});
// Add document with auto-generated ID
const newUserRef = await addDoc(collection(db, "myproject/users"), {
name: "Jane Doe",
email: "jane@example.com",
});
// Set document with specific ID (URL-safe string up to 255 characters)
const userId = "ben_wen";
await setDoc(doc(db, `users/${userId}`), {
name: "John Doe",
email: "john@example.com",
});
// Update specific fields
await updateDoc(doc(db, "myproject/users/user123"), {
age: 31,
lastLogin: new Date().toISOString(),
});
// Delete document
await deleteDoc(doc(db, "myproject/users/user123"));4. Path Structure & Naming Rules
Project IDs: BaseBase allows flexible naming for projects using URL-safe strings up to 24 characters long:
- ✅ Allowed characters:
a-z,A-Z,0-9,_,- - ✅ Examples:
test_project,myapp2024,ben_wen - ❌ Not allowed: spaces, special characters like
/,@,., etc. - ❌ Must be 24 characters or less
Collection Names: Collection names must be lowercase and use underscores to separate words:
- ✅ Allowed characters:
a-z,0-9,_,- - ✅ Examples:
users,user_preferences,blog_posts,order_items - ✅ Recommended: Use underscores (
_) to separate words for readability - ❌ Not allowed: uppercase letters, camelCase, spaces, special characters
- ❌ Examples of invalid names:
Users,userPreferences,blog-Posts,user preferences - ❌ Must be 255 characters or less
Document IDs: Document IDs can be URL-safe strings up to 255 characters long:
- ✅ Allowed characters:
a-z,A-Z,0-9,_,- - ✅ Examples:
user-123,very-long-document-identifier-with-lots-of-detail,ben_wen - ❌ Not allowed: spaces, special characters like
/,@,., etc. - ❌ Must be 255 characters or less
Path Structure: All paths are relative to the specified project. This eliminates ambiguity:
- ✅
"users"= collection named "users" - ✅
"users/ben_wen"= document "ben_wen" in "users" collection - ✅
"users/ben_wen/posts"= "posts" subcollection under document "ben_wen"
To work with a different project, use the optional projectName parameter:
// Work with documents in another project
const otherUserRef = doc(db, "otherproject/users/user123");
const otherUserData = await getDoc(otherUserRef);
// Add to another project's collection
await addDoc(collection(db, "users", "otherProject"), {
name: "Cross-project user",
});
// Set document with same ID across projects (powerful for data consistency)
const userId = "user_ben";
// Set base user data in main project
await setDoc(doc(db, `users/${userId}`), {
name: "John Doe",
email: "john@example.com",
phone: "+1234567890",
});
// Set app-specific data in another project using same ID
await setDoc(doc(db, `users/${userId}`, "news_app"), {
sources: ["techcrunch", "ars-technica"],
preferences: { theme: "dark", notifications: true },
});5. Querying Data
import {
db,
query,
where,
orderBy,
limit,
getDocs,
collection,
} from "basebase-js";
// Advanced queries with proper collection naming
const q = query(
collection(db, "myproject/user_profiles"),
where("age", ">=", 18),
where("status", "==", "active"),
orderBy("name", "asc"),
limit(10)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.data());
});
// Example with multiple collections using underscore convention
const ordersQuery = query(
collection(db, "myproject/order_items"),
where("category", "==", "electronics"),
where("price", ">=", 100),
orderBy("created_at", "desc"),
limit(20)
);6. Executing Server-Side Tasks
BaseBase supports executing server-side tasks with the doTask API:
import { doTask } from "basebase-js";
// Execute a task with parameters
const result = await doTask("getPage", {
url: "https://example.com",
selector: "h1",
});
console.log("Page content:", result);
// Execute task with custom BaseBase instance (server environment)
import { getDatabase } from "basebase-js";
const db = getDatabase("your_jwt_token");
const processResult = await doTask(
"processData",
{
data: "some input",
},
db
);
// Tasks with no parameters
const status = await doTask("getStatus");Task Requirements:
- Authentication required (JWT token)
- Tasks are project-scoped (called within your authenticated project)
- 30-second execution timeout
- Structured error responses
7. Task Management (CRUD & Triggering)
BaseBase supports creating, managing, and triggering cloud tasks with Firebase-like syntax:
Task CRUD Operations
import {
setTask,
getTask,
listTasks,
updateTask,
deleteTask,
} from "basebase-js";
// Set a new task
const newTask = await setTask({
name: "myTask",
code: 'exports.handler = async (data) => { return { message: "Hello World!" }; }',
description: "My custom task",
timeout: 30,
memoryMB: 128,
});
// Get task details
const task = await getTask("myTask");
console.log(task.implementationCode);
// List all tasks
const tasks = await listTasks();
tasks.forEach((task) => console.log(task.id));
// Update a task
const updatedTask = await updateTask("myTask", {
code: 'exports.handler = async (data) => { return { message: "Updated!" }; }',
timeout: 60,
});
// Delete a task
await deleteTask("myTask");Task Triggering with Cron
import {
addTrigger,
setTrigger,
getTrigger,
listTriggers,
updateTrigger,
deleteTrigger,
} from "basebase-js";
// Add a triggered task
const trigger = await addTrigger({
name: "dailyCleanup",
taskName: "cleanupTask",
schedule: "0 2 * * *", // Daily at 2 AM
timeZone: "America/New_York",
data: { target: "temp_files" },
enabled: true,
});
// Get trigger details
const triggerInfo = await getTrigger("dailyCleanup");
console.log(`Next run: ${triggerInfo.nextRun}`);
// List all triggers
const triggers = await listTriggers();
triggers.forEach((trigger) =>
console.log(`${trigger.name}: ${trigger.schedule}`)
);
// Update a trigger
const updatedTrigger = await updateTrigger("dailyCleanup", {
schedule: "0 3 * * *", // Change to 3 AM
enabled: false,
});
// Delete a trigger
await deleteTrigger("dailyCleanup");Cron Expression Examples
// Every minute
"* * * * *";
// Every 15 minutes
"*/15 * * * *";
// Daily at 2 AM
"0 2 * * *";
// Every Monday at 9 AM
"0 9 * * 1";
// First day of every month at midnight
"0 0 1 * *";
// Every weekday at 6 PM
"0 18 * * 1-5";Task Management Requirements:
- Authentication required (JWT token)
- Tasks and triggers are project-scoped
- Task code must export a
handlerfunction - Cron expressions use standard 5-field format
- Timezone support for triggering
📖 API Reference
Authentication
requestCode(username, phone, baseUrl?)
Request an SMS verification code. Username must be alphanumeric characters only with no spaces.
const response = await requestCode("john_doe", "+1234567890");verifyCode(phone, code, projectApiKey, baseUrl?)
Verify SMS code and get authentication token.
const result = await verifyCode("+1234567890", "123456", "bb_your_api_key");getAuthState()
Get current authentication state.
const authState = getAuthState();
console.log("Authenticated:", authState.isAuthenticated);
console.log("User:", authState.user);signOut()
Sign out the current user.
signOut();onAuthStateChanged(callback)
Listen for authentication state changes.
const unsubscribe = onAuthStateChanged((authState) => {
if (authState.isAuthenticated) {
console.log("User signed in:", authState.user);
} else {
console.log("User signed out");
}
});
// Later, stop listening
unsubscribe();Document Operations
doc(db, path, projectName?)
Create a document reference. Paths are always relative to the specified project.
// Document in your project
const docRef = doc(db, "myproject/users/user123");
// Document in another project
const otherDocRef = doc(db, "otherproject/users/user123");collection(db, path, projectName?)
Create a collection reference. Paths are always relative to the specified project.
// Collection in your project
const collectionRef = collection(db, "myproject/users");
// Collection in another project
const otherCollectionRef = collection(db, "users", "otherProject");getDoc(docRef)
Get a document snapshot.
const docRef = doc(db, "myproject/users/user123");
const snapshot = await getDoc(docRef);
if (snapshot.exists) {
console.log("User data:", snapshot.data());
}getDocs(collectionRef)
Get all documents in a collection.
const collectionRef = collection(db, "myproject/users");
const snapshot = await getDocs(collectionRef);
snapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});addDoc(collectionRef, data)
Add a new document with auto-generated ID.
const docRef = await addDoc(collection(db, "myproject/users"), {
name: "Jane Doe",
email: "jane@example.com",
});
console.log("New document ID:", docRef.id);setDoc(docRef, data, options?)
Set a document with a specific ID, optionally merging with existing data.
const userId = "john_doe";
const userRef = doc(db, `users/${userId}`);
await setDoc(userRef, {
name: "John Doe",
email: "john@example.com",
});
// Merge with existing data
await setDoc(userRef, { age: 30 }, { merge: true });updateDoc(docRef, data)
Update specific fields in a document.
const docRef = doc(db, "myproject/users/user123");
await updateDoc(docRef, {
age: 31,
lastLogin: new Date().toISOString(),
});deleteDoc(docRef)
Delete a document.
const docRef = doc(db, "myproject/users/user123");
await deleteDoc(docRef);Querying
query(collectionRef, ...constraints)
Create a query with constraints.
const q = query(
collection(db, "myproject/users"),
where("age", ">=", 18),
orderBy("name"),
limit(10)
);where(field, operator, value)
Create a where constraint.
Supported operators: ==, !=, <, <=, >, >=, array-contains, in, not-in, array-contains-any, matches
where("status", "==", "active");
where("age", ">=", 18);
where("tags", "array-contains", "javascript");
where("category", "in", ["tech", "science"]);orderBy(field, direction?)
Create an orderBy constraint.
orderBy("createdAt", "desc");
orderBy("name"); // defaults to 'asc'limit(count)
Limit the number of results.
limit(10);runStructuredQuery(collectionRef, ...constraints)
NEW: Execute queries using server-side structured query processing (recommended for better performance).
This function uses the POST :runQuery endpoint with structured query support, providing server-side filtering and sorting instead of client-side processing.
import {
db,
collection,
where,
orderBy,
limit,
runStructuredQuery,
} from "basebase-js";
// Server-side structured query with filtering, ordering, and limiting
const usersRef = collection(db, "myproject/users");
const results = await runStructuredQuery(
usersRef,
where("age", ">=", 18), // Server-side filtering
where("status", "==", "active"), // Multiple filters supported
orderBy("name", "asc"), // Server-side sorting
limit(10) // Limit results
);
// Process results
results.forEach((doc) => {
console.log("User:", doc.data());
});Advantages of runStructuredQuery:
- ✅ Server-side processing - Better performance for large datasets
- ✅ Native database filtering - More efficient than client-side filtering
- ✅ Reduced network traffic - Only matching documents are returned
- ✅ Same API - Uses the same
where,orderBy,limitconstraints
Supported operators: ==, !=, <, <=, >, >=, array-contains, in, not-in, array-contains-any, matches
Example with multiple filters:
// Find active adult users, sorted by creation date, limit 20
const results = await runStructuredQuery(
collection(db, "myproject/users"),
where("age", ">=", 21),
where("status", "==", "active"),
where("role", "in", ["admin", "moderator"]),
orderBy("createdAt", "desc"),
limit(20)
);Custom server configuration: For custom BaseBase server instances, use explicit database configuration:
import { getDatabase, collection, runStructuredQuery } from "basebase-js";
// Create database instance with custom server
const db = getDatabase({
token: "your_jwt_token",
baseUrl: "http://localhost:8000",
});
const results = await runStructuredQuery(
collection(db, "myproject/users"),
where("age", ">=", 18),
orderBy("name"),
limit(10)
);Tasks
doTask(taskName, parameters?, basebaseInstance?)
Execute a server-side task with optional parameters. Supports fully qualified task names using project/task syntax.
import { doTask } from "basebase-js";
// Execute task from your project
const result = await doTask("myTask", { param: "value" });
// Execute system task from basebase project
const page = await doTask("basebase/getPage", {
url: "https://example.com",
selector: "h1",
});
// Execute task from specific project
const data = await doTask("project_id/processData", { input: "test" });Parameters:
taskName(string): The name of the task to execute, optionally fully qualified asproject/taskparameters(object, optional): Parameters to pass to the taskbasebaseInstance(Basebase, optional): Custom BaseBase instance for server environments
Returns: Promise that resolves to the task result
Throws: BasebaseError if authentication fails, task doesn't exist, or execution fails
Task Name Formats:
"myTask"- Executes task from your authenticated project"basebase/getPage"- Executes system task from the basebase project"project_id/taskName"- Executes task from a specific project
Note: Tasks are executed within the context of the specified project. If no project is specified, your authenticated project is used.
Task Management
setTask(taskData, basebaseInstance?)
Set a new cloud task with a specific name.
const task = await setTask({
name: "myTask",
code: 'exports.handler = async (data) => { return { message: "Hello!" }; }',
description: "My custom task",
timeout: 30,
memoryMB: 128,
});getTask(taskName, basebaseInstance?)
Retrieve a cloud task by name.
const task = await getTask("myTask");
console.log(task.implementationCode);listTasks(basebaseInstance?)
List all cloud tasks in the project.
const tasks = await listTasks();
tasks.forEach((task) => console.log(task.id));updateTask(taskName, updates, basebaseInstance?)
Update an existing cloud task.
const updatedTask = await updateTask("myTask", {
code: 'exports.handler = async (data) => { return { message: "Updated!" }; }',
timeout: 60,
});deleteTask(taskName, basebaseInstance?)
Delete a cloud task.
await deleteTask("myTask");Task Triggering
addTrigger(triggerData, basebaseInstance?)
Add a triggered task with cron syntax (server assigns random ID).
const trigger = await addTrigger({
name: "dailyCleanup",
taskName: "cleanupTask",
schedule: "0 2 * * *", // Daily at 2 AM
timeZone: "America/New_York",
data: { target: "temp_files" },
enabled: true,
});setTrigger(triggerId, triggerData, basebaseInstance?)
Create a triggered task with a custom ID using cron syntax.
const trigger = await setTrigger("my-daily-cleanup", {
name: "dailyCleanup",
taskName: "cleanupTask",
schedule: "0 2 * * *", // Daily at 2 AM
timeZone: "America/New_York",
data: { target: "temp_files" },
enabled: true,
});Why use setTrigger over addTrigger?
- Intuitive IDs: Use meaningful names like
"daily-cleanup"instead of random UUIDs - Idempotent: Calling
setTriggerwith the same ID will update the existing trigger - Predictable: You know exactly what the trigger ID will be for future operations
getTrigger(triggerName, basebaseInstance?)
Retrieve a triggered task by name.
const trigger = await getTrigger("dailyCleanup");
console.log(`Next run: ${trigger.nextRun}`);listTriggers(basebaseInstance?)
List all triggered tasks in the project.
const triggers = await listTriggers();
triggers.forEach((trigger) =>
console.log(`${trigger.name}: ${trigger.schedule}`)
);updateTrigger(triggerName, updates, basebaseInstance?)
Update an existing triggered task.
const updatedTrigger = await updateTrigger("dailyCleanup", {
schedule: "0 3 * * *", // Change to 3 AM
enabled: false,
});deleteTrigger(triggerName, basebaseInstance?)
Delete a triggered task.
await deleteTrigger("dailyCleanup");🧪 Testing
A simple test webapp is included at test.html to demonstrate SDK functionality:
Build the SDK:
npm install npm run build
Open the test webapp: Open
test.htmlin your browser (requires internet connection for js-cookie CDN). The webapp includes:- Phone verification authentication flow
- Document operations on a "test" collection
- Real-time UI updates
Test workflow:
- Set your API key in the test interface
- Request SMS verification code
- Verify code to authenticate
- Add/view/update/delete documents
- Test sign out functionality
Note: The test webapp uses the UMD build (
dist/index.umd.js) for browser compatibility and loads js-cookie from CDN. It defaults tohttps://api.basebase.us. Adjust the configuration for your BaseBase server instance.
🔧 Configuration
Browser Applications
Authentication required first:
import { requestCode, verifyCode, doc, getDoc, setDoc } from "basebase-js";
// 1. Authenticate first
await verifyCode("+1234567890", "123456", "bb_your_api_key");
// 2. Then use database - no setup required
const userRef = doc(db, "myproject/users/user123");
const snapshot = await getDoc(userRef);Server/Node.js Applications
For server environments, use the getDatabase function with a JWT token:
import { getDatabase, doc, getDoc } from "basebase-js";
// Create a database instance with your JWT token
const db = getDatabase("user_jwt_token_here");
// Now use normally
const userRef = doc(db, "myproject/users/user123");
const snapshot = await getDoc(userRef);TypeScript Configuration
The SDK is written in TypeScript and provides full type safety:
import { BasebaseDocumentData, DocumentSnapshot } from "basebase-js";
interface User extends BasebaseDocumentData {
name: string;
email: string;
age: number;
}
const userSnap: DocumentSnapshot = await getDoc(
doc(db, "myproject/users/user123")
);
const userData = userSnap.data() as User;🔒 Security
API Key Management
- Store your API key securely
- Never expose API keys in client-side code in production
- Use environment variables for configuration
Authentication Flow
- User provides phone number and username (alphanumeric only)
- SMS verification code is sent
- User enters code to get JWT token
- Token is stored in cookies and localStorage
- Token is automatically included in API requests
🚀 Migration from Firebase
BaseBase SDK is designed to be API-compatible with Firebase/Firestore. Here's how to migrate:
Before (Firebase)
import { initializeApp } from "firebase/app";
import {
getFirestore,
collection,
doc,
getDoc,
getDocs,
} from "firebase/firestore";
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const userRef = doc(db, "myproject/users/user123");
const userSnap = await getDoc(userRef);After (BaseBase)
import { verifyCode, db, doc, getDoc } from "basebase-js";
// Authenticate first
await verifyCode("+1234567890", "123456", "bb_your_api_key");
// Then use database - no initialization needed!
const userRef = doc(db, "myproject/users/user123");
const userSnap = await getDoc(userRef);Key Differences
- Authentication required first - BaseBase uses phone verification
- No initialization required - BaseBase exports a ready-to-use
db