50 lines
2.2 KiB
TypeScript
50 lines
2.2 KiB
TypeScript
/**
|
|
* Socket.IO payloads are only statically typed at the call site — a
|
|
* misbehaving or compromised gateway can send anything at runtime. These
|
|
* guards protect the dereference sites that would otherwise throw (`.map` on
|
|
* a non-array, `.toFixed` on a non-number) or render an object as a React
|
|
* child.
|
|
*/
|
|
|
|
export function asString(value: unknown, fallback = ''): string {
|
|
return typeof value === 'string' ? value : fallback;
|
|
}
|
|
|
|
/** Like `asString`, but an empty string also falls back — used for guarded
|
|
* contract-provided reason strings (e.g. a denial or failure message) where
|
|
* an empty string is not a meaningful value to display in place of the
|
|
* stable fallback copy. */
|
|
export function asNonEmptyString(value: unknown, fallback: string): string {
|
|
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
}
|
|
|
|
export function asFiniteNumber(value: unknown, fallback = 0): number {
|
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
/** Like `asFiniteNumber`, but returns `null` on failure instead of a numeric
|
|
* fallback — callers that must not fabricate a plausible-looking value (e.g.
|
|
* `0 tokens` / `$0.0000` for genuinely unknown usage) use this to render an
|
|
* honest "unavailable" label instead. */
|
|
export function asFiniteNumberOrNull(value: unknown): number | null {
|
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
}
|
|
|
|
export function asStringArray(value: unknown): string[] {
|
|
return Array.isArray(value) && value.every((item) => typeof item === 'string') ? value : [];
|
|
}
|
|
|
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null;
|
|
}
|
|
|
|
/** The single point of truth for what counts as a valid conversation ID
|
|
* anywhere a scoped server event may adopt one into state — a non-empty
|
|
* string, nothing else. Every site that establishes or compares
|
|
* `state.conversationId` against a raw socket payload must route through
|
|
* this guard so a malformed first frame (null/object/number/empty string)
|
|
* can never be adopted verbatim. */
|
|
export function asConversationId(value: unknown): string | null {
|
|
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
}
|