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.js—Timestampclass,convertToPBfunction,TIMESTAMP_FIELDSsetappointments.js—handleEditAppointment, date reading inopenEditAppointmentModal, date grouping- Any file using
Timestamp.fromDate()orTimestamp.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.