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,71 @@
import { Uuid } from '@standardnotes/common'
import { Copy } from '@standardnotes/utils'
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { FillItemContent, ItemContent } from '../../Content/ItemContent'
import { ContentReference } from '../../Reference/ContentReference'
import { DecryptedTransferPayload } from '../../TransferPayload/Interfaces/DecryptedTransferPayload'
import { DecryptedPayloadInterface } from '../Interfaces/DecryptedPayload'
import { PayloadSource } from '../Types/PayloadSource'
import { PurePayload } from './PurePayload'
export class DecryptedPayload<
C extends ItemContent = ItemContent,
T extends DecryptedTransferPayload<C> = DecryptedTransferPayload<C>,
>
extends PurePayload<T>
implements DecryptedPayloadInterface<C>
{
override readonly content: C
override readonly deleted: false
constructor(rawPayload: T, source = PayloadSource.Constructor) {
super(rawPayload, source)
this.content = Copy(FillItemContent<C>(rawPayload.content))
this.deleted = false
}
get references(): ContentReference[] {
return this.content.references || []
}
public getReference(uuid: Uuid): ContentReference {
const result = this.references.find((ref) => ref.uuid === uuid)
if (!result) {
throw new Error('Reference not found')
}
return result
}
override ejected(): DecryptedTransferPayload<C> {
return {
...super.ejected(),
content: this.content,
deleted: this.deleted,
}
}
copy(override?: Partial<T>, source = this.source): this {
const result = new DecryptedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as this
}
copyAsSyncResolved(override?: Partial<T> & SyncResolvedParams, source = this.source): SyncResolvedPayload {
const result = new DecryptedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as SyncResolvedPayload
}
}

View File

@@ -0,0 +1,54 @@
import { DeletedTransferPayload } from './../../TransferPayload/Interfaces/DeletedTransferPayload'
import { DeletedPayloadInterface } from '../Interfaces/DeletedPayload'
import { PayloadSource } from '../Types/PayloadSource'
import { PurePayload } from './PurePayload'
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
export class DeletedPayload extends PurePayload<DeletedTransferPayload> implements DeletedPayloadInterface {
override readonly deleted: true
override readonly content: undefined
constructor(rawPayload: DeletedTransferPayload, source = PayloadSource.Constructor) {
super(rawPayload, source)
this.deleted = true
this.content = undefined
}
get discardable(): boolean | undefined {
return !this.dirty
}
override ejected(): DeletedTransferPayload {
return {
...super.ejected(),
deleted: this.deleted,
content: undefined,
}
}
copy(override?: Partial<DeletedTransferPayload>, source = this.source): this {
const result = new DeletedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as this
}
copyAsSyncResolved(
override?: Partial<DeletedTransferPayload> & SyncResolvedParams,
source = this.source,
): SyncResolvedPayload {
const result = new DeletedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as SyncResolvedPayload
}
}

View File

@@ -0,0 +1,68 @@
import { ProtocolVersion, protocolVersionFromEncryptedString } from '@standardnotes/common'
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { EncryptedTransferPayload } from '../../TransferPayload/Interfaces/EncryptedTransferPayload'
import { EncryptedPayloadInterface } from '../Interfaces/EncryptedPayload'
import { PayloadSource } from '../Types/PayloadSource'
import { PurePayload } from './PurePayload'
export class EncryptedPayload extends PurePayload<EncryptedTransferPayload> implements EncryptedPayloadInterface {
override readonly content: string
override readonly deleted: false
readonly auth_hash?: string
readonly enc_item_key: string
readonly errorDecrypting: boolean
readonly items_key_id: string | undefined
readonly version: ProtocolVersion
readonly waitingForKey: boolean
constructor(rawPayload: EncryptedTransferPayload, source = PayloadSource.Constructor) {
super(rawPayload, source)
this.auth_hash = rawPayload.auth_hash
this.content = rawPayload.content
this.deleted = false
this.enc_item_key = rawPayload.enc_item_key
this.errorDecrypting = rawPayload.errorDecrypting
this.items_key_id = rawPayload.items_key_id
this.version = protocolVersionFromEncryptedString(this.content)
this.waitingForKey = rawPayload.waitingForKey
}
override ejected(): EncryptedTransferPayload {
return {
...super.ejected(),
enc_item_key: this.enc_item_key,
items_key_id: this.items_key_id,
auth_hash: this.auth_hash,
errorDecrypting: this.errorDecrypting,
waitingForKey: this.waitingForKey,
content: this.content,
deleted: this.deleted,
}
}
copy(override?: Partial<EncryptedTransferPayload>, source = this.source): this {
const result = new EncryptedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as this
}
copyAsSyncResolved(
override?: Partial<EncryptedTransferPayload> & SyncResolvedParams,
source = this.source,
): SyncResolvedPayload {
const result = new EncryptedPayload(
{
...this.ejected(),
...override,
},
source,
)
return result as SyncResolvedPayload
}
}

