feat: add models package
This commit is contained in:
10
packages/models/src/Domain/Utilities/Item/FindItem.ts
Normal file
10
packages/models/src/Domain/Utilities/Item/FindItem.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||
|
||||
export function FindItem<I extends ItemInterface = ItemInterface>(items: I[], uuid: Uuid): I | undefined {
|
||||
return items.find((item) => item.uuid === uuid)
|
||||
}
|
||||
|
||||
export function SureFindItem<I extends ItemInterface = ItemInterface>(items: I[], uuid: Uuid): I {
|
||||
return FindItem(items, uuid) as I
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ItemContentsEqual } from './ItemContentsEqual'
|
||||
|
||||
export function ItemContentsDiffer(
|
||||
item1: DecryptedItemInterface,
|
||||
item2: DecryptedItemInterface,
|
||||
excludeContentKeys: (keyof ItemContent)[] = [],
|
||||
) {
|
||||
return !ItemContentsEqual(
|
||||
item1.content as ItemContent,
|
||||
item2.content as ItemContent,
|
||||
[...item1.contentKeysToIgnoreWhenCheckingEquality(), ...excludeContentKeys],
|
||||
item1.appDataContentKeysToIgnoreWhenCheckingEquality(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { omitInPlace, sortedCopy } from '@standardnotes/utils'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DefaultAppDomain } from '../../Abstract/Item/Types/DefaultAppDomain'
|
||||
import { AppDataField } from '../../Abstract/Item/Types/AppDataField'
|
||||
|
||||
export function ItemContentsEqual<C extends ItemContent = ItemContent>(
|
||||
leftContent: C,
|
||||
rightContent: C,
|
||||
keysToIgnore: (keyof C)[],
|
||||
appDataKeysToIgnore: AppDataField[],
|
||||
) {
|
||||
/* Create copies of objects before running omit as not to modify source values directly. */
|
||||
const leftContentCopy: Partial<C> = sortedCopy(leftContent)
|
||||
if (leftContentCopy.appData) {
|
||||
const domainData = leftContentCopy.appData[DefaultAppDomain]
|
||||
omitInPlace(domainData, appDataKeysToIgnore)
|
||||
/**
|
||||
* We don't want to disqualify comparison if one object contains an empty domain object
|
||||
* and the other doesn't contain a domain object. This can happen if you create an item
|
||||
* without setting dirty, which means it won't be initialized with a client_updated_at
|
||||
*/
|
||||
if (domainData) {
|
||||
if (Object.keys(domainData).length === 0) {
|
||||
delete leftContentCopy.appData
|
||||
}
|
||||
} else {
|
||||
delete leftContentCopy.appData
|
||||
}
|
||||
}
|
||||
omitInPlace<Partial<C>>(leftContentCopy, keysToIgnore)
|
||||
|
||||
const rightContentCopy: Partial<C> = sortedCopy(rightContent)
|
||||
if (rightContentCopy.appData) {
|
||||
const domainData = rightContentCopy.appData[DefaultAppDomain]
|
||||
omitInPlace(domainData, appDataKeysToIgnore)
|
||||
if (domainData) {
|
||||
if (Object.keys(domainData).length === 0) {
|
||||
delete rightContentCopy.appData
|
||||
}
|
||||
} else {
|
||||
delete rightContentCopy.appData
|
||||
}
|
||||
}
|
||||
omitInPlace<Partial<C>>(rightContentCopy, keysToIgnore)
|
||||
|
||||
return JSON.stringify(leftContentCopy) === JSON.stringify(rightContentCopy)
|
||||
}
|
||||
113
packages/models/src/Domain/Utilities/Item/ItemGenerator.ts
Normal file
113
packages/models/src/Domain/Utilities/Item/ItemGenerator.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { EncryptedItem } from '../../Abstract/Item/Implementations/EncryptedItem'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||
import { FileItem } from '../../Syncable/File/File'
|
||||
import { SNFeatureRepo } from '../../Syncable/FeatureRepo/FeatureRepo'
|
||||
import { SNActionsExtension } from '../../Syncable/ActionsExtension/ActionsExtension'
|
||||
import { SNComponent } from '../../Syncable/Component/Component'
|
||||
import { SNEditor } from '../../Syncable/Editor/Editor'
|
||||
import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem'
|
||||
import { SNNote } from '../../Syncable/Note/Note'
|
||||
import { SmartView } from '../../Syncable/SmartView/SmartView'
|
||||
import { SNTag } from '../../Syncable/Tag/Tag'
|
||||
import { SNTheme } from '../../Syncable/Theme/Theme'
|
||||
import { SNUserPrefs } from '../../Syncable/UserPrefs/UserPrefs'
|
||||
import { FileMutator } from '../../Syncable/File/FileMutator'
|
||||
import { MutationType } from '../../Abstract/Item/Types/MutationType'
|
||||
import { ThemeMutator } from '../../Syncable/Theme/ThemeMutator'
|
||||
import { UserPrefsMutator } from '../../Syncable/UserPrefs/UserPrefsMutator'
|
||||
import { ActionsExtensionMutator } from '../../Syncable/ActionsExtension/ActionsExtensionMutator'
|
||||
import { ComponentMutator } from '../../Syncable/Component/ComponentMutator'
|
||||
import { TagMutator } from '../../Syncable/Tag/TagMutator'
|
||||
import { NoteMutator } from '../../Syncable/Note/NoteMutator'
|
||||
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
|
||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||
import { DecryptedItemMutator } from '../../Abstract/Item/Mutator/DecryptedItemMutator'
|
||||
import {
|
||||
DeletedPayloadInterface,
|
||||
EncryptedPayloadInterface,
|
||||
isDecryptedPayload,
|
||||
isDeletedPayload,
|
||||
isEncryptedPayload,
|
||||
} from '../../Abstract/Payload'
|
||||
import { DeletedItem } from '../../Abstract/Item/Implementations/DeletedItem'
|
||||
import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/EncryptedItem'
|
||||
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
|
||||
|
||||
type ItemClass<C extends ItemContent = ItemContent> = new (payload: DecryptedPayloadInterface<C>) => DecryptedItem<C>
|
||||
|
||||
type MutatorClass<C extends ItemContent = ItemContent> = new (
|
||||
item: DecryptedItemInterface<C>,
|
||||
type: MutationType,
|
||||
) => DecryptedItemMutator<C>
|
||||
|
||||
type MappingEntry<C extends ItemContent = ItemContent> = {
|
||||
itemClass: ItemClass<C>
|
||||
mutatorClass?: MutatorClass<C>
|
||||
}
|
||||
|
||||
const ContentTypeClassMapping: Partial<Record<ContentType, MappingEntry>> = {
|
||||
[ContentType.ActionsExtension]: {
|
||||
itemClass: SNActionsExtension,
|
||||
mutatorClass: ActionsExtensionMutator,
|
||||
},
|
||||
[ContentType.Component]: { itemClass: SNComponent, mutatorClass: ComponentMutator },
|
||||
[ContentType.Editor]: { itemClass: SNEditor },
|
||||
[ContentType.ExtensionRepo]: { itemClass: SNFeatureRepo },
|
||||
[ContentType.File]: { itemClass: FileItem, mutatorClass: FileMutator },
|
||||
[ContentType.Note]: { itemClass: SNNote, mutatorClass: NoteMutator },
|
||||
[ContentType.SmartView]: { itemClass: SmartView, mutatorClass: TagMutator },
|
||||
[ContentType.Tag]: { itemClass: SNTag, mutatorClass: TagMutator },
|
||||
[ContentType.Theme]: { itemClass: SNTheme, mutatorClass: ThemeMutator },
|
||||
[ContentType.UserPrefs]: { itemClass: SNUserPrefs, mutatorClass: UserPrefsMutator },
|
||||
} as unknown as Partial<Record<ContentType, MappingEntry>>
|
||||
|
||||
export function CreateDecryptedMutatorForItem<
|
||||
I extends DecryptedItemInterface,
|
||||
M extends DecryptedItemMutator = DecryptedItemMutator,
|
||||
>(item: I, type: MutationType): M {
|
||||
const lookupValue = ContentTypeClassMapping[item.content_type]?.mutatorClass
|
||||
if (lookupValue) {
|
||||
return new lookupValue(item, type) as M
|
||||
} else {
|
||||
return new DecryptedItemMutator(item, type) as M
|
||||
}
|
||||
}
|
||||
|
||||
export function RegisterItemClass<
|
||||
C extends ItemContent = ItemContent,
|
||||
M extends DecryptedItemMutator<C> = DecryptedItemMutator<C>,
|
||||
>(contentType: ContentType, itemClass: ItemClass<C>, mutatorClass: M) {
|
||||
const entry: MappingEntry<C> = {
|
||||
itemClass: itemClass,
|
||||
mutatorClass: mutatorClass as unknown as MutatorClass<C>,
|
||||
}
|
||||
ContentTypeClassMapping[contentType] = entry as unknown as MappingEntry<ItemContent>
|
||||
}
|
||||
|
||||
export function CreateDecryptedItemFromPayload<
|
||||
C extends ItemContent = ItemContent,
|
||||
T extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
|
||||
>(payload: DecryptedPayloadInterface<C>): T {
|
||||
const lookupClass = ContentTypeClassMapping[payload.content_type]
|
||||
const itemClass = lookupClass ? lookupClass.itemClass : DecryptedItem
|
||||
const item = new itemClass(payload)
|
||||
return item as unknown as T
|
||||
}
|
||||
|
||||
export function CreateItemFromPayload<
|
||||
C extends ItemContent = ItemContent,
|
||||
T extends DecryptedItemInterface<C> = DecryptedItemInterface<C>,
|
||||
>(
|
||||
payload: DecryptedPayloadInterface<C> | EncryptedPayloadInterface | DeletedPayloadInterface,
|
||||
): EncryptedItemInterface | DeletedItemInterface | T {
|
||||
if (isDecryptedPayload(payload)) {
|
||||
return CreateDecryptedItemFromPayload<C, T>(payload)
|
||||
} else if (isEncryptedPayload(payload)) {
|
||||
return new EncryptedItem(payload)
|
||||
} else if (isDeletedPayload(payload)) {
|
||||
return new DeletedItem(payload)
|
||||
} else {
|
||||
throw Error('Unhandled case in CreateItemFromPayload')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user