@jobsearch-works/firestore-models v2.3.0
Firestore Models
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-modelsSingle 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}/emailToken→ClientEmailTokenclients/{id}/jobPreferences→ClientJobPreferencesclients/{id}/personalData→ClientPersonalData
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-models5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago