2025-06-30 18:58:24 +02:00
|
|
|
/*
|
|
|
|
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
*/
|
2025-06-24 15:19:11 +02:00
|
|
|
import type { ApiEntity, ApiTombstone } from "~/shared/types/api";
|
|
|
|
import type { Id } from "~/shared/types/common";
|
2025-06-23 22:46:39 +02:00
|
|
|
import { DateTime, Zone } from "~/shared/utils/luxon";
|
|
|
|
|
2025-06-24 15:19:11 +02:00
|
|
|
export abstract class ClientEntity<Api extends ApiEntity> {
|
2025-06-23 22:46:39 +02:00
|
|
|
/**
|
|
|
|
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.
|
|
|
|
*/
|
2025-06-24 15:19:11 +02:00
|
|
|
abstract apiUpdate(api: Api, opts: { zone: Zone, locale: string }): void
|
2025-06-23 22:46:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
Serialise this entity to the API format. Not allowed if {@link deleted} is true.
|
|
|
|
*/
|
2025-06-24 15:19:11 +02:00
|
|
|
abstract toApi(): Api | ApiTombstone
|
2025-06-23 22:46:39 +02:00
|
|
|
}
|