Skip to content

Frequently Asked Questions

Architecture

Why 4 packages instead of 1?

Different consumers need different things:

  • A static website only needs @cyber-eco/types for TypeScript interfaces (zero runtime cost)
  • A lightweight app needs @cyber-eco/types + @cyber-eco/auth (for SSO hooks)
  • A full CyberEco app needs all four packages
  • A testing environment needs @cyber-eco/types + @cyber-eco/services with a MockStorageAdapter (no Firebase at all)

A single monolithic package would force every consumer to install Firebase, React, jsonwebtoken, and the entire service layer even if they only need type definitions.

Why Turborepo instead of Nx?

The main cybereco-monorepo uses Nx — and that's the right choice for an app monorepo with code generation, executors, and complex build graphs.

This repository is a library-only monorepo (no apps, just packages). Turborepo's sweet spot is exactly this: near-zero configuration, automatic topological builds via dependsOn: ["^build"], and excellent caching. For 4 packages with a linear dependency chain, the entire turbo.json is ~15 lines.

Using a different tool also creates clean separation: Nx manages apps, Turborepo manages published packages.

Why not publish to public npm?

Not yet. The packages are in active development (pre-1.0) and the API is still stabilizing. Publishing to GitHub Packages (private, scoped to @cyber-eco) lets us iterate quickly without the social contract of a public npm package.

When the API stabilizes post-1.0, we plan to publish to public npm.

Why doesn't @cyber-eco/services depend on @cyber-eco/firebase?

This is the most important architectural decision in the entire project.

If @cyber-eco/services imported @cyber-eco/firebase, then every consumer of the services package would transitively depend on Firebase. This would make it impossible to: - Test services without Firebase mocks - Run services against a different storage backend - Migrate to IPFS/blockchain without rewriting all services

Instead, services depend only on the StorageAdapter interface from @cyber-eco/types. The consumer wires the FirebaseStorageAdapter at runtime. This is classic dependency inversion.

Usage

How do I add a new StorageAdapter?

  1. Create a new package (e.g., @cyber-eco/postgres)
  2. Add @cyber-eco/types as a dependency
  3. Create a class that implements StorageAdapter
  4. Implement all methods (getDocument, setDocument, query, etc.)
  5. Pass an instance to createDataLayer({ adapter: new PostgresAdapter(...) })

All domain services will work unchanged.

How do I add a new domain service?

  1. Create a new file in packages/services/src/domain/ (e.g., VotingService.ts)
  2. Accept IDataLayerService in the constructor
  3. Use this.dataLayer.get(), this.dataLayer.create(), this.dataLayer.query() for all data operations
  4. Add the service to the createDataLayer factory return type
  5. Export from packages/services/src/index.ts

How do I run tests without Firebase emulators?

Use the MockStorageAdapter:

import { MockStorageAdapter } from '@cyber-eco/services/testing';
import { createDataLayer } from '@cyber-eco/services';

const adapter = new MockStorageAdapter();
adapter.seed('users', 'user1', { name: 'Alice', email: 'alice@example.com' });

const dataLayer = createDataLayer({ adapter });

const user = await dataLayer.sharedData.getSharedUserProfile('user1', 'user1');
// Returns { id: 'user1', name: 'Alice', email: 'alice@example.com' }

No Firebase SDK, no emulators, no network. Tests run in milliseconds.

What happens when types change — do I need to update all packages?

All 4 packages use fixed versioning via Changesets. When @cyber-eco/types changes, all packages get the same version bump. Consumers install the same version of all packages and compatibility is guaranteed.

How does the permission bootstrap (circular dependency) work?

The DataLayerService needs a permission checker function. The PermissionService needs the DataLayerService to read user data. This is a circular dependency.

The createDataLayer() factory solves it in three steps: 1. Create DataLayerService without permission checking 2. Create PermissionService using the DataLayerService 3. Wire the PermissionService.evaluatePermission back into the DataLayerService

After step 3, all operations go through permission checks.

Can I use @cyber-eco/auth without @cyber-eco/services?

Yes. @cyber-eco/auth provides authentication utilities (SSO, JWT, hooks, 2FA) that work independently. It does not depend on @cyber-eco/services.

Common standalone use cases: - useHubAuth() hook in a lightweight app - SharedAuthState for cross-app SSO - AuthTokenService for URL-based token processing - Zod validation schemas for input validation

Migration

What's the migration path from the current monorepo?

  1. Install the published packages alongside existing libs/ directories
  2. Update .npmrc to include the GitHub Packages registry
  3. Migrate one app at a time (start with Website — fewest imports)
  4. For each app: replace @cyber-eco/shared-types -> @cyber-eco/types, @cyber-eco/firebase-config -> @cyber-eco/firebase
  5. Replace direct Firestore imports in services with createDataLayer() factory
  6. Verify tests pass
  7. Once all apps are migrated, delete the old libs/ directories