Skip to content

Commit

Permalink
refactor: use branded ids for releases, improve separation of concern…
Browse files Browse the repository at this point in the history
…s in data stores, etc
  • Loading branch information
bjoerge committed Nov 18, 2024
1 parent de69e5d commit 35801d6
Show file tree
Hide file tree
Showing 63 changed files with 1,067 additions and 801 deletions.
3 changes: 3 additions & 0 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
"@sanity/eventsource": "^5.0.0",
"@sanity/export": "^3.41.0",
"@sanity/icons": "^3.4.0",
"@sanity/id-utils": "^1.0.0",
"@sanity/image-url": "^1.0.2",
"@sanity/import": "^3.37.3",
"@sanity/insert-menu": "1.0.11",
Expand Down Expand Up @@ -260,7 +261,9 @@
"speakingurl": "^14.0.1",
"tar-fs": "^2.1.1",
"tar-stream": "^3.1.7",
"ts-brand": "^0.2.0",
"use-device-pixel-ratio": "^1.1.0",
"use-effect-event": "^1.0.2",
"use-hot-module-reload": "^2.0.0",
"use-sync-external-store": "^1.2.0",
"vite": "^4.5.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
useMemo,
useRef,
} from 'react'
import {usePerspective, useReleases} from 'sanity'
import {useReleases} from 'sanity'

import {type FIXME} from '../../../FIXME'
import {useSchema} from '../../../hooks'
import {useReleasesStack} from '../../../releases/store/useReleasesStack'
import {useDocumentPreviewStore} from '../../../store'
import {isNonNullable} from '../../../util'
import {useFormValue} from '../../contexts/FormValue'
Expand All @@ -35,7 +36,7 @@ interface Options {
export function useReferenceInput(options: Options) {
const {path, schemaType, version} = options
const schema = useSchema()
const perspective = usePerspective()

const releases = useReleases()
const documentPreviewStore = useDocumentPreviewStore()
const {EditReferenceLinkComponent, onEditReference, activePath, initialValueTemplateItems} =
Expand Down Expand Up @@ -116,6 +117,7 @@ export function useReferenceInput(options: Options) {
)
}, [disableNew, initialValueTemplateItems, schemaType.to])

