Skip to content

Commit

Permalink
chore(live-preview): strongly types message events (#10148)
Browse files Browse the repository at this point in the history
Live Preview message events were typed with the generic `MessageEvent`
interface without passing any of the Live Preview specific properties,
leading to unknown types upon use. To fix this, there is a new
`LivePreviewMessageEvent` which properly extends the underlying
`MessageEvent` interface, providing much needed type safety to these
functions. In the same vein, the `UpdatedDocument` type was not being
properly shared across packages, leading to multiple independent
definitions of this type. This type is now exported from `payload`
itself and renamed to `DocumentEvent` for improved semantics. Same with
the `FieldSchemaJSON` type. This PR also adjusts where globally scoped
variables are set, putting them within the shared `_payloadLivePreview`
namespace instead of setting them individually at the top-level.
  • Loading branch information
jacobsfletch authored Dec 23, 2024
1 parent 0588394 commit 466f109
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 99 deletions.
43 changes: 25 additions & 18 deletions packages/live-preview/src/handleMessage.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import type { LivePreviewMessageEvent } from './types.js'

import { isLivePreviewEvent } from './isLivePreviewEvent.js'
import { mergeData } from './mergeData.js'

// For performance reasons, `fieldSchemaJSON` will only be sent once on the initial message
// We need to cache this value so that it can be used across subsequent messages
// To do this, save `fieldSchemaJSON` when it arrives as a global variable
// Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
let payloadLivePreviewFieldSchema = undefined // TODO: type this from `fieldSchemaToJSON` return type

// Each time the data is merged, cache the result as a `previousData` variable
// This will ensure changes compound overtop of each other
let payloadLivePreviewPreviousData = undefined
const _payloadLivePreview = {
/**
* For performance reasons, `fieldSchemaJSON` will only be sent once on the initial message
* We need to cache this value so that it can be used across subsequent messages
* To do this, save `fieldSchemaJSON` when it arrives as a global variable
* Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
*/
fieldSchema: undefined,
/**
* Each time the data is merged, cache the result as a `previousData` variable
* This will ensure changes compound overtop of each other
*/
previousData: undefined,
}

export const handleMessage = async <T>(args: {
apiRoute?: string
depth?: number
event: MessageEvent
event: LivePreviewMessageEvent<T>
initialData: T
serverURL: string
}): Promise<T> => {
const { apiRoute, depth, event, initialData, serverURL } = args

if (isLivePreviewEvent(event, serverURL)) {
const { data, externallyUpdatedRelationship, fieldSchemaJSON } = event.data
const { data, externallyUpdatedRelationship, fieldSchemaJSON, locale } = event.data

if (!payloadLivePreviewFieldSchema && fieldSchemaJSON) {
payloadLivePreviewFieldSchema = fieldSchemaJSON
if (!_payloadLivePreview?.fieldSchema && fieldSchemaJSON) {
_payloadLivePreview.fieldSchema = fieldSchemaJSON
}

if (!payloadLivePreviewFieldSchema) {
if (!_payloadLivePreview?.fieldSchema) {
// eslint-disable-next-line no-console
console.warn(
'Payload Live Preview: No `fieldSchemaJSON` was received from the parent window. Unable to merge data.',
Expand All @@ -40,14 +47,14 @@ export const handleMessage = async <T>(args: {
apiRoute,
depth,
externallyUpdatedRelationship,
fieldSchema: payloadLivePreviewFieldSchema,
fieldSchema: _payloadLivePreview.fieldSchema,
incomingData: data,
initialData: payloadLivePreviewPreviousData || initialData,
locale: event.data.locale,
initialData: _payloadLivePreview?.previousData || initialData,
locale,
serverURL,
})

payloadLivePreviewPreviousData = mergedData
_payloadLivePreview.previousData = mergedData

return mergedData
}
Expand Down
1 change: 1 addition & 0 deletions packages/live-preview/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export { mergeData } from './mergeData.js'
export { ready } from './ready.js'
export { subscribe } from './subscribe.js'
export { traverseRichText } from './traverseRichText.js'
export type { LivePreviewMessageEvent } from './types.js'
export { unsubscribe } from './unsubscribe.js'
9 changes: 4 additions & 5 deletions packages/live-preview/src/mergeData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { PaginatedDocs } from 'payload'
import type { fieldSchemaToJSON } from 'payload/shared'
import type { DocumentEvent, FieldSchemaJSON, PaginatedDocs } from 'payload'

import type { PopulationsByCollection, UpdatedDocument } from './types.js'
import type { PopulationsByCollection } from './types.js'

import { traverseFields } from './traverseFields.js'

Expand Down Expand Up @@ -32,8 +31,8 @@ export const mergeData = async <T>(args: {
serverURL: string
}) => Promise<Response>
depth?: number
externallyUpdatedRelationship?: UpdatedDocument
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
externallyUpdatedRelationship?: DocumentEvent
fieldSchema: FieldSchemaJSON
incomingData: Partial<T>
initialData: T
locale?: string
Expand Down
5 changes: 3 additions & 2 deletions packages/live-preview/src/traverseFields.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { DocumentEvent } from 'payload'
import type { fieldSchemaToJSON } from 'payload/shared'

import type { PopulationsByCollection, UpdatedDocument } from './types.js'
import type { PopulationsByCollection } from './types.js'

import { traverseRichText } from './traverseRichText.js'

export const traverseFields = <T>(args: {
externallyUpdatedRelationship?: UpdatedDocument
externallyUpdatedRelationship?: DocumentEvent
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
incomingData: T
localeChanged: boolean
Expand Down
6 changes: 4 additions & 2 deletions packages/live-preview/src/traverseRichText.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { PopulationsByCollection, UpdatedDocument } from './types.js'
import type { DocumentEvent } from 'payload'

import type { PopulationsByCollection } from './types.js'

export const traverseRichText = ({
externallyUpdatedRelationship,
incomingData,
populationsByCollection,
result,
}: {
externallyUpdatedRelationship?: UpdatedDocument
externallyUpdatedRelationship?: DocumentEvent
incomingData: any
populationsByCollection: PopulationsByCollection
result: any
Expand Down
15 changes: 9 additions & 6 deletions packages/live-preview/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { DocumentEvent, FieldSchemaJSON } from 'payload'

export type LivePreviewArgs = {}

export type LivePreview = void
Expand All @@ -10,9 +12,10 @@ export type PopulationsByCollection = {
}>
}

// TODO: import this from `payload/admin/components/utilities/DocumentEvents/types.ts`
export type UpdatedDocument = {
entitySlug: string
id?: number | string
updatedAt: string
}
export type LivePreviewMessageEvent<T> = MessageEvent<{
data: T
externallyUpdatedRelationship?: DocumentEvent
fieldSchemaJSON: FieldSchemaJSON
locale?: string
type: 'payload-live-preview'
}>
6 changes: 6 additions & 0 deletions packages/payload/src/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,9 @@ export type ClientFieldSchemaMap = Map<
| ClientField
| ClientTab
>

export type DocumentEvent = {
entitySlug: string
id?: number | string
updatedAt: string
}
1 change: 1 addition & 0 deletions packages/payload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@ export {
type CustomVersionParser,
} from './utilities/dependencies/dependencyChecker.js'
export { getDependencies } from './utilities/dependencies/getDependencies.js'
export type { FieldSchemaJSON } from './utilities/fieldSchemaToJSON.js'
export {
findUp,
findUpSync,
Expand Down
12 changes: 4 additions & 8 deletions packages/ui/src/providers/DocumentEvents/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
'use client'
import React, { createContext, useContext, useState } from 'react'
import type { DocumentEvent } from 'payload'

export type UpdatedDocument = {
entitySlug: string
id?: number | string
updatedAt: string
}
import React, { createContext, useContext, useState } from 'react'

const Context = createContext({
mostRecentUpdate: null,
reportUpdate: (doc: UpdatedDocument) => null,
reportUpdate: (doc: DocumentEvent) => null,
})

export const DocumentEventsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [mostRecentUpdate, reportUpdate] = useState<UpdatedDocument>(null)
const [mostRecentUpdate, reportUpdate] = useState<DocumentEvent>(null)

return <Context.Provider value={{ mostRecentUpdate, reportUpdate }}>{children}</Context.Provider>
}
Expand Down
11 changes: 8 additions & 3 deletions test/live-preview/int.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Payload } from 'payload'

import { handleMessage, mergeData, traverseRichText } from '@payloadcms/live-preview'
import {
handleMessage,
type LivePreviewMessageEvent,
mergeData,
traverseRichText,
} from '@payloadcms/live-preview'
import path from 'path'
import { getFileByPath } from 'payload'
import { fieldSchemaToJSON } from 'payload/shared'
Expand Down Expand Up @@ -97,7 +102,7 @@ describe('Collections - Live Preview', () => {
type: 'payload-live-preview',
},
origin: serverURL,
} as MessageEvent,
} as MessageEvent as LivePreviewMessageEvent<Page>,
initialData: {
title: 'Test Page',
} as Page,
Expand All @@ -118,7 +123,7 @@ describe('Collections - Live Preview', () => {
type: 'payload-live-preview',
},
origin: serverURL,
} as MessageEvent,
} as MessageEvent as LivePreviewMessageEvent<Page>,
initialData: {
title: 'Test Page',
} as Page,
Expand Down
Loading

0 comments on commit 466f109

Please sign in to comment.