Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export interface LatestMap<T, Keys extends string | number = string | number, TR

// @beta @input
export interface LatestMapArguments<T, Keys extends string | number = string | number> extends LatestMapArgumentsRaw<T, Keys> {
keyValidator?: StateSchemaValidator<Keys>;
validator: StateSchemaValidator<T>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export interface LatestMap<T, Keys extends string | number = string | number, TR

// @beta @input
export interface LatestMapArguments<T, Keys extends string | number = string | number> extends LatestMapArgumentsRaw<T, Keys> {
keyValidator?: StateSchemaValidator<Keys>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep this optional long-term? If I recall correctly, we decided to make the main validator required since we believe it's best practice to use runtime validation in addition to the compile-time. I can understand how key validation might be more optional, but do we lose any compile-time help with this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that it can stay optional. We can guarantee that keys are string | number. Key validator would only be useful if you wanted to reduce from there.
With the new proposal to return a boolean, we can actually change the signature to keyValidator(key: string | number): asserts key is Keys. When not provided, there wouldn't be anything to infer the Key type, but it should work when provided. I think the optionality won't matter. I will confirm.

validator: StateSchemaValidator<T>;
}

Expand Down
21 changes: 20 additions & 1 deletion packages/framework/presence/src/latestMapValueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ class LatestMapValueManagerImpl<
public readonly value: InternalTypes.MapValueState<T, Keys>,
controlSettings: BroadcastControlSettings | undefined,
private readonly validator: StateSchemaValidator<T> | undefined,
private readonly keyValidator: StateSchemaValidator<Keys> | undefined,
) {
this.controls = new OptionalBroadcastControl(controlSettings);

Expand All @@ -479,6 +480,10 @@ class LatestMapValueManagerImpl<
);
}

private isInvalidKey(key: Keys): boolean {
return this.keyValidator !== undefined && this.keyValidator(key) === undefined;
}

public get presence(): Presence {
return this.datastore.presence;
}
Expand Down Expand Up @@ -513,6 +518,9 @@ class LatestMapValueManagerImpl<
}
const items = new Map<Keys, LatestData<T, ValueAccessor<T>>>();
for (const [key, item] of objectEntries(clientStateMap.items)) {
if (this.isInvalidKey(key)) {
continue;
}
if (isValueRequiredState(item)) {
items.set(key, {
value: createValidatedGetter(item, validator),
Expand Down Expand Up @@ -561,6 +569,9 @@ class LatestMapValueManagerImpl<
};
const postUpdateActions: PostUpdateAction[] = [];
for (const key of updatedItemKeys) {
if (this.isInvalidKey(key)) {
continue;
}
const item = value.items[key];
const hadPriorValue = currentState.items[key]?.value;
currentState.items[key] = item;
Expand Down Expand Up @@ -625,10 +636,16 @@ export interface LatestMapArgumentsRaw<T, Keys extends string | number = string
export interface LatestMapArguments<T, Keys extends string | number = string | number>
extends LatestMapArgumentsRaw<T, Keys> {
/**
* An optional function that will be called at runtime to validate the presence data. A runtime validator is strongly
* An optional function that will be called at runtime to validate the key presence data value. A runtime validator is strongly
* recommended. See {@link StateSchemaValidator}.
*/
validator: StateSchemaValidator<T>;
/**
* An optional function that will be called at runtime to validate the presence
* data key. A runtime validator is strongly recommended when key type is not
* simply `string | number`. See {@link StateSchemaValidator}.
*/
keyValidator?: StateSchemaValidator<Keys>;
}

// #region factory function overloads
Expand Down Expand Up @@ -691,6 +708,7 @@ export const latestMap: LatestMapFactory = <
const settings = args?.settings;
const initialValues = args?.local;
const validator = args?.validator;
const keyValidator = args?.keyValidator;

const timestamp = Date.now();
const value: InternalTypes.MapValueState<
Expand Down Expand Up @@ -730,6 +748,7 @@ export const latestMap: LatestMapFactory = <
value,
settings,
validator,
keyValidator,
),
),
});
Expand Down
Loading