2.3.0 • Published 5 months ago

@jobsearch-works/firestore-models v2.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

Firestore Models

npm version license

Type-safe Firestore document models and path builders.

A shared library for standardizing Firestore document schemas and paths across multiple projects.

This library provides:

  • Type-safe Firestore document models
  • Pure Firestore path string builders (SDK-free)
  • Centralized schema definitions for use across apps

📦 Installation

npm install @jobsearch-works/firestore-models

Single Source of Truth

This package is the single source of truth for:

  • Database paths (@firestorePaths.ts)
  • Document types (@types)
  • Static data (@static)

Example usage:

// Paths (from @firestorePaths.ts)
import { firestorePaths } from "@jobsearch-works/firestore-models";
const path = firestorePaths.clients.jobPreferences(
  "hovdKgHzIDS7c8Kzt13QNG0xsEN2"
);

// Types (from @types)
import { ClientJobPreferences } from "@jobsearch-works/firestore-models";
const data: ClientJobPreferences = {
  /* ... */
};

// Static data (from @static)
import { locations } from "@jobsearch-works/firestore-models/static/locations";
import jobClassifications from "@jobsearch-works/firestore-models/static/jobClassification";

Quick Start

import { firestorePaths } from "@jobsearch-works/firestore-models";
import { doc, getDoc } from "firebase/firestore";
import { firestore } from "./firebaseConfig";

// Get a client's job preferences
const clientId = "hovdKgHzIDS7c8Kzt13QNG0xsEN2"; // Real Firebase ID
const path = firestorePaths.clients.jobPreferences(clientId);
const ref = doc(firestore, path);
const snap = await getDoc(ref);
const data = { id: snap.id, ...snap.data() };

Type Naming Convention

Types follow their Firestore paths. For example:

  • clients/{id}/emailTokenClientEmailToken
  • clients/{id}/jobPreferencesClientJobPreferences
  • clients/{id}/personalDataClientPersonalData

This makes it easy to find the right type for any path.

Type Casting Results

import { ClientJobPreferences } from "@jobsearch-works/firestore-models";
import { doc, getDoc } from "firebase/firestore";

// 1. Get the document
const path = firestorePaths.clients.jobPreferences(
  "hovdKgHzIDS7c8Kzt13QNG0xsEN2"
);
const ref = doc(firestore, path);
const snap = await getDoc(ref);

// 2. Cast the result (pick one method)
const data = { id: snap.id, ...snap.data() } as ClientJobPreferences;

// OR use the helper function
function withId<T>(snap: DocumentSnapshot): T & { id: string } {
  return { id: snap.id, ...snap.data() } as T & { id: string };
}
const data = withId<ClientJobPreferences>(snap);

React + Zustand Example

import { create } from "zustand";
import { doc, onSnapshot } from "firebase/firestore";
import { firestorePaths } from "@jobsearch-works/firestore-models";
import { ClientJobPreferences } from "@jobsearch-works/firestore-models";

// Store
interface JobPreferencesStore {
  preferences: ClientJobPreferences | null;
  loading: boolean;
  error: Error | null;
  fetchPreferences: (clientId: string) => void;
}

export const useJobPreferences = create<JobPreferencesStore>((set) => ({
  preferences: null,
  loading: false,
  error: null,
  fetchPreferences: (clientId: string) => {
    set({ loading: true });
    const path = firestorePaths.clients.jobPreferences(clientId);
    const ref = doc(firestore, path);

    return onSnapshot(
      ref,
      (snap) => {
        const data = { id: snap.id, ...snap.data() } as ClientJobPreferences;
        set({ preferences: data, loading: false });
      },
      (error) => set({ error, loading: false })
    );
  },
}));

