feat: add models package

This commit is contained in:
Karol Sójko
2022-07-05 20:47:11 +02:00
parent 60d1554ff7
commit b614c71e79
199 changed files with 8772 additions and 22 deletions

View File

@@ -0,0 +1,55 @@
import { DecryptedPayloadInterface } from './../../Abstract/Payload/Interfaces/DecryptedPayload'
import { ComponentContent } from '../../Syncable/Component/ComponentContent'
import { ComponentArea } from '@standardnotes/features'
import { ContentType } from '@standardnotes/common'
import { ComponentMutator, SNComponent } from '../../Syncable/Component'
import { CreateDecryptedItemFromPayload } from '../Item/ItemGenerator'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { MutationType } from '../../Abstract/Item/Types/MutationType'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { isDecryptedPayload } from '../../Abstract/Payload'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
export type AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => SyncResolvedPayload[]
const NoteDuplicationAffectedPayloads: AffectorFunction = (
basePayload: FullyFormedPayloadInterface,
duplicatePayload: FullyFormedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
) => {
/** If note has editor, maintain editor relationship in duplicate note */
const components = baseCollection
.all(ContentType.Component)
.filter(isDecryptedPayload)
.map((payload) => {
return CreateDecryptedItemFromPayload<ComponentContent, SNComponent>(
payload as DecryptedPayloadInterface<ComponentContent>,
)
})
const editor = components
.filter((c) => c.area === ComponentArea.Editor)
.find((e) => {
return e.isExplicitlyEnabledForItem(basePayload.uuid)
})
if (!editor) {
return []
}
/** Modify the editor to include new note */
const mutator = new ComponentMutator(editor, MutationType.NoUpdateUserTimestamps)
mutator.associateWithItem(duplicatePayload.uuid)
const result = mutator.getResult() as SyncResolvedPayload
return [result]
}
export const AffectorMapping = {
[ContentType.Note]: NoteDuplicationAffectedPayloads,
} as Partial<Record<ContentType, AffectorFunction>>

View File

