smithery/neversight

firebase-data-connect

Firebase Data Connect integration for GraphQL-based data access with PostgreSQL.

Installation

$ npx skills add smithery/neversight --skill firebase-data-connect

Summary

  • Firebase Data Connect integration for GraphQL-based data access with PostgreSQL.
  • Use when building GraphQL schemas, queries, mutations, or integrating Firebase Data Connect with Angular applications.
  • Supports type-safe generated SDKs, real-time subscriptions, and server-side data validation.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/neversight · top by installs.

npx skills add smithery/neversight

Browse all from smithery/neversight

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

LicenseComplete terms in LICENSE.txt

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 14,621 B
  • docs SUMMARY.md 321 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Firebase Data Connect

Expert guidance for integrating Firebase Data Connect with Angular applications, providing type-safe GraphQL operations backed by PostgreSQL.

When to Use This Skill

Activate this skill when you need to:

  • Define GraphQL schemas for Firebase Data Connect
  • Create queries and mutations for data operations
  • Integrate Data Connect with Angular services
  • Generate type-safe TypeScript SDKs
  • Implement real-time data subscriptions
  • Configure Data Connect connectors and services
  • Migrate from Firestore to Data Connect
  • Optimize GraphQL query performance

What is Firebase Data Connect?

Firebase Data Connect is a relational database service that provides:

  • GraphQL API for type-safe data access
  • PostgreSQL backend for relational data
  • Generated SDKs for TypeScript/JavaScript
  • Real-time subscriptions for live data
  • Server-side validation and security rules
  • Local emulator for development and testing

Prerequisites

Required Tools

  • Firebase CLI (npm install -g firebase-tools)
  • Node.js 18+ and npm/pnpm
  • Angular 20+ project
  • Firebase project with Data Connect enabled

Installation

# Install Firebase CLI
npm install -g firebase-tools

# Login to Firebase
firebase login

# Initialize Data Connect in your project
firebase init dataconnect

# Install Angular Fire (if not already installed)
npm install @angular/fire

Project Structure

dataconnect/
├── dataconnect.yaml          # Connector configuration
├── schema/
│   ├── schema.gql            # GraphQL schema definitions
│   └── types.gql             # Custom types and enums
├── queries/
│   ├── users.gql             # User queries
│   └── workspace.gql         # Workspace queries
├── mutations/
│   ├── createUser.gql        # User mutations
│   └── updateWorkspace.gql   # Workspace mutations
└── seed_data.gql             # Development seed data

src/dataconnect-generated/    # Generated TypeScript SDK
└── index.ts                  # Auto-generated type-safe operations

Schema Definition

Basic Schema Example

# dataconnect/schema/schema.gql

type User @table {
  id: UUID! @default(expr: "uuidV4()")
  email: String! @unique
  displayName: String!
  photoURL: String
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
  
  # Relationships
  workspaces: [WorkspaceMember!]! @relationFrom(field: "user")
}

type Workspace @table {
  id: UUID! @default(expr: "uuidV4()")
  name: String!
  description: String
  ownerId: UUID!
  createdAt: Timestamp! @default(expr: "request.time")
  
  # Relationships
  owner: User! @relation(fields: "ownerId")
  members: [WorkspaceMember!]! @relationFrom(field: "workspace")
}

type WorkspaceMember @table {
  id: UUID! @default(expr: "uuidV4()")
  userId: UUID!
  workspaceId: UUID!
  role: MemberRole!
  joinedAt: Timestamp! @default(expr: "request.time")
  
  # Relationships
  user: User! @relation(fields: "userId")
  workspace: Workspace! @relation(fields: "workspaceId")
  
  # Composite unique constraint
  @unique(fields: ["userId", "workspaceId"])
}

enum MemberRole {
  OWNER
  ADMIN
  MEMBER
  VIEWER
}

Queries

Query Definition

# dataconnect/queries/users.gql

query GetUser($userId: UUID!) @auth(level: USER) {
  user(id: $userId) {
    id
    email
    displayName
    photoURL
    createdAt
  }
}

query ListUserWorkspaces($userId: UUID!) @auth(level: USER) {
  workspaceMembers(where: { userId: { eq: $userId } }) {
    workspace {
      id
      name
      description
      owner {
        id
        displayName
      }
    }
    role
    joinedAt
  }
}

query SearchWorkspaces($searchTerm: String!) @auth(level: USER) {
  workspaces(
    where: { 
      name: { contains: $searchTerm } 
    }
    orderBy: { createdAt: DESC }
    limit: 20
  ) {
    id
    name
    description
    owner {
      displayName
    }
    createdAt
  }
}

