diff --git a/packages/runtime/container-runtime/src/channelCollection.ts b/packages/runtime/container-runtime/src/channelCollection.ts index f40e5bba8693..1f6501e73d7b 100644 --- a/packages/runtime/container-runtime/src/channelCollection.ts +++ b/packages/runtime/container-runtime/src/channelCollection.ts @@ -406,7 +406,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable { const attachMessage = contents as InboundAttachMessage; // We need to process the GC Data for both local and remote attach messages const foundGCData = processAttachMessageGCData( - attachMessage.snapshot, + attachMessage.snapshot ?? undefined, (nodeId, toPath) => { // nodeId is the relative path under the node being attached. Always starts with "/", but no trailing "/" after an id const fromPath = `/${attachMessage.id}${nodeId === "/" ? "" : nodeId}`; diff --git a/packages/runtime/runtime-utils/.eslintrc.cjs b/packages/runtime/runtime-utils/.eslintrc.cjs index c489a4dd5f2d..000b4c1391ff 100644 --- a/packages/runtime/runtime-utils/.eslintrc.cjs +++ b/packages/runtime/runtime-utils/.eslintrc.cjs @@ -4,10 +4,7 @@ */ module.exports = { - extends: [ - require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"), - "prettier", - ], + extends: [require.resolve("@fluidframework/eslint-config-fluid/recommended"), "prettier"], parserOptions: { project: ["./tsconfig.json", "./src/test/tsconfig.json"], }, diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md index 8e6c41e27586..63d2f85b52ed 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.legacy.alpha.api.md @@ -10,7 +10,7 @@ export function compareFluidHandles(a: IFluidHandle, b: IFluidHandle): boolean; // @alpha export function convertToSummaryTreeWithStats(snapshot: ITree, fullTree?: boolean): ISummaryTreeWithStats; -// @alpha (undocumented) +// @alpha export const create404Response: (request: IRequest) => IResponse; // @alpha diff --git a/packages/runtime/runtime-utils/src/dataStoreHelpers.ts b/packages/runtime/runtime-utils/src/dataStoreHelpers.ts index 48dfdda8dded..61db135f4e51 100644 --- a/packages/runtime/runtime-utils/src/dataStoreHelpers.ts +++ b/packages/runtime/runtime-utils/src/dataStoreHelpers.ts @@ -17,16 +17,22 @@ interface IResponseException extends Error { message: string; code: number; stack?: string; - underlyingResponseHeaders?: { [key: string]: any }; + underlyingResponseHeaders?: { [key: string]: unknown }; } /** + * Converts an error object into an IResponse * @internal */ -export function exceptionToResponse(err: any): IResponse { +export function exceptionToResponse(err: unknown): IResponse { const status = 500; - if (err !== null && typeof err === "object" && err.errorFromRequestFluidObject === true) { - const responseErr: IResponseException = err; + if ( + err !== null && + typeof err === "object" && + "errorFromRequestFluidObject" in err && + err.errorFromRequestFluidObject === true + ) { + const responseErr = err as IResponseException; return { mimeType: "text/plain", status: responseErr.code, @@ -38,7 +44,7 @@ export function exceptionToResponse(err: any): IResponse { }; } - // Capture error objects, not stack itself, as stack retrieval is very expensive operation, so we delay it + // Capture error objects, not stack itself, as stack retrieval is very expensive operation const errWithStack = generateErrorWithStack(); return { @@ -46,16 +52,25 @@ export function exceptionToResponse(err: any): IResponse { status, value: `${err}`, get stack() { - return (err?.stack as string | undefined) ?? errWithStack.stack; + // Use type assertion after checking if err is an object with stack + return ( + (typeof err === "object" && err !== null && "stack" in err + ? (err.stack as string | undefined) + : undefined) ?? errWithStack.stack + ); }, }; } /** + * Converts an IResponse object back into an Error object that can be thrown + * @param response - The IResponse object to convert + * @param request - The original IRequest object + * @returns An Error object with additional properties from the response * @internal */ export function responseToException(response: IResponse, request: IRequest): Error { - const message = response.value; + const message = response.value as string; const errWithStack = generateErrorWithStack(); const responseErr: Error & IResponseException = { errorFromRequestFluidObject: true, @@ -72,20 +87,29 @@ export function responseToException(response: IResponse, request: IRequest): Err } /** + * Creates a 404 "not found" response for the given request + * @param request - The request that resulted in the 404 response + * @returns An IResponse object with 404 status code * @legacy * @alpha */ -export const create404Response = (request: IRequest) => +export const create404Response = (request: IRequest): IResponse => createResponseError(404, "not found", request); /** + * Creates an error response with the specified status code and message + * @param status - HTTP status code for the error (must not be 200) + * @param value - Error message or description + * @param request - The request that resulted in this error + * @param headers - Optional headers to include in the response + * @returns An IResponse object representing the error * @internal */ export function createResponseError( status: number, value: string, request: IRequest, - headers?: { [key: string]: any }, + headers?: { [key: string]: unknown }, ): IResponse { assert(status !== 200, 0x19b /* "Cannot not create response error on 200 status" */); // Omit query string which could contain personal data unfit for logging @@ -111,6 +135,11 @@ export function createResponseError( export type Factory = IFluidDataStoreFactory & Partial; /** + * Creates a combined IFluidDataStoreFactory and IFluidDataStoreRegistry implementation + * from a factory type and implementation + * @param type - The unique identifier for this data store factory + * @param factory - The factory implementation or promise that resolves to one + * @returns A combined factory and registry implementation * @internal */ export function createDataStoreFactory( @@ -125,8 +154,13 @@ export function createDataStoreFactory( get IFluidDataStoreRegistry() { return this; }, - instantiateDataStore: async (context, existing) => - (await factory).instantiateDataStore(context, existing), - get: async (name: string) => (await factory).IFluidDataStoreRegistry?.get(name), + instantiateDataStore: async (context, existing) => { + const resolvedFactory = await factory; + return resolvedFactory.instantiateDataStore(context, existing); + }, + get: async (name: string) => { + const resolvedFactory = await factory; + return resolvedFactory.IFluidDataStoreRegistry?.get(name); + }, }; } diff --git a/packages/runtime/runtime-utils/src/handles.ts b/packages/runtime/runtime-utils/src/handles.ts index a65dbee02dbe..d6f781914de8 100644 --- a/packages/runtime/runtime-utils/src/handles.ts +++ b/packages/runtime/runtime-utils/src/handles.ts @@ -23,8 +23,11 @@ export interface ISerializedHandle { * Is the input object a @see ISerializedHandle? * @internal */ -export const isSerializedHandle = (value: any): value is ISerializedHandle => - value?.type === "__fluid_handle__"; +export const isSerializedHandle = (value: unknown): value is ISerializedHandle => + typeof value === "object" && + value !== null && + "type" in value && + value.type === "__fluid_handle__"; /** * Setting to opt into compatibility with handles from before {@link fluidHandleSymbol} existed (Fluid Framework client 2.0.0-rc.3.0.0 and earlier). diff --git a/packages/runtime/runtime-utils/src/objectstorageutils.ts b/packages/runtime/runtime-utils/src/objectstorageutils.ts index 1b893cc27d05..2511ac7907d5 100644 --- a/packages/runtime/runtime-utils/src/objectstorageutils.ts +++ b/packages/runtime/runtime-utils/src/objectstorageutils.ts @@ -6,15 +6,18 @@ import { ITree } from "@fluidframework/driver-definitions/internal"; /** + * Normalizes a storage path by removing leading and trailing slashes and splitting into parts + * @param path - The storage path to normalize (e.g. "/foo/bar/") + * @returns Array of path segments (e.g. ["foo", "bar"]) * @internal */ -export function getNormalizedObjectStoragePathParts(path: string) { +export function getNormalizedObjectStoragePathParts(path: string): string[] { let normalizePath = path; if (normalizePath.startsWith("/")) { - normalizePath = normalizePath.substr(1); + normalizePath = normalizePath.slice(1); } if (normalizePath.endsWith("/")) { - normalizePath = normalizePath.substr(0, normalizePath.length - 1); + normalizePath = normalizePath.slice(0, -1); } if (normalizePath.length > 0) { return normalizePath.split("/"); @@ -23,6 +26,11 @@ export function getNormalizedObjectStoragePathParts(path: string) { } /** + * Lists all blobs at the specified path in the given tree + * @param inputTree - The tree to search within + * @param path - The path to search at (e.g. "foo/bar") + * @returns Promise that resolves to an array of blob names at that path + * @throws Error if the path does not exist in the tree * @internal */ export async function listBlobsAtTreePath( @@ -42,7 +50,7 @@ export async function listBlobsAtTreePath( // so we must redundantly determine that the entry's type is "Tree" tree = treeEntry?.type === "Tree" ? treeEntry.value : undefined; } - if (tree?.entries === undefined || pathParts.length !== 0) { + if (tree?.entries === undefined || pathParts.length > 0) { throw new Error("path does not exist"); } return tree.entries.filter((e) => e.type === "Blob").map((e) => e.path); diff --git a/packages/runtime/runtime-utils/src/requestParser.ts b/packages/runtime/runtime-utils/src/requestParser.ts index f6328590f053..e792fe8be7a1 100644 --- a/packages/runtime/runtime-utils/src/requestParser.ts +++ b/packages/runtime/runtime-utils/src/requestParser.ts @@ -17,21 +17,25 @@ export class RequestParser implements IRequest { */ public static getPathParts(url: string): readonly string[] { const queryStartIndex = url.indexOf("?"); - return url - .substring(0, queryStartIndex < 0 ? url.length : queryStartIndex) - .split("/") - .reduce((pv, cv) => { - if (cv !== undefined && cv.length > 0) { - pv.push(decodeURIComponent(cv)); - } - return pv; - }, []); + const pathParts: string[] = []; + const urlPath = url.slice( + 0, + Math.max(0, queryStartIndex < 0 ? url.length : queryStartIndex), + ); + + for (const part of urlPath.split("/")) { + if (part !== undefined && part.length > 0) { + pathParts.push(decodeURIComponent(part)); + } + } + + return pathParts; } private requestPathParts: readonly string[] | undefined; public readonly query: string; - public static create(request: Readonly) { + public static create(request: Readonly): RequestParser { // Perf optimizations. if (request instanceof RequestParser) { return request; @@ -41,7 +45,8 @@ export class RequestParser implements IRequest { protected constructor(private readonly request: Readonly) { const queryStartIndex = this.request.url.indexOf("?"); - this.query = queryStartIndex >= 0 ? this.request.url.substring(queryStartIndex) : ""; + this.query = + queryStartIndex >= 0 ? this.request.url.slice(Math.max(0, queryStartIndex)) : ""; if (request.headers !== undefined) { this.headers = request.headers; } @@ -67,7 +72,7 @@ export class RequestParser implements IRequest { * Returns true if it's a terminating path, i.e. no more elements after `elements` entries and empty query. * @param elements - number of elements in path */ - public isLeaf(elements: number) { + public isLeaf(elements: number): boolean { return this.query === "" && this.pathParts.length === elements; } diff --git a/packages/runtime/runtime-utils/src/runtimeFactoryHelper.ts b/packages/runtime/runtime-utils/src/runtimeFactoryHelper.ts index d7033ae3de82..e56a455a2a08 100644 --- a/packages/runtime/runtime-utils/src/runtimeFactoryHelper.ts +++ b/packages/runtime/runtime-utils/src/runtimeFactoryHelper.ts @@ -15,7 +15,7 @@ import { IContainerRuntime } from "@fluidframework/container-runtime-definitions * @alpha */ export abstract class RuntimeFactoryHelper implements IRuntimeFactory { - public get IRuntimeFactory() { + public get IRuntimeFactory(): this { return this; } diff --git a/packages/runtime/runtime-utils/src/summaryUtils.ts b/packages/runtime/runtime-utils/src/summaryUtils.ts index 2e227c1710cf..8828fed2554f 100644 --- a/packages/runtime/runtime-utils/src/summaryUtils.ts +++ b/packages/runtime/runtime-utils/src/summaryUtils.ts @@ -59,26 +59,35 @@ export function mergeStats(...stats: ISummaryStats[]): ISummaryStats { } /** + * Calculates the byte length of an UTF-8 encoded string + * @param str - The string to calculate the byte length of + * @returns The byte length of the string * @internal */ export function utf8ByteLength(str: string): number { // returns the byte length of an utf8 string let s = str.length; for (let i = str.length - 1; i >= 0; i--) { - const code = str.charCodeAt(i); - if (code > 0x7f && code <= 0x7ff) { - s++; - } else if (code > 0x7ff && code <= 0xffff) { - s += 2; - } - if (code >= 0xdc00 && code <= 0xdfff) { - i--; // trail surrogate + const code = str.codePointAt(i); + // Add null check to handle potential undefined + if (code !== undefined) { + if (code > 0x7f && code <= 0x7ff) { + s++; + } else if (code > 0x7ff && code <= 0xffff) { + s += 2; + } + if (code >= 0xdc00 && code <= 0xdfff) { + i--; // trail surrogate + } } } return s; } /** + * Gets the size of a blob + * @param content - The content of the blob + * @returns The size of the blob * @internal */ export function getBlobSize(content: ISummaryBlob["content"]): number { @@ -103,12 +112,16 @@ function calculateStatsCore(summaryObject: SummaryObject, stats: ISummaryStats): stats.totalBlobSize += getBlobSize(summaryObject.content); return; } - default: + default: { return; + } } } /** + * Calculates the stats for a summary object + * @param summary - The summary object to calculate stats for + * @returns The calculated stats * @internal */ export function calculateStats(summary: SummaryObject): ISummaryStats { @@ -118,6 +131,10 @@ export function calculateStats(summary: SummaryObject): ISummaryStats { } /** + * Adds a blob to the summary tree + * @param summary - The summary tree to add the blob to + * @param key - The key to store the blob at + * @param content - The content of the blob to be added * @internal */ export function addBlobToSummary( @@ -135,6 +152,10 @@ export function addBlobToSummary( } /** + * Adds a summarize result to the summary tree + * @param summary - The summary tree to add the summarize result to + * @param key - The key to store the summarize result at + * @param summarizeResult - The summarize result to be added * @internal */ export function addSummarizeResultToSummary( @@ -250,7 +271,7 @@ export class SummaryTreeBuilder implements ISummaryTreeWithStats { * Adds an {@link @fluidframework/driver-definitions#ISummaryAttachment} to the summary. This blob needs to already be uploaded to storage. * @param id - The id of the uploaded attachment to be added to the summary tree. */ - public addAttachment(id: string) { + public addAttachment(id: string): void { this.summaryTree[this.attachmentCounter++] = { id, type: SummaryType.Attachment }; } @@ -302,8 +323,9 @@ export function convertToSummaryTreeWithStats( break; } - default: + default: { throw new Error("Unexpected TreeEntry type"); + } } } @@ -357,7 +379,7 @@ export function convertSnapshotTreeToSummaryTree( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const content: ArrayBufferLike = snapshot.blobsContents[id]!; if (content !== undefined) { - decoded = bufferToString(content, "utf-8"); + decoded = bufferToString(content, "utf8"); } // 0.44 back-compat We still put contents in same blob for back-compat so need to add blob // only for blobPath -> blobId mapping and not for blobId -> blob value contents. @@ -393,6 +415,7 @@ export function convertSummaryTreeToITree(summaryTree: ISummaryTree): ITree { switch (value.type) { case SummaryType.Blob: { let parsedContent: string; + // eslint-disable-next-line unicorn/text-encoding-identifier-case let encoding: "utf-8" | "base64" = "utf-8"; if (typeof value.content === "string") { parsedContent = value.content; @@ -418,8 +441,9 @@ export function convertSummaryTreeToITree(summaryTree: ISummaryTree): ITree { throw new Error("Should not have Handle type in summary tree"); } - default: + default: { unreachableCase(value, "Unexpected summary tree type"); + } } } return { @@ -443,7 +467,7 @@ export function convertSummaryTreeToITree(summaryTree: ISummaryTree): ITree { * @internal */ export function processAttachMessageGCData( - snapshot: ITree | null, + snapshot: ITree | undefined, addedGCOutboundRoute: (fromNodeId: string, toPath: string) => void, ): boolean { const gcDataEntry = snapshot?.entries.find((e) => e.path === gcDataBlobKey); @@ -455,15 +479,16 @@ export function processAttachMessageGCData( } assert( + // eslint-disable-next-line unicorn/text-encoding-identifier-case gcDataEntry.type === TreeEntry.Blob && gcDataEntry.value.encoding === "utf-8", 0x8ff /* GC data should be a utf-8-encoded blob */, ); const gcData = JSON.parse(gcDataEntry.value.contents) as IGarbageCollectionData; for (const [nodeId, outboundRoutes] of Object.entries(gcData.gcNodes)) { - outboundRoutes.forEach((toPath) => { + for (const toPath of outboundRoutes) { addedGCOutboundRoute(nodeId, toPath); - }); + } } return true; } @@ -507,9 +532,9 @@ export class TelemetryContext implements ITelemetryContext, ITelemetryContextExt */ serialize(): string { const jsonObject = {}; - this.telemetry.forEach((value, key) => { + for (const [key, value] of this.telemetry.entries()) { jsonObject[key] = value; - }); + } return JSON.stringify(jsonObject); } } @@ -519,7 +544,7 @@ export class TelemetryContext implements ITelemetryContext, ITelemetryContextExt * @param str - A string that may contain leading slashes. * @returns A new string without leading slashes. */ -function trimLeadingSlashes(str: string) { +function trimLeadingSlashes(str: string): string { return str.replace(/^\/+/g, ""); } @@ -528,7 +553,7 @@ function trimLeadingSlashes(str: string) { * @param str - A string that may contain trailing slashes. * @returns A new string without trailing slashes. */ -function trimTrailingSlashes(str: string) { +function trimTrailingSlashes(str: string): string { return str.replace(/\/+$/g, ""); } @@ -546,7 +571,7 @@ export class GCDataBuilder implements IGarbageCollectionData { return gcNodes; } - public addNode(id: string, outboundRoutes: string[]) { + public addNode(id: string, outboundRoutes: string[]): void { this.gcNodesSet[id] = new Set(outboundRoutes); } @@ -556,7 +581,7 @@ export class GCDataBuilder implements IGarbageCollectionData { * - Prefixes the given `prefixId` to the given nodes' ids. * - Adds the outbound routes of the nodes against the normalized and prefixed id. */ - public prefixAndAddNodes(prefixId: string, gcNodes: { [id: string]: string[] }) { + public prefixAndAddNodes(prefixId: string, gcNodes: { [id: string]: string[] }): void { for (const [id, outboundRoutes] of Object.entries(gcNodes)) { // Remove any leading slashes from the id. let normalizedId = trimLeadingSlashes(id); @@ -571,7 +596,7 @@ export class GCDataBuilder implements IGarbageCollectionData { } } - public addNodes(gcNodes: { [id: string]: string[] }) { + public addNodes(gcNodes: { [id: string]: string[] }): void { for (const [id, outboundRoutes] of Object.entries(gcNodes)) { this.gcNodesSet[id] = new Set(outboundRoutes); } @@ -580,7 +605,7 @@ export class GCDataBuilder implements IGarbageCollectionData { /** * Adds the given outbound route to the outbound routes of all GC nodes. */ - public addRouteToAllNodes(outboundRoute: string) { + public addRouteToAllNodes(outboundRoute: string): void { for (const outboundRoutes of Object.values(this.gcNodesSet)) { outboundRoutes.add(outboundRoute); } diff --git a/packages/runtime/runtime-utils/src/test/dataStoreHelpers.spec.ts b/packages/runtime/runtime-utils/src/test/dataStoreHelpers.spec.ts index e3627b21bb37..ee65dfd2ed91 100644 --- a/packages/runtime/runtime-utils/src/test/dataStoreHelpers.spec.ts +++ b/packages/runtime/runtime-utils/src/test/dataStoreHelpers.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { createResponseError, diff --git a/packages/runtime/runtime-utils/src/test/handles.spec.ts b/packages/runtime/runtime-utils/src/test/handles.spec.ts index 37b3f9a7d1b5..8c3855105e59 100644 --- a/packages/runtime/runtime-utils/src/test/handles.spec.ts +++ b/packages/runtime/runtime-utils/src/test/handles.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { fluidHandleSymbol } from "@fluidframework/core-interfaces"; @@ -15,6 +15,7 @@ describe("Handles", () => { assert(!isFluidHandle(0)); assert(!isFluidHandle({})); assert(!isFluidHandle(undefined)); + // eslint-disable-next-line unicorn/no-null assert(!isFluidHandle(null)); assert(!isFluidHandle([])); assert(!isFluidHandle({ get: () => {} })); @@ -26,6 +27,7 @@ describe("Handles", () => { assert(isFluidHandle(loopy)); assert(!isFluidHandle({ IFluidHandle: 5 })); assert(!isFluidHandle({ IFluidHandle: {} })); + // eslint-disable-next-line unicorn/no-null assert(!isFluidHandle({ IFluidHandle: null })); // Symbol based: diff --git a/packages/runtime/runtime-utils/src/test/requestParser.spec.ts b/packages/runtime/runtime-utils/src/test/requestParser.spec.ts index b60fb719654f..5f50ccf39bf4 100644 --- a/packages/runtime/runtime-utils/src/test/requestParser.spec.ts +++ b/packages/runtime/runtime-utils/src/test/requestParser.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { RequestParser } from "../requestParser.js"; @@ -89,7 +89,7 @@ describe("RequestParser", () => { }); }); - const testSubRequest = function (uri: string) { + const testSubRequest = function (uri: string): void { describe(".createSubRequest with query params", () => { let requestParser2: RequestParser; beforeEach(() => { diff --git a/packages/runtime/runtime-utils/src/test/summaryUtils.spec.ts b/packages/runtime/runtime-utils/src/test/summaryUtils.spec.ts index 17527dcdbd03..6f94b2386147 100644 --- a/packages/runtime/runtime-utils/src/test/summaryUtils.spec.ts +++ b/packages/runtime/runtime-utils/src/test/summaryUtils.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { IsoBuffer, Uint8ArrayToString, stringToBuffer } from "@fluid-internal/client-utils"; import { @@ -380,7 +380,7 @@ describe("Summary Utils", () => { const serialized = telemetryContext.serialize(); - const obj = JSON.parse(serialized); + const obj = JSON.parse(serialized) as Record; assert.strictEqual(obj.pre1_prop1, 10); assert.strictEqual(obj.pre1_prop2, undefined); diff --git a/packages/runtime/runtime-utils/src/test/utils.spec.ts b/packages/runtime/runtime-utils/src/test/utils.spec.ts index 65b2cddb3ad9..c1f061e5cadb 100644 --- a/packages/runtime/runtime-utils/src/test/utils.spec.ts +++ b/packages/runtime/runtime-utils/src/test/utils.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; +import { strict as assert } from "node:assert"; import { encodeCompactIdToString } from "../utils.js"; diff --git a/packages/runtime/runtime-utils/src/unpackUsedRoutes.ts b/packages/runtime/runtime-utils/src/unpackUsedRoutes.ts index 98cb9a020629..642ace537f45 100644 --- a/packages/runtime/runtime-utils/src/unpackUsedRoutes.ts +++ b/packages/runtime/runtime-utils/src/unpackUsedRoutes.ts @@ -11,7 +11,9 @@ import { assert } from "@fluidframework/core-utils/internal"; * @returns A map of used routes of each children of the the given node. * @internal */ -export function unpackChildNodesUsedRoutes(usedRoutes: readonly string[]) { +export function unpackChildNodesUsedRoutes( + usedRoutes: readonly string[], +): Map { // Remove the node's self used route, if any, and generate the children used routes. const filteredUsedRoutes = usedRoutes.filter((route) => route !== "" && route !== "/"); const childUsedRoutesMap: Map = new Map(); @@ -25,10 +27,10 @@ export function unpackChildNodesUsedRoutes(usedRoutes: readonly string[]) { const childUsedRoute = route.slice(childId.length + 1); const childUsedRoutes = childUsedRoutesMap.get(childId); - if (childUsedRoutes !== undefined) { - childUsedRoutes.push(childUsedRoute); - } else { + if (childUsedRoutes === undefined) { childUsedRoutesMap.set(childId, [childUsedRoute]); + } else { + childUsedRoutes.push(childUsedRoute); } } return childUsedRoutesMap; diff --git a/packages/runtime/runtime-utils/src/utils.ts b/packages/runtime/runtime-utils/src/utils.ts index b46d206b5df0..c81d9829ac93 100644 --- a/packages/runtime/runtime-utils/src/utils.ts +++ b/packages/runtime/runtime-utils/src/utils.ts @@ -47,7 +47,7 @@ export async function seqFromTree( * @returns A string - representation of an input * @internal */ -export function encodeCompactIdToString(idArg: number | string, prefix = "") { +export function encodeCompactIdToString(idArg: number | string, prefix = ""): string { if (typeof idArg === "string") { return idArg; } @@ -75,7 +75,7 @@ export function encodeCompactIdToString(idArg: number | string, prefix = "") { // 100000 -> 'XZf' const encode = num % 64; const base = encode < 27 ? 65 : encode < 54 ? 97 - 27 : 48 - 54; - id = String.fromCharCode(base + encode) + id; + id = String.fromCodePoint(base + encode) + id; num = Math.floor(num / 64) - 1; } while (num !== -1); return prefix + id;