@@ -0,0 +1,20 @@
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { DeletedPayloadInterface } from '../../Abstract/Payload/Interfaces/DeletedPayload'
import { EncryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/EncryptedPayload'
import {
DecryptedTransferPayload,
DeletedTransferPayload,
EncryptedTransferPayload,
} from '../../Abstract/TransferPayload'
export type ConditionalPayloadType<T> = T extends DecryptedTransferPayload<infer C>
? DecryptedPayloadInterface<C>
: T extends EncryptedTransferPayload
? EncryptedPayloadInterface
: DeletedPayloadInterface
export type ConditionalTransferPayloadType<P> = P extends DecryptedPayloadInterface<infer C>
? DecryptedTransferPayload<C>
: P extends EncryptedPayloadInterface
? EncryptedTransferPayload
: DeletedTransferPayload

View File

@@ -0,0 +1,19 @@
import { CreatePayload } from './CreatePayload'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { DecryptedTransferPayload } from '../../Abstract/TransferPayload'
export function CopyPayloadWithContentOverride<C extends ItemContent = ItemContent>(
payload: DecryptedPayloadInterface<C>,
contentOverride: Partial<C>,
): DecryptedPayloadInterface<C> {
const params: DecryptedTransferPayload<C> = {
...payload.ejected(),
content: {
...payload.content,
...contentOverride,
},
}
const result = CreatePayload(params, payload.source)
return result
}

View File

@@ -0,0 +1,26 @@
import { EncryptedPayload } from '../../Abstract/Payload/Implementations/EncryptedPayload'
import { DeletedPayload } from '../../Abstract/Payload/Implementations/DeletedPayload'
import { DecryptedPayload } from '../../Abstract/Payload/Implementations/DecryptedPayload'
import {
FullyFormedTransferPayload,
isDecryptedTransferPayload,
isDeletedTransferPayload,
isEncryptedTransferPayload,
} from '../../Abstract/TransferPayload'
import { PayloadSource } from '../../Abstract/Payload/Types/PayloadSource'
import { ConditionalPayloadType } from './ConditionalPayloadType'
export function CreatePayload<T extends FullyFormedTransferPayload>(
from: T,
source: PayloadSource,
): ConditionalPayloadType<T> {
if (isDecryptedTransferPayload(from)) {
return new DecryptedPayload(from, source) as unknown as ConditionalPayloadType<T>
} else if (isEncryptedTransferPayload(from)) {
return new EncryptedPayload(from, source) as unknown as ConditionalPayloadType<T>
} else if (isDeletedTransferPayload(from)) {
return new DeletedPayload(from, source) as unknown as ConditionalPayloadType<T>
} else {
throw Error('Unhandled case in CreatePayload')
}
}

View File

@@ -0,0 +1,10 @@
import { Uuid } from '@standardnotes/common'
import { PayloadInterface } from '../../Abstract/Payload/Interfaces/PayloadInterface'
export function FindPayload<P extends PayloadInterface = PayloadInterface>(payloads: P[], uuid: Uuid): P | undefined {
return payloads.find((payload) => payload.uuid === uuid)
}
export function SureFindPayload<P extends PayloadInterface = PayloadInterface>(payloads: P[], uuid: Uuid): P {
return FindPayload(payloads, uuid) as P
}

View File

@@ -0,0 +1,15 @@
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { CreateDecryptedItemFromPayload } from '../Item/ItemGenerator'
/**
* Compares the .content fields for equality, creating new SNItem objects
* to properly handle .content intricacies.
*/
export function PayloadContentsEqual(
payloadA: DecryptedPayloadInterface,
payloadB: DecryptedPayloadInterface,
): boolean {
const itemA = CreateDecryptedItemFromPayload(payloadA)
const itemB = CreateDecryptedItemFromPayload(payloadB)
return itemA.isItemContentEqualWith(itemB)
}

View File

@@ -0,0 +1,98 @@
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { DeletedPayloadInterface } from '../../Abstract/Payload/Interfaces/DeletedPayload'
import { EncryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/EncryptedPayload'
import { isDecryptedPayload, isDeletedPayload, isEncryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
export interface PayloadSplit<C extends ItemContent = ItemContent> {
encrypted: EncryptedPayloadInterface[]
decrypted: DecryptedPayloadInterface<C>[]
deleted: DeletedPayloadInterface[]
}
export interface PayloadSplitWithDiscardables<C extends ItemContent = ItemContent> {
encrypted: EncryptedPayloadInterface[]
decrypted: DecryptedPayloadInterface<C>[]
deleted: DeletedPayloadInterface[]
discardable: DeletedPayloadInterface[]
}
export interface NonDecryptedPayloadSplit {
encrypted: EncryptedPayloadInterface[]
deleted: DeletedPayloadInterface[]
}
export function CreatePayloadSplit<C extends ItemContent = ItemContent>(
payloads: FullyFormedPayloadInterface<C>[],
): PayloadSplit<C> {
const split: PayloadSplit<C> = {
encrypted: [],
decrypted: [],
deleted: [],
}
for (const payload of payloads) {
if (isDecryptedPayload(payload)) {
split.decrypted.push(payload)
} else if (isEncryptedPayload(payload)) {
split.encrypted.push(payload)
} else if (isDeletedPayload(payload)) {
split.deleted.push(payload)
} else {
throw Error('Unhandled case in CreatePayloadSplit')
}
}
return split
}
export function CreatePayloadSplitWithDiscardables<C extends ItemContent = ItemContent>(
payloads: FullyFormedPayloadInterface<C>[],
): PayloadSplitWithDiscardables<C> {
const split: PayloadSplitWithDiscardables<C> = {
encrypted: [],
decrypted: [],
deleted: [],
discardable: [],
}
for (const payload of payloads) {
if (isDecryptedPayload(payload)) {
split.decrypted.push(payload)
} else if (isEncryptedPayload(payload)) {
split.encrypted.push(payload)
} else if (isDeletedPayload(payload)) {
if (payload.discardable) {
split.discardable.push(payload)
} else {
split.deleted.push(payload)
}
} else {
throw Error('Unhandled case in CreatePayloadSplitWithDiscardables')
}
}
return split
}
export function CreateNonDecryptedPayloadSplit(
payloads: (EncryptedPayloadInterface | DeletedPayloadInterface)[],
): NonDecryptedPayloadSplit {
const split: NonDecryptedPayloadSplit = {
encrypted: [],
deleted: [],
}
for (const payload of payloads) {
if (isEncryptedPayload(payload)) {
split.encrypted.push(payload)
} else if (isDeletedPayload(payload)) {
split.deleted.push(payload)
} else {
throw Error('Unhandled case in CreateNonDecryptedPayloadSplit')
}
}
return split
}

View File

@@ -0,0 +1,97 @@
import { DeletedPayload } from '../../Abstract/Payload/Implementations/DeletedPayload'
import { ContentType } from '@standardnotes/common'
import { extendArray, UuidGenerator } from '@standardnotes/utils'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { isEncryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { EncryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/EncryptedPayload'
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { getIncrementedDirtyIndex } from '../../Runtime/DirtyCounter/DirtyCounter'
/**
* Return the payloads that result if you alternated the uuid for the payload.
* Alternating a UUID involves instructing related items to drop old references of a uuid
* for the new one.
* @returns An array of payloads that have changed as a result of copying.
*/
export function PayloadsByAlternatingUuid<P extends DecryptedPayloadInterface = DecryptedPayloadInterface>(
payload: P,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
): SyncResolvedPayload[] {
const results: SyncResolvedPayload[] = []
/**
* We need to clone payload and give it a new uuid,
* then delete item with old uuid from db (cannot modify uuids in our IndexedDB setup)
*/
const copy = payload.copyAsSyncResolved({
uuid: UuidGenerator.GenerateUuid(),
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
lastSyncBegan: undefined,
lastSyncEnd: new Date(),
duplicate_of: payload.uuid,
})
results.push(copy)
/**
* Get the payloads that make reference to payload and remove
* payload as a relationship, instead adding the new copy.
*/
const updatedReferencing = PayloadsByUpdatingReferencingPayloadReferences(
payload,
baseCollection,
[copy],
[payload.uuid],
)
extendArray(results, updatedReferencing)
if (payload.content_type === ContentType.ItemsKey) {
/**
* Update any payloads who are still encrypted and whose items_key_id point to this uuid
*/
const matchingPayloads = baseCollection
.all()
.filter((p) => isEncryptedPayload(p) && p.items_key_id === payload.uuid) as EncryptedPayloadInterface[]
const adjustedPayloads = matchingPayloads.map((a) =>
a.copyAsSyncResolved({
items_key_id: copy.uuid,
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
lastSyncEnd: new Date(),
}),
)
if (adjustedPayloads.length > 0) {
extendArray(results, adjustedPayloads)
}
}
const deletedSelf = new DeletedPayload(
{
created_at: payload.created_at,
updated_at: payload.updated_at,
created_at_timestamp: payload.created_at_timestamp,
updated_at_timestamp: payload.updated_at_timestamp,
/**
* Do not set as dirty; this item is non-syncable
* and should be immediately discarded
*/
dirty: false,
content: undefined,
uuid: payload.uuid,
content_type: payload.content_type,
deleted: true,
},
payload.source,
)
results.push(deletedSelf as SyncResolvedPayload)
return results
}

View File

@@ -0,0 +1,81 @@
import { PayloadSource } from './../../Abstract/Payload/Types/PayloadSource'
import { extendArray, UuidGenerator } from '@standardnotes/utils'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { AffectorMapping } from './AffectorFunction'
import { PayloadsByUpdatingReferencingPayloadReferences } from './PayloadsByUpdatingReferencingPayloadReferences'
import { isDecryptedPayload } from '../../Abstract/Payload/Interfaces/TypeCheck'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { getIncrementedDirtyIndex } from '../../Runtime/DirtyCounter/DirtyCounter'
/**
* Copies payload and assigns it a new uuid.
* @returns An array of payloads that have changed as a result of copying.
*/
export function PayloadsByDuplicating<C extends ItemContent = ItemContent>(dto: {
payload: FullyFormedPayloadInterface<C>
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>
isConflict?: boolean
additionalContent?: Partial<C>
source?: PayloadSource
}): SyncResolvedPayload[] {
const { payload, baseCollection, isConflict, additionalContent, source } = dto
const results: SyncResolvedPayload[] = []
const override = {
uuid: UuidGenerator.GenerateUuid(),
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
lastSyncBegan: undefined,
lastSyncEnd: new Date(),
duplicate_of: payload.uuid,
}
let copy: SyncResolvedPayload
if (isDecryptedPayload(payload)) {
const contentOverride: C = {
...payload.content,
...additionalContent,
}
if (isConflict) {
contentOverride.conflict_of = payload.uuid
}
copy = payload.copyAsSyncResolved({
...override,
content: contentOverride,
deleted: false,
})
} else {
copy = payload.copyAsSyncResolved(
{
...override,
},
source || payload.source,
)
}
results.push(copy)
if (isDecryptedPayload(payload) && isDecryptedPayload(copy)) {
/**
* Get the payloads that make reference to payload and add the copy.
*/
const updatedReferencing = PayloadsByUpdatingReferencingPayloadReferences(payload, baseCollection, [copy])
extendArray(results, updatedReferencing)
}
const affector = AffectorMapping[payload.content_type]
if (affector) {
const affected = affector(payload, copy, baseCollection)
if (affected) {
extendArray(results, affected)
}
}
return results
}

View File

@@ -0,0 +1,52 @@
import { Uuid } from '@standardnotes/common'
import { remove } from 'lodash'
import { ImmutablePayloadCollection } from '../../Runtime/Collection/Payload/ImmutablePayloadCollection'
import { ContentReference } from '../../Abstract/Reference/ContentReference'
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload/Interfaces/UnionTypes'
import { isDecryptedPayload } from '../../Abstract/Payload'
import { SyncResolvedPayload } from '../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { getIncrementedDirtyIndex } from '../../Runtime/DirtyCounter/DirtyCounter'
export function PayloadsByUpdatingReferencingPayloadReferences(
payload: DecryptedPayloadInterface,
baseCollection: ImmutablePayloadCollection<FullyFormedPayloadInterface>,
add: FullyFormedPayloadInterface[] = [],
removeIds: Uuid[] = [],
): SyncResolvedPayload[] {
const referencingPayloads = baseCollection.elementsReferencingElement(payload).filter(isDecryptedPayload)
const results: SyncResolvedPayload[] = []
for (const referencingPayload of referencingPayloads) {
const references = referencingPayload.content.references.slice()
const reference = referencingPayload.getReference(payload.uuid)
for (const addPayload of add) {
const newReference: ContentReference = {
...reference,
uuid: addPayload.uuid,
content_type: addPayload.content_type,
}
references.push(newReference)
}
for (const id of removeIds) {
remove(references, { uuid: id })
}
const result = referencingPayload.copyAsSyncResolved({
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
lastSyncEnd: new Date(),
content: {
...referencingPayload.content,
references,
},
})
results.push(result)
}
return results
}