From 08ba814da01869a976e4d8a9191f6521193a8c6c Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Mon, 6 Oct 2025 23:05:16 +0200 Subject: [PATCH] Add PayloadID type and relation helpers, fix filterOptions casting issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PayloadID type for string | number IDs - Add PayloadRelation type for populated/unpopulated relations - Add isPopulated() type guard to check if relation is populated - Add resolveID() helper to extract ID from relation (object or ID) - Add resolveIDs() helper for arrays of relations - Fix filterOptions in Emails.ts to safely resolve ID before filtering - This prevents MongoDB ObjectId casting errors when id is an object - Bump version to 0.4.15 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- package.json | 2 +- src/collections/Emails.ts | 4 +++- src/types/index.ts | 6 ++++++ src/utils/helpers.ts | 45 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index da85819..87d3cba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-mailing", - "version": "0.4.14", + "version": "0.4.15", "description": "Template-based email system with scheduling and job processing for PayloadCMS", "type": "module", "main": "dist/index.js", diff --git a/src/collections/Emails.ts b/src/collections/Emails.ts index 19c99f5..fcd2a5c 100644 --- a/src/collections/Emails.ts +++ b/src/collections/Emails.ts @@ -1,6 +1,7 @@ import type { CollectionConfig } from 'payload' import { findExistingJobs, ensureEmailJob, updateEmailJobRelationship } from '../utils/jobScheduler.js' import { createContextLogger } from '../utils/logger.js' +import { resolveID } from '../utils/helpers.js' const Emails: CollectionConfig = { slug: 'emails', @@ -197,9 +198,10 @@ const Emails: CollectionConfig = { readOnly: true, }, filterOptions: ({ id }) => { + const emailId = resolveID({ id }) return { 'input.emailId': { - equals: id, + equals: emailId ? String(emailId) : '', }, } }, diff --git a/src/types/index.ts b/src/types/index.ts index e74ed24..7e01045 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,12 @@ import { Payload } from 'payload' import type { CollectionConfig, RichTextField } from 'payload' +// Payload ID type (string or number) +export type PayloadID = string | number + +// Payload relation type - can be populated (object with id) or unpopulated (just the ID) +export type PayloadRelation = T | PayloadID + // JSON value type that matches Payload's JSON field type export type JSONValue = string | number | boolean | { [k: string]: unknown } | unknown[] | null | undefined diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index c477c89..6e7de20 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,5 +1,5 @@ import { Payload } from 'payload' -import { TemplateVariables } from '../types/index.js' +import { TemplateVariables, PayloadID, PayloadRelation } from '../types/index.js' /** * Parse and validate email addresses @@ -74,6 +74,49 @@ export const sanitizeFromName = (fromName: string | null | undefined): string | return sanitized.length > 0 ? sanitized : undefined } +/** + * Type guard to check if a Payload relation is populated (object) or unpopulated (ID) + */ +export const isPopulated = ( + value: PayloadRelation | null | undefined +): value is T => { + return value !== null && value !== undefined && typeof value === 'object' && 'id' in value +} + +/** + * Resolves a Payload relation to just the ID + * Handles both populated (object with id) and unpopulated (string/number) values + */ +export const resolveID = ( + value: PayloadRelation | null | undefined +): PayloadID | undefined => { + if (value === null || value === undefined) return undefined + + if (typeof value === 'string' || typeof value === 'number') { + return value + } + + if (typeof value === 'object' && 'id' in value) { + return value.id + } + + return undefined +} + +/** + * Resolves an array of Payload relations to an array of IDs + * Handles mixed arrays of populated and unpopulated values + */ +export const resolveIDs = ( + values: (PayloadRelation | null | undefined)[] | null | undefined +): PayloadID[] => { + if (!values || !Array.isArray(values)) return [] + + return values + .map(value => resolveID(value)) + .filter((id): id is PayloadID => id !== undefined) +} + export const getMailing = (payload: Payload) => { const mailing = (payload as any).mailing if (!mailing) {