Files
2026-07-12 10:17:17 -04:00

4.4 KiB

PocketBase Timestamp + convertToPB Guard

The problem

The pocketbase.js adapter exports a Timestamp class for Firebase API compatibility. Three interlocking bugs make writes silently fail when Timestamps are used without proper support infrastructure.

Root causes

1. Timestamp static methods don't exist by default

Firebase APIs like Timestamp.fromDate(date) and Timestamp.now() are NOT present on the PocketBase adapter's Timestamp class. Any code that calls them (e.g., handleEditAppointment in appointments.js) throws TypeError: Timestamp.fromDate is not a function.

Because async event handlers don't surface their rejections to the browser UI, the user sees nothing — no error, no notification. The function just silently dies.

Fix — add to the Timestamp class in pocketbase.js:

static fromDate(date) { return new Timestamp(date); }
static now() { return new Timestamp(new Date()); }

2. convertToPB doesn't recognize Timestamp objects

convertToPB checks instanceof Date to serialize dates to ISO strings. But Timestamp does NOT extend Date — it wraps a _date property. So Timestamp objects fall through to the generic JSON.stringify() branch and produce garbage like {"_date":"2026-06-15T..."}. PocketBase either rejects this or stores junk that convertFromPB can't parse back.

Fix — add Timestamp check BEFORE the generic object branch in convertToPB:

} else if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Timestamp)) {
    result[key] = JSON.stringify(value);
} else if (value instanceof Timestamp) {
    result[key] = value.toISOString();

3. new Date(timestampObj) returns Invalid Date

Since Timestamp is not a Date, new Date(timestampObj) produces an Invalid Date. This breaks code that reads from PocketBase (where convertFromPB created Timestamp objects) and tries to display or process dates.

Fix — always use .toDate():

const date = obj?.toDate ? obj.toDate() : new Date(obj);

Preferred pattern for new writes

For new records and updates, prefer plain new Date() over Timestamp.fromDate(). convertToPB handles Date instances natively at the instanceof Date branch. Timestamp is only needed for reading FROM PocketBase (to preserve the Firebase-compatible API surface).

// BAD — Timestamp chain of pain
appointmentDateTime: Timestamp.fromDate(new Date(...)),
updatedAt: Timestamp.now()

// GOOD — plain Date, convertToPB handles it
appointmentDateTime: new Date(...),
updatedAt: new Date()

Async handler "nothing happens" pattern

When a button click produces zero visible effect, the most common cause is an uncaught synchronous throw in an async handler before the first await. The fix:

async function handleSave() {
    console.log('=== SAVE CLICKED ===');  // proves handler fired
    try {
        // ALL code — validation, DOM reads, API calls — inside one try
        await updateDoc(...);
    } catch (error) {
        console.error('Save error:', error);
        alert('Save failed: ' + (error.message || error));  // hard fallback
        showNotification('Failed to save.', true);
    }
}

No nested try/catch — one try wrapping the entire body.

Files affected

  • pocketbase.jsTimestamp class, convertToPB function, TIMESTAMP_FIELDS set
  • appointments.jshandleEditAppointment, date reading in openEditAppointmentModal, date grouping
  • Any file using Timestamp.fromDate() or Timestamp.now()
  • Any file doing new Date(someTimestampObject) on PocketBase-loaded data

TIMESTAMP_FIELDS Maintenance Rule

The TIMESTAMP_FIELDS set in pocketbase.js controls which ISO string fields convertFromPB auto-converts to Timestamp objects on retrieval. When you add a new date/datetime field to ANY PocketBase collection, you MUST also add its field name to this set. Missing fields come back as raw ISO strings, which break .toDate() calls and cause new Date() fallbacks (producing today's date instead of the stored date).

Current set (as of June 2026):

writeupTime, createdAt, updatedAt, lastUpdated,
appointmentDateTime, completionTime, lastActivity,
timestamp, dateCreated, startDate, endDate, deadline,
dueDateTime, promisedTime

dueDateTime and promisedTime were added June 2026 after tasks showed overdue with today's date because these fields were missing from both the PocketBase schema AND TIMESTAMP_FIELDS.