# PocketBase Adapter — Firestore Compatibility Patterns

Critical return-shape requirements that the PocketBase adapter MUST satisfy for multi-file Firebase apps to work without per-file changes.

## 1. `getDoc` — `exists()` MUST be a function

```javascript
// BUG: returns boolean — every consumer calls .exists() and gets TypeError
return { exists: true, data: () => ... };

// FIX: returns function — matches Firestore SDK API
return {
    id: record.id,
    data: () => convertFromPB(record),
    exists: () => true,    // <-- function, not boolean
    ref: { id: record.id }
};
```

**Why this bites you:** The Firebase SDK documents `exists()` as a method. Every consumer in the migrated app does `if (snap.exists()) { ... }`. A boolean `true` is truthy so `if (snap.exists)` evaluates `if (true)` and the branch runs. But if the record is **not found**, the boolean `false` means `if (false)` skips the branch — which happens to pass in that case. The real crash is: `snap.exists()` returning `false` vs boolean `false` being the same — but a missing `exists` key entirely kills settings pages, customer details, and any code path that calls `exists()`.

## 2. `getDocs` — MUST return snapshot shape, not raw array

```javascript
// Firestore consumer code (ubiquitous):
const results = await getDocs(q);
results.docs.map(doc => ({ id: doc.id, ...doc.data() }));
results.forEach(doc => { ... });
if (results.empty) { ... }
console.log(results.size);  // <-- also used

// PocketBase adapter BUG: returns a bare array
// → docs.map crashes (TypeError: .docs is undefined)
// → .empty, .size are undefined

// FIX: createQuerySnapshot() helper
function createQuerySnapshot(docs) {
    const docList = Array.isArray(docs) ? docs : [];
    return {
        docs: docList,
        size: docList.length,
        empty: docList.length === 0,
        forEach(callback) { docList.forEach(callback); }
    };
}

async function getDocs(qOrCollectionRef) {
    // ... do query, get items from PocketBase ...
    const docs = items.map(rec => ({
        id: rec.id,
        data: () => convertFromPB(rec),
        exists: true,
        ref: { id: rec.id }
    }));
    return createQuerySnapshot(docs);
}
```

## 3. `addDoc` — MUST inject userId from sub-collection refs

```javascript
// App code (Firebase sub-collection pattern):
const servicesRef = collection(db, 'users', currentUser.uid, 'services');
await addDoc(servicesRef, { name: 'Brake Pads', price: 199.99 });

// PocketBase adapter — the ref has _userId tracked but addDoc ignores it
// BUG: record stored in 'services' collection WITHOUT userId field
// → queries with where('userId', '==', currentUser.uid) return nothing

// FIX:
async function addDoc(collRef, data) {
    const pbData = convertToPB(data);
    if (collRef._userId && !pbData.userId) {
        pbData.userId = collRef._userId;  // <-- inject from ref
    }
    const record = await pb.collection(collRef._name).create(pbData);
    return { id: record.id };
}
```

## 4. `onSnapshot` — MUST pass proper snapshot shape

```javascript
// Consumer code expects:
onSnapshot(query, (snapshot) => {
    snapshot.forEach(doc => { ... });   // <-- pulls from snapshot.docs
    snapshot.docs.map(...);              // <-- same
    snapshot.empty;                      // <-- also used
});

// BUG: bare getDocs().then(docs => callback(snapshot from createQuerySnapshot))
// must pass the full snapshot, not raw docs

// FIX:
function onSnapshot(queryOrDocRef, callback) {
    if (queryOrDocRef && (queryOrDocRef._type === 'query' || queryOrDocRef._type === 'collection')) {
        getDocs(queryOrDocRef).then(snapshot => callback(snapshot));
    } else {
        getDoc(queryOrDocRef).then(doc => callback(doc));
    }
    return () => {};
}
```

## Diagnosis: How to find these bugs

```javascript
// In browser DevTools console:
// 1) Check getDocs return shape
const snap = await getDocs(query(collection(db, 'repairOrders'), where('userId', '==', '...')));
console.assert(Array.isArray(snap.docs), 'getDocs.docs must be array');
console.assert(typeof snap.empty === 'boolean', 'getDocs.empty must be boolean');
console.assert(typeof snap.size === 'number', 'getDocs.size must be number');
console.assert(typeof snap.forEach === 'function', 'getDocs.forEach must be function');

// 2) Check getDoc return shape
const doc = await getDoc(doc(db, 'repairOrders', 'some-id'));
console.assert(typeof doc.exists === 'function', 'getDoc.exists must be function, got ' + typeof doc.exists);
console.assert(typeof doc.data === 'function', 'getDoc.data must be function');

// 3) Check addDoc injects userId
const ref = collection(db, 'users', 'test-uid', 'services');
const result = await addDoc(ref, { name: 'test' });
const saved = await getDoc(doc(db, 'services', result.id));
console.assert(saved.data().userId === 'test-uid', 'addDoc must inject userId from ref');
```

## Common consumer patterns across app files

These patterns appear in `dashboard.js`, `appointments.js`, `customers.js`, `repair-orders.js`, `settings.js`, `quote-tab-manager.js`, `customer-lookup.js`, and `shared/data-manager.js`:

| Pattern | Found in | Breaks if... |
|---|---|---|
| `querySnapshot.docs.map(...)` | All files | `getDocs` returns raw array |
| `querySnapshot.empty` | `repair-orders.js`, `appointments.js`, `settings.js` | Snapshot has no `.empty` |
| `querySnapshot.size` | `dashboard.js`, `repair-orders.js`, `customers.js`, `customer-lookup.js` | Snapshot has no `.size` |
| `querySnapshot.forEach(doc => {})` | `customers.js`, `customer-lookup.js`, `dashboard.js`, `settings.js`, `shared/data-manager.js` | Snapshot has no `.forEach` |
| `if (docSnap.exists())` | `quote-tab-manager.js`, `repair-orders.js`, `settings.js`, `dashboard.js`, `invoice-manager.js`, `customers.js` | `exists` is boolean not function |
| `collection(db, 'users', uid, '...')` | All files (sub-collection pattern) | `addDoc` doesn't inject `userId` |