View File

@@ -0,0 +1,104 @@
import { ContentType } from '@standardnotes/common'
import { deepFreeze, useBoolean } from '@standardnotes/utils'
import { PayloadInterface } from '../Interfaces/PayloadInterface'
import { PayloadSource } from '../Types/PayloadSource'
import { TransferPayload } from '../../TransferPayload/Interfaces/TransferPayload'
import { ItemContent } from '../../Content/ItemContent'
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
type RequiredKeepUndefined<T> = { [K in keyof T]-?: [T[K]] } extends infer U
? U extends Record<keyof U, [unknown]>
? { [K in keyof U]: U[K][0] }
: never
: never
export abstract class PurePayload<T extends TransferPayload<C>, C extends ItemContent = ItemContent>
implements PayloadInterface<T>
{
readonly source: PayloadSource
readonly uuid: string
readonly content_type: ContentType
readonly deleted: boolean
readonly content: C | string | undefined
readonly created_at: Date
readonly updated_at: Date
readonly created_at_timestamp: number
readonly updated_at_timestamp: number
readonly dirtyIndex?: number
readonly globalDirtyIndexAtLastSync?: number
readonly dirty?: boolean
readonly lastSyncBegan?: Date
readonly lastSyncEnd?: Date
readonly duplicate_of?: string
constructor(rawPayload: T, source = PayloadSource.Constructor) {
this.source = source
this.uuid = rawPayload.uuid
if (!this.uuid) {
throw Error(
`Attempting to construct payload with null uuid
Content type: ${rawPayload.content_type}`,
)
}
this.content = rawPayload.content
this.content_type = rawPayload.content_type
this.deleted = useBoolean(rawPayload.deleted, false)
this.dirty = rawPayload.dirty
this.duplicate_of = rawPayload.duplicate_of
this.created_at = new Date(rawPayload.created_at || new Date())
this.updated_at = new Date(rawPayload.updated_at || 0)
this.created_at_timestamp = rawPayload.created_at_timestamp || 0
this.updated_at_timestamp = rawPayload.updated_at_timestamp || 0
this.lastSyncBegan = rawPayload.lastSyncBegan ? new Date(rawPayload.lastSyncBegan) : undefined
this.lastSyncEnd = rawPayload.lastSyncEnd ? new Date(rawPayload.lastSyncEnd) : undefined
this.dirtyIndex = rawPayload.dirtyIndex
this.globalDirtyIndexAtLastSync = rawPayload.globalDirtyIndexAtLastSync
const timeToAllowSubclassesToFinishConstruction = 0
setTimeout(() => {
deepFreeze(this)
}, timeToAllowSubclassesToFinishConstruction)
}
ejected(): TransferPayload {
const comprehensive: RequiredKeepUndefined<TransferPayload> = {
uuid: this.uuid,
content: this.content,
deleted: this.deleted,
content_type: this.content_type,
created_at: this.created_at,
updated_at: this.updated_at,
created_at_timestamp: this.created_at_timestamp,
updated_at_timestamp: this.updated_at_timestamp,
dirty: this.dirty,
duplicate_of: this.duplicate_of,
dirtyIndex: this.dirtyIndex,
globalDirtyIndexAtLastSync: this.globalDirtyIndexAtLastSync,
lastSyncBegan: this.lastSyncBegan,
lastSyncEnd: this.lastSyncEnd,
}
return comprehensive
}
public get serverUpdatedAt(): Date {
return this.updated_at
}
public get serverUpdatedAtTimestamp(): number {
return this.updated_at_timestamp
}
abstract copy(override?: Partial<TransferPayload>, source?: PayloadSource): this
abstract copyAsSyncResolved(override?: Partial<T> & SyncResolvedParams, source?: PayloadSource): SyncResolvedPayload
}

