From 2a44a96a206c32c0510e4cf7e2700cd69dc471f1 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Fri, 21 Aug 2026 18:20:50 -0400 Subject: [PATCH 01/20] Trigger CI From 8aa11b36a5f96ad2852789c2c8e05f774fb3e032 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 12 Aug 2026 00:27:35 -0400 Subject: [PATCH 02/20] Dev Console Aliases --- .changeset/dev-console-browser-aliases.md | 5 + packages/functions/package.json | 9 ++ .../functions/src/aliases/browser.test.ts | 143 ++++++++++++++++ packages/functions/src/aliases/browser.ts | 152 ++++++++++++++++++ packages/functions/src/aliases/environment.ts | 6 + packages/functions/src/aliases/loaders.ts | 6 + packages/functions/src/aliases/types.ts | 23 +++ .../functions/src/public/browser-aliases.ts | 31 ++++ 8 files changed, 375 insertions(+) create mode 100644 .changeset/dev-console-browser-aliases.md create mode 100644 packages/functions/src/aliases/browser.test.ts create mode 100644 packages/functions/src/aliases/browser.ts create mode 100644 packages/functions/src/public/browser-aliases.ts diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md new file mode 100644 index 00000000000..4f8ac1d67e3 --- /dev/null +++ b/.changeset/dev-console-browser-aliases.md @@ -0,0 +1,5 @@ +--- +"@osdk/functions": minor +--- + +Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read resolved custom aliases in the browser. Call `await initAliases()` once at startup to fetch and cache the served deployment config, then read values synchronously with `custom("myAlias")`. diff --git a/packages/functions/package.json b/packages/functions/package.json index 7ecaca77a3a..0a43751fc19 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -45,6 +45,15 @@ "require": "./build/cjs/public/unstable-do-not-use.cjs", "default": "./build/browser/public/unstable-do-not-use.js" }, + "./browser-aliases": { + "browser": "./build/browser/public/browser-aliases.js", + "import": { + "types": "./build/types/public/browser-aliases.d.ts", + "default": "./build/esm/public/browser-aliases.js" + }, + "require": "./build/cjs/public/browser-aliases.cjs", + "default": "./build/browser/public/browser-aliases.js" + }, "./*": { "browser": "./build/browser/public/*.js", "import": { diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/functions/src/aliases/browser.test.ts new file mode 100644 index 00000000000..453b7dad78b --- /dev/null +++ b/packages/functions/src/aliases/browser.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { custom, initAliases, resetAliasesCache } from "./browser.js"; + +interface FakeResponseInit { + ok?: boolean; + status?: number; + statusText?: string; + body?: unknown; +} + +function fakeResponse(init: FakeResponseInit): Response { + return { + ok: init.ok ?? true, + status: init.status ?? 200, + statusText: init.statusText ?? "OK", + json: () => Promise.resolve(init.body), + } as unknown as Response; +} + +function mockFetch(init: FakeResponseInit): typeof globalThis.fetch { + return vi.fn(() => + Promise.resolve(fakeResponse(init)), + ) as unknown as typeof fetch; +} + +const CONFIG_WITH_ALIASES = { + clientId: "client-123", + foundryUrl: "https://foundry.example.com", + aliases: JSON.stringify({ + apiBaseUrl: "https://api.prod.internal", + featureXEnabled: "false", + }), +}; + +describe("browser aliases", () => { + afterEach(() => { + resetAliasesCache(); + }); + + describe("custom", () => { + it("returns a resolved alias after init", async () => { + const fetchImpl = mockFetch({ body: CONFIG_WITH_ALIASES }); + await initAliases({ fetch: fetchImpl }); + + expect(custom("apiBaseUrl")).toBe("https://api.prod.internal"); + expect(custom("featureXEnabled")).toBe("false"); + }); + + it("throws before init", () => { + expect(() => custom("apiBaseUrl")).toThrow( + "Aliases have not been initialized", + ); + }); + + it("throws on unknown alias with available list", async () => { + await initAliases({ fetch: mockFetch({ body: CONFIG_WITH_ALIASES }) }); + + expect(() => custom("nonexistent")).toThrow( + "Custom alias 'nonexistent' not found. Available aliases: " + + "[apiBaseUrl, featureXEnabled]", + ); + }); + + it("treats a config with no aliases key as empty", async () => { + await initAliases({ + fetch: mockFetch({ body: { clientId: "client-123" } }), + }); + + expect(() => custom("anything")).toThrow( + "Custom alias 'anything' not found. Available aliases: []", + ); + }); + }); + + describe("initAliases", () => { + it("fetches only once across repeated calls", async () => { + const fetchImpl = mockFetch({ body: CONFIG_WITH_ALIASES }); + await initAliases({ fetch: fetchImpl }); + await initAliases({ fetch: fetchImpl }); + custom("apiBaseUrl"); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + it("deduplicates concurrent calls into a single fetch", async () => { + const fetchImpl = mockFetch({ body: CONFIG_WITH_ALIASES }); + await Promise.all([ + initAliases({ fetch: fetchImpl }), + initAliases({ fetch: fetchImpl }), + initAliases({ fetch: fetchImpl }), + ]); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + }); + + it("re-fetches after resetAliasesCache", async () => { + const fetchImpl = mockFetch({ body: CONFIG_WITH_ALIASES }); + await initAliases({ fetch: fetchImpl }); + resetAliasesCache(); + await initAliases({ fetch: fetchImpl }); + + expect(fetchImpl).toHaveBeenCalledTimes(2); + }); + + it("throws and allows retry on a non-ok response", async () => { + const failing = mockFetch({ + ok: false, + status: 404, + statusText: "Not Found", + }); + await expect(initAliases({ fetch: failing })).rejects.toThrow( + "Failed to load aliases", + ); + + // A subsequent successful init should work (in-flight was cleared). + await initAliases({ fetch: mockFetch({ body: CONFIG_WITH_ALIASES }) }); + expect(custom("apiBaseUrl")).toBe("https://api.prod.internal"); + }); + + it("throws when the aliases blob is not valid JSON", async () => { + await expect( + initAliases({ fetch: mockFetch({ body: { aliases: "{not json" } }) }), + ).rejects.toThrow("Failed to parse resolved aliases"); + }); + }); +}); diff --git a/packages/functions/src/aliases/browser.ts b/packages/functions/src/aliases/browser.ts new file mode 100644 index 00000000000..19693cdbe10 --- /dev/null +++ b/packages/functions/src/aliases/browser.ts @@ -0,0 +1,152 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Browser-safe alias runtime for Dev Console applications. +// +// Unlike the Node loaders (which read a file from the container filesystem via +// fs), a browser has no filesystem or process.env. Instead the platform writes +// the installer's resolved values into the served deployment config file, and +// this module fetches that file once, caches it, and then serves custom() +// synchronously. This file must stay free of `fs`/`process` so it can be +// bundled into a browser app. + +import type { Custom, DeploymentConfig } from "./types.js"; + +export type { Custom } from "./types.js"; + +/** + * Default path to the deployment config file served by Foundry website + * hosting. Resolved relative to the document base URI so apps served under a + * subpath still find it. + */ +export const DEFAULT_DEPLOYMENT_CONFIG_PATH = + ".palantir/deployment.config.json"; + +export interface InitAliasesOptions { + /** + * Path or URL to the deployment config file. Relative paths are resolved + * against `document.baseURI`. Defaults to + * {@link DEFAULT_DEPLOYMENT_CONFIG_PATH}. + */ + path?: string; + /** + * Custom fetch implementation. Defaults to the global `fetch`. Useful for + * testing or non-standard hosting. + */ + fetch?: typeof globalThis.fetch; +} + +let cachedCustomAliases: Record | undefined; +let inFlight: Promise | undefined; + +/** + * Fetches and caches the resolved aliases for this installation. Call once at + * application startup and await it before reading any aliases. Repeated calls + * are deduplicated and become no-ops once the aliases are cached. + */ +export async function initAliases(options?: InitAliasesOptions): Promise { + if (cachedCustomAliases !== undefined) { + return; + } + if (inFlight === undefined) { + inFlight = loadAliases(options).catch((error: unknown) => { + // Clear the in-flight promise so a failed load can be retried. + inFlight = undefined; + throw error; + }); + } + await inFlight; +} + +async function loadAliases(options?: InitAliasesOptions): Promise { + const fetchImpl = options?.fetch ?? globalThis.fetch; + if (typeof fetchImpl !== "function") { + throw new TypeError( + "No fetch implementation available to load aliases. Pass one via " + + "initAliases({ fetch }).", + ); + } + + const url = resolveUrl(options?.path ?? DEFAULT_DEPLOYMENT_CONFIG_PATH); + const response = await fetchImpl(url); + if (!response.ok) { + throw new Error( + `Failed to load aliases from ${url}: ${response.status} ${ + response.statusText + }`, + ); + } + + const config = (await response.json()) as DeploymentConfig; + cachedCustomAliases = parseAliases(config.aliases); +} + +function resolveUrl(path: string): string { + if (typeof document !== "undefined" && document.baseURI) { + return new URL(path, document.baseURI).toString(); + } + return path; +} + +function parseAliases(raw: string | undefined): Record { + // Apps with no aliases declared simply omit the key. + if (raw == null || raw === "") { + return {}; + } + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (error) { + throw new Error( + `Failed to parse resolved aliases: ${(error as Error).message}`, + ); + } + if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) { + throw new Error("Resolved aliases must be a JSON object of string values."); + } + return parsed as Record; +} + +/** + * Returns the resolved value for a custom alias. Aliases must have been loaded + * via {@link initAliases} first; otherwise this throws. + */ +export function custom(alias: string): Custom { + if (cachedCustomAliases === undefined) { + throw new Error( + "Aliases have not been initialized. Call `await initAliases()` before " + + "reading aliases.", + ); + } + if (!(alias in cachedCustomAliases)) { + const available = Object.keys(cachedCustomAliases); + throw new Error( + `Custom alias '${alias}' not found. Available aliases: [${available.join( + ", ", + )}]`, + ); + } + return cachedCustomAliases[alias] as Custom; +} + +/** + * Clears the cached aliases. Primarily for tests; production code should not + * need to reset the cache. + */ +export function resetAliasesCache(): void { + cachedCustomAliases = undefined; + inFlight = undefined; +} diff --git a/packages/functions/src/aliases/environment.ts b/packages/functions/src/aliases/environment.ts index 50163000179..54c8c67caa8 100644 --- a/packages/functions/src/aliases/environment.ts +++ b/packages/functions/src/aliases/environment.ts @@ -20,6 +20,12 @@ export const ALIASES_JSON_FILE_ENV_VAR = "ALIASES_JSON_FILE"; export const RESOURCES_JSON_FILE_ENV_VAR = "RESOURCES_JSON_FILE"; export function detectEnvironment(): AliasEnvironment { + // Dev Console apps run in a browser, where there is no process.env to read. + // Detect that first so we never touch process in a browser context. + if (typeof document !== "undefined") { + return AliasEnvironment.BROWSER; + } + const aliasesFileSet = ALIASES_JSON_FILE_ENV_VAR in process.env; const resourcesFileSet = RESOURCES_JSON_FILE_ENV_VAR in process.env; diff --git a/packages/functions/src/aliases/loaders.ts b/packages/functions/src/aliases/loaders.ts index abc8fc350ac..d76884a9ce8 100644 --- a/packages/functions/src/aliases/loaders.ts +++ b/packages/functions/src/aliases/loaders.ts @@ -218,5 +218,11 @@ export function loadResolvedAliases(): ResolvedAliases { return loadPublishedAliases(); case AliasEnvironment.LIVE_PREVIEW: return loadPreviewAliases(); + case AliasEnvironment.BROWSER: + throw new Error( + "Browser alias environment detected. This filesystem-based loader " + + "cannot run in a browser. Import from '@osdk/functions/browser-aliases' " + + "and call `await initAliases()` before reading aliases instead.", + ); } } diff --git a/packages/functions/src/aliases/types.ts b/packages/functions/src/aliases/types.ts index 9aaf66866a3..e4c4fb4236c 100644 --- a/packages/functions/src/aliases/types.ts +++ b/packages/functions/src/aliases/types.ts @@ -52,6 +52,29 @@ export interface ResolvedAliases { export enum AliasEnvironment { PUBLISHED = "PUBLISHED", LIVE_PREVIEW = "LIVE_PREVIEW", + // Dev Console applications run in the browser, where there is no filesystem + // or process.env. Resolved aliases are fetched from the served deployment + // config file instead. Use the "@osdk/functions/browser-aliases" subpath. + BROWSER = "BROWSER", +} + +// Browser mode types (deployment.config.json) + +/** + * Shape of the deployment config file that Foundry website hosting serves at + * {@link ../public/browser-aliases}'s default path. It is a flat map of + * strings; resolved custom aliases are packed under `aliases` as a stringified + * JSON object (a `Record`) so they cannot collide with the + * reserved system keys. + */ +export interface DeploymentConfig { + clientId?: string; + redirectUrl?: string; + foundryUrl?: string; + ontologyRid?: string; + ontologyApiName?: string; + /** Stringified JSON `Record` of resolved custom alias values. */ + aliases?: string; } // Live preview mode types (resources.json) diff --git a/packages/functions/src/public/browser-aliases.ts b/packages/functions/src/public/browser-aliases.ts new file mode 100644 index 00000000000..a6adf849370 --- /dev/null +++ b/packages/functions/src/public/browser-aliases.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Public entry point for reading aliases from a browser (Dev Console apps). +// This subpath is intentionally free of `fs`/`process` so it can be bundled +// into a browser application. Usage: +// +// import { initAliases, custom } from "@osdk/functions/browser-aliases"; +// await initAliases(); +// const apiBaseUrl = custom("apiBaseUrl"); + +export { + custom, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, + resetAliasesCache, +} from "../aliases/browser.js"; +export type { Custom, InitAliasesOptions } from "../aliases/browser.js"; From 5eff4b2962acb41e62b210c7d9d6568e11031f24 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 12 Aug 2026 12:59:17 -0400 Subject: [PATCH 03/20] fix --- packages/functions/package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/functions/package.json b/packages/functions/package.json index 0a43751fc19..fe65cd3dad2 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -18,6 +18,15 @@ "require": "./build/cjs/index.cjs", "default": "./build/browser/index.js" }, + "./browser-aliases": { + "browser": "./build/browser/public/browser-aliases.js", + "import": { + "types": "./build/types/public/browser-aliases.d.ts", + "default": "./build/esm/public/browser-aliases.js" + }, + "require": "./build/cjs/public/browser-aliases.cjs", + "default": "./build/browser/public/browser-aliases.js" + }, "./experimental": { "browser": "./build/browser/public/experimental.js", "import": { @@ -45,15 +54,6 @@ "require": "./build/cjs/public/unstable-do-not-use.cjs", "default": "./build/browser/public/unstable-do-not-use.js" }, - "./browser-aliases": { - "browser": "./build/browser/public/browser-aliases.js", - "import": { - "types": "./build/types/public/browser-aliases.d.ts", - "default": "./build/esm/public/browser-aliases.js" - }, - "require": "./build/cjs/public/browser-aliases.cjs", - "default": "./build/browser/public/browser-aliases.js" - }, "./*": { "browser": "./build/browser/public/*.js", "import": { From b7f9c9a7879eefd7c6c10d6e303884d23eab027a Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 12 Aug 2026 13:54:38 -0400 Subject: [PATCH 04/20] fix --- packages/functions/browser-aliases.d.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/functions/browser-aliases.d.ts diff --git a/packages/functions/browser-aliases.d.ts b/packages/functions/browser-aliases.d.ts new file mode 100644 index 00000000000..b2270b1fd37 --- /dev/null +++ b/packages/functions/browser-aliases.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from "./build/cjs/public/browser-aliases.cjs"; From d345a82a5af6f242afd4611afabcd8cf2e32aede Mon Sep 17 00:00:00 2001 From: James Zhang Date: Mon, 17 Aug 2026 13:44:54 -0400 Subject: [PATCH 05/20] Read dev aliases from public/resources.json when no deployment config exists --- .changeset/dev-console-browser-aliases.md | 2 +- .../functions/src/aliases/browser.test.ts | 104 ++++++++++++++++- packages/functions/src/aliases/browser.ts | 105 +++++++++++++++--- packages/functions/src/aliases/types.ts | 18 +++ .../functions/src/public/browser-aliases.ts | 15 ++- 5 files changed, 223 insertions(+), 21 deletions(-) diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index 4f8ac1d67e3..67b06c8aef0 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -2,4 +2,4 @@ "@osdk/functions": minor --- -Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read resolved custom aliases in the browser. Call `await initAliases()` once at startup to fetch and cache the served deployment config, then read values synchronously with `custom("myAlias")`. +Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read resolved custom aliases in the browser. Call `await initAliases()` once at startup to fetch and cache the config, then read values synchronously with `custom("myAlias")`. Pass `path: DEFAULT_DECLARATIONS_PATH` during local development to read the authored defaults from `public/resources.json`, since the installed deployment config only exists on a hosted site. diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/functions/src/aliases/browser.test.ts index 453b7dad78b..c794df96aa8 100644 --- a/packages/functions/src/aliases/browser.test.ts +++ b/packages/functions/src/aliases/browser.test.ts @@ -16,7 +16,13 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { custom, initAliases, resetAliasesCache } from "./browser.js"; +import { + custom, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, + resetAliasesCache, +} from "./browser.js"; interface FakeResponseInit { ok?: boolean; @@ -49,6 +55,20 @@ const CONFIG_WITH_ALIASES = { }), }; +// The author-maintained declaration file, as served from public/ in dev. +const DECLARATIONS_FILE = { + aliases: { + custom: { + apiBaseUrl: { + value: "https://api.dev.example.com", + description: "Base URL for the partner API", + required: true, + }, + featureXEnabled: { value: "false" }, + }, + }, +}; + describe("browser aliases", () => { afterEach(() => { resetAliasesCache(); @@ -139,5 +159,87 @@ describe("browser aliases", () => { initAliases({ fetch: mockFetch({ body: { aliases: "{not json" } }) }), ).rejects.toThrow("Failed to parse resolved aliases"); }); + + it("defaults to the deployment config path", async () => { + const fetchImpl = mockFetch({ body: CONFIG_WITH_ALIASES }); + await initAliases({ fetch: fetchImpl }); + + expect(fetchImpl).toHaveBeenCalledTimes(1); + expect(String(vi.mocked(fetchImpl).mock.calls[0][0])).toContain( + DEFAULT_DEPLOYMENT_CONFIG_PATH, + ); + }); + + it("fetches the path it is given", async () => { + const fetchImpl = mockFetch({ body: DECLARATIONS_FILE }); + await initAliases({ fetch: fetchImpl, path: DEFAULT_DECLARATIONS_PATH }); + + expect(String(vi.mocked(fetchImpl).mock.calls[0][0])).toContain( + DEFAULT_DECLARATIONS_PATH, + ); + }); + }); + + // Dev mode: the declaration file nests values under aliases.custom, so the + // loader has to flatten it. Prod and dev are told apart by the runtime type of + // `aliases` (string vs object), never by falling back between paths. + describe("declaration file (dev) shape", () => { + it("flattens declared defaults", async () => { + await initAliases({ + fetch: mockFetch({ body: DECLARATIONS_FILE }), + path: DEFAULT_DECLARATIONS_PATH, + }); + + expect(custom("apiBaseUrl")).toBe("https://api.dev.example.com"); + expect(custom("featureXEnabled")).toBe("false"); + }); + + it("ignores description and required metadata", async () => { + await initAliases({ + fetch: mockFetch({ body: DECLARATIONS_FILE }), + path: DEFAULT_DECLARATIONS_PATH, + }); + + // Metadata is for packaging, not the browser: only values come through. + expect(custom("apiBaseUrl")).not.toContain("Base URL"); + }); + + it("treats an alias with no value as empty rather than throwing", async () => { + await initAliases({ + fetch: mockFetch({ + body: { aliases: { custom: { needsValue: {} } } }, + }), + path: DEFAULT_DECLARATIONS_PATH, + }); + + expect(custom("needsValue")).toBe(""); + }); + + it("treats an empty custom block as no aliases", async () => { + await initAliases({ + fetch: mockFetch({ body: { aliases: { custom: {} } } }), + path: DEFAULT_DECLARATIONS_PATH, + }); + + expect(() => custom("anything")).toThrow("Available aliases: []"); + }); + + it("treats a file with no custom block as no aliases", async () => { + await initAliases({ + fetch: mockFetch({ body: { aliases: {} } }), + path: DEFAULT_DECLARATIONS_PATH, + }); + + expect(() => custom("anything")).toThrow("Available aliases: []"); + }); + + it("throws when aliases.custom is not an object", async () => { + await expect( + initAliases({ + fetch: mockFetch({ body: { aliases: { custom: ["nope"] } } }), + path: DEFAULT_DECLARATIONS_PATH, + }), + ).rejects.toThrow("'aliases.custom' must be an object"); + }); }); }); diff --git a/packages/functions/src/aliases/browser.ts b/packages/functions/src/aliases/browser.ts index 19693cdbe10..c305b62b39b 100644 --- a/packages/functions/src/aliases/browser.ts +++ b/packages/functions/src/aliases/browser.ts @@ -17,29 +17,63 @@ // Browser-safe alias runtime for Dev Console applications. // // Unlike the Node loaders (which read a file from the container filesystem via -// fs), a browser has no filesystem or process.env. Instead the platform writes -// the installer's resolved values into the served deployment config file, and -// this module fetches that file once, caches it, and then serves custom() -// synchronously. This file must stay free of `fs`/`process` so it can be -// bundled into a browser app. +// fs), a browser has no filesystem or process.env. Instead this module fetches a +// served JSON file once, caches it, and then serves custom() synchronously. This +// file must stay free of `fs`/`process` so it can be bundled into a browser app. +// +// Two files can supply aliases, mirroring how the Node runtime has PUBLISHED and +// LIVE_PREVIEW modes: +// +// production .palantir/deployment.config.json written at install, so it +// carries the INSTALLER's values +// development public/resources.json the author's declaration file, +// so it carries the DEFAULTS +// +// The caller chooses the path (see InitAliasesOptions.path), because only the +// application knows whether it is running a dev server. We deliberately do NOT +// fall back from one path to the other: in production BOTH files are served, so +// a fallback would silently serve the developer's defaults instead of the +// installer's values. +// +// The two files are told apart by the runtime type of their `aliases` field +// (string vs object), which is unambiguous, so callers only need to pass a path. -import type { Custom, DeploymentConfig } from "./types.js"; +import type { + AliasDeclarationsFile, + Custom, + DeploymentConfig, +} from "./types.js"; export type { Custom } from "./types.js"; /** * Default path to the deployment config file served by Foundry website * hosting. Resolved relative to the document base URI so apps served under a - * subpath still find it. + * subpath still find it. Carries the installer's resolved values. */ export const DEFAULT_DEPLOYMENT_CONFIG_PATH = ".palantir/deployment.config.json"; +/** + * Path to the author-maintained declaration file, served from `public/` by the + * Vite dev server. Carries the developer's declared defaults, so it is the + * right source during local development where there is no installer. + */ +export const DEFAULT_DECLARATIONS_PATH = "resources.json"; + export interface InitAliasesOptions { /** - * Path or URL to the deployment config file. Relative paths are resolved - * against `document.baseURI`. Defaults to - * {@link DEFAULT_DEPLOYMENT_CONFIG_PATH}. + * Path or URL to fetch aliases from. Relative paths are resolved against + * `document.baseURI`. Defaults to {@link DEFAULT_DEPLOYMENT_CONFIG_PATH}. + * + * During local development, pass {@link DEFAULT_DECLARATIONS_PATH} instead, + * since the deployment config file only exists on an installed site: + * + * ```ts + * await initAliases({ + * path: import.meta.env.DEV ? DEFAULT_DECLARATIONS_PATH : undefined, + * }); + * ``` */ path?: string; /** @@ -90,8 +124,49 @@ async function loadAliases(options?: InitAliasesOptions): Promise { ); } - const config = (await response.json()) as DeploymentConfig; - cachedCustomAliases = parseAliases(config.aliases); + const config = (await response.json()) as + | DeploymentConfig + | AliasDeclarationsFile; + cachedCustomAliases = extractAliases(config, url); +} + +/** + * Reads aliases out of either supported file shape. The deployment config packs + * resolved values into a stringified JSON object; the declaration file nests + * them under `aliases.custom` with packaging metadata alongside each value. + */ +function extractAliases( + config: DeploymentConfig | AliasDeclarationsFile, + url: string, +): Record { + const aliases = config.aliases; + + // Apps that declare no aliases simply omit the key. + if (aliases == null || aliases === "") { + return {}; + } + + // Production: deployment.config.json stores a stringified JSON object. + if (typeof aliases === "string") { + return parseResolvedAliases(aliases); + } + + // Development: the declaration file nests { custom: { key: { value } } }. + const declarations = aliases.custom; + if (declarations == null) { + return {}; + } + if (typeof declarations !== "object" || Array.isArray(declarations)) { + throw new TypeError( + `Failed to read aliases from ${url}: 'aliases.custom' must be an object.`, + ); + } + return Object.fromEntries( + Object.entries(declarations).map(([key, declaration]) => [ + key, + declaration?.value ?? "", + ]), + ); } function resolveUrl(path: string): string { @@ -101,11 +176,7 @@ function resolveUrl(path: string): string { return path; } -function parseAliases(raw: string | undefined): Record { - // Apps with no aliases declared simply omit the key. - if (raw == null || raw === "") { - return {}; - } +function parseResolvedAliases(raw: string): Record { let parsed: unknown; try { parsed = JSON.parse(raw); diff --git a/packages/functions/src/aliases/types.ts b/packages/functions/src/aliases/types.ts index e4c4fb4236c..34f3c2d1a7f 100644 --- a/packages/functions/src/aliases/types.ts +++ b/packages/functions/src/aliases/types.ts @@ -66,6 +66,9 @@ export enum AliasEnvironment { * strings; resolved custom aliases are packed under `aliases` as a stringified * JSON object (a `Record`) so they cannot collide with the * reserved system keys. + * + * This is the PRODUCTION shape, written at Marketplace install time and + * therefore carrying the installer's resolved values. */ export interface DeploymentConfig { clientId?: string; @@ -77,6 +80,21 @@ export interface DeploymentConfig { aliases?: string; } +/** + * Shape of the author-maintained declaration file (`public/resources.json`). + * This is the DEVELOPMENT shape: there is no installer locally, so the values + * here are the developer's declared defaults. `description` and `required` are + * consumed at packaging time and are irrelevant to the browser runtime. + */ +export interface AliasDeclarationsFile { + aliases?: { + custom?: Record< + string, + { value?: string; description?: string; required?: boolean } + >; + }; +} + // Live preview mode types (resources.json) export interface ModelResource { diff --git a/packages/functions/src/public/browser-aliases.ts b/packages/functions/src/public/browser-aliases.ts index a6adf849370..73750170ecd 100644 --- a/packages/functions/src/public/browser-aliases.ts +++ b/packages/functions/src/public/browser-aliases.ts @@ -18,12 +18,23 @@ // This subpath is intentionally free of `fs`/`process` so it can be bundled // into a browser application. Usage: // -// import { initAliases, custom } from "@osdk/functions/browser-aliases"; -// await initAliases(); +// import { +// custom, +// DEFAULT_DECLARATIONS_PATH, +// initAliases, +// } from "@osdk/functions/browser-aliases"; +// +// // Prod reads the installer's resolved values; dev reads the authored +// // defaults, since the deployment config only exists on an installed site. +// await initAliases({ +// path: import.meta.env.DEV ? DEFAULT_DECLARATIONS_PATH : undefined, +// }); +// // const apiBaseUrl = custom("apiBaseUrl"); export { custom, + DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, initAliases, resetAliasesCache, From 17037a9f12b2e8a1d2fe087951fcf1b169760c1c Mon Sep 17 00:00:00 2001 From: James Zhang Date: Mon, 17 Aug 2026 17:08:33 -0400 Subject: [PATCH 06/20] splitting this pr into 2 --- .changeset/dev-console-browser-aliases.md | 2 +- .../functions/src/aliases/browser.test.ts | 96 ++++++++++++++++++- packages/functions/src/aliases/browser.ts | 84 ++++++++++++---- .../functions/src/public/browser-aliases.ts | 14 +-- 4 files changed, 164 insertions(+), 32 deletions(-) diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index 67b06c8aef0..bba3b55a32a 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -2,4 +2,4 @@ "@osdk/functions": minor --- -Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read resolved custom aliases in the browser. Call `await initAliases()` once at startup to fetch and cache the config, then read values synchronously with `custom("myAlias")`. Pass `path: DEFAULT_DECLARATIONS_PATH` during local development to read the authored defaults from `public/resources.json`, since the installed deployment config only exists on a hosted site. +Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read custom aliases in the browser. Call `await initAliases()` once at startup, then read values synchronously with `custom("myAlias")`. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only on a 404, so a transient server error surfaces instead of silently substituting defaults for the installer's values. diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/functions/src/aliases/browser.test.ts index c794df96aa8..a781e1eade5 100644 --- a/packages/functions/src/aliases/browser.test.ts +++ b/packages/functions/src/aliases/browser.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { custom, @@ -46,6 +46,26 @@ function mockFetch(init: FakeResponseInit): typeof globalThis.fetch { ) as unknown as typeof fetch; } +/** + * Serves a different response per path, so the 404 fallback can be exercised. + * Any path not listed responds 404. + */ +function mockFetchByPath( + responses: Record, +): typeof globalThis.fetch { + return vi.fn((input: unknown) => { + const url = String(input); + const match = Object.entries(responses).find(([path]) => + url.endsWith(path), + ); + return Promise.resolve( + fakeResponse( + match?.[1] ?? { ok: false, status: 404, statusText: "Not Found" }, + ), + ); + }) as unknown as typeof fetch; +} + const CONFIG_WITH_ALIASES = { clientId: "client-123", foundryUrl: "https://foundry.example.com", @@ -140,10 +160,12 @@ describe("browser aliases", () => { }); it("throws and allows retry on a non-ok response", async () => { + // A 500 rather than a 404: a 404 means "absent" and triggers the fallback, + // while a server error is a genuine failure that should surface. const failing = mockFetch({ ok: false, - status: 404, - statusText: "Not Found", + status: 500, + statusText: "Internal Server Error", }); await expect(initAliases({ fetch: failing })).rejects.toThrow( "Failed to load aliases", @@ -178,6 +200,74 @@ describe("browser aliases", () => { DEFAULT_DECLARATIONS_PATH, ); }); + + it("throws rather than falling back when given an explicit path", async () => { + // An explicit path is a deliberate choice, so a missing file is an error. + await expect( + initAliases({ fetch: mockFetchByPath({}), path: "custom/place.json" }), + ).rejects.toThrow("404"); + }); + }); + + // No environment detection: the deployment config only exists on an installed + // site, so a 404 (and only a 404) means "use the author's declared defaults". + describe("fallback when the deployment config is absent", () => { + let warn: ReturnType; + + beforeEach(() => { + warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + warn.mockRestore(); + }); + + it("uses declared defaults when the deployment config 404s", async () => { + await initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }); + + expect(custom("apiBaseUrl")).toBe("https://api.dev.example.com"); + expect(warn).toHaveBeenCalledOnce(); + }); + + it("prefers the deployment config when both files exist", async () => { + // Both are served in production, so the installer's values must win. + await initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { body: CONFIG_WITH_ALIASES }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }); + + expect(custom("apiBaseUrl")).toBe("https://api.prod.internal"); + expect(warn).not.toHaveBeenCalled(); + }); + + it("throws on a server error instead of degrading to defaults", async () => { + // The dangerous case: a transient failure must not silently swap the + // installer's values for the developer's defaults. + await expect( + initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { + ok: false, + status: 500, + statusText: "Internal Server Error", + }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }), + ).rejects.toThrow("Failed to load aliases"); + }); + + it("treats both files missing as no aliases rather than an error", async () => { + await initAliases({ fetch: mockFetchByPath({}) }); + + expect(() => custom("anything")).toThrow("Available aliases: []"); + }); }); // Dev mode: the declaration file nests values under aliases.custom, so the diff --git a/packages/functions/src/aliases/browser.ts b/packages/functions/src/aliases/browser.ts index c305b62b39b..a7902806b6f 100644 --- a/packages/functions/src/aliases/browser.ts +++ b/packages/functions/src/aliases/browser.ts @@ -29,14 +29,15 @@ // development public/resources.json the author's declaration file, // so it carries the DEFAULTS // -// The caller chooses the path (see InitAliasesOptions.path), because only the -// application knows whether it is running a dev server. We deliberately do NOT -// fall back from one path to the other: in production BOTH files are served, so -// a fallback would silently serve the developer's defaults instead of the -// installer's values. +// Callers do not choose: we try the deployment config and fall back to the +// declaration file ONLY on a 404. A 404 on a same-origin static path means the +// file genuinely is not there (local dev, or a site deployed without going +// through Marketplace). Any other failure throws, because in production BOTH +// files are served, so falling back on a transient error would silently serve +// the developer's defaults in place of the installer's values. // // The two files are told apart by the runtime type of their `aliases` field -// (string vs object), which is unambiguous, so callers only need to pass a path. +// (string vs object), which is unambiguous. import type { AliasDeclarationsFile, @@ -63,17 +64,14 @@ export const DEFAULT_DECLARATIONS_PATH = "resources.json"; export interface InitAliasesOptions { /** - * Path or URL to fetch aliases from. Relative paths are resolved against - * `document.baseURI`. Defaults to {@link DEFAULT_DEPLOYMENT_CONFIG_PATH}. + * Escape hatch to force a single specific file. Relative paths are resolved + * against `document.baseURI`. * - * During local development, pass {@link DEFAULT_DECLARATIONS_PATH} instead, - * since the deployment config file only exists on an installed site: - * - * ```ts - * await initAliases({ - * path: import.meta.env.DEV ? DEFAULT_DECLARATIONS_PATH : undefined, - * }); - * ``` + * Normal applications should omit this. By default `initAliases()` tries + * {@link DEFAULT_DEPLOYMENT_CONFIG_PATH} and falls back to + * {@link DEFAULT_DECLARATIONS_PATH} on a 404, which covers local development + * and installed sites alike. When this option is set there is no fallback: a + * missing file throws. */ path?: string; /** @@ -114,8 +112,57 @@ async function loadAliases(options?: InitAliasesOptions): Promise { ); } - const url = resolveUrl(options?.path ?? DEFAULT_DEPLOYMENT_CONFIG_PATH); + // An explicit path is honored as given: the caller chose that file, so a + // missing file is an error rather than a cue to look somewhere else. + if (options?.path != null) { + const explicit = await fetchAliases(fetchImpl, options.path); + if (explicit === undefined) { + throw new Error( + `Failed to load aliases from ${resolveUrl(options.path)}: 404`, + ); + } + cachedCustomAliases = explicit; + return; + } + + const resolved = await fetchAliases( + fetchImpl, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + ); + if (resolved !== undefined) { + cachedCustomAliases = resolved; + return; + } + + // The deployment config only exists on a site installed through Marketplace, + // so a 404 is expected during local development and for a site deployed + // straight from Developer Console. Fall back to the author's declared + // defaults. Warn so that a fallback on a real installed site is visible. + console.warn( + `No alias config at ${resolveUrl(DEFAULT_DEPLOYMENT_CONFIG_PATH)}, ` + + `falling back to declared defaults in ${resolveUrl( + DEFAULT_DECLARATIONS_PATH, + )}. ` + + "This is expected during local development.", + ); + cachedCustomAliases = + (await fetchAliases(fetchImpl, DEFAULT_DECLARATIONS_PATH)) ?? {}; +} + +/** + * Fetches and reads one alias file. Returns `undefined` when the file is absent + * (404), which callers may treat as a cue to fall back. Any other failure throws, + * so a transient server error never silently degrades to the declared defaults. + */ +async function fetchAliases( + fetchImpl: typeof globalThis.fetch, + path: string, +): Promise | undefined> { + const url = resolveUrl(path); const response = await fetchImpl(url); + if (response.status === 404) { + return undefined; + } if (!response.ok) { throw new Error( `Failed to load aliases from ${url}: ${response.status} ${ @@ -127,7 +174,7 @@ async function loadAliases(options?: InitAliasesOptions): Promise { const config = (await response.json()) as | DeploymentConfig | AliasDeclarationsFile; - cachedCustomAliases = extractAliases(config, url); + return extractAliases(config, url); } /** @@ -183,6 +230,7 @@ function parseResolvedAliases(raw: string): Record { } catch (error) { throw new Error( `Failed to parse resolved aliases: ${(error as Error).message}`, + { cause: error }, ); } if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) { diff --git a/packages/functions/src/public/browser-aliases.ts b/packages/functions/src/public/browser-aliases.ts index 73750170ecd..732d6e2a6e0 100644 --- a/packages/functions/src/public/browser-aliases.ts +++ b/packages/functions/src/public/browser-aliases.ts @@ -18,17 +18,11 @@ // This subpath is intentionally free of `fs`/`process` so it can be bundled // into a browser application. Usage: // -// import { -// custom, -// DEFAULT_DECLARATIONS_PATH, -// initAliases, -// } from "@osdk/functions/browser-aliases"; +// import { custom, initAliases } from "@osdk/functions/browser-aliases"; // -// // Prod reads the installer's resolved values; dev reads the authored -// // defaults, since the deployment config only exists on an installed site. -// await initAliases({ -// path: import.meta.env.DEV ? DEFAULT_DECLARATIONS_PATH : undefined, -// }); +// // Once at bootstrap. Reads the installer's resolved values on an installed +// // site, and the author's declared defaults everywhere else. +// await initAliases(); // // const apiBaseUrl = custom("apiBaseUrl"); From 3914c0b5d19106247eb4db33efe27adabe193fb4 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Mon, 17 Aug 2026 17:33:38 -0400 Subject: [PATCH 07/20] Temporarily publish snapshots from this branch --- .github/workflows/release.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca41bbc1e22..539de041694 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ on: - main - release/* - next + # TEMPORARY: publish snapshots from this branch for stack testing. Revert + # this line (and the `always()` on the snapshot job below) before merging. + - jzhang/dev-console-aliases concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -50,7 +53,11 @@ jobs: snapshot: name: Snapshot needs: release - if: ${{ !failure() && !cancelled() }} + # `always()` is required: on a non-release branch the `release` job is + # skipped by its own `if`, and a skipped dependency skips this job too + # unless the condition forces evaluation. TEMPORARY, revert with the branch + # trigger above. + if: ${{ always() && !failure() && !cancelled() }} runs-on: ubuntu-latest permissions: contents: read From baf7495607dd5500163f55be1031f7761cdb71d6 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Mon, 17 Aug 2026 18:45:04 -0400 Subject: [PATCH 08/20] Use fixed snapshot tag and add timeout --- .github/workflows/release.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 539de041694..fbaaf70b49e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,8 @@ jobs: # trigger above. if: ${{ always() && !failure() && !cancelled() }} runs-on: ubuntu-latest + # TEMPORARY: fail fast instead of hanging for an hour. Revert with the rest. + timeout-minutes: 25 permissions: contents: read id-token: write @@ -83,7 +85,9 @@ jobs: run: pnpm exec turbo run build - name: Update versions for snapshots - run: pnpm exec changeset version --snapshot ${GITHUB_REF_NAME//\//__} + # TEMPORARY: fixed tag, matching the previously-working branch-snapshot + # hack. The ref-derived name contains a slash on this branch. Revert. + run: pnpm exec changeset version --snapshot pr-jzhang - name: Make sure code is up to date run: pnpm exec turbo run postVersioning @@ -92,4 +96,5 @@ jobs: run: pnpm exec turbo run build - name: Publish results - run: pnpm exec changeset publish --no-git-tag --tag next-${GITHUB_REF_NAME//\//__} + # TEMPORARY: fixed tag, see above. Revert. + run: pnpm exec changeset publish --no-git-tag --tag pr-jzhang From 985cec55131659f9aae724ac3784c4c216d7cdb4 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Tue, 18 Aug 2026 11:45:52 -0400 Subject: [PATCH 09/20] Treat an HTML response as an absent alias config --- .../functions/src/aliases/browser.test.ts | 43 ++++++++++++++++++- packages/functions/src/aliases/browser.ts | 19 ++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/functions/src/aliases/browser.test.ts index a781e1eade5..38b2165b646 100644 --- a/packages/functions/src/aliases/browser.test.ts +++ b/packages/functions/src/aliases/browser.test.ts @@ -29,17 +29,29 @@ interface FakeResponseInit { status?: number; statusText?: string; body?: unknown; + contentType?: string; } function fakeResponse(init: FakeResponseInit): Response { + const contentType = init.contentType ?? "application/json"; return { ok: init.ok ?? true, status: init.status ?? 200, statusText: init.statusText ?? "OK", - json: () => Promise.resolve(init.body), + headers: { + get: (h: string) => + h.toLowerCase() === "content-type" ? contentType : null, + }, + json: () => + init.body === undefined + ? Promise.reject(new SyntaxError("Unexpected token '<'")) + : Promise.resolve(init.body), } as unknown as Response; } +/** A single-page-app host answering 200 with index.html for an unknown path. */ +const SPA_FALLBACK: FakeResponseInit = { contentType: "text/html" }; + function mockFetch(init: FakeResponseInit): typeof globalThis.fetch { return vi.fn(() => Promise.resolve(fakeResponse(init)), @@ -268,6 +280,35 @@ describe("browser aliases", () => { expect(() => custom("anything")).toThrow("Available aliases: []"); }); + + // A dev server or static host rewrites unknown paths to index.html and + // answers 200, so absence does not always look like a 404. + it("falls back when the host serves index.html instead of 404ing", async () => { + await initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: SPA_FALLBACK, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }); + + expect(custom("apiBaseUrl")).toBe("https://api.dev.example.com"); + expect(warn).toHaveBeenCalledOnce(); + }); + + it("does not mistake malformed JSON for an absent file", async () => { + // Declared as JSON but not valid JSON: a real error, not a missing file, so + // it must surface rather than silently falling back to defaults. + await expect( + initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { + contentType: "application/json", + }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }), + ).rejects.toThrow(); + }); }); // Dev mode: the declaration file nests values under aliases.custom, so the diff --git a/packages/functions/src/aliases/browser.ts b/packages/functions/src/aliases/browser.ts index a7902806b6f..3b1253ae5e1 100644 --- a/packages/functions/src/aliases/browser.ts +++ b/packages/functions/src/aliases/browser.ts @@ -171,12 +171,31 @@ async function fetchAliases( ); } + // A single-page-app host rewrites unknown paths to index.html and answers 200 + // rather than 404 (the Vite dev server and Foundry website hosting both do + // this). An HTML body therefore means "this file is not here", exactly like a + // 404, so treat it the same way instead of trying to parse markup as JSON. + if (isHtml(response)) { + return undefined; + } + const config = (await response.json()) as | DeploymentConfig | AliasDeclarationsFile; return extractAliases(config, url); } +/** + * True when the response body is HTML rather than the JSON we asked for, which + * indicates a single-page-app rewrite rather than a real config file. Only the + * declared content type is consulted: a response that claims to be JSON but does + * not parse is a genuine error and must not be mistaken for an absent file. + */ +function isHtml(response: Response): boolean { + const contentType = response.headers?.get?.("content-type") ?? ""; + return contentType.toLowerCase().includes("text/html"); +} + /** * Reads aliases out of either supported file shape. The deployment config packs * resolved values into a stringified JSON object; the declaration file nests From e4f03c46ce1f78de193dd9c7f06f049cd46874e8 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Tue, 18 Aug 2026 12:46:26 -0400 Subject: [PATCH 10/20] Detect absent alias config from the body, not the content type --- .../functions/src/aliases/browser.test.ts | 45 +++++++++---- packages/functions/src/aliases/browser.ts | 65 ++++++++++++------- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/functions/src/aliases/browser.test.ts index 38b2165b646..2b743e7d396 100644 --- a/packages/functions/src/aliases/browser.test.ts +++ b/packages/functions/src/aliases/browser.test.ts @@ -28,7 +28,10 @@ interface FakeResponseInit { ok?: boolean; status?: number; statusText?: string; + /** Served as the response body, JSON-encoded. */ body?: unknown; + /** Raw response body. Takes precedence over `body`. */ + text?: string; contentType?: string; } @@ -42,15 +45,20 @@ function fakeResponse(init: FakeResponseInit): Response { get: (h: string) => h.toLowerCase() === "content-type" ? contentType : null, }, - json: () => - init.body === undefined - ? Promise.reject(new SyntaxError("Unexpected token '<'")) - : Promise.resolve(init.body), + text: () => + Promise.resolve( + init.text ?? (init.body === undefined ? "" : JSON.stringify(init.body)), + ), } as unknown as Response; } +const INDEX_HTML = "\n"; + /** A single-page-app host answering 200 with index.html for an unknown path. */ -const SPA_FALLBACK: FakeResponseInit = { contentType: "text/html" }; +const SPA_FALLBACK: FakeResponseInit = { + contentType: "text/html", + text: INDEX_HTML, +}; function mockFetch(init: FakeResponseInit): typeof globalThis.fetch { return vi.fn(() => @@ -295,19 +303,34 @@ describe("browser aliases", () => { expect(warn).toHaveBeenCalledOnce(); }); + // Foundry website hosting types responses from the file extension and does + // not recognize .json, so the rewritten index.html can arrive without an + // html content type. Absence must still be detected from the body alone. + it("falls back on markup served without an html content type", async () => { + await initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { + contentType: "application/octet-stream", + text: INDEX_HTML, + }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }); + + expect(custom("apiBaseUrl")).toBe("https://api.dev.example.com"); + }); + it("does not mistake malformed JSON for an absent file", async () => { - // Declared as JSON but not valid JSON: a real error, not a missing file, so - // it must surface rather than silently falling back to defaults. + // Neither markup nor valid JSON: a real error, not a missing file, so it + // must surface rather than silently falling back to defaults. await expect( initAliases({ fetch: mockFetchByPath({ - [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { - contentType: "application/json", - }, + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { text: "{ not json" }, [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, }), }), - ).rejects.toThrow(); + ).rejects.toThrow("not valid JSON"); }); }); diff --git a/packages/functions/src/aliases/browser.ts b/packages/functions/src/aliases/browser.ts index 3b1253ae5e1..407cbc0b626 100644 --- a/packages/functions/src/aliases/browser.ts +++ b/packages/functions/src/aliases/browser.ts @@ -30,11 +30,13 @@ // so it carries the DEFAULTS // // Callers do not choose: we try the deployment config and fall back to the -// declaration file ONLY on a 404. A 404 on a same-origin static path means the -// file genuinely is not there (local dev, or a site deployed without going -// through Marketplace). Any other failure throws, because in production BOTH -// files are served, so falling back on a transient error would silently serve -// the developer's defaults in place of the installer's values. +// declaration file only when it is absent. Absence takes two forms, because a +// single-page-app host rewrites unknown paths to index.html and answers 200 +// rather than 404: either a 404, or a 200 carrying markup. Both mean the file +// genuinely is not there (local dev, or a site deployed without going through +// Marketplace). Any other failure throws, because in production BOTH files are +// served, so falling back on a transient error would silently serve the +// developer's defaults in place of the installer's values. // // The two files are told apart by the runtime type of their `aliases` field // (string vs object), which is unambiguous. @@ -150,9 +152,9 @@ async function loadAliases(options?: InitAliasesOptions): Promise { } /** - * Fetches and reads one alias file. Returns `undefined` when the file is absent - * (404), which callers may treat as a cue to fall back. Any other failure throws, - * so a transient server error never silently degrades to the declared defaults. + * Fetches and reads one alias file. Returns `undefined` when the file is absent, + * which callers may treat as a cue to fall back. Any other failure throws, so a + * transient server error never silently degrades to the declared defaults. */ async function fetchAliases( fetchImpl: typeof globalThis.fetch, @@ -171,29 +173,48 @@ async function fetchAliases( ); } + const body = await response.text(); + // A single-page-app host rewrites unknown paths to index.html and answers 200 // rather than 404 (the Vite dev server and Foundry website hosting both do - // this). An HTML body therefore means "this file is not here", exactly like a - // 404, so treat it the same way instead of trying to parse markup as JSON. - if (isHtml(response)) { + // this). Markup where JSON was expected therefore means "this file is not + // here", exactly like a 404, so treat it the same way. + if (isMarkup(body)) { return undefined; } - const config = (await response.json()) as - | DeploymentConfig - | AliasDeclarationsFile; - return extractAliases(config, url); + return extractAliases(parseJson(body, url), url); +} + +/** + * True when the body is markup rather than the JSON we asked for, which indicates + * a single-page-app rewrite rather than a real config file. + * + * The body is sniffed rather than the declared content type because that type is + * not a reliable signal: Foundry website hosting derives it from the file + * extension and does not recognize `.json`, so even a real config file can arrive + * as `application/octet-stream`. A leading `<` is unambiguous, since valid JSON + * can never begin with one. + */ +function isMarkup(body: string): boolean { + return body.trimStart().startsWith("<"); } /** - * True when the response body is HTML rather than the JSON we asked for, which - * indicates a single-page-app rewrite rather than a real config file. Only the - * declared content type is consulted: a response that claims to be JSON but does - * not parse is a genuine error and must not be mistaken for an absent file. + * Parses a fetched alias file. A body that is neither markup nor valid JSON is a + * genuine error and must surface rather than being mistaken for an absent file. */ -function isHtml(response: Response): boolean { - const contentType = response.headers?.get?.("content-type") ?? ""; - return contentType.toLowerCase().includes("text/html"); +function parseJson( + body: string, + url: string, +): DeploymentConfig | AliasDeclarationsFile { + try { + return JSON.parse(body) as DeploymentConfig | AliasDeclarationsFile; + } catch (error) { + throw new Error(`Failed to read aliases from ${url}: not valid JSON.`, { + cause: error, + }); + } } /** From c0b4c47b82d2b9637398dc471213d0fd9a51e92d Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 19 Aug 2026 11:59:45 -0400 Subject: [PATCH 11/20] Extract aliases into @osdk/aliases and add a browser runtime --- .changeset/dev-console-browser-aliases.md | 7 +- .lintstagedrc.mjs | 1 + .monorepolint.config.mjs | 1 + .../index.d.ts} | 2 +- .../aliases/index.ts => aliases/node.d.ts} | 7 +- packages/aliases/oxlint.config.ts | 49 +++++++++++ packages/aliases/package.json | 73 ++++++++++++++++ .../aliases => aliases/src}/aliases.test.ts | 0 .../aliases => aliases/src}/browser.test.ts | 0 .../src/aliases => aliases/src}/browser.ts | 0 .../src/aliases => aliases/src}/custom.ts | 1 + .../src/aliases => aliases/src}/dataset.ts | 1 + .../aliases => aliases/src}/environment.ts | 0 packages/aliases/src/index.test.ts | 84 +++++++++++++++++++ packages/aliases/src/index.ts | 42 ++++++++++ .../src/aliases => aliases/src}/loaders.ts | 4 +- .../src/aliases => aliases/src}/mediaset.ts | 1 + .../src/aliases => aliases/src}/model.ts | 1 + packages/aliases/src/public/node.ts | 30 +++++++ .../src/aliases => aliases/src}/source.ts | 1 + .../src/aliases => aliases/src}/stream.ts | 1 + .../src}/test-data/aliases.json | 0 .../src}/test-data/resources.json | 0 .../src/aliases => aliases/src}/types.ts | 4 +- packages/aliases/tsconfig.json | 11 +++ packages/aliases/vitest.config.mts | 39 +++++++++ packages/functions/package.json | 10 +-- packages/functions/src/index.ts | 6 +- .../functions/src/public/browser-aliases.ts | 36 -------- 29 files changed, 354 insertions(+), 58 deletions(-) rename packages/{functions/browser-aliases.d.ts => aliases/index.d.ts} (91%) rename packages/{functions/src/aliases/index.ts => aliases/node.d.ts} (78%) create mode 100644 packages/aliases/oxlint.config.ts create mode 100644 packages/aliases/package.json rename packages/{functions/src/aliases => aliases/src}/aliases.test.ts (100%) rename packages/{functions/src/aliases => aliases/src}/browser.test.ts (100%) rename packages/{functions/src/aliases => aliases/src}/browser.ts (100%) rename packages/{functions/src/aliases => aliases/src}/custom.ts (99%) rename packages/{functions/src/aliases => aliases/src}/dataset.ts (99%) rename packages/{functions/src/aliases => aliases/src}/environment.ts (100%) create mode 100644 packages/aliases/src/index.test.ts create mode 100644 packages/aliases/src/index.ts rename packages/{functions/src/aliases => aliases/src}/loaders.ts (97%) rename packages/{functions/src/aliases => aliases/src}/mediaset.ts (99%) rename packages/{functions/src/aliases => aliases/src}/model.ts (99%) create mode 100644 packages/aliases/src/public/node.ts rename packages/{functions/src/aliases => aliases/src}/source.ts (99%) rename packages/{functions/src/aliases => aliases/src}/stream.ts (99%) rename packages/{functions/src/aliases => aliases/src}/test-data/aliases.json (100%) rename packages/{functions/src/aliases => aliases/src}/test-data/resources.json (100%) rename packages/{functions/src/aliases => aliases/src}/types.ts (96%) create mode 100644 packages/aliases/tsconfig.json create mode 100644 packages/aliases/vitest.config.mts delete mode 100644 packages/functions/src/public/browser-aliases.ts diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index bba3b55a32a..c7772c0b7e6 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -1,5 +1,10 @@ --- +"@osdk/aliases": minor "@osdk/functions": minor --- -Add a browser-safe `@osdk/functions/browser-aliases` subpath so Dev Console apps can read custom aliases in the browser. Call `await initAliases()` once at startup, then read values synchronously with `custom("myAlias")`. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only on a 404, so a transient server error surfaces instead of silently substituting defaults for the installer's values. +Extract the alias runtime into a new `@osdk/aliases` package so consumers that are not Functions can read aliases without depending on `@osdk/functions`. + +`@osdk/functions` re-exports it, so its public `Aliases` namespace is unchanged. + +The new package has two entry points. `@osdk/aliases` is browser-safe, for applications served to a browser such as Developer Console apps: call `await initAliases()` once at startup, then read values synchronously with `custom("myAlias")`. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only when that file is genuinely absent, meaning a 404 or the markup a single-page-app host serves in place of one, so a transient server error surfaces instead of silently substituting defaults for the installer's values. `@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node with a container filesystem. diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index 97757befb66..e2638b3c262 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -52,6 +52,7 @@ const OXC_PACKAGE_GLOB = `packages/{${ * at that nested config instead of the root one. Map of package dir -> config. */ const OXC_NESTED_CONFIG_PACKAGES = { + "aliases": "packages/aliases/oxlint.config.ts", "react-components-storybook": "packages/react-components-storybook/oxlint.config.ts", "react-components": "packages/react-components/oxlint.config.ts", diff --git a/.monorepolint.config.mjs b/.monorepolint.config.mjs index 5c95c64547e..5d377bb7f61 100644 --- a/.monorepolint.config.mjs +++ b/.monorepolint.config.mjs @@ -271,6 +271,7 @@ const archetypeRules = archetypes(standardPackageRules, { .addArchetype( "oxc migrated libraries with carve-outs", [ + "@osdk/aliases", "@osdk/maker", "@osdk/maker-experimental", "@osdk/maker-import", diff --git a/packages/functions/browser-aliases.d.ts b/packages/aliases/index.d.ts similarity index 91% rename from packages/functions/browser-aliases.d.ts rename to packages/aliases/index.d.ts index b2270b1fd37..b8f72b88336 100644 --- a/packages/functions/browser-aliases.d.ts +++ b/packages/aliases/index.d.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from "./build/cjs/public/browser-aliases.cjs"; +export * from "./build/cjs/index.cjs"; diff --git a/packages/functions/src/aliases/index.ts b/packages/aliases/node.d.ts similarity index 78% rename from packages/functions/src/aliases/index.ts rename to packages/aliases/node.d.ts index 96088f536d3..17997f3ec20 100644 --- a/packages/functions/src/aliases/index.ts +++ b/packages/aliases/node.d.ts @@ -14,9 +14,4 @@ * limitations under the License. */ -export * from "./custom.js"; -export * from "./dataset.js"; -export * from "./mediaset.js"; -export * from "./model.js"; -export * from "./source.js"; -export * from "./stream.js"; +export * from "./build/cjs/public/node.cjs"; diff --git a/packages/aliases/oxlint.config.ts b/packages/aliases/oxlint.config.ts new file mode 100644 index 00000000000..fe07d1f53e9 --- /dev/null +++ b/packages/aliases/oxlint.config.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from "oxlint"; + +import root from "../../oxlint.config.ts"; + +// Nested oxlint config for @osdk/aliases. This package's source was moved here +// verbatim from @osdk/functions, which carries the same carve-outs, so the same +// rules are disabled to keep the extraction a move rather than a rewrite. Every +// rule below is surfaced only by the moved test setup, not by the runtime source. +// This list is a strict subset of the one in packages/functions/oxlint.config.ts. +// +// `extends` only carries `rules`/`plugins`/`overrides`, so the root's +// `ignorePatterns` are re-applied explicitly. +export default defineConfig({ + extends: [root], + ignorePatterns: root.ignorePatterns, + + rules: { + // --- typescript --- + // `delete process.env[computed]` in test setup; the pattern is intentional. + "typescript/no-dynamic-delete": "off", + + // --- unicorn --- + // `__dirname` in test setup, reading fixture files relative to the test. + "unicorn/prefer-module": "off", + // `require("fs")` / `"fs"` -> `"node:fs"`; the autofix rewrites specifiers, + // and the test deliberately uses `node:fs` separately from the mocked `fs`. + "unicorn/prefer-node-protocol": "off", + + // --- node --- + // `require(...)` inside `vi.hoisted(...)`, which must not be a static import. + "node/global-require": "off", + }, +}); diff --git a/packages/aliases/package.json b/packages/aliases/package.json new file mode 100644 index 00000000000..c08627c7f6d --- /dev/null +++ b/packages/aliases/package.json @@ -0,0 +1,73 @@ +{ + "name": "@osdk/aliases", + "version": "0.1.0", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/palantir/osdk-ts.git" + }, + "exports": { + ".": { + "browser": "./build/browser/index.js", + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" + }, + "require": "./build/cjs/index.cjs", + "default": "./build/browser/index.js" + }, + "./node": { + "browser": "./build/browser/public/node.js", + "import": { + "types": "./build/types/public/node.d.ts", + "default": "./build/esm/public/node.js" + }, + "require": "./build/cjs/public/node.cjs", + "default": "./build/browser/public/node.js" + }, + "./*": { + "browser": "./build/browser/public/*.js", + "import": { + "types": "./build/types/public/*.d.ts", + "default": "./build/esm/public/*.js" + }, + "require": "./build/cjs/public/*.cjs", + "default": "./build/browser/public/*.js" + } + }, + "scripts": { + "check-attw": "attw --pack .", + "check-spelling": "cspell --quiet .", + "clean": "rm -rf lib dist types build tsconfig.tsbuildinfo", + "fix-lint": "oxlint -c ./oxlint.config.ts --fix . && oxfmt -c ../../oxfmt.config.ts .", + "lint": "oxlint -c ./oxlint.config.ts . && oxfmt -c ../../oxfmt.config.ts --check .", + "test": "vitest run --pool=forks", + "transpileBrowser": "monorepo.tool.transpile -f esm -m normal -t browser", + "transpileCjs": "monorepo.tool.transpile -f cjs -m bundle -t node", + "transpileEsm": "monorepo.tool.transpile -f esm -m normal -t node", + "transpileTypes": "monorepo.tool.transpile -f esm -m types -t node", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "devDependencies": { + "@osdk/monorepo.api-extractor": "workspace:~", + "@osdk/monorepo.tsconfig": "workspace:~", + "typescript": "~5.5.4" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "build/cjs", + "build/esm", + "build/browser", + "build/types", + "CHANGELOG.md", + "package.json", + "templates", + "*.d.ts" + ], + "main": "./build/cjs/index.cjs", + "module": "./build/esm/index.js", + "types": "./build/cjs/index.d.cts", + "type": "module" +} diff --git a/packages/functions/src/aliases/aliases.test.ts b/packages/aliases/src/aliases.test.ts similarity index 100% rename from packages/functions/src/aliases/aliases.test.ts rename to packages/aliases/src/aliases.test.ts diff --git a/packages/functions/src/aliases/browser.test.ts b/packages/aliases/src/browser.test.ts similarity index 100% rename from packages/functions/src/aliases/browser.test.ts rename to packages/aliases/src/browser.test.ts diff --git a/packages/functions/src/aliases/browser.ts b/packages/aliases/src/browser.ts similarity index 100% rename from packages/functions/src/aliases/browser.ts rename to packages/aliases/src/browser.ts diff --git a/packages/functions/src/aliases/custom.ts b/packages/aliases/src/custom.ts similarity index 99% rename from packages/functions/src/aliases/custom.ts rename to packages/aliases/src/custom.ts index c54ad26de6c..ee3f174e863 100644 --- a/packages/functions/src/aliases/custom.ts +++ b/packages/aliases/src/custom.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Custom } from "./types.js"; + export type { Custom } from "./types.js"; export function custom(alias: string): Custom { diff --git a/packages/functions/src/aliases/dataset.ts b/packages/aliases/src/dataset.ts similarity index 99% rename from packages/functions/src/aliases/dataset.ts rename to packages/aliases/src/dataset.ts index 0355c4c0467..f9ac41cea7b 100644 --- a/packages/functions/src/aliases/dataset.ts +++ b/packages/aliases/src/dataset.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Dataset } from "./types.js"; + export type { Dataset } from "./types.js"; export function dataset(alias: string): Dataset { diff --git a/packages/functions/src/aliases/environment.ts b/packages/aliases/src/environment.ts similarity index 100% rename from packages/functions/src/aliases/environment.ts rename to packages/aliases/src/environment.ts diff --git a/packages/aliases/src/index.test.ts b/packages/aliases/src/index.test.ts new file mode 100644 index 00000000000..be01abb54a5 --- /dev/null +++ b/packages/aliases/src/index.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// The public surface of the browser entry point. Both import styles are +// supported, so both are asserted here: dropping either one later would be a +// breaking change for consumers, and that should fail a test rather than pass +// review unnoticed. + +import { afterEach, describe, expect, it } from "vitest"; + +import * as browser from "./browser.js"; +import { + Aliases, + custom, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, + resetAliasesCache, +} from "./index.js"; + +const DECLARATIONS = { + aliases: { custom: { apiBaseUrl: { value: "https://api.example.com" } } }, +}; + +function mockFetch(): typeof globalThis.fetch { + return (() => + Promise.resolve({ + ok: true, + status: 200, + statusText: "OK", + headers: { get: () => "application/json" }, + text: () => Promise.resolve(JSON.stringify(DECLARATIONS)), + })) as unknown as typeof globalThis.fetch; +} + +describe("browser entry point", () => { + afterEach(() => { + resetAliasesCache(); + }); + + it("exposes the Aliases namespace", async () => { + await Aliases.initAliases({ path: "resources.json", fetch: mockFetch() }); + + expect(Aliases.custom("apiBaseUrl")).toBe("https://api.example.com"); + }); + + it("exposes the same members as named exports", () => { + // Same function identities, not merely same names, so the two styles can + // never drift apart. + expect(Aliases.custom).toBe(custom); + expect(Aliases.initAliases).toBe(initAliases); + expect(Aliases.resetAliasesCache).toBe(resetAliasesCache); + expect(Aliases.DEFAULT_DECLARATIONS_PATH).toBe(DEFAULT_DECLARATIONS_PATH); + expect(Aliases.DEFAULT_DEPLOYMENT_CONFIG_PATH).toBe( + DEFAULT_DEPLOYMENT_CONFIG_PATH, + ); + }); + + it("does not re-export the filesystem loaders", () => { + // Those live behind "@osdk/aliases/node". Leaking them here would pull `fs` + // into a browser bundle. + expect(Aliases).not.toHaveProperty("dataset"); + expect(Aliases).not.toHaveProperty("source"); + }); + + it("namespace covers every public member of the module", () => { + // Guards against a new export being added to browser.ts but omitted from + // the namespace, which would make the two styles inconsistent. + expect(Object.keys(Aliases).sort()).toEqual(Object.keys(browser).sort()); + }); +}); diff --git a/packages/aliases/src/index.ts b/packages/aliases/src/index.ts new file mode 100644 index 00000000000..e89d8793974 --- /dev/null +++ b/packages/aliases/src/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Default entry point: the browser-safe alias runtime, for applications served +// to a browser such as Developer Console apps. It is free of `fs` and `process` +// so it can be bundled. +// +// import { Aliases } from "@osdk/aliases"; +// await Aliases.initAliases(); +// const apiBaseUrl = Aliases.custom("apiBaseUrl"); +// +// The `Aliases` namespace is the preferred form, because it matches the +// namespace @osdk/functions already exposes for the filesystem runtime, and +// because a bare `custom("apiBaseUrl")` does not say what it reads. The same +// members are also exported individually for callers who prefer named imports. +// +// Code running in Node with a filesystem (Functions) should import the +// "@osdk/aliases/node" subpath instead, which reads aliases from disk. + +export * as Aliases from "./browser.js"; + +export { + custom, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, + resetAliasesCache, +} from "./browser.js"; +export type { Custom, InitAliasesOptions } from "./browser.js"; diff --git a/packages/functions/src/aliases/loaders.ts b/packages/aliases/src/loaders.ts similarity index 97% rename from packages/functions/src/aliases/loaders.ts rename to packages/aliases/src/loaders.ts index d76884a9ce8..47535b332cf 100644 --- a/packages/functions/src/aliases/loaders.ts +++ b/packages/aliases/src/loaders.ts @@ -221,8 +221,8 @@ export function loadResolvedAliases(): ResolvedAliases { case AliasEnvironment.BROWSER: throw new Error( "Browser alias environment detected. This filesystem-based loader " + - "cannot run in a browser. Import from '@osdk/functions/browser-aliases' " + - "and call `await initAliases()` before reading aliases instead.", + "cannot run in a browser. Import from '@osdk/aliases' and call " + + "`await initAliases()` before reading aliases instead.", ); } } diff --git a/packages/functions/src/aliases/mediaset.ts b/packages/aliases/src/mediaset.ts similarity index 99% rename from packages/functions/src/aliases/mediaset.ts rename to packages/aliases/src/mediaset.ts index b25db5b902b..faa9299a33d 100644 --- a/packages/functions/src/aliases/mediaset.ts +++ b/packages/aliases/src/mediaset.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Mediaset } from "./types.js"; + export type { Mediaset } from "./types.js"; export function mediaset(alias: string): Mediaset { diff --git a/packages/functions/src/aliases/model.ts b/packages/aliases/src/model.ts similarity index 99% rename from packages/functions/src/aliases/model.ts rename to packages/aliases/src/model.ts index 55cbf066620..d780c123c8c 100644 --- a/packages/functions/src/aliases/model.ts +++ b/packages/aliases/src/model.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Model } from "./types.js"; + export type { Model } from "./types.js"; export function model(alias: string): Model { diff --git a/packages/aliases/src/public/node.ts b/packages/aliases/src/public/node.ts new file mode 100644 index 00000000000..9daaaccbd5b --- /dev/null +++ b/packages/aliases/src/public/node.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Filesystem-backed alias runtime, for code running in Node with a container +// filesystem (Functions). Reads the aliases file whose path the runtime supplies +// through an environment variable. This entry point uses `fs` and so cannot be +// bundled into a browser; browser applications should import "@osdk/aliases". +// +// `@osdk/functions` re-exports this as its `Aliases` namespace, so its public +// API is unchanged. + +export * from "../custom.js"; +export * from "../dataset.js"; +export * from "../mediaset.js"; +export * from "../model.js"; +export * from "../source.js"; +export * from "../stream.js"; diff --git a/packages/functions/src/aliases/source.ts b/packages/aliases/src/source.ts similarity index 99% rename from packages/functions/src/aliases/source.ts rename to packages/aliases/src/source.ts index bc84e594b9e..62301aedde5 100644 --- a/packages/functions/src/aliases/source.ts +++ b/packages/aliases/src/source.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Source } from "./types.js"; + export type { Source } from "./types.js"; export function source(alias: string): Source { diff --git a/packages/functions/src/aliases/stream.ts b/packages/aliases/src/stream.ts similarity index 99% rename from packages/functions/src/aliases/stream.ts rename to packages/aliases/src/stream.ts index b2940604dc7..c06c150be03 100644 --- a/packages/functions/src/aliases/stream.ts +++ b/packages/aliases/src/stream.ts @@ -16,6 +16,7 @@ import { loadResolvedAliases } from "./loaders.js"; import type { Stream } from "./types.js"; + export type { Stream } from "./types.js"; export function stream(alias: string): Stream { diff --git a/packages/functions/src/aliases/test-data/aliases.json b/packages/aliases/src/test-data/aliases.json similarity index 100% rename from packages/functions/src/aliases/test-data/aliases.json rename to packages/aliases/src/test-data/aliases.json diff --git a/packages/functions/src/aliases/test-data/resources.json b/packages/aliases/src/test-data/resources.json similarity index 100% rename from packages/functions/src/aliases/test-data/resources.json rename to packages/aliases/src/test-data/resources.json diff --git a/packages/functions/src/aliases/types.ts b/packages/aliases/src/types.ts similarity index 96% rename from packages/functions/src/aliases/types.ts rename to packages/aliases/src/types.ts index 34f3c2d1a7f..d60751172f6 100644 --- a/packages/functions/src/aliases/types.ts +++ b/packages/aliases/src/types.ts @@ -54,7 +54,7 @@ export enum AliasEnvironment { LIVE_PREVIEW = "LIVE_PREVIEW", // Dev Console applications run in the browser, where there is no filesystem // or process.env. Resolved aliases are fetched from the served deployment - // config file instead. Use the "@osdk/functions/browser-aliases" subpath. + // config file instead. Use the browser entry point, "@osdk/aliases". BROWSER = "BROWSER", } @@ -62,7 +62,7 @@ export enum AliasEnvironment { /** * Shape of the deployment config file that Foundry website hosting serves at - * {@link ../public/browser-aliases}'s default path. It is a flat map of + * the browser entry point's default path. It is a flat map of * strings; resolved custom aliases are packed under `aliases` as a stringified * JSON object (a `Record`) so they cannot collide with the * reserved system keys. diff --git a/packages/aliases/tsconfig.json b/packages/aliases/tsconfig.json new file mode 100644 index 00000000000..6c1ec6f1753 --- /dev/null +++ b/packages/aliases/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@osdk/monorepo.tsconfig/base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build/esm" + }, + "include": [ + "./src/**/*" + ], + "references": [] +} diff --git a/packages/aliases/vitest.config.mts b/packages/aliases/vitest.config.mts new file mode 100644 index 00000000000..8d0b9a1d9b8 --- /dev/null +++ b/packages/aliases/vitest.config.mts @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + pool: "forks", + exclude: [...configDefaults.exclude, "**/build/**/*"], + coverage: { + include: ["src/**"], + // Exclude tests, generated code, and index.ts barrels (no logic). + exclude: [ + "**/*.test.*", + "**/__tests__/**", + "**/__mocks__/**", + "**/generatedNoCheck/**", + "**/*.d.ts", + "**/index.ts", + ], + }, + fakeTimers: { + toFake: ["setTimeout", "clearTimeout", "Date"], + }, + }, +}); diff --git a/packages/functions/package.json b/packages/functions/package.json index fe65cd3dad2..2654bcd174b 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -18,15 +18,6 @@ "require": "./build/cjs/index.cjs", "default": "./build/browser/index.js" }, - "./browser-aliases": { - "browser": "./build/browser/public/browser-aliases.js", - "import": { - "types": "./build/types/public/browser-aliases.d.ts", - "default": "./build/esm/public/browser-aliases.js" - }, - "require": "./build/cjs/public/browser-aliases.cjs", - "default": "./build/browser/public/browser-aliases.js" - }, "./experimental": { "browser": "./build/browser/public/experimental.js", "import": { @@ -79,6 +70,7 @@ "typecheck": "tsc --noEmit --emitDeclarationOnly false" }, "dependencies": { + "@osdk/aliases": "workspace:~", "@osdk/foundry.core": "^2.44.0", "@osdk/foundry.mediasets": "^2.44.0", "@osdk/foundry.ontologies": "^2.44.0", diff --git a/packages/functions/src/index.ts b/packages/functions/src/index.ts index 8ff838d7b4a..19e1ad2a601 100644 --- a/packages/functions/src/index.ts +++ b/packages/functions/src/index.ts @@ -34,7 +34,11 @@ export type { TwoDimensionalAggregation, } from "@osdk/client"; -export * as Aliases from "./aliases/index.js"; +// The alias runtime lives in @osdk/aliases so that consumers which are not +// Functions (Developer Console apps, and later other pro-code surfaces) can use +// it without depending on this package. Re-exported here so the public +// `Aliases` namespace of @osdk/functions is unchanged. +export * as Aliases from "@osdk/aliases/node"; export { createEditBatch } from "./edits/createEditBatch.js"; export type { EditBatch } from "./edits/EditBatch.js"; export type { Edits } from "./edits/types.js"; diff --git a/packages/functions/src/public/browser-aliases.ts b/packages/functions/src/public/browser-aliases.ts deleted file mode 100644 index 732d6e2a6e0..00000000000 --- a/packages/functions/src/public/browser-aliases.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2026 Palantir Technologies, Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Public entry point for reading aliases from a browser (Dev Console apps). -// This subpath is intentionally free of `fs`/`process` so it can be bundled -// into a browser application. Usage: -// -// import { custom, initAliases } from "@osdk/functions/browser-aliases"; -// -// // Once at bootstrap. Reads the installer's resolved values on an installed -// // site, and the author's declared defaults everywhere else. -// await initAliases(); -// -// const apiBaseUrl = custom("apiBaseUrl"); - -export { - custom, - DEFAULT_DECLARATIONS_PATH, - DEFAULT_DEPLOYMENT_CONFIG_PATH, - initAliases, - resetAliasesCache, -} from "../aliases/browser.js"; -export type { Custom, InitAliasesOptions } from "../aliases/browser.js"; From ef7ac773c1563a0e00fe9440acb50cfdf19958a8 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 19 Aug 2026 12:15:00 -0400 Subject: [PATCH 12/20] Remove unused BROWSER alias environment --- packages/aliases/src/environment.ts | 6 ------ packages/aliases/src/loaders.ts | 6 ------ packages/aliases/src/types.ts | 4 ---- 3 files changed, 16 deletions(-) diff --git a/packages/aliases/src/environment.ts b/packages/aliases/src/environment.ts index 54c8c67caa8..50163000179 100644 --- a/packages/aliases/src/environment.ts +++ b/packages/aliases/src/environment.ts @@ -20,12 +20,6 @@ export const ALIASES_JSON_FILE_ENV_VAR = "ALIASES_JSON_FILE"; export const RESOURCES_JSON_FILE_ENV_VAR = "RESOURCES_JSON_FILE"; export function detectEnvironment(): AliasEnvironment { - // Dev Console apps run in a browser, where there is no process.env to read. - // Detect that first so we never touch process in a browser context. - if (typeof document !== "undefined") { - return AliasEnvironment.BROWSER; - } - const aliasesFileSet = ALIASES_JSON_FILE_ENV_VAR in process.env; const resourcesFileSet = RESOURCES_JSON_FILE_ENV_VAR in process.env; diff --git a/packages/aliases/src/loaders.ts b/packages/aliases/src/loaders.ts index 47535b332cf..abc8fc350ac 100644 --- a/packages/aliases/src/loaders.ts +++ b/packages/aliases/src/loaders.ts @@ -218,11 +218,5 @@ export function loadResolvedAliases(): ResolvedAliases { return loadPublishedAliases(); case AliasEnvironment.LIVE_PREVIEW: return loadPreviewAliases(); - case AliasEnvironment.BROWSER: - throw new Error( - "Browser alias environment detected. This filesystem-based loader " + - "cannot run in a browser. Import from '@osdk/aliases' and call " + - "`await initAliases()` before reading aliases instead.", - ); } } diff --git a/packages/aliases/src/types.ts b/packages/aliases/src/types.ts index d60751172f6..f3e7f148d88 100644 --- a/packages/aliases/src/types.ts +++ b/packages/aliases/src/types.ts @@ -52,10 +52,6 @@ export interface ResolvedAliases { export enum AliasEnvironment { PUBLISHED = "PUBLISHED", LIVE_PREVIEW = "LIVE_PREVIEW", - // Dev Console applications run in the browser, where there is no filesystem - // or process.env. Resolved aliases are fetched from the served deployment - // config file instead. Use the browser entry point, "@osdk/aliases". - BROWSER = "BROWSER", } // Browser mode types (deployment.config.json) From cce99700aff50c595be7f38eab90d921af84ce81 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 19 Aug 2026 12:56:34 -0400 Subject: [PATCH 13/20] update lock --- pnpm-lock.yaml | 245 ++++++++++++++++++++++--------------------------- 1 file changed, 111 insertions(+), 134 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fd2d24da07..69e1fe785bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -310,13 +310,13 @@ importers: dependencies: '@docusaurus/core': specifier: 3.4.0 - version: 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + version: 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/plugin-content-docs': specifier: 3.4.0 - version: 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + version: 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/preset-classic': specifier: 3.4.0 - version: 3.4.0(@algolia/client-search@5.46.0)(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.3) + version: 3.4.0(@algolia/client-search@5.46.0)(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.5.4) '@mdx-js/react': specifier: ^3.0.0 version: 3.1.1(@types/react@18.3.24)(react@18.3.1) @@ -1715,6 +1715,18 @@ importers: specifier: ~5.5.4 version: 5.5.4 + packages/aliases: + devDependencies: + '@osdk/monorepo.api-extractor': + specifier: workspace:~ + version: link:../monorepo.api-extractor + '@osdk/monorepo.tsconfig': + specifier: workspace:~ + version: link:../monorepo.tsconfig + typescript: + specifier: ~5.5.4 + version: 5.5.4 + packages/api: dependencies: '@types/geojson': @@ -4027,6 +4039,9 @@ importers: packages/functions: dependencies: + '@osdk/aliases': + specifier: workspace:~ + version: link:../aliases '@osdk/client': specifier: workspace:^ version: link:../client @@ -8337,7 +8352,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -24087,7 +24102,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/core@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/core@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: '@babel/core': 7.28.4 '@babel/generator': 7.28.3 @@ -24101,10 +24116,10 @@ snapshots: '@babel/traverse': 7.28.4 '@docusaurus/cssnano-preset': 3.4.0 '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) autoprefixer: 10.4.22(postcss@8.5.6) babel-loader: 9.2.1(@babel/core@7.28.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) babel-plugin-dynamic-import-node: 2.3.3 @@ -24135,10 +24150,10 @@ snapshots: mini-css-extract-plugin: 2.9.4(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) p-map: 4.0.0 postcss: 8.5.6 - postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) prompts: 2.4.2 react: 18.3.1 - react-dev-utils: 12.0.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) + react-dev-utils: 12.0.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) react-dom: 18.3.1(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' @@ -24190,11 +24205,11 @@ snapshots: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/mdx-loader@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: '@docusaurus/logger': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 @@ -24245,15 +24260,15 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-content-blog@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.3.1 @@ -24284,16 +24299,16 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-content-docs@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/module-type-aliases': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.1 @@ -24322,13 +24337,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-pages@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-content-pages@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) fs-extra: 11.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24352,11 +24367,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-debug@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) fs-extra: 11.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24380,11 +24395,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-analytics@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-google-analytics@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -24406,11 +24421,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-gtag@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-google-gtag@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24433,11 +24448,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-google-tag-manager@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -24459,14 +24474,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-sitemap@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/plugin-sitemap@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/logger': 3.4.0 '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) fs-extra: 11.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24490,20 +24505,20 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@3.4.0(@algolia/client-search@5.46.0)(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.3)': - dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-debug': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-google-analytics': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-google-gtag': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-google-tag-manager': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-sitemap': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/theme-classic': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/theme-search-algolia': 3.4.0(@algolia/client-search@5.46.0)(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.3) + '@docusaurus/preset-classic@3.4.0(@algolia/client-search@5.46.0)(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.5.4)': + dependencies: + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-debug': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-google-analytics': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-google-gtag': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-google-tag-manager': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-sitemap': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/theme-classic': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/theme-search-algolia': 3.4.0(@algolia/client-search@5.46.0)(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.5.4) '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24533,20 +24548,20 @@ snapshots: '@types/react': 18.3.24 react: 18.3.1 - '@docusaurus/theme-classic@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/theme-classic@3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/module-type-aliases': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/theme-translations': 3.4.0 '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@mdx-js/react': 3.1.1(@types/react@18.3.24)(react@18.3.1) clsx: 2.1.1 copy-text-to-clipboard: 3.2.2 @@ -24581,14 +24596,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)': + '@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4)': dependencies: - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/module-type-aliases': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/plugin-content-blog': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/plugin-content-pages': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@types/history': 4.7.11 '@types/react': 18.3.24 @@ -24619,16 +24634,16 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-search-algolia@3.4.0(@algolia/client-search@5.46.0)(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.8.3)': + '@docusaurus/theme-search-algolia@3.4.0(@algolia/client-search@5.46.0)(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(@types/react@18.3.24)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.5.4)': dependencies: '@docsearch/react': 3.9.0(@algolia/client-search@5.46.0)(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/logger': 3.4.0 - '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + '@docusaurus/plugin-content-docs': 3.4.0(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) + '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@rspack/core@1.6.7)(@swc/core@1.7.39)(esbuild@0.27.3)(eslint@9.35.0(jiti@2.5.1))(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4) '@docusaurus/theme-translations': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) + '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) algoliasearch: 4.25.3 algoliasearch-helper: 3.26.1(algoliasearch@4.25.3) clsx: 2.1.1 @@ -24692,10 +24707,10 @@ snapshots: optionalDependencies: '@docusaurus/types': 3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3)': + '@docusaurus/utils-validation@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4)': dependencies: '@docusaurus/logger': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3) + '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4) '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) fs-extra: 11.3.1 joi: 17.13.3 @@ -24711,11 +24726,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.8.3)': + '@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@swc/core@1.7.39)(esbuild@0.27.3)(typescript@5.5.4)': dependencies: '@docusaurus/logger': 3.4.0 '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(@swc/core@1.7.39)(esbuild@0.27.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@svgr/webpack': 8.1.0(typescript@5.8.3) + '@svgr/webpack': 8.1.0(typescript@5.5.4) escape-string-regexp: 4.0.0 file-loader: 6.2.0(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) fs-extra: 11.3.1 @@ -28988,17 +29003,6 @@ snapshots: - supports-color - typescript - '@svgr/core@8.1.0(typescript@5.8.3)': - dependencies: - '@babel/core': 7.28.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.8.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: '@babel/types': 7.28.4 @@ -29014,16 +29018,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))': - dependencies: - '@babel/core': 7.28.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) - '@svgr/core': 8.1.0(typescript@5.8.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.5.4))(typescript@5.5.4)': dependencies: '@svgr/core': 8.1.0(typescript@5.5.4) @@ -29033,25 +29027,16 @@ snapshots: transitivePeerDependencies: - typescript - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3)': - dependencies: - '@svgr/core': 8.1.0(typescript@5.8.3) - cosmiconfig: 8.3.6(typescript@5.8.3) - deepmerge: 4.3.1 - svgo: 3.3.3 - transitivePeerDependencies: - - typescript - - '@svgr/webpack@8.1.0(typescript@5.8.3)': + '@svgr/webpack@8.1.0(typescript@5.5.4)': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.28.4) '@babel/preset-env': 7.26.0(@babel/core@7.28.4) '@babel/preset-react': 7.25.9(@babel/core@7.28.4) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.4) - '@svgr/core': 8.1.0(typescript@5.8.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3) + '@svgr/core': 8.1.0(typescript@5.5.4) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4))(typescript@5.5.4) transitivePeerDependencies: - supports-color - typescript @@ -32188,15 +32173,6 @@ snapshots: optionalDependencies: typescript: 5.5.4 - cosmiconfig@8.3.6(typescript@5.8.3): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.1.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.8.3 - crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -34124,7 +34100,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@6.5.3(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): + fork-ts-checker-webpack-plugin@6.5.3(eslint@9.35.0(jiti@2.5.1))(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): dependencies: '@babel/code-frame': 7.27.1 '@types/json-schema': 7.0.15 @@ -34139,7 +34115,7 @@ snapshots: schema-utils: 2.7.0 semver: 7.7.4 tapable: 1.1.3 - typescript: 5.8.3 + typescript: 5.5.4 webpack: 5.106.1(@swc/core@1.7.39)(esbuild@0.27.3) optionalDependencies: eslint: 9.35.0(jiti@2.5.1) @@ -38271,9 +38247,9 @@ snapshots: tsx: 4.20.5 yaml: 2.9.0 - postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): + postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): dependencies: - cosmiconfig: 8.3.6(typescript@5.8.3) + cosmiconfig: 8.3.6(typescript@5.5.4) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.4 @@ -38987,7 +38963,7 @@ snapshots: date-fns: 2.30.0 react: 18.3.1 - react-dev-utils@12.0.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): + react-dev-utils@12.0.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)): dependencies: '@babel/code-frame': 7.27.1 address: 1.2.2 @@ -38998,7 +38974,7 @@ snapshots: escape-string-regexp: 4.0.0 filesize: 8.0.7 find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) + fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.35.0(jiti@2.5.1))(typescript@5.5.4)(webpack@5.106.1(@swc/core@1.7.39)(esbuild@0.27.3)) global-modules: 2.0.0 globby: 11.1.0 gzip-size: 6.0.0 @@ -39015,7 +38991,7 @@ snapshots: text-table: 0.2.0 webpack: 5.106.1(@swc/core@1.7.39)(esbuild@0.27.3) optionalDependencies: - typescript: 5.8.3 + typescript: 5.5.4 transitivePeerDependencies: - eslint - supports-color @@ -41552,7 +41528,8 @@ snapshots: typescript@5.8.2: {} - typescript@5.8.3: {} + typescript@5.8.3: + optional: true typewise-core@1.2.0: {} From 61706a5e07fd17ef423b4559e145130fa063f736 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Wed, 19 Aug 2026 13:21:50 -0400 Subject: [PATCH 14/20] Move browser alias runtime behind the experimental subpath --- .changeset/dev-console-browser-aliases.md | 2 +- packages/aliases/experimental.d.ts | 17 +++++++ packages/aliases/package.json | 9 ++++ packages/aliases/src/browser.ts | 6 +++ .../{index.test.ts => experimental.test.ts} | 4 +- packages/aliases/src/index.ts | 32 ++++---------- packages/aliases/src/public/experimental.ts | 44 +++++++++++++++++++ packages/aliases/src/public/node.ts | 3 +- 8 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 packages/aliases/experimental.d.ts rename packages/aliases/src/{index.test.ts => experimental.test.ts} (97%) create mode 100644 packages/aliases/src/public/experimental.ts diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index c7772c0b7e6..88c621b7f20 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -7,4 +7,4 @@ Extract the alias runtime into a new `@osdk/aliases` package so consumers that a `@osdk/functions` re-exports it, so its public `Aliases` namespace is unchanged. -The new package has two entry points. `@osdk/aliases` is browser-safe, for applications served to a browser such as Developer Console apps: call `await initAliases()` once at startup, then read values synchronously with `custom("myAlias")`. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only when that file is genuinely absent, meaning a 404 or the markup a single-page-app host serves in place of one, so a transient server error surfaces instead of silently substituting defaults for the installer's values. `@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node with a container filesystem. +The new package has two entry points. `@osdk/aliases/experimental` is browser-safe, for applications served to a browser such as Developer Console apps: call `await Aliases.initAliases()` once at startup, then read values synchronously with `Aliases.custom("myAlias")`. It sits behind the `experimental` subpath because both custom aliases and the shape of this API are provisional; expect it to move to the package root once the design settles. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only when that file is genuinely absent, meaning a 404 or the markup a single-page-app host serves in place of one, so a transient server error surfaces instead of silently substituting defaults for the installer's values. `@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node with a container filesystem. diff --git a/packages/aliases/experimental.d.ts b/packages/aliases/experimental.d.ts new file mode 100644 index 00000000000..fe26fffdfaf --- /dev/null +++ b/packages/aliases/experimental.d.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from "./build/cjs/public/experimental.cjs"; diff --git a/packages/aliases/package.json b/packages/aliases/package.json index c08627c7f6d..b647d37c7a5 100644 --- a/packages/aliases/package.json +++ b/packages/aliases/package.json @@ -16,6 +16,15 @@ "require": "./build/cjs/index.cjs", "default": "./build/browser/index.js" }, + "./experimental": { + "browser": "./build/browser/public/experimental.js", + "import": { + "types": "./build/types/public/experimental.d.ts", + "default": "./build/esm/public/experimental.js" + }, + "require": "./build/cjs/public/experimental.cjs", + "default": "./build/browser/public/experimental.js" + }, "./node": { "browser": "./build/browser/public/node.js", "import": { diff --git a/packages/aliases/src/browser.ts b/packages/aliases/src/browser.ts index 407cbc0b626..0948728c3c2 100644 --- a/packages/aliases/src/browser.ts +++ b/packages/aliases/src/browser.ts @@ -90,6 +90,9 @@ let inFlight: Promise | undefined; * Fetches and caches the resolved aliases for this installation. Call once at * application startup and await it before reading any aliases. Repeated calls * are deduplicated and become no-ops once the aliases are cached. + * + * @experimental Exposed only via "@osdk/aliases/experimental". Both custom + * aliases and the shape of this API are provisional and may change. */ export async function initAliases(options?: InitAliasesOptions): Promise { if (cachedCustomAliases !== undefined) { @@ -282,6 +285,9 @@ function parseResolvedAliases(raw: string): Record { /** * Returns the resolved value for a custom alias. Aliases must have been loaded * via {@link initAliases} first; otherwise this throws. + * + * @experimental Exposed only via "@osdk/aliases/experimental". Both custom + * aliases and the shape of this API are provisional and may change. */ export function custom(alias: string): Custom { if (cachedCustomAliases === undefined) { diff --git a/packages/aliases/src/index.test.ts b/packages/aliases/src/experimental.test.ts similarity index 97% rename from packages/aliases/src/index.test.ts rename to packages/aliases/src/experimental.test.ts index be01abb54a5..bbc21a123c8 100644 --- a/packages/aliases/src/index.test.ts +++ b/packages/aliases/src/experimental.test.ts @@ -29,7 +29,7 @@ import { DEFAULT_DEPLOYMENT_CONFIG_PATH, initAliases, resetAliasesCache, -} from "./index.js"; +} from "./public/experimental.js"; const DECLARATIONS = { aliases: { custom: { apiBaseUrl: { value: "https://api.example.com" } } }, @@ -46,7 +46,7 @@ function mockFetch(): typeof globalThis.fetch { })) as unknown as typeof globalThis.fetch; } -describe("browser entry point", () => { +describe("experimental browser entry point", () => { afterEach(() => { resetAliasesCache(); }); diff --git a/packages/aliases/src/index.ts b/packages/aliases/src/index.ts index e89d8793974..17ba8ab425d 100644 --- a/packages/aliases/src/index.ts +++ b/packages/aliases/src/index.ts @@ -14,29 +14,13 @@ * limitations under the License. */ -// Default entry point: the browser-safe alias runtime, for applications served -// to a browser such as Developer Console apps. It is free of `fs` and `process` -// so it can be bundled. +// Intentionally empty: this package has no stable API yet. // -// import { Aliases } from "@osdk/aliases"; -// await Aliases.initAliases(); -// const apiBaseUrl = Aliases.custom("apiBaseUrl"); +// The package root is reserved for the browser alias runtime once its design +// settles. Until then it lives at "@osdk/aliases/experimental", so that callers +// see the stability of what they are importing at the import site rather than +// having to read docs. Promoting it here later is additive for consumers, who +// keep working off the experimental subpath until it is deprecated. // -// The `Aliases` namespace is the preferred form, because it matches the -// namespace @osdk/functions already exposes for the filesystem runtime, and -// because a bare `custom("apiBaseUrl")` does not say what it reads. The same -// members are also exported individually for callers who prefer named imports. -// -// Code running in Node with a filesystem (Functions) should import the -// "@osdk/aliases/node" subpath instead, which reads aliases from disk. - -export * as Aliases from "./browser.js"; - -export { - custom, - DEFAULT_DECLARATIONS_PATH, - DEFAULT_DEPLOYMENT_CONFIG_PATH, - initAliases, - resetAliasesCache, -} from "./browser.js"; -export type { Custom, InitAliasesOptions } from "./browser.js"; +// The filesystem runtime, which is not experimental, is at +// "@osdk/aliases/node". diff --git a/packages/aliases/src/public/experimental.ts b/packages/aliases/src/public/experimental.ts new file mode 100644 index 00000000000..2dc16278d0a --- /dev/null +++ b/packages/aliases/src/public/experimental.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXPERIMENTAL browser-safe alias runtime, for applications served to a browser +// such as Developer Console apps. Free of `fs` and `process` so it can be +// bundled. +// +// import { Aliases } from "@osdk/aliases/experimental"; +// await Aliases.initAliases(); +// const apiBaseUrl = Aliases.custom("apiBaseUrl"); +// +// This lives behind the "experimental" subpath deliberately: both custom aliases +// themselves and the shape of this API are provisional, so the import path says +// so at every call site. Expect it to move to the package root once the design +// settles, at which point this subpath will be deprecated rather than removed +// out from under callers. +// +// Code running in Node with a filesystem (Functions) should import +// "@osdk/aliases/node" instead, which reads aliases from disk and is not +// experimental. + +export * as Aliases from "../browser.js"; + +export { + custom, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, + resetAliasesCache, +} from "../browser.js"; +export type { Custom, InitAliasesOptions } from "../browser.js"; diff --git a/packages/aliases/src/public/node.ts b/packages/aliases/src/public/node.ts index 9daaaccbd5b..f0e6a51ee26 100644 --- a/packages/aliases/src/public/node.ts +++ b/packages/aliases/src/public/node.ts @@ -17,7 +17,8 @@ // Filesystem-backed alias runtime, for code running in Node with a container // filesystem (Functions). Reads the aliases file whose path the runtime supplies // through an environment variable. This entry point uses `fs` and so cannot be -// bundled into a browser; browser applications should import "@osdk/aliases". +// bundled into a browser; browser applications should import +// "@osdk/aliases/experimental". // // `@osdk/functions` re-exports this as its `Aliases` namespace, so its public // API is unchanged. From 121535265fbcf7b9e409b04086ecd93dff2f8aaf Mon Sep 17 00:00:00 2001 From: James Zhang Date: Thu, 20 Aug 2026 16:29:32 -0400 Subject: [PATCH 15/20] API extractor, prototype lookup, value validation --- .changeset/dev-console-browser-aliases.md | 6 +- etc/functions.report.api.md | 65 ++------- packages/aliases/src/browser.test.ts | 83 +++++++++++ packages/aliases/src/browser.ts | 151 +++++++++----------- packages/aliases/src/experimental.test.ts | 12 +- packages/aliases/src/index.ts | 14 +- packages/aliases/src/public/experimental.ts | 37 +++-- packages/functions/src/aliases/index.ts | 36 +++++ packages/functions/src/index.ts | 8 +- 9 files changed, 241 insertions(+), 171 deletions(-) create mode 100644 packages/functions/src/aliases/index.ts diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index 88c621b7f20..a1096e43dc2 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -3,8 +3,8 @@ "@osdk/functions": minor --- -Extract the alias runtime into a new `@osdk/aliases` package so consumers that are not Functions can read aliases without depending on `@osdk/functions`. +Add `@osdk/aliases`, extracting the alias runtime so consumers that are not Functions can read aliases without depending on `@osdk/functions`. The `Aliases` namespace exported by `@osdk/functions` is unchanged. -`@osdk/functions` re-exports it, so its public `Aliases` namespace is unchanged. +`@osdk/aliases/experimental` is a new browser-safe entry point for applications served to a browser, such as Developer Console apps: call `await Aliases.initAliases()` once at startup, then read values synchronously with `Aliases.custom("myAlias")`. It reads the installer's resolved values on a Marketplace-installed site and falls back to the author's declared defaults in `public/resources.json` otherwise. It sits behind the `experimental` subpath because both custom aliases and the shape of this API are provisional. -The new package has two entry points. `@osdk/aliases/experimental` is browser-safe, for applications served to a browser such as Developer Console apps: call `await Aliases.initAliases()` once at startup, then read values synchronously with `Aliases.custom("myAlias")`. It sits behind the `experimental` subpath because both custom aliases and the shape of this API are provisional; expect it to move to the package root once the design settles. It reads the installer's resolved values from the deployment config on a Marketplace-installed site, and falls back to the author's declared defaults in `public/resources.json` when that file is absent (local development, or a site deployed without Marketplace). The fallback triggers only when that file is genuinely absent, meaning a 404 or the markup a single-page-app host serves in place of one, so a transient server error surfaces instead of silently substituting defaults for the installer's values. `@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node with a container filesystem. +`@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node. diff --git a/etc/functions.report.api.md b/etc/functions.report.api.md index c66ee607962..102ed50a151 100644 --- a/etc/functions.report.api.md +++ b/etc/functions.report.api.md @@ -8,6 +8,10 @@ import type { ActionDefinition } from '@osdk/client'; import { Attachment } from '@osdk/client'; import type { Client } from '@osdk/client'; import type { CompileTimeMetadata } from '@osdk/client'; +import { Custom } from '@osdk/aliases/node'; +import { custom } from '@osdk/aliases/node'; +import { Dataset } from '@osdk/aliases/node'; +import { dataset } from '@osdk/aliases/node'; import { Geometry } from 'geojson'; import { GeometryCollection } from 'geojson'; import type { GroupId as GroupId_2 } from '@osdk/foundry.core'; @@ -15,7 +19,11 @@ import type { InterfaceDefinition } from '@osdk/client'; import { LineString } from 'geojson'; import type { Media } from '@osdk/client'; import { MediaReference } from '@osdk/client'; +import { Mediaset } from '@osdk/aliases/node'; +import { mediaset } from '@osdk/aliases/node'; import { MediaUpload } from '@osdk/client'; +import { Model } from '@osdk/aliases/node'; +import { model } from '@osdk/aliases/node'; import { MultiLineString } from 'geojson'; import { MultiPoint } from 'geojson'; import { MultiPolygon } from 'geojson'; @@ -28,6 +36,10 @@ import { Polygon } from 'geojson'; import type { PropertyKeys } from '@osdk/client'; import type { QueryDefinition } from '@osdk/client'; import { Range as Range_2 } from '@osdk/client'; +import { Source } from '@osdk/aliases/node'; +import { source } from '@osdk/aliases/node'; +import { Stream } from '@osdk/aliases/node'; +import { stream } from '@osdk/aliases/node'; import { ThreeDimensionalAggregation } from '@osdk/client'; import { TwoDimensionalAggregation } from '@osdk/client'; import type { UserId as UserId_2 } from '@osdk/foundry.core'; @@ -72,23 +84,6 @@ export type ClassificationMarking = T & { // @public (undocumented) export function createEditBatch(_client: Client): EditBatch; -// @public (undocumented) -type Custom = string & { - readonly __brand: "Custom" -}; - -// @public (undocumented) -function custom(alias: string): Custom; - -// @public (undocumented) -interface Dataset { - // (undocumented) - rid: string; -} - -// @public (undocumented) -function dataset(alias: string): Dataset; - // @public (undocumented) export type DateISOString = T & { __dateBrand?: void @@ -216,26 +211,8 @@ export type MandatoryMarking = T & { export { MediaReference } -// @public (undocumented) -interface Mediaset { - // (undocumented) - rid: string; -} - -// @public (undocumented) -function mediaset(alias: string): Mediaset; - export { MediaUpload } -// @public (undocumented) -interface Model { - // (undocumented) - rid: string; -} - -// @public (undocumented) -function model(alias: string): Model; - export { MultiLineString } export { MultiPoint } @@ -308,24 +285,6 @@ export type Short = T & { _shortBrand?: void }; -// @public (undocumented) -interface Source { - // (undocumented) - rid: string; -} - -// @public (undocumented) -function source(alias: string): Source; - -// @public (undocumented) -interface Stream { - // (undocumented) - rid: string; -} - -// @public (undocumented) -function stream(alias: string): Stream; - export { ThreeDimensionalAggregation } // @public (undocumented) diff --git a/packages/aliases/src/browser.test.ts b/packages/aliases/src/browser.test.ts index 2b743e7d396..4b5b8f982cb 100644 --- a/packages/aliases/src/browser.test.ts +++ b/packages/aliases/src/browser.test.ts @@ -147,6 +147,29 @@ describe("browser aliases", () => { "Custom alias 'anything' not found. Available aliases: []", ); }); + + // `alias in cache` would match inherited properties, so these would resolve + // to a function or the prototype object despite the declared return type. + it.each(["toString", "constructor", "__proto__", "hasOwnProperty"])( + "does not resolve inherited property %s", + async (inherited) => { + await initAliases({ fetch: mockFetch({ body: CONFIG_WITH_ALIASES }) }); + + expect(() => custom(inherited)).toThrow( + `Custom alias '${inherited}' not found`, + ); + }, + ); + + it("resolves an alias whose key shadows an inherited property", async () => { + // The guard must reject inherited keys without rejecting a real alias that + // happens to share the name. + await initAliases({ + fetch: mockFetch({ body: { aliases: '{"toString":"shadowed"}' } }), + }); + + expect(custom("toString")).toBe("shadowed"); + }); }); describe("initAliases", () => { @@ -337,6 +360,66 @@ describe("browser aliases", () => { // Dev mode: the declaration file nests values under aliases.custom, so the // loader has to flatten it. Prod and dev are told apart by the runtime type of // `aliases` (string vs object), never by falling back between paths. + describe("value validation", () => { + // The file is served, not written by application code, so nothing upstream + // guarantees the values are strings. Without checking, a number would flow + // into custom() and violate its declared return type. + it("rejects a non-string resolved value", async () => { + await expect( + initAliases({ + fetch: mockFetch({ body: { aliases: '{"apiBaseUrl":5}' } }), + }), + ).rejects.toThrow("Alias 'apiBaseUrl' must be a string, got number"); + }); + + it("rejects a nested object resolved value", async () => { + await expect( + initAliases({ + fetch: mockFetch({ body: { aliases: '{"apiBaseUrl":{"a":1}}' } }), + }), + ).rejects.toThrow("must be a string, got object"); + }); + + it("rejects a non-string declared default", async () => { + await expect( + initAliases({ + path: DEFAULT_DECLARATIONS_PATH, + fetch: mockFetch({ + body: { aliases: { custom: { apiBaseUrl: { value: 5 } } } }, + }), + }), + ).rejects.toThrow("must be a string, got number"); + }); + }); + + describe("absence detection", () => { + // Treating any leading '<' as absence would let a proxy or auth error page + // silently substitute the author's defaults for the installer's values. + it("does not treat a non-html markup body as absent", async () => { + await expect( + initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { + text: "AccessDenied", + }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }), + ).rejects.toThrow("not valid JSON"); + }); + + it("treats an html document as absent", async () => { + await initAliases({ + fetch: mockFetchByPath({ + [DEFAULT_DEPLOYMENT_CONFIG_PATH]: { text: INDEX_HTML }, + [DEFAULT_DECLARATIONS_PATH]: { body: DECLARATIONS_FILE }, + }), + }); + + expect(custom("apiBaseUrl")).toBe("https://api.dev.example.com"); + }); + }); + describe("declaration file (dev) shape", () => { it("flattens declared defaults", async () => { await initAliases({ diff --git a/packages/aliases/src/browser.ts b/packages/aliases/src/browser.ts index 0948728c3c2..26b9d12f35a 100644 --- a/packages/aliases/src/browser.ts +++ b/packages/aliases/src/browser.ts @@ -21,25 +21,25 @@ // served JSON file once, caches it, and then serves custom() synchronously. This // file must stay free of `fs`/`process` so it can be bundled into a browser app. // -// Two files can supply aliases, mirroring how the Node runtime has PUBLISHED and -// LIVE_PREVIEW modes: +// Two files can supply aliases: // // production .palantir/deployment.config.json written at install, so it // carries the INSTALLER's values // development public/resources.json the author's declaration file, // so it carries the DEFAULTS // -// Callers do not choose: we try the deployment config and fall back to the -// declaration file only when it is absent. Absence takes two forms, because a -// single-page-app host rewrites unknown paths to index.html and answers 200 -// rather than 404: either a 404, or a 200 carrying markup. Both mean the file -// genuinely is not there (local dev, or a site deployed without going through -// Marketplace). Any other failure throws, because in production BOTH files are -// served, so falling back on a transient error would silently serve the -// developer's defaults in place of the installer's values. +// Callers do not choose. We try the deployment config and fall back to the +// declaration file only when it appears absent, which is either a 404 or a 200 +// carrying an HTML document (single-page-app hosts rewrite unknown paths to +// index.html). Any other failure throws rather than falling back, because in +// production both files are served, so treating an error as absence would +// silently serve the author's defaults in place of the installer's values. +// +// Absence detection is a heuristic, not a proof: a proxy or authentication page +// could also arrive as a 200 with an HTML document. See isHtmlDocument. // // The two files are told apart by the runtime type of their `aliases` field -// (string vs object), which is unambiguous. +// (string vs object). import type { AliasDeclarationsFile, @@ -49,31 +49,18 @@ import type { export type { Custom } from "./types.js"; -/** - * Default path to the deployment config file served by Foundry website - * hosting. Resolved relative to the document base URI so apps served under a - * subpath still find it. Carries the installer's resolved values. - */ +/** Written at install time, so it carries the installer's resolved values. */ export const DEFAULT_DEPLOYMENT_CONFIG_PATH = ".palantir/deployment.config.json"; -/** - * Path to the author-maintained declaration file, served from `public/` by the - * Vite dev server. Carries the developer's declared defaults, so it is the - * right source during local development where there is no installer. - */ +/** The author's declaration file, so it carries the declared defaults. */ export const DEFAULT_DECLARATIONS_PATH = "resources.json"; export interface InitAliasesOptions { /** - * Escape hatch to force a single specific file. Relative paths are resolved - * against `document.baseURI`. - * - * Normal applications should omit this. By default `initAliases()` tries - * {@link DEFAULT_DEPLOYMENT_CONFIG_PATH} and falls back to - * {@link DEFAULT_DECLARATIONS_PATH} on a 404, which covers local development - * and installed sites alike. When this option is set there is no fallback: a - * missing file throws. + * Escape hatch to force one specific file, relative to `document.baseURI`. + * Setting it disables the fallback, so a missing file throws. Normal + * applications should omit it. */ path?: string; /** @@ -117,8 +104,7 @@ async function loadAliases(options?: InitAliasesOptions): Promise { ); } - // An explicit path is honored as given: the caller chose that file, so a - // missing file is an error rather than a cue to look somewhere else. + // An explicit path is honored as given, so a missing file is an error. if (options?.path != null) { const explicit = await fetchAliases(fetchImpl, options.path); if (explicit === undefined) { @@ -139,10 +125,8 @@ async function loadAliases(options?: InitAliasesOptions): Promise { return; } - // The deployment config only exists on a site installed through Marketplace, - // so a 404 is expected during local development and for a site deployed - // straight from Developer Console. Fall back to the author's declared - // defaults. Warn so that a fallback on a real installed site is visible. + // Warn so that a fallback on a real installed site, where the deployment + // config should exist, is visible rather than silent. console.warn( `No alias config at ${resolveUrl(DEFAULT_DEPLOYMENT_CONFIG_PATH)}, ` + `falling back to declared defaults in ${resolveUrl( @@ -154,11 +138,7 @@ async function loadAliases(options?: InitAliasesOptions): Promise { (await fetchAliases(fetchImpl, DEFAULT_DECLARATIONS_PATH)) ?? {}; } -/** - * Fetches and reads one alias file. Returns `undefined` when the file is absent, - * which callers may treat as a cue to fall back. Any other failure throws, so a - * transient server error never silently degrades to the declared defaults. - */ +/** Returns `undefined` when the file appears absent. Any other failure throws. */ async function fetchAliases( fetchImpl: typeof globalThis.fetch, path: string, @@ -170,19 +150,15 @@ async function fetchAliases( } if (!response.ok) { throw new Error( - `Failed to load aliases from ${url}: ${response.status} ${ - response.statusText - }`, + `Failed to load aliases from ${url}: ${response.status} ${response.statusText}`, ); } const body = await response.text(); - // A single-page-app host rewrites unknown paths to index.html and answers 200 - // rather than 404 (the Vite dev server and Foundry website hosting both do - // this). Markup where JSON was expected therefore means "this file is not - // here", exactly like a 404, so treat it the same way. - if (isMarkup(body)) { + // Single-page-app hosts rewrite unknown paths to index.html and answer 200, + // so an HTML document is how "not found" usually presents. + if (isHtmlDocument(body)) { return undefined; } @@ -190,23 +166,18 @@ async function fetchAliases( } /** - * True when the body is markup rather than the JSON we asked for, which indicates - * a single-page-app rewrite rather than a real config file. - * - * The body is sniffed rather than the declared content type because that type is - * not a reliable signal: Foundry website hosting derives it from the file - * extension and does not recognize `.json`, so even a real config file can arrive - * as `application/octet-stream`. A leading `<` is unambiguous, since valid JSON - * can never begin with one. + * A document preamble only, not any body starting with `<`. This result means + * "absent", which triggers the fallback, and a proxy or auth page can also be a + * 200 with markup; requiring a preamble keeps XML and partial-HTML error bodies + * from qualifying. Sniffed rather than read from the content type because + * Foundry website hosting derives that from the file extension and does not + * recognize `.json`. */ -function isMarkup(body: string): boolean { - return body.trimStart().startsWith("<"); +function isHtmlDocument(body: string): boolean { + const start = body.trimStart().slice(0, 32).toLowerCase(); + return start.startsWith(" { const aliases = config.aliases; - // Apps that declare no aliases simply omit the key. if (aliases == null || aliases === "") { return {}; } - // Production: deployment.config.json stores a stringified JSON object. + // Production packs resolved values into a stringified JSON object. if (typeof aliases === "string") { return parseResolvedAliases(aliases); } - // Development: the declaration file nests { custom: { key: { value } } }. + // Development nests { custom: { key: { value } } }. const declarations = aliases.custom; if (declarations == null) { return {}; @@ -251,11 +217,13 @@ function extractAliases( `Failed to read aliases from ${url}: 'aliases.custom' must be an object.`, ); } - return Object.fromEntries( - Object.entries(declarations).map(([key, declaration]) => [ - key, - declaration?.value ?? "", - ]), + return toStringRecord( + Object.fromEntries( + Object.entries(declarations).map(([key, declaration]) => [ + key, + declaration?.value ?? "", + ]), + ), ); } @@ -279,7 +247,29 @@ function parseResolvedAliases(raw: string): Record { if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) { throw new Error("Resolved aliases must be a JSON object of string values."); } - return parsed as Record; + return toStringRecord(parsed as Record); +} + +/** + * Narrows to string values. The file is served rather than written by + * application code, so without this a number would reach `custom()` and violate + * its declared return type. + */ +function toStringRecord( + parsed: Record, +): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(parsed)) { + if (typeof value !== "string") { + throw new TypeError( + `Alias '${key}' must be a string, got ${ + Array.isArray(value) ? "array" : typeof value + }.`, + ); + } + result[key] = value; + } + return result; } /** @@ -296,7 +286,9 @@ export function custom(alias: string): Custom { "reading aliases.", ); } - if (!(alias in cachedCustomAliases)) { + // `hasOwn`, not `in`: `in` matches inherited properties, so `toString` would + // resolve to a function and `__proto__` to the prototype. + if (!Object.hasOwn(cachedCustomAliases, alias)) { const available = Object.keys(cachedCustomAliases); throw new Error( `Custom alias '${alias}' not found. Available aliases: [${available.join( @@ -307,10 +299,7 @@ export function custom(alias: string): Custom { return cachedCustomAliases[alias] as Custom; } -/** - * Clears the cached aliases. Primarily for tests; production code should not - * need to reset the cache. - */ +/** For tests. Deliberately not part of the public entry point. */ export function resetAliasesCache(): void { cachedCustomAliases = undefined; inFlight = undefined; diff --git a/packages/aliases/src/experimental.test.ts b/packages/aliases/src/experimental.test.ts index bbc21a123c8..fa791092848 100644 --- a/packages/aliases/src/experimental.test.ts +++ b/packages/aliases/src/experimental.test.ts @@ -22,13 +22,13 @@ import { afterEach, describe, expect, it } from "vitest"; import * as browser from "./browser.js"; +import { resetAliasesCache } from "./browser.js"; import { Aliases, custom, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, initAliases, - resetAliasesCache, } from "./public/experimental.js"; const DECLARATIONS = { @@ -62,7 +62,6 @@ describe("experimental browser entry point", () => { // never drift apart. expect(Aliases.custom).toBe(custom); expect(Aliases.initAliases).toBe(initAliases); - expect(Aliases.resetAliasesCache).toBe(resetAliasesCache); expect(Aliases.DEFAULT_DECLARATIONS_PATH).toBe(DEFAULT_DECLARATIONS_PATH); expect(Aliases.DEFAULT_DEPLOYMENT_CONFIG_PATH).toBe( DEFAULT_DEPLOYMENT_CONFIG_PATH, @@ -76,9 +75,10 @@ describe("experimental browser entry point", () => { expect(Aliases).not.toHaveProperty("source"); }); - it("namespace covers every public member of the module", () => { - // Guards against a new export being added to browser.ts but omitted from - // the namespace, which would make the two styles inconsistent. - expect(Object.keys(Aliases).sort()).toEqual(Object.keys(browser).sort()); + it("keeps the test-only cache reset out of the public surface", () => { + // Tests import it from ../browser.js. Exporting it would make cache + // invalidation supported and would race with an in-flight initAliases(). + expect(Aliases).not.toHaveProperty("resetAliasesCache"); + expect(browser.resetAliasesCache).toBeTypeOf("function"); }); }); diff --git a/packages/aliases/src/index.ts b/packages/aliases/src/index.ts index 17ba8ab425d..5f2f6117418 100644 --- a/packages/aliases/src/index.ts +++ b/packages/aliases/src/index.ts @@ -14,13 +14,7 @@ * limitations under the License. */ -// Intentionally empty: this package has no stable API yet. -// -// The package root is reserved for the browser alias runtime once its design -// settles. Until then it lives at "@osdk/aliases/experimental", so that callers -// see the stability of what they are importing at the import site rather than -// having to read docs. Promoting it here later is additive for consumers, who -// keep working off the experimental subpath until it is deprecated. -// -// The filesystem runtime, which is not experimental, is at -// "@osdk/aliases/node". +// Intentionally empty: this package has no stable API yet. The root is reserved +// for the browser runtime once its design settles, so that today's callers see +// the stability of what they import at the import site. Until then, see +// "@osdk/aliases/experimental" and "@osdk/aliases/node". diff --git a/packages/aliases/src/public/experimental.ts b/packages/aliases/src/public/experimental.ts index 2dc16278d0a..1821cb2aa41 100644 --- a/packages/aliases/src/public/experimental.ts +++ b/packages/aliases/src/public/experimental.ts @@ -15,30 +15,41 @@ */ // EXPERIMENTAL browser-safe alias runtime, for applications served to a browser -// such as Developer Console apps. Free of `fs` and `process` so it can be -// bundled. +// such as Developer Console apps. // // import { Aliases } from "@osdk/aliases/experimental"; // await Aliases.initAliases(); // const apiBaseUrl = Aliases.custom("apiBaseUrl"); // -// This lives behind the "experimental" subpath deliberately: both custom aliases -// themselves and the shape of this API are provisional, so the import path says -// so at every call site. Expect it to move to the package root once the design -// settles, at which point this subpath will be deprecated rather than removed -// out from under callers. +// Behind the "experimental" subpath deliberately: both custom aliases and the +// shape of this API are provisional, so the import path says so at every call +// site. Expect it to move to the package root once the design settles, with this +// subpath deprecated rather than removed. // -// Code running in Node with a filesystem (Functions) should import -// "@osdk/aliases/node" instead, which reads aliases from disk and is not -// experimental. +// Code running in Node with a filesystem (Functions) wants "@osdk/aliases/node". -export * as Aliases from "../browser.js"; +import { + custom, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, + initAliases, +} from "../browser.js"; + +// Assembled member by member rather than with `export * as Aliases` so that +// resetAliasesCache stays out of the supported surface. It exists for tests, +// which import ../browser.js directly; exporting it would make cache +// invalidation public and would race with an in-flight initAliases(). +export const Aliases = { + custom, + initAliases, + DEFAULT_DECLARATIONS_PATH, + DEFAULT_DEPLOYMENT_CONFIG_PATH, +} as const; export { custom, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, initAliases, - resetAliasesCache, -} from "../browser.js"; +}; export type { Custom, InitAliasesOptions } from "../browser.js"; diff --git a/packages/functions/src/aliases/index.ts b/packages/functions/src/aliases/index.ts new file mode 100644 index 00000000000..08ad1af9626 --- /dev/null +++ b/packages/functions/src/aliases/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Compatibility boundary: the alias runtime lives in @osdk/aliases so consumers +// that are not Functions can read aliases without depending on this package. +// +// A local module with explicit named re-exports, because API Extractor cannot +// process `export * as ns` from an external package ("fetchAstModuleExportInfo() +// is not supported for external modules") and rejects a namespace whose target +// uses `export *` ("The Aliases namespace import includes a star export"). + +export { custom } from "@osdk/aliases/node"; +export type { Custom } from "@osdk/aliases/node"; +export { dataset } from "@osdk/aliases/node"; +export type { Dataset } from "@osdk/aliases/node"; +export { mediaset } from "@osdk/aliases/node"; +export type { Mediaset } from "@osdk/aliases/node"; +export { model } from "@osdk/aliases/node"; +export type { Model } from "@osdk/aliases/node"; +export { source } from "@osdk/aliases/node"; +export type { Source } from "@osdk/aliases/node"; +export { stream } from "@osdk/aliases/node"; +export type { Stream } from "@osdk/aliases/node"; diff --git a/packages/functions/src/index.ts b/packages/functions/src/index.ts index 19e1ad2a601..d3a535a227e 100644 --- a/packages/functions/src/index.ts +++ b/packages/functions/src/index.ts @@ -34,11 +34,9 @@ export type { TwoDimensionalAggregation, } from "@osdk/client"; -// The alias runtime lives in @osdk/aliases so that consumers which are not -// Functions (Developer Console apps, and later other pro-code surfaces) can use -// it without depending on this package. Re-exported here so the public -// `Aliases` namespace of @osdk/functions is unchanged. -export * as Aliases from "@osdk/aliases/node"; +// Re-exported from @osdk/aliases through a local facade so the public `Aliases` +// namespace of @osdk/functions is unchanged. See ./aliases/index.ts. +export * as Aliases from "./aliases/index.js"; export { createEditBatch } from "./edits/createEditBatch.js"; export type { EditBatch } from "./edits/EditBatch.js"; export type { Edits } from "./edits/types.js"; From d5e96c12467637a15afadc0b5947e7129469f922 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Thu, 20 Aug 2026 16:42:13 -0400 Subject: [PATCH 16/20] Fix isolatedDeclarations build and __proto__ alias handling --- packages/aliases/src/browser.test.ts | 49 ++++++++++++++++----- packages/aliases/src/browser.ts | 5 ++- packages/aliases/src/public/experimental.ts | 11 ++++- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/packages/aliases/src/browser.test.ts b/packages/aliases/src/browser.test.ts index 4b5b8f982cb..7e97e6404d2 100644 --- a/packages/aliases/src/browser.test.ts +++ b/packages/aliases/src/browser.test.ts @@ -148,9 +148,19 @@ describe("browser aliases", () => { ); }); - // `alias in cache` would match inherited properties, so these would resolve - // to a function or the prototype object despite the declared return type. - it.each(["toString", "constructor", "__proto__", "hasOwnProperty"])( + // Two halves of one requirement, run over the same names: an inherited + // property is never an alias, and a real alias sharing that name still + // resolves. `__proto__` catches the most, since `in` accepts it inherited + // and plain property assignment silently drops it. + const INHERITED_NAMES = [ + "toString", + "constructor", + "__proto__", + "hasOwnProperty", + "valueOf", + ]; + + it.each(INHERITED_NAMES)( "does not resolve inherited property %s", async (inherited) => { await initAliases({ fetch: mockFetch({ body: CONFIG_WITH_ALIASES }) }); @@ -161,15 +171,32 @@ describe("browser aliases", () => { }, ); - it("resolves an alias whose key shadows an inherited property", async () => { - // The guard must reject inherited keys without rejecting a real alias that - // happens to share the name. - await initAliases({ - fetch: mockFetch({ body: { aliases: '{"toString":"shadowed"}' } }), - }); + it.each(INHERITED_NAMES)( + "resolves a real alias named %s from the deployment config", + async (name) => { + await initAliases({ + fetch: mockFetch({ + body: { aliases: JSON.stringify({ [name]: "real-value" }) }, + }), + }); - expect(custom("toString")).toBe("shadowed"); - }); + expect(custom(name)).toBe("real-value"); + }, + ); + + it.each(INHERITED_NAMES)( + "resolves a real alias named %s from the declaration file", + async (name) => { + await initAliases({ + path: DEFAULT_DECLARATIONS_PATH, + fetch: mockFetch({ + body: { aliases: { custom: { [name]: { value: "real-value" } } } }, + }), + }); + + expect(custom(name)).toBe("real-value"); + }, + ); }); describe("initAliases", () => { diff --git a/packages/aliases/src/browser.ts b/packages/aliases/src/browser.ts index 26b9d12f35a..896978c0285 100644 --- a/packages/aliases/src/browser.ts +++ b/packages/aliases/src/browser.ts @@ -258,7 +258,10 @@ function parseResolvedAliases(raw: string): Record { function toStringRecord( parsed: Record, ): Record { - const result: Record = {}; + // Null prototype, so assigning a key named `__proto__` creates an own + // property. On a normal object that assignment hits the inherited `__proto__` + // setter, which ignores a string value, silently dropping the alias. + const result = Object.create(null) as Record; for (const [key, value] of Object.entries(parsed)) { if (typeof value !== "string") { throw new TypeError( diff --git a/packages/aliases/src/public/experimental.ts b/packages/aliases/src/public/experimental.ts index 1821cb2aa41..d03b2ed12a3 100644 --- a/packages/aliases/src/public/experimental.ts +++ b/packages/aliases/src/public/experimental.ts @@ -39,12 +39,19 @@ import { // resetAliasesCache stays out of the supported surface. It exists for tests, // which import ../browser.js directly; exporting it would make cache // invalidation public and would race with an in-flight initAliases(). -export const Aliases = { +// Explicitly typed: `--isolatedDeclarations` cannot infer a declaration for an +// object literal built from shorthand properties (TS9016). +export const Aliases: { + readonly custom: typeof custom; + readonly initAliases: typeof initAliases; + readonly DEFAULT_DECLARATIONS_PATH: typeof DEFAULT_DECLARATIONS_PATH; + readonly DEFAULT_DEPLOYMENT_CONFIG_PATH: typeof DEFAULT_DEPLOYMENT_CONFIG_PATH; +} = { custom, initAliases, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, -} as const; +}; export { custom, From fecb7a14d0f462aa4919cfe19e9645e9b9f6aa3d Mon Sep 17 00:00:00 2001 From: James Zhang Date: Thu, 20 Aug 2026 16:47:50 -0400 Subject: [PATCH 17/20] Tighten comments in the alias runtime --- packages/aliases/src/browser.test.ts | 9 ------- packages/aliases/src/browser.ts | 30 ++++++--------------- packages/aliases/src/public/experimental.ts | 10 +++---- packages/functions/src/aliases/index.ts | 11 ++++---- packages/functions/src/index.ts | 3 +-- 5 files changed, 18 insertions(+), 45 deletions(-) diff --git a/packages/aliases/src/browser.test.ts b/packages/aliases/src/browser.test.ts index 7e97e6404d2..8038bb4db7f 100644 --- a/packages/aliases/src/browser.test.ts +++ b/packages/aliases/src/browser.test.ts @@ -148,10 +148,6 @@ describe("browser aliases", () => { ); }); - // Two halves of one requirement, run over the same names: an inherited - // property is never an alias, and a real alias sharing that name still - // resolves. `__proto__` catches the most, since `in` accepts it inherited - // and plain property assignment silently drops it. const INHERITED_NAMES = [ "toString", "constructor", @@ -388,9 +384,6 @@ describe("browser aliases", () => { // loader has to flatten it. Prod and dev are told apart by the runtime type of // `aliases` (string vs object), never by falling back between paths. describe("value validation", () => { - // The file is served, not written by application code, so nothing upstream - // guarantees the values are strings. Without checking, a number would flow - // into custom() and violate its declared return type. it("rejects a non-string resolved value", async () => { await expect( initAliases({ @@ -420,8 +413,6 @@ describe("browser aliases", () => { }); describe("absence detection", () => { - // Treating any leading '<' as absence would let a proxy or auth error page - // silently substitute the author's defaults for the installer's values. it("does not treat a non-html markup body as absent", async () => { await expect( initAliases({ diff --git a/packages/aliases/src/browser.ts b/packages/aliases/src/browser.ts index 896978c0285..890b3f9cbfa 100644 --- a/packages/aliases/src/browser.ts +++ b/packages/aliases/src/browser.ts @@ -14,32 +14,18 @@ * limitations under the License. */ -// Browser-safe alias runtime for Dev Console applications. -// -// Unlike the Node loaders (which read a file from the container filesystem via -// fs), a browser has no filesystem or process.env. Instead this module fetches a -// served JSON file once, caches it, and then serves custom() synchronously. This -// file must stay free of `fs`/`process` so it can be bundled into a browser app. +// Browser-safe alias runtime. Must stay free of `fs`/`process` so it can be +// bundled into a browser app. // // Two files can supply aliases: // -// production .palantir/deployment.config.json written at install, so it -// carries the INSTALLER's values -// development public/resources.json the author's declaration file, -// so it carries the DEFAULTS -// -// Callers do not choose. We try the deployment config and fall back to the -// declaration file only when it appears absent, which is either a 404 or a 200 -// carrying an HTML document (single-page-app hosts rewrite unknown paths to -// index.html). Any other failure throws rather than falling back, because in -// production both files are served, so treating an error as absence would -// silently serve the author's defaults in place of the installer's values. -// -// Absence detection is a heuristic, not a proof: a proxy or authentication page -// could also arrive as a 200 with an HTML document. See isHtmlDocument. +// .palantir/deployment.config.json the installer's values, written at install +// public/resources.json the author's declared defaults // -// The two files are told apart by the runtime type of their `aliases` field -// (string vs object). +// The deployment config is tried first; the declaration file is used only when it +// appears absent. Any other failure throws rather than falling back, because on +// an installed site both files are served, so treating an error as absence would +// serve the author's defaults in place of the installer's values. import type { AliasDeclarationsFile, diff --git a/packages/aliases/src/public/experimental.ts b/packages/aliases/src/public/experimental.ts index d03b2ed12a3..9185cfd5efe 100644 --- a/packages/aliases/src/public/experimental.ts +++ b/packages/aliases/src/public/experimental.ts @@ -35,12 +35,10 @@ import { initAliases, } from "../browser.js"; -// Assembled member by member rather than with `export * as Aliases` so that -// resetAliasesCache stays out of the supported surface. It exists for tests, -// which import ../browser.js directly; exporting it would make cache -// invalidation public and would race with an in-flight initAliases(). -// Explicitly typed: `--isolatedDeclarations` cannot infer a declaration for an -// object literal built from shorthand properties (TS9016). +// Assembled member by member, not `export * as`, to keep the test-only +// resetAliasesCache out of the supported surface. +// +// Explicitly typed: `--isolatedDeclarations` cannot infer this (TS9016). export const Aliases: { readonly custom: typeof custom; readonly initAliases: typeof initAliases; diff --git a/packages/functions/src/aliases/index.ts b/packages/functions/src/aliases/index.ts index 08ad1af9626..2395f32b19a 100644 --- a/packages/functions/src/aliases/index.ts +++ b/packages/functions/src/aliases/index.ts @@ -14,13 +14,12 @@ * limitations under the License. */ -// Compatibility boundary: the alias runtime lives in @osdk/aliases so consumers -// that are not Functions can read aliases without depending on this package. +// Compatibility boundary keeping the public `Aliases` namespace of +// @osdk/functions unchanged now that the runtime lives in @osdk/aliases. // -// A local module with explicit named re-exports, because API Extractor cannot -// process `export * as ns` from an external package ("fetchAstModuleExportInfo() -// is not supported for external modules") and rejects a namespace whose target -// uses `export *` ("The Aliases namespace import includes a star export"). +// Explicit named re-exports rather than `export *`, because API Extractor +// supports neither a namespace re-export of an external package nor a namespace +// whose target uses `export *`. export { custom } from "@osdk/aliases/node"; export type { Custom } from "@osdk/aliases/node"; diff --git a/packages/functions/src/index.ts b/packages/functions/src/index.ts index d3a535a227e..b8ec8dd0cc9 100644 --- a/packages/functions/src/index.ts +++ b/packages/functions/src/index.ts @@ -34,8 +34,7 @@ export type { TwoDimensionalAggregation, } from "@osdk/client"; -// Re-exported from @osdk/aliases through a local facade so the public `Aliases` -// namespace of @osdk/functions is unchanged. See ./aliases/index.ts. +// Compatibility facade; see ./aliases/index.ts. export * as Aliases from "./aliases/index.js"; export { createEditBatch } from "./edits/createEditBatch.js"; export type { EditBatch } from "./edits/EditBatch.js"; From 1a255bcdc2639ad915538c2c88491f2aa3f9cd7f Mon Sep 17 00:00:00 2001 From: James Zhang Date: Fri, 21 Aug 2026 18:22:28 -0400 Subject: [PATCH 18/20] Trigger CI From 8d16795d50d542e48ec31de6126c805c9d24cb74 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Tue, 25 Aug 2026 12:43:22 -0400 Subject: [PATCH 19/20] Replace browser alias initialization with loader --- packages/aliases/src/browser.ts | 32 +++++++++++++---- packages/aliases/src/experimental.test.ts | 36 ++++++++++++++----- packages/aliases/src/public/experimental.ts | 38 ++++++--------------- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/packages/aliases/src/browser.ts b/packages/aliases/src/browser.ts index 890b3f9cbfa..36378dc875d 100644 --- a/packages/aliases/src/browser.ts +++ b/packages/aliases/src/browser.ts @@ -42,7 +42,7 @@ export const DEFAULT_DEPLOYMENT_CONFIG_PATH = /** The author's declaration file, so it carries the declared defaults. */ export const DEFAULT_DECLARATIONS_PATH = "resources.json"; -export interface InitAliasesOptions { +export interface LoadAliasesOptions { /** * Escape hatch to force one specific file, relative to `document.baseURI`. * Setting it disables the fallback, so a missing file throws. Normal @@ -56,18 +56,36 @@ export interface InitAliasesOptions { fetch?: typeof globalThis.fetch; } +/** Aliases loaded for this application. */ +export interface LoadedAliases { + /** Returns a resolved custom alias. */ + custom(alias: string): Custom; +} + let cachedCustomAliases: Record | undefined; let inFlight: Promise | undefined; +const loadedAliases: LoadedAliases = Object.freeze({ custom }); + /** - * Fetches and caches the resolved aliases for this installation. Call once at - * application startup and await it before reading any aliases. Repeated calls - * are deduplicated and become no-ops once the aliases are cached. + * Loads and caches the aliases for this installation, then returns a + * synchronous reader. Repeated and concurrent calls share the same load. * * @experimental Exposed only via "@osdk/aliases/experimental". Both custom * aliases and the shape of this API are provisional and may change. */ -export async function initAliases(options?: InitAliasesOptions): Promise { +export async function load( + options?: LoadAliasesOptions, +): Promise { + await initAliases(options); + return loadedAliases; +} + +/** + * Populates the alias cache. Concurrent calls share a request, and failed + * requests may be retried. + */ +export async function initAliases(options?: LoadAliasesOptions): Promise { if (cachedCustomAliases !== undefined) { return; } @@ -81,12 +99,12 @@ export async function initAliases(options?: InitAliasesOptions): Promise { await inFlight; } -async function loadAliases(options?: InitAliasesOptions): Promise { +async function loadAliases(options?: LoadAliasesOptions): Promise { const fetchImpl = options?.fetch ?? globalThis.fetch; if (typeof fetchImpl !== "function") { throw new TypeError( "No fetch implementation available to load aliases. Pass one via " + - "initAliases({ fetch }).", + "Aliases.load({ fetch }).", ); } diff --git a/packages/aliases/src/experimental.test.ts b/packages/aliases/src/experimental.test.ts index fa791092848..3e2e5afc5f9 100644 --- a/packages/aliases/src/experimental.test.ts +++ b/packages/aliases/src/experimental.test.ts @@ -19,16 +19,15 @@ // breaking change for consumers, and that should fail a test rather than pass // review unnoticed. -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import * as browser from "./browser.js"; import { resetAliasesCache } from "./browser.js"; import { Aliases, - custom, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, - initAliases, + load, } from "./public/experimental.js"; const DECLARATIONS = { @@ -36,14 +35,15 @@ const DECLARATIONS = { }; function mockFetch(): typeof globalThis.fetch { - return (() => + return vi.fn(() => Promise.resolve({ ok: true, status: 200, statusText: "OK", headers: { get: () => "application/json" }, text: () => Promise.resolve(JSON.stringify(DECLARATIONS)), - })) as unknown as typeof globalThis.fetch; + }), + ) as unknown as typeof globalThis.fetch; } describe("experimental browser entry point", () => { @@ -52,16 +52,33 @@ describe("experimental browser entry point", () => { }); it("exposes the Aliases namespace", async () => { - await Aliases.initAliases({ path: "resources.json", fetch: mockFetch() }); + const aliases = await Aliases.load({ + path: "resources.json", + fetch: mockFetch(), + }); - expect(Aliases.custom("apiBaseUrl")).toBe("https://api.example.com"); + expect(aliases.custom("apiBaseUrl")).toBe("https://api.example.com"); + }); + + it("caches concurrent and repeated loads", async () => { + const fetchImpl = mockFetch(); + const options = { path: "resources.json", fetch: fetchImpl }; + + const [first, second] = await Promise.all([ + Aliases.load(options), + Aliases.load(options), + ]); + const third = await Aliases.load(options); + + expect(first).toBe(second); + expect(first).toBe(third); + expect(fetchImpl).toHaveBeenCalledOnce(); }); it("exposes the same members as named exports", () => { // Same function identities, not merely same names, so the two styles can // never drift apart. - expect(Aliases.custom).toBe(custom); - expect(Aliases.initAliases).toBe(initAliases); + expect(Aliases.load).toBe(load); expect(Aliases.DEFAULT_DECLARATIONS_PATH).toBe(DEFAULT_DECLARATIONS_PATH); expect(Aliases.DEFAULT_DEPLOYMENT_CONFIG_PATH).toBe( DEFAULT_DEPLOYMENT_CONFIG_PATH, @@ -73,6 +90,7 @@ describe("experimental browser entry point", () => { // into a browser bundle. expect(Aliases).not.toHaveProperty("dataset"); expect(Aliases).not.toHaveProperty("source"); + expect(Aliases).not.toHaveProperty("custom"); }); it("keeps the test-only cache reset out of the public surface", () => { diff --git a/packages/aliases/src/public/experimental.ts b/packages/aliases/src/public/experimental.ts index 9185cfd5efe..e759147285b 100644 --- a/packages/aliases/src/public/experimental.ts +++ b/packages/aliases/src/public/experimental.ts @@ -14,47 +14,31 @@ * limitations under the License. */ -// EXPERIMENTAL browser-safe alias runtime, for applications served to a browser -// such as Developer Console apps. +// Experimental browser-safe aliases for Developer Console apps. // // import { Aliases } from "@osdk/aliases/experimental"; -// await Aliases.initAliases(); -// const apiBaseUrl = Aliases.custom("apiBaseUrl"); +// const aliases = await Aliases.load(); +// const apiBaseUrl = aliases.custom("apiBaseUrl"); // -// Behind the "experimental" subpath deliberately: both custom aliases and the -// shape of this API are provisional, so the import path says so at every call -// site. Expect it to move to the package root once the design settles, with this -// subpath deprecated rather than removed. -// -// Code running in Node with a filesystem (Functions) wants "@osdk/aliases/node". +// Functions and other Node runtimes use "@osdk/aliases/node". import { - custom, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, - initAliases, + load, } from "../browser.js"; -// Assembled member by member, not `export * as`, to keep the test-only -// resetAliasesCache out of the supported surface. -// -// Explicitly typed: `--isolatedDeclarations` cannot infer this (TS9016). +// Explicitly assembled to exclude test helpers. The type annotation is required +// by `--isolatedDeclarations`. export const Aliases: { - readonly custom: typeof custom; - readonly initAliases: typeof initAliases; + readonly load: typeof load; readonly DEFAULT_DECLARATIONS_PATH: typeof DEFAULT_DECLARATIONS_PATH; readonly DEFAULT_DEPLOYMENT_CONFIG_PATH: typeof DEFAULT_DEPLOYMENT_CONFIG_PATH; } = { - custom, - initAliases, + load, DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, }; -export { - custom, - DEFAULT_DECLARATIONS_PATH, - DEFAULT_DEPLOYMENT_CONFIG_PATH, - initAliases, -}; -export type { Custom, InitAliasesOptions } from "../browser.js"; +export { DEFAULT_DECLARATIONS_PATH, DEFAULT_DEPLOYMENT_CONFIG_PATH, load }; +export type { Custom, LoadAliasesOptions, LoadedAliases } from "../browser.js"; From 856e8897f11721923f403716a973f6bc2bfe22a6 Mon Sep 17 00:00:00 2001 From: James Zhang Date: Tue, 25 Aug 2026 18:00:53 -0400 Subject: [PATCH 20/20] Update browser aliases changeset --- .changeset/dev-console-browser-aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/dev-console-browser-aliases.md b/.changeset/dev-console-browser-aliases.md index a1096e43dc2..45fb61ac1f3 100644 --- a/.changeset/dev-console-browser-aliases.md +++ b/.changeset/dev-console-browser-aliases.md @@ -5,6 +5,6 @@ Add `@osdk/aliases`, extracting the alias runtime so consumers that are not Functions can read aliases without depending on `@osdk/functions`. The `Aliases` namespace exported by `@osdk/functions` is unchanged. -`@osdk/aliases/experimental` is a new browser-safe entry point for applications served to a browser, such as Developer Console apps: call `await Aliases.initAliases()` once at startup, then read values synchronously with `Aliases.custom("myAlias")`. It reads the installer's resolved values on a Marketplace-installed site and falls back to the author's declared defaults in `public/resources.json` otherwise. It sits behind the `experimental` subpath because both custom aliases and the shape of this API are provisional. +`@osdk/aliases/experimental` is a new browser-safe entry point for applications served to a browser, such as Developer Console apps: call `const aliases = await Aliases.load()`, then read values synchronously with `aliases.custom("myAlias")`. Repeated and concurrent loads share the cached request and reader. It reads the installer's resolved values on a Marketplace-installed site and falls back to the author's declared defaults in `public/resources.json` otherwise. It sits behind the `experimental` subpath because both custom aliases and the shape of this API are provisional. `@osdk/aliases/node` is the existing filesystem-backed runtime for code running in Node.