// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export const mod = (a: number, n: number): number => { return ((a % n) + n) % n; }; export function assert( predicate: T, message = 'Assertion failed!', ): asserts predicate { if (!predicate) { throw new Error(message); } } export type Keys = T extends T ? keyof T : never; export type RequiredKeys = { [K in keyof T] -?: Record extends Pick? never : K; }[keyof T]; export type OptionalKeys = { [K in keyof T] -?: Record extends Pick? K : never; }[keyof T]; export type DeepImmutable = { readonly[K in keyof T]: DeepImmutable; }; export type DeepMutable = { -readonly[K in keyof T]: DeepMutable; }; export type DeepPartial = { [K in keyof T]?: DeepPartial>; }; export type Mutable = { -readonly[K in keyof T]: T[K]; }; export const deepFreeze = (object: T): DeepImmutable => { for (const name of Reflect.ownKeys(object)) { const value = object[name as keyof T]; if ((value && typeof value === 'object') || typeof value === 'function') { deepFreeze(value); } } return Object.freeze(object); }; export class InsertAssignment { value: T; constructor(value: T) { this.value = value; } } export class ArrayAssignments { value: Record; constructor(value: Record) { this.value = value; } } export type Assignments = T extends Readonly>? R[]|ArrayAssignments|InsertAssignment>: {[K in keyof T]: Assignments}; export const immutableDeepAssign = ( object: DeepImmutable, assignments: DeepImmutable>>, ): DeepImmutable => { if (assignments instanceof ArrayAssignments) { assert(Array.isArray(object), `Expected an array. Got ${typeof object}.`); const updatedObject = [...object] as Mutable; const keys = Object.keys(assignments.value) .sort( (a, b) => Number(b) - Number(a), ) as Array; for (const key of keys) { const update = assignments.value[Number(key)]; if (update === undefined) { updatedObject.splice(Number(key), 1); } else if (update instanceof InsertAssignment) { updatedObject.splice(Number(key), 0, update.value); } else { updatedObject[Number(key)] = immutableDeepAssign( updatedObject[key], update, ); } } return Object.freeze(updatedObject); } if (typeof assignments === 'object' && !Array.isArray(assignments)) { assert(!Array.isArray(object), 'Expected an object. Got an array.'); const updatedObject = {...object} as Mutable; const keys = Object.keys(assignments) as Array; for (const key of keys) { const update = assignments[key]; if (update === undefined) { delete updatedObject[key]; } else { updatedObject[key] = immutableDeepAssign( updatedObject[key], update as typeof updatedObject[typeof key], ); } } return Object.freeze(updatedObject); } return assignments as DeepImmutable; };