owltide/utils/client-entity.ts

88 lines
2.3 KiB
TypeScript
Raw Permalink Normal View History

/*
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ApiEntity, ApiTombstone } from "~/shared/types/api";
import type { Id } from "~/shared/types/common";
import { DateTime, Zone } from "~/shared/utils/luxon";
export abstract class ClientEntity<Api extends ApiEntity> {
/**
Millisecond offset used to indicate this is a new entitity.
*/
static newEntityMillis = -1;
/**
Timestamp of the entity received from server. If this is
a new entity this will have a millisecond offset equal to
{@link ClientEntity.newEntityMillis}.
*/
serverUpdatedAt: DateTime;
/**
True if the server has deleted this entity, but the client
is holding on to it in order to resolve an edit conflcit
*/
serverDeleted: boolean;
constructor(
/**
Server supplied id of this entity. Each kind of entity has its own namespace of ids.
*/
public readonly id: Id,
/**
Server's timestamp of this entity at the time it was modified. If the entity
is unmodified this will track {@link serverUpdatedAt}. If this is a new entity
it'll have a millesecond offset equal to {@link ClientEntity.newEntityMillis}.
*/
public updatedAt: DateTime,
/**
Flag indicating the client intends to delete this entity.
*/
public deleted: boolean,
) {
this.serverUpdatedAt = updatedAt;
this.serverDeleted = deleted;
}
/**
True if this entity does not yet exist on the server.
*/
isNew() {
return this.serverUpdatedAt.toMillis() === ClientEntity.newEntityMillis;
}
/**
True if both the server and the client have modified this entity
independently of each other.
*/
isConflict() {
return this.serverUpdatedAt.toMillis() !== this.updatedAt.toMillis();
}
/**
True if this entity has been modified on the client.
*/
isModified() {
return (
this.isNew()
|| this.deleted
|| this.serverDeleted
);
}
/**
Discard any client side modifications to this entity. Not allowed if
{@link serverDeleted} is true or this is a new entity.
*/
abstract discard(): void
/**
Apply an update delivered from the API to this entity.
*/
abstract apiUpdate(api: Api, opts: { zone: Zone, locale: string }): void
/**
Serialise this entity to the API format. Not allowed if {@link deleted} is true.
*/
abstract toApi(): Api | ApiTombstone
}