// Component
function JobPreferences() {
  const { preferences, loading, error, fetchPreferences } = useJobPreferences();
  const clientId = "hovdKgHzIDS7c8Kzt13QNG0xsEN2";

  useEffect(() => {
    const unsubscribe = fetchPreferences(clientId);
    return () => unsubscribe();
  }, [clientId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!preferences) return null;

  return (
    <div>
      <h2>Job Preferences</h2>
      <p>Desired Positions: {preferences.desiredPositions.join(", ")}</p>
      <p>
        Min Salary: {preferences.minSalary} {preferences.currency}
      </p>
    </div>
  );
}

Path Structure

// Collection paths
firestorePaths.clients.collection(); // "clients"
firestorePaths.clients.emails.collection("hovdKgHzIDS7c8Kzt13QNG0xsEN2"); // "clients/hovdKgHzIDS7c8Kzt13QNG0xsEN2/emails"

// Document paths
firestorePaths.clients.doc("hovdKgHzIDS7c8Kzt13QNG0xsEN2"); // "clients/hovdKgHzIDS7c8Kzt13QNG0xsEN2"
firestorePaths.clients.personalData("hovdKgHzIDS7c8Kzt13QNG0xsEN2"); // "clients/hovdKgHzIDS7c8Kzt13QNG0xsEN2/personalData"

Type Safety

import {
  ClientJobPreferences,
  JobCategory,
  JobTitle,
  LocationId,
} from "@jobsearch-works/firestore-models";

// Type-safe job preferences
const preferences: ClientJobPreferences = {
  clientId: "hovdKgHzIDS7c8Kzt13QNG0xsEN2",
  desiredPositions: ["Software Engineering"], // TypeScript autocomplete
  categories: ["Information & Communication Technology"], // TypeScript autocomplete
  locations: ["sydney", "london"], // TypeScript autocomplete
  minSalary: 100000,
  currency: "AUD",
  industries: ["Technology"],
  createdAt: new Date(),
  updatedAt: new Date(),
};

Static Data

import jobClassifications from "@jobsearch-works/firestore-models/static/jobClassification";
import { locations } from "@jobsearch-works/firestore-models/static/locations";

// Job categories and titles
type JobCategory = keyof typeof jobClassifications;
type JobTitle = (typeof jobClassifications)[JobCategory][number];

// Location IDs
type LocationId = (typeof locations)[number]["id"];

Installation

npm install @jobsearch-works/firestore-models
2.3.0

5 months ago

2.2.5

5 months ago

2.2.4

5 months ago

2.2.3

5 months ago

2.2.2

5 months ago

2.2.1

5 months ago

2.1.0

5 months ago

2.0.5

6 months ago

2.0.4

6 months ago

2.0.3

6 months ago

2.0.2

6 months ago

2.0.1

6 months ago

2.0.0

6 months ago

1.1.15

6 months ago

1.1.14

6 months ago

1.1.13

6 months ago

1.1.12

6 months ago

1.1.11

6 months ago

1.1.10

6 months ago

1.1.9

6 months ago

1.1.8

6 months ago

1.1.7

6 months ago

1.1.6

6 months ago

1.1.5

6 months ago

1.1.4

6 months ago

1.1.3

6 months ago

1.1.2

6 months ago

1.1.1

6 months ago

1.1.0

6 months ago

1.0.34

6 months ago

1.0.33

6 months ago

1.0.32

6 months ago

1.0.31

6 months ago

1.0.30

6 months ago

1.0.29

6 months ago

1.0.28

6 months ago

1.0.27

6 months ago

1.0.26

6 months ago

1.0.25

6 months ago

1.0.24

6 months ago

1.0.23

6 months ago

1.0.22

6 months ago

1.0.21

6 months ago

1.0.20

6 months ago

1.0.19

6 months ago

1.0.18

6 months ago

1.0.17

6 months ago

1.0.16

6 months ago

1.0.15

6 months ago

1.0.14

6 months ago

1.0.13

6 months ago

1.0.12

6 months ago

1.0.11

6 months ago

1.0.10

6 months ago

1.0.9

6 months ago

1.0.8

6 months ago

1.0.7

6 months ago

1.0.3

6 months ago

1.0.2

6 months ago

1.0.1

6 months ago

1.0.0

6 months ago