Mutations

Mutation Definition

# dataconnect/mutations/workspace.gql

mutation CreateWorkspace(
  $name: String!
  $description: String
  $ownerId: UUID!
) @auth(level: USER) {
  workspace_insert(data: {
    name: $name
    description: $description
    ownerId: $ownerId
  }) {
    id
    name
    description
    createdAt
  }
}

mutation AddWorkspaceMember(
  $workspaceId: UUID!
  $userId: UUID!
  $role: MemberRole!
) @auth(level: USER) {
  workspaceMember_insert(data: {
    workspaceId: $workspaceId
    userId: $userId
    role: $role
  }) {
    id
    user {
      displayName
      email
    }
    role
    joinedAt
  }
}

mutation UpdateWorkspace(
  $id: UUID!
  $name: String
  $description: String
) @auth(level: USER) {
  workspace_update(
    id: $id
    data: {
      name: $name
      description: $description
    }
  ) {
    id
    name
    description
    updatedAt
  }
}

Angular Integration

Generated SDK Usage

// src/app/infrastructure/firebase/data-connect.service.ts
import { Injectable, inject } from '@angular/core';
import { ConnectorConfig, getDataConnect } from '@angular/fire/data-connect';
import { 
  getUser, 
  listUserWorkspaces,
  createWorkspace,
  addWorkspaceMember 
} from '@/dataconnect-generated';

@Injectable({ providedIn: 'root' })
export class DataConnectService {
  private dataConnect = getDataConnect();
  
  // Execute query
  async getUserById(userId: string) {
    const result = await getUser(this.dataConnect, { userId });
    return result.data.user;
  }
  
  // Execute query with variables
  async getUserWorkspaces(userId: string) {
    const result = await listUserWorkspaces(this.dataConnect, { userId });
    return result.data.workspaceMembers;
  }
  
  // Execute mutation
  async createNewWorkspace(name: string, description: string, ownerId: string) {
    const result = await createWorkspace(this.dataConnect, {
      name,
      description,
      ownerId
    });
    return result.data.workspace_insert;
  }
}

Repository Pattern

