Saltar a contenido

Quickstart

Get a fully wired CyberEco data layer running in under 5 minutes. This guide shows you how to install the packages, connect to Firebase, and perform CRUD operations.

1. Install Packages

npm install @cyber-eco/types @cyber-eco/firebase @cyber-eco/services

You also need Firebase as a peer dependency:

npm install firebase

Package roles

Package Purpose
@cyber-eco/types TypeScript interfaces, constants, zero runtime deps
@cyber-eco/firebase FirebaseStorageAdapter -- the production storage backend
@cyber-eco/services DataLayerService orchestrator + domain services

2. Initialize Firebase

Create a Firebase configuration file:

src/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: 'your-api-key',
  authDomain: 'your-project.firebaseapp.com',
  projectId: 'your-project-id',
  storageBucket: 'your-project.appspot.com',
  messagingSenderId: '123456789',
  appId: '1:123456789:web:abc123',
};

const app = initializeApp(firebaseConfig);

export function getDb() {
  return getFirestore(app);
}

3. Create the StorageAdapter

The FirebaseStorageAdapter takes a lazy getter function -- not a Firestore instance directly. This prevents module-level initialization crashes and supports lazy initialization patterns.

src/adapter.ts
import { FirebaseStorageAdapter } from '@cyber-eco/firebase';
import { getDb } from './firebase';

export const adapter = new FirebaseStorageAdapter(() => getDb());

Why a lazy getter?

Passing () => getFirestore() instead of getFirestore() means Firebase is only initialized when the first database operation occurs. This avoids crashes when the module is imported in environments where Firebase is not yet configured (e.g., during testing or SSR).

4. Create the Data Layer

The createDataLayer() factory wires together all domain services with the adapter:

src/data-layer.ts
import { createDataLayer } from '@cyber-eco/services';
import { adapter } from './adapter';

const dataLayer = createDataLayer({
  adapter,
  cache: {},                       // Enable in-memory caching (default TTL)
  permissions: { enabled: true },  // Enable permission checking
  sync: { enabled: false },        // Disable sync broadcasting
  webhooks: { enabled: false },    // Disable webhook events
});

export const { core, permissions, sharedData, notifications, dashboard, dataExport } = dataLayer;

The factory returns a CyberEcoDataLayer object with the following services:

Service Description
core DataLayerService -- the main CRUD orchestrator
permissions PermissionService -- role-based access control
sharedData SharedDataService -- cross-app data sharing
notifications NotificationService -- user notifications
dashboard DashboardService -- dashboard analytics
dataExport DataExportService -- data export/portability

5. CRUD Operations

All operations on core require a userId as the first parameter. This is used for permission checking.

Create a Document

const userId = 'user-abc123';

// Create a document (ID auto-generated)
const taskId = await core.create(userId, 'tasks', {
  title: 'Review pull request',
  status: 'pending',
  assignee: userId,
});

console.log(`Created task: ${taskId}`);

Read a Document

interface Task {
  id: string;
  title: string;
  status: string;
  assignee: string;
  createdAt: string;
  updatedAt: string;
}

const task = await core.get<Task>(userId, 'tasks', taskId);
console.log(task?.title); // "Review pull request"

Query Documents

const pendingTasks = await core.query<Task>(userId, 'tasks', [
  { field: 'status', operator: '==', value: 'pending' },
  { field: 'assignee', operator: '==', value: userId },
]);

console.log(`Found ${pendingTasks.data.length} pending tasks`);
console.log(`Has more: ${pendingTasks.hasMore}`);

Query supports the following operators:

Operator Description Example
== Equal to { field: 'status', operator: '==', value: 'active' }
!= Not equal to { field: 'status', operator: '!=', value: 'deleted' }
< Less than { field: 'age', operator: '<', value: 18 }
<= Less than or equal { field: 'priority', operator: '<=', value: 3 }
> Greater than { field: 'score', operator: '>', value: 100 }
>= Greater than or equal { field: 'createdAt', operator: '>=', value: '2024-01-01' }
in Value in array { field: 'status', operator: 'in', value: ['active', 'pending'] }
array-contains Array field contains value { field: 'tags', operator: 'array-contains', value: 'urgent' }
array-contains-any Array field contains any of values { field: 'tags', operator: 'array-contains-any', value: ['urgent', 'important'] }

Update a Document

await core.update(userId, 'tasks', taskId, {
  status: 'completed',
});

Delete a Document

await core.delete(userId, 'tasks', taskId);

Batch Write

Execute multiple operations atomically:

await core.batchWrite(userId, [
  { type: 'set', collection: 'tasks', id: 'task-1', data: { title: 'Task 1', status: 'pending' } },
  { type: 'set', collection: 'tasks', id: 'task-2', data: { title: 'Task 2', status: 'pending' } },
  { type: 'update', collection: 'tasks', id: 'task-3', data: { status: 'completed' } },
  { type: 'delete', collection: 'tasks', id: 'task-old' },
]);

Subscribe to Real-Time Updates

const unsubscribe = core.subscribe<Task>(userId, 'tasks', taskId, (task) => {
  if (task) {
    console.log(`Task updated: ${task.title} (${task.status})`);
  } else {
    console.log('Task deleted');
  }
});

// Later: stop listening
unsubscribe();

6. Using Domain Services

Beyond basic CRUD, the data layer exposes specialized domain services.

Permissions

import type { AppRole } from '@cyber-eco/types';

// Grant app access with roles
await permissions.grantAppAccess(
  'target-user-id',  // user to grant access to
  'my-app-id',       // app ID
  ['member'] as AppRole[],  // roles
  userId,             // granted by
  ['read', 'write'],  // features
);

// Check if user has a role
const isAdmin = await permissions.hasRole('target-user-id', 'my-app-id', 'admin');

// Check feature access
const canWrite = await permissions.hasAppFeature('target-user-id', 'my-app-id', 'write');

Shared Data

// SharedDataService provides cross-app data sharing capabilities
const shared = dataLayer.sharedData;

Notifications

// NotificationService manages user notification preferences and delivery
const notifs = dataLayer.notifications;

Complete Example

Here is a complete working example that ties everything together:

src/index.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { FirebaseStorageAdapter } from '@cyber-eco/firebase';
import { createDataLayer } from '@cyber-eco/services';

// 1. Initialize Firebase
const app = initializeApp({
  apiKey: 'your-api-key',
  authDomain: 'your-project.firebaseapp.com',
  projectId: 'your-project-id',
  storageBucket: 'your-project.appspot.com',
  messagingSenderId: '123456789',
  appId: '1:123456789:web:abc123',
});

// 2. Create adapter with lazy getter
const adapter = new FirebaseStorageAdapter(() => getFirestore(app));

// 3. Create fully wired data layer
const { core, permissions } = createDataLayer({
  adapter,
  permissions: { enabled: false },  // Disable for quick demo
});

// 4. Use it
async function main() {
  const userId = 'demo-user';

  // Create
  const id = await core.create(userId, 'notes', {
    title: 'Hello CyberEco',
    body: 'My first note using the CyberEco data layer.',
  });

  // Read
  const note = await core.get(userId, 'notes', id);
  console.log('Created:', note);

  // Query
  const allNotes = await core.query(userId, 'notes', []);
  console.log(`Total notes: ${allNotes.data.length}`);

  // Update
  await core.update(userId, 'notes', id, { body: 'Updated content.' });

  // Delete
  await core.delete(userId, 'notes', id);
  console.log('Deleted note.');
}

main().catch(console.error);

Next Steps