Add Camino datatypes package export
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
export type UnknownFieldClock = {
|
||||
kind: "unknown";
|
||||
};
|
||||
|
||||
export type RevisionFieldClock = {
|
||||
kind: "revision";
|
||||
revision: bigint;
|
||||
};
|
||||
|
||||
export type FieldClock = UnknownFieldClock | RevisionFieldClock;
|
||||
|
||||
export type CanonicalFieldValue<T> = {
|
||||
value: T;
|
||||
clock: FieldClock;
|
||||
};
|
||||
|
||||
export type PendingFieldWrite<T> = {
|
||||
clientMutationId: string;
|
||||
sequence: number;
|
||||
value: T;
|
||||
};
|
||||
|
||||
export type RegisterFieldState<T> = {
|
||||
canonical: CanonicalFieldValue<T>;
|
||||
visible: T;
|
||||
pending: PendingFieldWrite<T>[];
|
||||
nextPendingSequence: number;
|
||||
};
|
||||
|
||||
export type FieldApplyResult = {
|
||||
accepted: boolean;
|
||||
canonicalChanged: boolean;
|
||||
pendingChanged: boolean;
|
||||
visibleChanged: boolean;
|
||||
};
|
||||
|
||||
export const unknownFieldClock = (): FieldClock => ({ kind: "unknown" });
|
||||
|
||||
export const revisionFieldClock = (
|
||||
revision: string | number | bigint | undefined | null,
|
||||
): FieldClock => {
|
||||
if (typeof revision === "bigint") {
|
||||
return { kind: "revision", revision };
|
||||
}
|
||||
if (typeof revision === "number" && Number.isInteger(revision) && revision >= 0) {
|
||||
return { kind: "revision", revision: BigInt(revision) };
|
||||
}
|
||||
if (typeof revision === "string" && /^\d+$/.test(revision)) {
|
||||
return { kind: "revision", revision: BigInt(revision) };
|
||||
}
|
||||
return unknownFieldClock();
|
||||
};
|
||||
|
||||
export const fieldClockToRevisionString = (clock: FieldClock) =>
|
||||
clock.kind === "revision" ? clock.revision.toString() : "";
|
||||
|
||||
export const compareFieldClocks = (
|
||||
left: FieldClock,
|
||||
right: FieldClock,
|
||||
): "older" | "same" | "newer" | "unknown" => {
|
||||
if (left.kind === "revision" && right.kind === "revision") {
|
||||
if (left.revision < right.revision) {
|
||||
return "older";
|
||||
}
|
||||
if (left.revision > right.revision) {
|
||||
return "newer";
|
||||
}
|
||||
return "same";
|
||||
}
|
||||
if (left.kind === "unknown" && right.kind === "unknown") {
|
||||
return "same";
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
export const sameFieldClock = (left: FieldClock, right: FieldClock) =>
|
||||
compareFieldClocks(left, right) === "same";
|
||||
|
||||
const shouldAcceptCanonical = (incoming: FieldClock, current: FieldClock) => {
|
||||
const order = compareFieldClocks(incoming, current);
|
||||
if (order === "older") {
|
||||
return false;
|
||||
}
|
||||
if (order === "unknown" && current.kind !== "unknown") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const latestPendingValue = <T>(state: RegisterFieldState<T>) =>
|
||||
state.pending.at(-1)?.value;
|
||||
|
||||
const recomputeVisible = <T>(state: RegisterFieldState<T>) => {
|
||||
const pendingValue = latestPendingValue(state);
|
||||
return pendingValue === undefined ? state.canonical.value : pendingValue;
|
||||
};
|
||||
|
||||
export const createRegisterFieldState = <T>(
|
||||
initial: CanonicalFieldValue<T>,
|
||||
): RegisterFieldState<T> => ({
|
||||
canonical: initial,
|
||||
visible: initial.value,
|
||||
pending: [],
|
||||
nextPendingSequence: 1,
|
||||
});
|
||||
|
||||
export const applyCanonicalFieldValue = <T>(
|
||||
state: RegisterFieldState<T>,
|
||||
incoming: CanonicalFieldValue<T>,
|
||||
clientMutationId?: string,
|
||||
): FieldApplyResult => {
|
||||
const visibleBefore = state.visible;
|
||||
const pendingBefore = state.pending.length;
|
||||
if (!shouldAcceptCanonical(incoming.clock, state.canonical.clock)) {
|
||||
return {
|
||||
accepted: false,
|
||||
canonicalChanged: false,
|
||||
pendingChanged: false,
|
||||
visibleChanged: false,
|
||||
};
|
||||
}
|
||||
|
||||
const canonicalChanged =
|
||||
!Object.is(state.canonical.value, incoming.value) ||
|
||||
!sameFieldClock(state.canonical.clock, incoming.clock);
|
||||
|
||||
state.canonical = incoming;
|
||||
if (clientMutationId) {
|
||||
state.pending = state.pending.filter(
|
||||
(entry) => entry.clientMutationId !== clientMutationId,
|
||||
);
|
||||
}
|
||||
state.visible = recomputeVisible(state);
|
||||
|
||||
return {
|
||||
accepted: true,
|
||||
canonicalChanged,
|
||||
pendingChanged: state.pending.length !== pendingBefore,
|
||||
visibleChanged: !Object.is(state.visible, visibleBefore),
|
||||
};
|
||||
};
|
||||
|
||||
export const beginLocalFieldWrite = <T>(
|
||||
state: RegisterFieldState<T>,
|
||||
clientMutationId: string,
|
||||
value: T,
|
||||
): FieldApplyResult => {
|
||||
const visibleBefore = state.visible;
|
||||
state.pending.push({
|
||||
clientMutationId,
|
||||
sequence: state.nextPendingSequence++,
|
||||
value,
|
||||
});
|
||||
state.visible = value;
|
||||
return {
|
||||
accepted: true,
|
||||
canonicalChanged: false,
|
||||
pendingChanged: true,
|
||||
visibleChanged: !Object.is(state.visible, visibleBefore),
|
||||
};
|
||||
};
|
||||
|
||||
export const failLocalFieldWrite = <T>(
|
||||
state: RegisterFieldState<T>,
|
||||
clientMutationId: string,
|
||||
): FieldApplyResult => {
|
||||
const visibleBefore = state.visible;
|
||||
const pendingBefore = state.pending.length;
|
||||
state.pending = state.pending.filter(
|
||||
(entry) => entry.clientMutationId !== clientMutationId,
|
||||
);
|
||||
state.visible = recomputeVisible(state);
|
||||
return {
|
||||
accepted: true,
|
||||
canonicalChanged: false,
|
||||
pendingChanged: state.pending.length !== pendingBefore,
|
||||
visibleChanged: !Object.is(state.visible, visibleBefore),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./field-semantics.js";
|
||||
Reference in New Issue
Block a user