Add Camino datatypes package export

This commit is contained in:
Timothy J. Aveni
2026-07-12 19:36:56 -07:00
commit b018a0630e
13 changed files with 676 additions and 0 deletions
+179
View File
@@ -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),
};
};
+1
View File
@@ -0,0 +1 @@
export * from "./field-semantics.js";