Skip to content

Commit

Permalink
refactor(client-presence): stop fighting noUncheckedIndexAccess (#23592)
Browse files Browse the repository at this point in the history
1. tsc noUncheckedIndexAccess option does not respect `in` checks, but
is safer overall. In those cases, use the more relaxed read and check
for `undefined` result (even though `undefined` is not a possible
value).
- In several places, code blocks are reordered per
unicorn/no-negated-condition.

2. Also assignment to a key with subsequent access is not recognized as
that key having a value. So, capture the value for subsequent use.

3. Use `!` where type `keyof T` is insufficient as well.
  • Loading branch information
jason-ha authored Jan 16, 2025
1 parent 99e398f commit 34cecce
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 40 deletions.
12 changes: 5 additions & 7 deletions packages/framework/presence/src/latestMapValueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ class LatestMapValueManagerImpl<
public clientValue(client: ISessionClient): ReadonlyMap<Keys, LatestValueData<T>> {
const allKnownStates = this.datastore.knownValues(this.key);
const clientSessionId = client.sessionId;
if (!(clientSessionId in allKnownStates.states)) {
const clientStateMap = allKnownStates.states[clientSessionId];
if (clientStateMap === undefined) {
throw new Error("No entry for client");
}
const clientStateMap = allKnownStates.states[clientSessionId];
const items = new Map<Keys, LatestValueData<T>>();
for (const [key, item] of objectEntries(clientStateMap.items)) {
const value = item.value;
Expand All @@ -396,14 +396,12 @@ class LatestMapValueManagerImpl<
): void {
const allKnownStates = this.datastore.knownValues(this.key);
const clientSessionId: SpecificSessionClientId = client.sessionId;
if (!(clientSessionId in allKnownStates.states)) {
const currentState = (allKnownStates.states[clientSessionId] ??=
// New client - prepare new client state directory
allKnownStates.states[clientSessionId] = {
{
rev: value.rev,
items: {} as unknown as InternalTypes.MapValueState<T, Keys>["items"],
};
}
const currentState = allKnownStates.states[clientSessionId];
});
// Accumulate individual update keys
const updatedItemKeys: Keys[] = [];
for (const [key, item] of objectEntries(value.items)) {
Expand Down
20 changes: 10 additions & 10 deletions packages/framework/presence/src/latestValueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,14 @@ class LatestValueManagerImpl<T, Key extends string>

public clientValue(client: ISessionClient): LatestValueData<T> {
const allKnownStates = this.datastore.knownValues(this.key);
const clientSessionId = client.sessionId;
if (clientSessionId in allKnownStates.states) {
const { value, rev: revision } = allKnownStates.states[clientSessionId];
return { value, metadata: { revision, timestamp: Date.now() } };
const clientState = allKnownStates.states[client.sessionId];
if (clientState === undefined) {
throw new Error("No entry for clientId");
}
throw new Error("No entry for clientId");
return {
value: clientState.value,
metadata: { revision: clientState.rev, timestamp: Date.now() },
};
}

public update(
Expand All @@ -145,11 +147,9 @@ class LatestValueManagerImpl<T, Key extends string>
): void {
const allKnownStates = this.datastore.knownValues(this.key);
const clientSessionId = client.sessionId;
if (clientSessionId in allKnownStates.states) {
const currentState = allKnownStates.states[clientSessionId];
if (currentState.rev >= value.rev) {
return;
}
const currentState = allKnownStates.states[clientSessionId];
if (currentState !== undefined && currentState.rev >= value.rev) {
return;
}
this.datastore.update(this.key, clientSessionId, value);
this.events.emit("updated", {
Expand Down
10 changes: 5 additions & 5 deletions packages/framework/presence/src/presenceDatastoreManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ function mergeGeneralDatastoreMessageContent(
const mergedData = queueDatastore[workspaceName] ?? {};

// Iterate over each value manager and its data, merging it as needed.
for (const valueManagerKey of Object.keys(workspaceData)) {
for (const [clientSessionId, value] of objectEntries(workspaceData[valueManagerKey])) {
mergedData[valueManagerKey] ??= {};
const oldData = mergedData[valueManagerKey][clientSessionId];
mergedData[valueManagerKey][clientSessionId] = mergeValueDirectory(
for (const [valueManagerKey, valueManagerValue] of objectEntries(workspaceData)) {
for (const [clientSessionId, value] of objectEntries(valueManagerValue)) {
const mergeObject = (mergedData[valueManagerKey] ??= {});
const oldData = mergeObject[clientSessionId];
mergeObject[clientSessionId] = mergeValueDirectory(
oldData,
value,
0, // local values do not need a time shift
Expand Down
36 changes: 20 additions & 16 deletions packages/framework/presence/src/presenceStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ class PresenceStatesImpl<TSchema extends PresenceStatesSchema>
nodes[key as keyof TSchema] = newNodeData.manager;
if ("initialData" in newNodeData) {
const { value, allowableUpdateLatencyMs } = newNodeData.initialData;
datastore[key] ??= {};
datastore[key][clientSessionId] = value;
(datastore[key] ??= {})[clientSessionId] = value;
newValues[key] = value;
if (allowableUpdateLatencyMs !== undefined) {
cumulativeAllowableUpdateLatencyMs =
Expand Down Expand Up @@ -315,7 +314,9 @@ class PresenceStatesImpl<TSchema extends PresenceStatesSchema>
} {
return {
self: this.runtime.clientSessionId,
states: this.datastore[key],
// Caller must only use `key`s that are part of `this.datastore`.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
states: this.datastore[key]!,
};
}

Expand Down Expand Up @@ -364,13 +365,14 @@ class PresenceStatesImpl<TSchema extends PresenceStatesSchema>
this.nodes[key] = nodeData.manager;
if ("initialData" in nodeData) {
const { value, allowableUpdateLatencyMs } = nodeData.initialData;
if (key in this.datastore) {
let datastoreValue = this.datastore[key];
if (datastoreValue === undefined) {
datastoreValue = this.datastore[key] = {};
} else {
// Already have received state from other clients. Kept in `all`.
// TODO: Send current `all` state to state manager.
} else {
this.datastore[key] = {};
}
this.datastore[key][this.runtime.clientSessionId] = value;
datastoreValue[this.runtime.clientSessionId] = value;
this.runtime.localUpdate(
{ [key]: value },
{
Expand All @@ -389,13 +391,14 @@ class PresenceStatesImpl<TSchema extends PresenceStatesSchema>
this.controls.allowableUpdateLatencyMs = controls.allowableUpdateLatencyMs;
}
for (const [key, nodeFactory] of Object.entries(content)) {
if (key in this.nodes) {
const node = unbrandIVM(this.nodes[key]);
const brandedIVM = this.nodes[key];
if (brandedIVM === undefined) {
this.add(key, nodeFactory);
} else {
const node = unbrandIVM(brandedIVM);
if (!(node instanceof nodeFactory.instanceBase)) {
throw new TypeError(`State "${key}" previously created by different value manager.`);
}
} else {
this.add(key, nodeFactory);
}
}
return this as PresenceStates<TSchema & TSchemaAdditional>;
Expand All @@ -407,15 +410,16 @@ class PresenceStatesImpl<TSchema extends PresenceStatesSchema>
remoteDatastore: ValueUpdateRecord,
): void {
for (const [key, remoteAllKnownState] of Object.entries(remoteDatastore)) {
if (key in this.nodes) {
const node = unbrandIVM(this.nodes[key]);
const brandedIVM = this.nodes[key];
if (brandedIVM === undefined) {
// Assume all broadcast state is meant to be kept even if not currently registered.
mergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);
} else {
const node = unbrandIVM(brandedIVM);
for (const [clientSessionId, value] of objectEntries(remoteAllKnownState)) {
const client = this.runtime.lookupClient(clientSessionId);
node.update(client, received, value);
}
} else {
// Assume all broadcast state is meant to be kept even if not currently registered.
mergeUntrackedDatastore(key, remoteAllKnownState, this.datastore, timeModifier);
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions packages/framework/presence/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,5 @@
"outDir": "./lib",
"noImplicitAny": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": true,
},
}

0 comments on commit 34cecce

Please sign in to comment.