View File

@@ -0,0 +1,15 @@
import { Uuid } from '@standardnotes/common'
import { DecryptedTransferPayload } from './../../TransferPayload/Interfaces/DecryptedTransferPayload'
import { ItemContent } from '../../Content/ItemContent'
import { ContentReference } from '../../Reference/ContentReference'
import { PayloadInterface } from './PayloadInterface'
export interface DecryptedPayloadInterface<C extends ItemContent = ItemContent>
extends PayloadInterface<DecryptedTransferPayload> {
readonly content: C
deleted: false
ejected(): DecryptedTransferPayload<C>
get references(): ContentReference[]
getReference(uuid: Uuid): ContentReference
}

View File

@@ -0,0 +1,15 @@
import { DeletedTransferPayload } from '../../TransferPayload'
import { PayloadInterface } from './PayloadInterface'
export interface DeletedPayloadInterface extends PayloadInterface<DeletedTransferPayload> {
readonly deleted: true
readonly content: undefined
/**
* Whether a payload can be discarded and removed from storage.
* This value is true if a payload is marked as deleted and not dirty.
*/
discardable: boolean | undefined
ejected(): DeletedTransferPayload
}

View File

@@ -0,0 +1,18 @@
import { ProtocolVersion } from '@standardnotes/common'
import { EncryptedTransferPayload } from '../../TransferPayload/Interfaces/EncryptedTransferPayload'
import { PayloadInterface } from './PayloadInterface'
export interface EncryptedPayloadInterface extends PayloadInterface<EncryptedTransferPayload> {
readonly content: string
readonly deleted: false
readonly enc_item_key: string
readonly items_key_id: string | undefined
readonly errorDecrypting: boolean
readonly waitingForKey: boolean
readonly version: ProtocolVersion
/** @deprecated */
readonly auth_hash?: string
ejected(): EncryptedTransferPayload
}

View File

@@ -0,0 +1,41 @@
import { SyncResolvedParams, SyncResolvedPayload } from './../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { ContentType, Uuid } from '@standardnotes/common'
import { ItemContent } from '../../Content/ItemContent'
import { TransferPayload } from '../../TransferPayload/Interfaces/TransferPayload'
import { PayloadSource } from '../Types/PayloadSource'
export interface PayloadInterface<T extends TransferPayload = TransferPayload, C extends ItemContent = ItemContent> {
readonly source: PayloadSource
readonly uuid: Uuid
readonly content_type: ContentType
content: C | string | undefined
deleted: boolean
/** updated_at is set by the server only, and not the client.*/
readonly updated_at: Date
readonly created_at: Date
readonly created_at_timestamp: number
readonly updated_at_timestamp: number
get serverUpdatedAt(): Date
get serverUpdatedAtTimestamp(): number
readonly dirtyIndex?: number
readonly globalDirtyIndexAtLastSync?: number
readonly dirty?: boolean
readonly lastSyncBegan?: Date
readonly lastSyncEnd?: Date
readonly duplicate_of?: Uuid
/**
* "Ejected" means a payload for
* generic, non-contextual consumption, such as saving to a backup file or syncing
* with a server.
*/
ejected(): TransferPayload
copy(override?: Partial<T>, source?: PayloadSource): this
copyAsSyncResolved(override?: Partial<T> & SyncResolvedParams, source?: PayloadSource): SyncResolvedPayload
}

View File