// src/app/infrastructure/persistence/workspace-dataconnect.repository.ts
import { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { DataConnectService } from '../firebase/data-connect.service';
import { IWorkspaceRepository } from '@domain/repositories/workspace.repository';
import { Workspace } from '@domain/workspace/workspace.entity';

@Injectable({ providedIn: 'root' })
export class WorkspaceDataConnectRepository implements IWorkspaceRepository {
  constructor(private dataConnect: DataConnectService) {}
  
  findById(id: string): Observable<Workspace | null> {
    return from(this.dataConnect.getWorkspaceById(id)).pipe(
      map(data => data ? this.toDomain(data) : null)
    );
  }
  
  findByOwnerId(ownerId: string): Observable<Workspace[]> {
    return from(this.dataConnect.getUserWorkspaces(ownerId)).pipe(
      map(members => members.map(m => this.toDomain(m.workspace)))
    );
  }
  
  save(workspace: Workspace): Observable<Workspace> {
    return from(this.dataConnect.createNewWorkspace(
      workspace.name,
      workspace.description,
      workspace.ownerId
    )).pipe(
      map(data => this.toDomain(data))
    );
  }
  
  private toDomain(data: any): Workspace {
    // Map Data Connect response to domain entity
    return new Workspace({
      id: data.id,
      name: data.name,
      description: data.description,
      ownerId: data.ownerId,
      createdAt: new Date(data.createdAt)
    });
  }
}

NgRx Signals Integration

// src/app/application/store/workspace.store.ts
import { signalStore, withState, withMethods } from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { pipe, switchMap, tap } from 'rxjs';
import { tapResponse } from '@ngrx/operators';
import { inject } from '@angular/core';
import { patchState } from '@ngrx/signals';
import { DataConnectService } from '@infrastructure/firebase/data-connect.service';

export const WorkspaceStore = signalStore(
  { providedIn: 'root' },
  withState({
    workspaces: [] as Workspace[],
    selectedWorkspace: null as Workspace | null,
    loading: false,
    error: null as string | null
  }),
  withMethods((store, dataConnect = inject(DataConnectService)) => ({
    loadUserWorkspaces: rxMethod<string>(
      pipe(
        tap(() => patchState(store, { loading: true, error: null })),
        switchMap((userId) => dataConnect.getUserWorkspaces(userId)),
        tapResponse({
          next: (workspaces) => patchState(store, { 
            workspaces, 
            loading: false 
          }),
          error: (error: Error) => patchState(store, { 
            error: error.message, 
            loading: false 
          })
        })
      )
    ),
    
    createWorkspace: rxMethod<{ name: string; description: string; ownerId: string }>(
      pipe(
        tap(() => patchState(store, { loading: true })),
        switchMap(({ name, description, ownerId }) => 
          dataConnect.createNewWorkspace(name, description, ownerId)
        ),
        tapResponse({
          next: (workspace) => patchState(store, (state) => ({
            workspaces: [...state.workspaces, workspace],
            loading: false
          })),
          error: (error: Error) => patchState(store, { 
            error: error.message, 
            loading: false 
          })
        })
      )
    )
  }))
);

Configuration

dataconnect.yaml

# dataconnect/dataconnect.yaml
connectorId: my-connector
cloudSql:
  instanceId: my-instance
  database: my-database
schema:
  source: ./schema
  datasource:
    postgresql: {}
queries:
  source: ./queries
mutations:
  source: ./mutations

Angular Fire Configuration

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { provideDataConnect, getDataConnect } from '@angular/fire/data-connect';
import { environment } from './environments/environment';

export const appConfig: ApplicationConfig = {
  providers: [
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideDataConnect(() => getDataConnect({
      connector: 'my-connector',
      location: 'us-central1'
    })),
  ]
};

Local Development

Start Emulator

# Start Data Connect emulator
firebase emulators:start --only dataconnect

# Run with seed data
firebase emulators:start --only dataconnect --import=./seed-data

# Generate TypeScript SDK
firebase dataconnect:sdk:generate --output=src/dataconnect-generated

Seed Data

# dataconnect/seed_data.gql
mutation SeedUsers {
  user1: user_insert(data: {
    email: "[email protected]"
    displayName: "Admin User"
  }) { id }
  
  user2: user_insert(data: {
    email: "[email protected]"
    displayName: "Member User"
  }) { id }
}

mutation SeedWorkspaces {
  workspace1: workspace_insert(data: {
    name: "Default Workspace"
    description: "Main workspace"
    ownerId: "USER_ID_HERE"
  }) { id }
}

Best Practices

Schema Design

// ✅ Good - Use proper relationships
type Post @table {
  authorId: UUID!
  author: User! @relation(fields: "authorId")
}

// ❌ Bad - Duplicating data
type Post @table {
  authorEmail: String
  authorName: String
}

Query Optimization

# ✅ Good - Request only needed fields
query GetWorkspace($id: UUID!) {
  workspace(id: $id) {
    id
    name
    owner { displayName }
  }
}

# ❌ Bad - Over-fetching
query GetWorkspace($id: UUID!) {
  workspace(id: $id) {
    id
    name
    description
    owner {
      id
      email
      displayName
      photoURL
      createdAt
      updatedAt
    }
    members {
      user {
        id
        email
        displayName
      }
    }
  }
}

Error Handling

// ✅ Good - Handle errors properly
async loadWorkspace(id: string) {
  try {
    const result = await getWorkspace(this.dataConnect, { id });
    if (result.errors) {
      throw new Error(result.errors[0].message);
    }
    return result.data.workspace;
  } catch (error) {
    console.error('Failed to load workspace:', error);
    throw error;
  }
}

// ❌ Bad - Ignore errors
async loadWorkspace(id: string) {
  const result = await getWorkspace(this.dataConnect, { id });
  return result.data.workspace;
}

Security

Authentication Rules

# Require authentication
query GetUser($id: UUID!) @auth(level: USER) {
  user(id: $id) { ... }
}

# Require specific role
mutation DeleteWorkspace($id: UUID!) @auth(level: USER, expr: "auth.uid == resource.ownerId") {
  workspace_delete(id: $id) { id }
}

Input Validation

# Use constraints
type User @table {
  email: String! @unique
  displayName: String! @check(expr: "length(this) >= 3")
  age: Int @check(expr: "this >= 18")
}

Troubleshooting

Issue Cause Solution
SDK generation fails Schema syntax errors Run firebase dataconnect:validate
Query returns null Missing @auth directive Add proper authentication
Relationship error Incorrect field references Check @relation fields match column names
Emulator won't start Port already in use Change port in firebase.json or kill process
Type mismatch Stale generated code Re-run firebase dataconnect:sdk:generate

Migration from Firestore

// Before (Firestore)
const docRef = doc(firestore, 'workspaces', id);
const docSnap = await getDoc(docRef);
const data = docSnap.data();

// After (Data Connect)
const result = await getWorkspace(dataConnect, { id });
const data = result.data.workspace;

References