const releasesStack = useReleasesStack()
const getReferenceInfo = useCallback(
(id: string) =>
adapter.getReferenceInfo(
Expand All @@ -124,17 +126,11 @@ export function useReferenceInput(options: Options) {
schemaType,
{version},
{
bundleIds: releases.releasesIds,
bundleStack: perspective.bundlesPerspective,
releaseIds: releases.releasesIds,
bundleStack: releasesStack,
},
),
[
documentPreviewStore,
schemaType,
version,
releases.releasesIds,
perspective.bundlesPerspective,
],
[documentPreviewStore, schemaType, version, releases.releasesIds, releasesStack],
)

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type VersionsRecord,
type VersionTuple,
} from '../../../../preview/utils/getPreviewStateObservable'
import {type ReleaseId} from '../../../../releases'
import {createSearch} from '../../../../search'
import {
collate,
Expand Down Expand Up @@ -48,7 +49,7 @@ export function getReferenceInfo(
id: string,
referenceType: ReferenceSchemaType,
{version}: {version?: string} = {},
perspective: {bundleIds: string[]; bundleStack: string[]} = {bundleIds: [], bundleStack: []},
perspective: {releaseIds: ReleaseId[]; bundleStack: string[]} = {releaseIds: [], bundleStack: []},
): Observable<ReferenceInfo> {
const {publishedId, draftId, versionId} = getIdPair(id, {version})

Expand Down Expand Up @@ -144,28 +145,30 @@ export function getReferenceInfo(
refSchemaType,
)

const versions$ = from(perspective.bundleIds).pipe(
mergeMap<string, Observable<VersionTuple>>((bundleId) =>
const versions$ = from(perspective.releaseIds).pipe(
mergeMap((bundleId) =>
documentPreviewStore
.observePaths({_id: getVersionId(id, bundleId)}, previewPaths)
.pipe(
// eslint-disable-next-line max-nested-callbacks
map((result) =>
result
? [
bundleId,
{
snapshot: {
_id: versionId,
...prepareForPreview(result, refSchemaType),
map(
// eslint-disable-next-line max-nested-callbacks
(result): VersionTuple =>
result
? [
bundleId,
{
snapshot: {
_id: versionId,
...prepareForPreview(result, refSchemaType),
},
},
},
]
: [bundleId, {snapshot: undefined}],
]
: [bundleId, {snapshot: undefined}],
),
),
),
scan((byBundleId, [bundleId, value]) => {

scan((byBundleId: VersionsRecord, [bundleId, value]) => {
if (value.snapshot === null) {
return omit({...byBundleId}, [bundleId])
}
Expand Down
7 changes: 6 additions & 1 deletion packages/sanity/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,22 @@ export {
AddedVersion,
DiscardVersionDialog,
getBundleIdFromReleaseDocumentId,
getPerspectiveTone,
getPublishDateFromRelease,
getReleaseIdFromReleaseDocumentId,
getReleaseTone,
isDraftPerspective,
isPublishedPerspective,
isReleaseDocument,
isReleaseScheduledOrScheduling,
LATEST,
PUBLISHED_PERSPECTIVE,
type ReleaseDocument,
useCurrentRelease,
useDocumentVersions,
usePerspective,
useReleases,
useReleasesStack,
useStudioPerspectiveState,
useVersionOperations,
VersionChip,
versionDocumentExists,
Expand Down
66 changes: 20 additions & 46 deletions packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import {combineLatest, from, type Observable, of} from 'rxjs'
import {map, mergeMap, scan, startWith} from 'rxjs/operators'
import {type PreparedSnapshot} from 'sanity'

import {type ReleaseId} from '../../releases'
import {getDraftId, getPublishedId, getVersionId} from '../../util/draftUtils'
import {type DocumentPreviewStore} from '../documentPreviewStore'

/**
* @internal
*/
export type VersionsRecord = Record<string, PreparedSnapshot>
export type VersionsRecord = Record<ReleaseId, PreparedSnapshot>

export type VersionTuple = [bundleId: string, snapshot: PreparedSnapshot]
export type VersionTuple = [bundleId: ReleaseId, snapshot: PreparedSnapshot]

export interface PreviewState {
isLoading?: boolean
Expand All @@ -33,77 +34,50 @@ export function getPreviewStateObservable(
documentPreviewStore: DocumentPreviewStore,
schemaType: SchemaType,
documentId: string,
perspective: {
/**
* An array of all existing bundle ids.
*/
bundleIds: string[]

/**
* An array of release ids ordered chronologically to represent the state of documents at the
* given point in time.
*/
bundleStack: string[]
} = {
bundleIds: [],
bundleStack: [],
},
/**
* What additional releases to fetch versions from
*/
releases: ReleaseId[] = [],
): Observable<PreviewState> {
const draft$ = isLiveEditEnabled(schemaType)
? of({snapshot: null})
: documentPreviewStore.observeForPreview({_id: getDraftId(documentId)}, schemaType)

const versions$ = from(perspective.bundleIds).pipe(
mergeMap<string, Observable<VersionTuple>>((bundleId) =>
const versions$ = from(releases).pipe(
mergeMap((release) =>
documentPreviewStore
.observeForPreview({_id: getVersionId(documentId, bundleId)}, schemaType)
.pipe(map((storeValue) => [bundleId, storeValue])),
.observeForPreview({_id: getVersionId(documentId, release)}, schemaType)
.pipe(map((storeValue): VersionTuple => [release, storeValue])),
),
scan<VersionTuple, VersionsRecord>((byBundleId, [bundleId, value]) => {
if (value.snapshot === null) {
return omit({...byBundleId}, [bundleId])
scan((byVersionId, [releaseId, value]) => {
if (value.snapshot === undefined) {
return omit({...byVersionId}, [releaseId])
}

return {
...byBundleId,
[bundleId]: value,
...byVersionId,
[releaseId]: value,
}
}, {}),
startWith<VersionsRecord>({}),
)

// Iterate the release stack in descending precedence, returning the highest precedence existing
// version document.
const version$ = versions$.pipe(
map((versions) => {
for (const bundleId of perspective.bundleStack) {
if (bundleId in versions) {
return versions[bundleId]
}
}
return {snapshot: undefined}
}),
startWith<PreparedSnapshot>({snapshot: undefined}),
)

const published$ = documentPreviewStore.observeForPreview(
{_id: getPublishedId(documentId)},
schemaType,
)

return combineLatest([draft$, published$, version$, versions$]).pipe(
map(([draft, published, version, versions]) => ({
return combineLatest([draft$, published$, versions$]).pipe(
map(([draft, published, versions]) => ({
draft: draft.snapshot,
isLoading: false,
published: published.snapshot,
version: version.snapshot,
versions,
})),
startWith({
draft: null,
draft: undefined,
isLoading: true,
published: null,
version: null,
published: undefined,
versions: {},
}),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {CreatedRelease, type OriginInfo} from '../../__telemetry__/releases.tele
import {type EditableReleaseDocument} from '../../store/types'
import {useReleaseOperations} from '../../store/useReleaseOperations'
import {DEFAULT_RELEASE_TYPE} from '../../util/const'
import {createReleaseId} from '../../util/createReleaseId'
import {getBundleIdFromReleaseDocumentId} from '../../util/getBundleIdFromReleaseDocumentId'
import {generateReleaseDocumentId} from '../../util/releaseId'
import {ReleaseForm} from './ReleaseForm'

interface CreateReleaseDialogProps {
Expand All @@ -28,7 +28,7 @@ export function CreateReleaseDialog(props: CreateReleaseDialogProps): JSX.Elemen

const [value, setValue] = useState((): EditableReleaseDocument => {
return {
_id: createReleaseId(),
_id: generateReleaseDocumentId(),
metadata: {
releaseType: DEFAULT_RELEASE_TYPE,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DocumentId, getPublishedId, getVersionNameFromId, isVersionId} from '@sanity/id-utils'
import {Box} from '@sanity/ui'
import {useCallback, useState} from 'react'

Expand All @@ -6,11 +7,9 @@ import {LoadingBlock} from '../../../components'
import {useDocumentOperation, useSchema} from '../../../hooks'
import {useTranslation} from '../../../i18n'
import {Preview} from '../../../preview'
import {getPublishedId, getVersionFromId, isVersionId} from '../../../util/draftUtils'
import {usePerspective, useVersionOperations} from '../../hooks'
import {useVersionOperations} from '../../hooks'
import {releasesLocaleNamespace} from '../../i18n'
import {type ReleaseDocument} from '../../store'
import {getBundleIdFromReleaseDocumentId} from '../../util/getBundleIdFromReleaseDocumentId'
import {ReleaseId} from '../../util/releaseId'

/**
* @internal
Expand All @@ -20,11 +19,11 @@ export function DiscardVersionDialog(props: {
documentId: string
documentType: string
}): JSX.Element {
const {onClose, documentId, documentType} = props
const {onClose, documentType} = props
const documentId = DocumentId(props.documentId)
const {t} = useTranslation(releasesLocaleNamespace)
const {discardChanges} = useDocumentOperation(getPublishedId(documentId), documentType)

const {currentGlobalBundle} = usePerspective()
const {discardVersion} = useVersionOperations()
const schema = useSchema()
const [isDiscarding, setIsDiscarding] = useState(false)
Expand All @@ -35,11 +34,7 @@ export function DiscardVersionDialog(props: {
setIsDiscarding(true)

if (isVersionId(documentId)) {
await discardVersion(
getVersionFromId(documentId) ||
getBundleIdFromReleaseDocumentId((currentGlobalBundle as ReleaseDocument)._id),
documentId,
)
await discardVersion(ReleaseId(getVersionNameFromId(documentId)), documentId)
} else {
// on the document header you can also discard the draft
discardChanges.execute()
Expand All @@ -48,7 +43,7 @@ export function DiscardVersionDialog(props: {
setIsDiscarding(false)

onClose()
}, [currentGlobalBundle, discardChanges, discardVersion, documentId, onClose])
}, [discardChanges, discardVersion, documentId, onClose])

return (
<Dialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {getVersionId} from '../../../util/draftUtils'
import {useVersionOperations} from '../../hooks/useVersionOperations'
import {type ReleaseDocument} from '../../store/types'
import {getBundleIdFromReleaseDocumentId} from '../../util/getBundleIdFromReleaseDocumentId'
import {getReleaseIdFromReleaseDocumentId, type ReleaseDocumentId} from '../../util/releaseId'
import {DiscardVersionDialog} from '../dialog/DiscardVersionDialog'
import {ReleaseAvatar} from '../ReleaseAvatar'
import {VersionContextMenu} from './contextMenu/VersionContextMenu'
Expand Down Expand Up @@ -134,8 +135,8 @@ export const VersionChip = memo(function VersionChip(props: {
}, [setIsCreateReleaseDialogOpen])

const handleAddVersion = useCallback(
async (targetRelease: string) => {
await createVersion(getBundleIdFromReleaseDocumentId(targetRelease), docId)
async (targetRelease: ReleaseDocumentId) => {
await createVersion(getReleaseIdFromReleaseDocumentId(targetRelease), docId)
close()
},
[createVersion, docId, close],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {MenuItem} from '../../../../../ui-components/menuItem/MenuItem'
import {useTranslation} from '../../../../i18n/hooks/useTranslation'
import {isPublishedId} from '../../../../util/draftUtils'
import {type ReleaseDocument} from '../../../store/types'
import {type ReleaseDocumentId} from '../../../util/releaseId'
import {isReleaseScheduledOrScheduling} from '../../../util/util'
import {VersionContextMenuItem} from './VersionContextMenuItem'

Expand All @@ -26,7 +27,7 @@ export const VersionContextMenu = memo(function VersionContextMenu(props: {
isVersion: boolean
onDiscard: () => void
onCreateRelease: () => void
onCreateVersion: (targetId: string) => void
onCreateVersion: (targetId: ReleaseDocumentId) => void
disabled?: boolean
locked?: boolean
}) {
Expand Down
Loading

0 comments on commit 35801d6

Please sign in to comment.