Add PayloadID type and relation helpers, fix filterOptions casting issue

- Add PayloadID type for string | number IDs
- Add PayloadRelation<T> 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 <noreply@anthropic.com>
This commit is contained in:
2025-10-06 23:05:16 +02:00
parent f303eda652
commit 08ba814da0
4 changed files with 54 additions and 3 deletions

View File

@@ -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",

View File

@@ -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) : '',
},
}
},

View File

@@ -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 extends { id: PayloadID }> = T | PayloadID
// JSON value type that matches Payload's JSON field type
export type JSONValue = string | number | boolean | { [k: string]: unknown } | unknown[] | null | undefined

View File

@@ -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 = <T extends { id: PayloadID }>(
value: PayloadRelation<T> | 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 = <T extends { id: PayloadID }>(
value: PayloadRelation<T> | 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 = <T extends { id: PayloadID }>(
values: (PayloadRelation<T> | 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) {