@@ -0,0 +1,29 @@
import { ItemContent } from '../../Content/ItemContent'
import {
isDecryptedTransferPayload,
isDeletedTransferPayload,
isEncryptedTransferPayload,
isErrorDecryptingTransferPayload,
} from '../../TransferPayload'
import { DecryptedPayloadInterface } from './DecryptedPayload'
import { DeletedPayloadInterface } from './DeletedPayload'
import { EncryptedPayloadInterface } from './EncryptedPayload'
import { PayloadInterface } from './PayloadInterface'
export function isDecryptedPayload<C extends ItemContent = ItemContent>(
payload: PayloadInterface,
): payload is DecryptedPayloadInterface<C> {
return isDecryptedTransferPayload(payload)
}
export function isEncryptedPayload(payload: PayloadInterface): payload is EncryptedPayloadInterface {
return isEncryptedTransferPayload(payload)
}
export function isDeletedPayload(payload: PayloadInterface): payload is DeletedPayloadInterface {
return isDeletedTransferPayload(payload)
}
export function isErrorDecryptingPayload(payload: PayloadInterface): payload is EncryptedPayloadInterface {
return isErrorDecryptingTransferPayload(payload)
}

View File

@@ -0,0 +1,11 @@
import { ItemContent } from '../../Content/ItemContent'
import { DecryptedPayloadInterface } from './DecryptedPayload'
import { DeletedPayloadInterface } from './DeletedPayload'
import { EncryptedPayloadInterface } from './EncryptedPayload'
export type FullyFormedPayloadInterface<C extends ItemContent = ItemContent> =
| DecryptedPayloadInterface<C>
| EncryptedPayloadInterface
| DeletedPayloadInterface
export type AnyNonDecryptedPayloadInterface = EncryptedPayloadInterface | DeletedPayloadInterface

View File

@@ -0,0 +1,43 @@
export enum PayloadEmitSource {
/** When an observer registers to stream items, the items are pushed immediately to the observer */
InitialObserverRegistrationPush = 1,
/**
* Payload when a client modifies item property then maps it to update UI.
* This also indicates that the item was dirtied
*/
LocalChanged,
LocalInserted,
LocalDatabaseLoaded,
/** The payload returned by offline sync operation */
OfflineSyncSaved,
LocalRetrieved,
FileImport,
ComponentRetrieved,
/** Payloads received from an external component with the intention of creating a new item */
ComponentCreated,
/**
* When the payloads are about to sync, they are emitted by the sync service with updated
* values of lastSyncBegan. Payloads emitted from this source indicate that these payloads
* have been saved to disk, and are about to be synced
*/
PreSyncSave,
RemoteRetrieved,
RemoteSaved,
}
/**
* Whether the changed payload represents only an internal change that shouldn't
* require a UI refresh
*/
export function isPayloadSourceInternalChange(source: PayloadEmitSource): boolean {
return [PayloadEmitSource.RemoteSaved, PayloadEmitSource.PreSyncSave].includes(source)
}
export function isPayloadSourceRetrieved(source: PayloadEmitSource): boolean {
return [PayloadEmitSource.RemoteRetrieved, PayloadEmitSource.ComponentRetrieved].includes(source)
}

View File

@@ -0,0 +1,13 @@
export enum PayloadSource {
/**
* Payloads with a source of Constructor means that the payload was created
* in isolated space by the caller, and does not yet have any app-related affiliation.
*/
Constructor = 1,
RemoteRetrieved,
RemoteSaved,
FileImport,
}

View File

@@ -0,0 +1,8 @@
export function PayloadTimestampDefaults() {
return {
updated_at: new Date(0),
created_at: new Date(),
updated_at_timestamp: 0,
created_at_timestamp: 0,
}
}

View File

@@ -0,0 +1,13 @@
export * from './Implementations/PurePayload'
export * from './Implementations/DecryptedPayload'
export * from './Implementations/EncryptedPayload'
export * from './Implementations/DeletedPayload'
export * from './Interfaces/DecryptedPayload'
export * from './Interfaces/DeletedPayload'
export * from './Interfaces/EncryptedPayload'
export * from './Interfaces/PayloadInterface'
export * from './Interfaces/TypeCheck'
export * from './Interfaces/UnionTypes'
export * from './Types/PayloadSource'
export * from './Types/EmitSource'
export * from './Types/TimestampDefaults'