From b62ac809d171ac69931da1a324bcf91555ad63a3 Mon Sep 17 00:00:00 2001
From: Eric Anderson <eanderson@palantir.com>
Date: Tue, 23 Jul 2024 16:13:30 -0500
Subject: [PATCH] Confidential client (#488)

---
 .changeset/tame-rules-wash.md                 |   5 +
 docs/vite.md                                  |  24 ++
 etc/client.report.api.md                      |   8 +-
 packages/client/src/createPlatformClient.ts   |   5 +-
 packages/client/src/index.ts                  |   7 +-
 .../bin/testConfidentialClientNode.mjs        |   4 +
 packages/e2e.sandbox.oauth/package.json       |  59 +++++
 packages/e2e.sandbox.oauth/src/index.ts       |  17 ++
 .../src/testConfidentialClientNode.ts         |  56 +++++
 packages/e2e.sandbox.oauth/tsconfig.cjs.json  |  14 ++
 packages/e2e.sandbox.oauth/tsconfig.json      |  11 +
 packages/e2e.sandbox.oauth/tsup.config.js     |  21 ++
 packages/e2e.sandbox.todoapp/vite.config.mts  |   9 +
 packages/oauth/package.json                   |   1 -
 packages/oauth/src/BaseOauthClient.ts         |  42 ++++
 packages/oauth/src/ConfidentialOauthClient.ts |  22 ++
 packages/oauth/src/PublicOauthClient.ts       |  27 +-
 packages/oauth/src/common.ts                  | 237 ++++++++++++++++++
 .../src/createConfidentialOauthClient.ts      |  75 ++++++
 packages/oauth/src/createPublicOauthClient.ts | 204 +++------------
 packages/oauth/src/index.ts                   |   1 +
 pnpm-lock.yaml                                |  64 +++--
 22 files changed, 700 insertions(+), 213 deletions(-)
 create mode 100644 .changeset/tame-rules-wash.md
 create mode 100644 docs/vite.md
 create mode 100755 packages/e2e.sandbox.oauth/bin/testConfidentialClientNode.mjs
 create mode 100644 packages/e2e.sandbox.oauth/package.json
 create mode 100644 packages/e2e.sandbox.oauth/src/index.ts
 create mode 100644 packages/e2e.sandbox.oauth/src/testConfidentialClientNode.ts
 create mode 100644 packages/e2e.sandbox.oauth/tsconfig.cjs.json
 create mode 100644 packages/e2e.sandbox.oauth/tsconfig.json
 create mode 100644 packages/e2e.sandbox.oauth/tsup.config.js
 create mode 100644 packages/oauth/src/BaseOauthClient.ts
 create mode 100644 packages/oauth/src/ConfidentialOauthClient.ts
 create mode 100644 packages/oauth/src/common.ts
 create mode 100644 packages/oauth/src/createConfidentialOauthClient.ts

diff --git a/.changeset/tame-rules-wash.md b/.changeset/tame-rules-wash.md
new file mode 100644
index 000000000..c6c4860c5
--- /dev/null
+++ b/.changeset/tame-rules-wash.md
@@ -0,0 +1,5 @@
+---
+"@osdk/oauth": minor
+---
+
+Adds createConfidentialOauthClient
diff --git a/docs/vite.md b/docs/vite.md
new file mode 100644
index 000000000..8254073dc
--- /dev/null
+++ b/docs/vite.md
@@ -0,0 +1,24 @@
+# Using OSDK with Vite
+
+## Fixing common problems
+
+### "ReferenceError: process is not defined"
+
+The OSDK related libraries leverage a long standing convention to use `process.env.NODE_ENV` to determine the level of verbosity for `console.log`/`console.warn`/`console.error` and to create optimized production code.
+
+Recent versions of Vite have begun pushing developers to use `import.meta.env` instead of `process.env` which is a noble change with good intentions but one that creates problems for library authors trying to support multiple bundling frameworks.
+
+Out of the box, Vite will not perform the required replacement to optimize the code which leads to `process` being undefined and a runtime error for you. This can be worked around by updating your Vite config to process the replacement:
+
+```ts
+// ...
+export default defineConfig(({ mode }) => {
+  // ...
+  return {
+    define: {
+      "process.env.NODE_ENV": JSON.stringify(mode)
+    },
+    // ...
+  }
+}
+```
diff --git a/etc/client.report.api.md b/etc/client.report.api.md
index 1f4ac57c2..61c15374f 100644
--- a/etc/client.report.api.md
+++ b/etc/client.report.api.md
@@ -35,7 +35,7 @@ import type { QueryDefinition } from '@osdk/api';
 import type { QuerySignatureFromDef } from '@osdk/client.api';
 import { Result } from '@osdk/client.api';
 import type { SharedClient } from '@osdk/shared.client';
-import { SharedClientContext } from '@osdk/shared.client';
+import type { SharedClientContext } from '@osdk/shared.client';
 import type { VersionBound } from '@osdk/api';
 import { WhereClause } from '@osdk/client.api';
 
@@ -87,7 +87,7 @@ export const createClient: (baseUrl: string, ontologyRid: string | Promise<strin
 // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
 //
 // @public
-export function createPlatformClient(baseUrl: string, tokenProvider: () => Promise<string>, options?: undefined, fetchFn?: typeof globalThis.fetch): SharedClientContext;
+export function createPlatformClient(baseUrl: string, tokenProvider: () => Promise<string>, options?: undefined, fetchFn?: typeof globalThis.fetch): PlatformClient;
 
 export { InterfaceObjectSet }
 
@@ -107,6 +107,10 @@ export { PageResult }
 
 export { PalantirApiError }
 
+// @public (undocumented)
+export interface PlatformClient extends SharedClientContext {
+}
+
 export { Result }
 
 // @public (undocumented)
diff --git a/packages/client/src/createPlatformClient.ts b/packages/client/src/createPlatformClient.ts
index 59edfca09..beb162031 100644
--- a/packages/client/src/createPlatformClient.ts
+++ b/packages/client/src/createPlatformClient.ts
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+import type { SharedClientContext } from "@osdk/shared.client";
 import { createSharedClientContext } from "@osdk/shared.client.impl";
 import { USER_AGENT } from "./util/UserAgent.js";
 
+export interface PlatformClient extends SharedClientContext {}
+
 /**
  * Creates a client that can only be used with Platform APIs.
  *
@@ -34,7 +37,7 @@ export function createPlatformClient(
   tokenProvider: () => Promise<string>,
   options: undefined = undefined,
   fetchFn: typeof globalThis.fetch = fetch,
-) {
+): PlatformClient {
   return createSharedClientContext(
     baseUrl,
     tokenProvider,
diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts
index 9954610a3..29ba0670e 100644
--- a/packages/client/src/index.ts
+++ b/packages/client/src/index.ts
@@ -34,12 +34,11 @@ export type {
 export { isOk } from "@osdk/client.api";
 export { PalantirApiError } from "@osdk/shared.net.errors";
 
+export { ActionValidationError } from "./actions/ActionValidationError.js";
 export type { Client } from "./Client.js";
+export { createAttachmentFromRid } from "./createAttachmentFromRid.js";
 export { createClient } from "./createClient.js";
 export { createPlatformClient } from "./createPlatformClient.js";
-
-export { createAttachmentFromRid } from "./createAttachmentFromRid.js";
+export type { PlatformClient } from "./createPlatformClient.js";
 export { createAttachmentUpload } from "./object/AttachmentUpload.js";
-
-export { ActionValidationError } from "./actions/ActionValidationError.js";
 export type { ResultOrError } from "./ResultOrError.js";
diff --git a/packages/e2e.sandbox.oauth/bin/testConfidentialClientNode.mjs b/packages/e2e.sandbox.oauth/bin/testConfidentialClientNode.mjs
new file mode 100755
index 000000000..09295a6d5
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/bin/testConfidentialClientNode.mjs
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+import { testConfidentialClientNode } from "../build/esm/index.js";
+
+testConfidentialClientNode();
diff --git a/packages/e2e.sandbox.oauth/package.json b/packages/e2e.sandbox.oauth/package.json
new file mode 100644
index 000000000..21c9668e5
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/package.json
@@ -0,0 +1,59 @@
+{
+  "name": "@osdk/e2e.sandbox.oauth",
+  "private": true,
+  "version": "0.0.0",
+  "license": "Apache-2.0",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/palantir/osdk-ts.git"
+  },
+  "exports": {
+    ".": {
+      "require": "./build/cjs/index.cjs",
+      "browser": "./build/browser/index.js",
+      "import": "./build/esm/index.js"
+    },
+    "./*": {
+      "require": "./build/cjs/public/*.cjs",
+      "browser": "./build/browser/public/*.js",
+      "import": "./build/esm/public/*.js"
+    }
+  },
+  "scripts": {
+    "check-attw": "monorepo.tool.attw both",
+    "check-spelling": "cspell --quiet .",
+    "clean": "rm -rf lib dist types build tsconfig.tsbuildinfo",
+    "fix-lint": "eslint . --fix && dprint fmt --config $(find-up dprint.json)",
+    "lint": "eslint . && dprint check  --config $(find-up dprint.json)",
+    "transpile": "monorepo.tool.transpile",
+    "typecheck": "monorepo.tool.typecheck both"
+  },
+  "dependencies": {
+    "@osdk/client": "workspace:~",
+    "@osdk/oauth": "workspace:~",
+    "consola": "^3.2.3",
+    "tiny-invariant": "^1.3.3"
+  },
+  "devDependencies": {
+    "@osdk/monorepo.api-extractor": "workspace:~",
+    "@osdk/monorepo.tsconfig": "workspace:~",
+    "@osdk/monorepo.tsup": "workspace:~",
+    "typescript": "^5.5.2"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "files": [
+    "build/cjs",
+    "build/esm",
+    "build/browser",
+    "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/e2e.sandbox.oauth/src/index.ts b/packages/e2e.sandbox.oauth/src/index.ts
new file mode 100644
index 000000000..a99acf91d
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 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 { testConfidentialClientNode } from "./testConfidentialClientNode.js";
diff --git a/packages/e2e.sandbox.oauth/src/testConfidentialClientNode.ts b/packages/e2e.sandbox.oauth/src/testConfidentialClientNode.ts
new file mode 100644
index 000000000..57e75fe11
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/src/testConfidentialClientNode.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 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 { createConfidentialOauthClient } from "@osdk/oauth";
+import consola from "consola";
+import invariant from "tiny-invariant";
+
+declare const process: {
+  env: Record<string, string | undefined>;
+};
+
+export async function testConfidentialClientNode() {
+  const prefix = "TS_OSDK_E2E_OAUTH_CONFIDENTIAL_";
+  const FOUNDRY_CLIENT_ID = process.env[`${prefix}FOUNDRY_CLIENT_ID`];
+  const FOUNDRY_URL = process.env[`${prefix}FOUNDRY_URL`];
+  const FOUNDRY_CLIENT_SECRET = process.env[`${prefix}FOUNDRY_CLIENT_SECRET`];
+
+  invariant(
+    FOUNDRY_CLIENT_ID != null,
+    `${prefix}FOUNDRY_CLIENT_ID is required`,
+  );
+  invariant(
+    FOUNDRY_URL != null,
+    `${prefix}FOUNDRY_URL is required`,
+  );
+  invariant(
+    FOUNDRY_CLIENT_SECRET != null,
+    `${prefix}FOUNDRY_URL is required`,
+  );
+
+  const auth = createConfidentialOauthClient(
+    FOUNDRY_CLIENT_ID,
+    FOUNDRY_CLIENT_SECRET,
+    FOUNDRY_URL,
+  );
+
+  const token = await auth();
+  invariant(
+    token != null && token.length > 0,
+    "token should have been received",
+  );
+  consola.log(token);
+}
diff --git a/packages/e2e.sandbox.oauth/tsconfig.cjs.json b/packages/e2e.sandbox.oauth/tsconfig.cjs.json
new file mode 100644
index 000000000..3e2ecf7d1
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/tsconfig.cjs.json
@@ -0,0 +1,14 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "module": "CommonJS",
+    "moduleResolution": "Node",
+    "target": "ES6",
+    "rootDir": "src",
+    "outDir": "build/cjs"
+  },
+  "include": [
+    "./src/**/*"
+  ],
+  "references": []
+}
diff --git a/packages/e2e.sandbox.oauth/tsconfig.json b/packages/e2e.sandbox.oauth/tsconfig.json
new file mode 100644
index 000000000..6c1ec6f17
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/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/e2e.sandbox.oauth/tsup.config.js b/packages/e2e.sandbox.oauth/tsup.config.js
new file mode 100644
index 000000000..7a77e08bc
--- /dev/null
+++ b/packages/e2e.sandbox.oauth/tsup.config.js
@@ -0,0 +1,21 @@
+/*
+ * 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 { defineConfig } from "tsup";
+
+export default defineConfig(async (options) =>
+  (await import("@osdk/monorepo.tsup")).default(options, {})
+);
diff --git a/packages/e2e.sandbox.todoapp/vite.config.mts b/packages/e2e.sandbox.todoapp/vite.config.mts
index 05f45701b..a1e11ed87 100644
--- a/packages/e2e.sandbox.todoapp/vite.config.mts
+++ b/packages/e2e.sandbox.todoapp/vite.config.mts
@@ -9,6 +9,9 @@ export default defineConfig(({ mode }) => {
   const env = loadEnv(mode, process.cwd(), "");
 
   return {
+    define: {
+      "process.env.NODE_ENV": JSON.stringify(mode),
+    },
     plugins: [
       react(),
       visualizer({
@@ -25,6 +28,12 @@ export default defineConfig(({ mode }) => {
         "/object-set-service": `${env.VITE_FOUNDRY_URL}`,
       },
     },
+    optimizeDeps: {
+      // shared.client is a mixed package that needs to be properly processed by vite
+      // but normally linked packages do not get that treatment so we have to explicitly add it here
+      // and in the `commonjsOptions` below
+      include: ["@osdk/client > @osdk/shared.client"],
+    },
     build: {
       outDir: "build/site/",
     },
diff --git a/packages/oauth/package.json b/packages/oauth/package.json
index 409e673ab..205187ffb 100644
--- a/packages/oauth/package.json
+++ b/packages/oauth/package.json
@@ -36,7 +36,6 @@
     "@osdk/monorepo.tsconfig": "workspace:~",
     "@osdk/monorepo.tsup": "workspace:~",
     "jest-extended": "^4.0.2",
-    "ts-expect": "^1.3.0",
     "typescript": "^5.5.2"
   },
   "publishConfig": {
diff --git a/packages/oauth/src/BaseOauthClient.ts b/packages/oauth/src/BaseOauthClient.ts
new file mode 100644
index 000000000..1f91140d3
--- /dev/null
+++ b/packages/oauth/src/BaseOauthClient.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 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 type { Token } from "./Token.js";
+
+export type Events = {
+  signIn: CustomEvent<Token>;
+  signOut: Event;
+  refresh: CustomEvent<Token>;
+};
+
+export interface BaseOauthClient<K extends keyof Events & string> {
+  (): Promise<string>;
+
+  signIn: () => Promise<Token>;
+  signOut: () => Promise<void>;
+
+  addEventListener: <T extends K>(
+    type: T,
+    listener: ((evt: Events[T]) => void) | null,
+    options?: boolean | AddEventListenerOptions,
+  ) => void;
+
+  removeEventListener: <T extends K>(
+    type: T,
+    callback: ((evt: Events[T]) => void) | null,
+    options?: EventListenerOptions | boolean,
+  ) => void;
+}
diff --git a/packages/oauth/src/ConfidentialOauthClient.ts b/packages/oauth/src/ConfidentialOauthClient.ts
new file mode 100644
index 000000000..4672de2d0
--- /dev/null
+++ b/packages/oauth/src/ConfidentialOauthClient.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 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 type { BaseOauthClient } from "./BaseOauthClient.js";
+
+export interface ConfidentialOauthClient
+  extends BaseOauthClient<"signIn" | "signOut">
+{
+}
diff --git a/packages/oauth/src/PublicOauthClient.ts b/packages/oauth/src/PublicOauthClient.ts
index 72fa1cff4..1c921b448 100644
--- a/packages/oauth/src/PublicOauthClient.ts
+++ b/packages/oauth/src/PublicOauthClient.ts
@@ -14,30 +14,11 @@
  * limitations under the License.
  */
 
+import type { BaseOauthClient } from "./BaseOauthClient.js";
 import type { Token } from "./Token.js";
 
-export type Events = {
-  signIn: CustomEvent<Token>;
-  signOut: Event;
-  refresh: CustomEvent<Token>;
-};
-
-export interface PublicOauthClient {
-  (): Promise<string>;
-
-  signIn: () => Promise<Token>;
+export interface PublicOauthClient
+  extends BaseOauthClient<"signIn" | "signOut" | "refresh">
+{
   refresh: () => Promise<Token | undefined>;
-  signOut: () => Promise<void>;
-
-  addEventListener: <T extends keyof Events & string>(
-    type: T,
-    listener: ((evt: Events[T]) => void) | null,
-    options?: boolean | AddEventListenerOptions,
-  ) => void;
-
-  removeEventListener: <T extends keyof Events & string>(
-    type: T,
-    callback: ((evt: Events[T]) => void) | null,
-    options?: EventListenerOptions | boolean,
-  ) => void;
 }
diff --git a/packages/oauth/src/common.ts b/packages/oauth/src/common.ts
new file mode 100644
index 000000000..d5e9bbb4d
--- /dev/null
+++ b/packages/oauth/src/common.ts
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2024 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 type {
+  AuthorizationServer,
+  Client,
+  HttpRequestOptions,
+  OAuth2TokenEndpointResponse,
+} from "oauth4webapi";
+import { processRevocationResponse, revocationRequest } from "oauth4webapi";
+import invariant from "tiny-invariant";
+import { TypedEventTarget } from "typescript-event-target";
+import type { BaseOauthClient, Events } from "./BaseOauthClient.js";
+import { throwIfError } from "./throwIfError.js";
+import type { Token } from "./Token.js";
+
+// Node 18 is supposed to have a `CustomEvent` but it is not exposed on `globalThis`
+// which creates a problem for making a single codebase for node and browser. This polyfill works around it
+const CustomEvent = process.env.target === "browser"
+  ? globalThis.CustomEvent
+  : globalThis.CustomEvent
+    ?? class CustomEvent<T> extends Event {
+      #detail: T | null;
+
+      constructor(type: string, options: EventInit & { detail: T }) {
+        super(type, options);
+        this.#detail = options?.detail ?? null;
+      }
+
+      get detail() {
+        return this.#detail;
+      }
+    };
+
+declare const process: {
+  env: Record<string, string | undefined>;
+};
+
+const localStorage = globalThis.localStorage;
+
+type LocalStorageState =
+  // when we are going to the login page
+  | {
+    refresh_token?: never;
+    codeVerifier?: never;
+    state?: never;
+    oldUrl: string;
+  }
+  // when we are redirecting to oauth login
+  | {
+    refresh_token?: never;
+    codeVerifier: string;
+    state: string;
+    oldUrl: string;
+  }
+  // when we have the refresh token
+  | {
+    refresh_token?: string;
+    codeVerifier?: never;
+    state?: never;
+    oldUrl?: never;
+  }
+  | {
+    refresh_token?: never;
+    codeVerifier?: never;
+    state?: never;
+    oldUrl?: never;
+  };
+
+export function saveLocal(client: Client, x: LocalStorageState) {
+  // MUST `localStorage?` as nodejs does not have localStorage
+  localStorage?.setItem(
+    `@osdk/oauth : refresh : ${client.client_id}`,
+    JSON.stringify(x),
+  );
+}
+
+export function removeLocal(client: Client) {
+  // MUST `localStorage?` as nodejs does not have localStorage
+  localStorage?.removeItem(`@osdk/oauth : refresh : ${client.client_id}`);
+}
+
+export function readLocal(client: Client): LocalStorageState {
+  return JSON.parse(
+    // MUST `localStorage?` as nodejs does not have localStorage
+    localStorage?.getItem(`@osdk/oauth : refresh : ${client.client_id}`)
+      ?? "{}",
+  );
+}
+
+export function common<
+  R extends undefined | (() => Promise<Token | undefined>),
+>(
+  client: Client,
+  as: AuthorizationServer,
+  _signIn: () => Promise<Token>,
+  oauthHttpOptions: HttpRequestOptions,
+  refresh: R,
+): {
+  getToken: BaseOauthClient<keyof Events & string> & { refresh: R };
+  makeTokenAndSaveRefresh: (
+    resp: OAuth2TokenEndpointResponse,
+    type: "signIn" | "refresh",
+  ) => Token;
+} {
+  let token: Token | undefined;
+  const eventTarget = new TypedEventTarget<Events>();
+
+  function makeTokenAndSaveRefresh(
+    resp: OAuth2TokenEndpointResponse,
+    type: "signIn" | "refresh",
+  ): Token {
+    const { refresh_token, expires_in, access_token } = resp;
+    invariant(expires_in != null);
+    saveLocal(client, { refresh_token });
+    token = {
+      refresh_token,
+      expires_in,
+      access_token,
+      expires_at: Date.now() + expires_in * 1000,
+    };
+
+    eventTarget.dispatchTypedEvent(
+      type,
+      new CustomEvent(
+        type,
+        { detail: token },
+      ),
+    );
+    return token;
+  }
+
+  let refreshTimeout: ReturnType<typeof setTimeout>;
+  function rmTimeout() {
+    if (refreshTimeout) clearTimeout(refreshTimeout);
+  }
+  function restartRefreshTimer(evt: CustomEvent<Token>) {
+    if (refresh) {
+      rmTimeout();
+      refreshTimeout = setTimeout(
+        refresh,
+        evt.detail.expires_in * 1000 - 60 * 1000,
+      );
+    }
+  }
+
+  async function signOut() {
+    invariant(token, "not signed in");
+
+    const result = await processRevocationResponse(
+      await revocationRequest(
+        as,
+        client,
+        token.access_token,
+        oauthHttpOptions,
+      ),
+    );
+
+    rmTimeout();
+
+    // Clean up
+    removeLocal(client);
+    token = undefined;
+    throwIfError(result);
+    eventTarget.dispatchTypedEvent("signOut", new Event("signOut"));
+  }
+
+  let pendingSignIn: Promise<Token> | undefined;
+  async function signIn() {
+    if (pendingSignIn) {
+      return pendingSignIn;
+    }
+    try {
+      pendingSignIn = _signIn();
+      return await pendingSignIn;
+    } finally {
+      pendingSignIn = undefined;
+    }
+  }
+
+  eventTarget.addEventListener("signIn", restartRefreshTimer);
+  eventTarget.addEventListener("refresh", restartRefreshTimer);
+
+  const getToken = Object.assign(async function getToken() {
+    if (!token || Date.now() >= token.expires_at) {
+      token = await signIn();
+    }
+    return token!.access_token;
+  }, {
+    signIn,
+    refresh,
+    signOut,
+    rmTimeout,
+    addEventListener: eventTarget.addEventListener.bind(
+      eventTarget,
+    ) as typeof eventTarget.addEventListener,
+    removeEventListener: eventTarget.removeEventListener.bind(
+      eventTarget,
+    ) as typeof eventTarget.removeEventListener,
+  });
+
+  return { getToken, makeTokenAndSaveRefresh };
+}
+
+export function createAuthorizationServer(
+  ctxPath: string,
+  url: string,
+): Required<
+  Pick<
+    AuthorizationServer,
+    | "issuer"
+    | "token_endpoint"
+    | "authorization_endpoint"
+    | "revocation_endpoint"
+  >
+> {
+  const issuer = `${new URL(ctxPath, url)}`;
+  return {
+    token_endpoint: `${issuer}/api/oauth2/token`,
+    authorization_endpoint: `${issuer}/api/oauth2/authorize`,
+    revocation_endpoint: `${issuer}/api/oauth2/revoke_token`,
+    issuer,
+  };
+}
diff --git a/packages/oauth/src/createConfidentialOauthClient.ts b/packages/oauth/src/createConfidentialOauthClient.ts
new file mode 100644
index 000000000..83196f218
--- /dev/null
+++ b/packages/oauth/src/createConfidentialOauthClient.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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 type { Client, HttpRequestOptions } from "oauth4webapi";
+import {
+  clientCredentialsGrantRequest,
+  customFetch,
+  processClientCredentialsResponse,
+} from "oauth4webapi";
+import { common, createAuthorizationServer } from "./common.js";
+import type { ConfidentialOauthClient } from "./ConfidentialOauthClient.js";
+import { throwIfError } from "./throwIfError.js";
+
+/**
+ * @param client_id
+ * @param client_secret
+ * @param url the base url of your foundry server
+ * @param scopes
+ * @param fetchFn
+ * @param ctxPath
+ * @returns which can be used as a token provider
+ */
+export function createConfidentialOauthClient(
+  client_id: string,
+  client_secret: string,
+  url: string,
+  scopes: string[] = ["api:read-data", "api:write-data"],
+  fetchFn: typeof globalThis.fetch = globalThis.fetch,
+  ctxPath: string = "/multipass",
+): ConfidentialOauthClient {
+  const client: Client = { client_id, client_secret };
+  const authServer = createAuthorizationServer(ctxPath, url);
+  const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };
+
+  const { getToken, makeTokenAndSaveRefresh } = common(
+    client,
+    authServer,
+    _signIn,
+    oauthHttpOptions,
+    undefined,
+  );
+
+  async function _signIn() {
+    return makeTokenAndSaveRefresh(
+      throwIfError(
+        await processClientCredentialsResponse(
+          authServer,
+          client,
+          await clientCredentialsGrantRequest(
+            authServer,
+            client,
+            new URLSearchParams({ scope: scopes.join(" ") }),
+            oauthHttpOptions,
+          ),
+        ),
+      ),
+      "signIn",
+    );
+  }
+
+  return getToken;
+}
diff --git a/packages/oauth/src/createPublicOauthClient.ts b/packages/oauth/src/createPublicOauthClient.ts
index 5a41a8f15..2f55051f4 100644
--- a/packages/oauth/src/createPublicOauthClient.ts
+++ b/packages/oauth/src/createPublicOauthClient.ts
@@ -15,12 +15,7 @@
  */
 
 import delay from "delay";
-import type {
-  AuthorizationServer,
-  Client,
-  HttpRequestOptions,
-  OAuth2TokenEndpointResponse,
-} from "oauth4webapi";
+import type { Client, HttpRequestOptions } from "oauth4webapi";
 import {
   authorizationCodeGrantRequest,
   calculatePKCECodeChallenge,
@@ -28,64 +23,26 @@ import {
   generateRandomCodeVerifier,
   generateRandomState,
   processAuthorizationCodeOAuth2Response,
-  processRevocationResponse,
   refreshTokenGrantRequest,
-  revocationRequest,
   validateAuthResponse,
 } from "oauth4webapi";
-import invariant from "tiny-invariant";
-import { TypedEventTarget } from "typescript-event-target";
-import type { Events, PublicOauthClient } from "./PublicOauthClient.js";
+import {
+  common,
+  createAuthorizationServer,
+  readLocal,
+  removeLocal,
+  saveLocal,
+} from "./common.js";
+import type { PublicOauthClient } from "./PublicOauthClient.js";
 import { throwIfError } from "./throwIfError.js";
 import type { Token } from "./Token.js";
 
-const storageKey = "asdfasdfdhjlkajhgj";
-
 declare const process: undefined | {
   env?: {
     NODE_ENV: "production" | "development";
   };
 };
 
-type LocalStorageState =
-  // when we are going to the login page
-  | {
-    refresh_token?: never;
-    codeVerifier?: never;
-    state?: never;
-    oldUrl: string;
-  }
-  // when we are redirecting to oauth login
-  | {
-    refresh_token?: never;
-    codeVerifier: string;
-    state: string;
-    oldUrl: string;
-  }
-  // when we have the refresh token
-  | {
-    refresh_token?: string;
-    codeVerifier?: never;
-    state?: never;
-    oldUrl?: never;
-  }
-  | {
-    refresh_token?: never;
-    codeVerifier?: never;
-    state?: never;
-    oldUrl?: never;
-  };
-
-function saveLocal(x: LocalStorageState) {
-  localStorage.setItem(storageKey, JSON.stringify(x));
-}
-function removeLocal() {
-  localStorage.removeItem(storageKey);
-}
-function readLocal(): LocalStorageState {
-  return JSON.parse(localStorage.getItem(storageKey) ?? "{}");
-}
-
 /**
  * @param client_id
  * @param url the base url of your foundry server
@@ -109,19 +66,17 @@ export function createPublicOauthClient(
   fetchFn: typeof globalThis.fetch = globalThis.fetch,
   ctxPath: string = "/multipass",
 ): PublicOauthClient {
+  const client: Client = { client_id, token_endpoint_auth_method: "none" };
+  const authServer = createAuthorizationServer(ctxPath, url);
   const oauthHttpOptions: HttpRequestOptions = { [customFetch]: fetchFn };
-  const eventTarget = new TypedEventTarget<Events>();
 
-  const issuer = `${new URL(ctxPath, url)}`;
-
-  const as: AuthorizationServer = {
-    token_endpoint: `${issuer}/api/oauth2/token`,
-    authorization_endpoint: `${issuer}/api/oauth2/authorize`,
-    revocation_endpoint: `${issuer}/api/oauth2/revoke_token`,
-    issuer,
-  };
-
-  const client: Client = { client_id, token_endpoint_auth_method: "none" };
+  const { makeTokenAndSaveRefresh, getToken } = common(
+    client,
+    authServer,
+    _signIn,
+    oauthHttpOptions,
+    maybeRefresh.bind(globalThis, true),
+  );
 
   async function go(x: string) {
     if (useHistory) return window.history.replaceState({}, "", x);
@@ -131,29 +86,10 @@ export function createPublicOauthClient(
     throw new Error("Unable to redirect");
   }
 
-  function makeTokenAndSaveRefresh(
-    resp: OAuth2TokenEndpointResponse,
-    type: "signIn" | "refresh",
-  ): Token {
-    const { refresh_token, expires_in, access_token } = resp;
-    invariant(expires_in != null);
-    saveLocal({ refresh_token });
-    token = {
-      refresh_token,
-      expires_in,
-      access_token,
-      expires_at: Date.now() + expires_in * 1000,
-    };
-
-    eventTarget.dispatchTypedEvent(
-      type,
-      new CustomEvent(type, { detail: token }),
-    );
-    return token;
-  }
-
-  async function maybeRefresh(expectRefreshToken?: boolean) {
-    const { refresh_token } = readLocal();
+  async function maybeRefresh(
+    expectRefreshToken?: boolean,
+  ): Promise<Token | undefined> {
+    const { refresh_token } = readLocal(client);
     if (!refresh_token) {
       if (expectRefreshToken) throw new Error("No refresh token found");
       return;
@@ -165,10 +101,10 @@ export function createPublicOauthClient(
       return makeTokenAndSaveRefresh(
         throwIfError(
           await processAuthorizationCodeOAuth2Response(
-            as,
+            authServer,
             client,
             await refreshTokenGrantRequest(
-              as,
+              authServer,
               client,
               refresh_token,
               oauthHttpOptions,
@@ -185,7 +121,7 @@ export function createPublicOauthClient(
           e,
         );
       }
-      removeLocal();
+      removeLocal(client);
       if (expectRefreshToken) {
         throw new Error("Could not refresh token");
       }
@@ -193,21 +129,21 @@ export function createPublicOauthClient(
   }
 
   async function maybeHandleAuthReturn() {
-    const { codeVerifier, state, oldUrl } = readLocal();
+    const { codeVerifier, state, oldUrl } = readLocal(client);
     if (!codeVerifier) return;
 
     try {
       const ret = makeTokenAndSaveRefresh(
         throwIfError(
           await processAuthorizationCodeOAuth2Response(
-            as,
+            authServer,
             client,
             await authorizationCodeGrantRequest(
-              as,
+              authServer,
               client,
               throwIfError(
                 validateAuthResponse(
-                  as,
+                  authServer,
                   client,
                   new URL(window.location.href),
                   state,
@@ -232,22 +168,22 @@ export function createPublicOauthClient(
           e,
         );
       }
-      removeLocal();
+      removeLocal(client);
     }
   }
 
   async function initiateLoginRedirect(): Promise<void> {
     if (loginPage && window.location.href !== loginPage) {
-      saveLocal({ oldUrl: postLoginPage });
+      saveLocal(client, { oldUrl: postLoginPage });
       return await go(loginPage);
     }
 
     const state = generateRandomState()!;
     const codeVerifier = generateRandomCodeVerifier();
-    const oldUrl = readLocal().oldUrl ?? window.location.toString();
-    saveLocal({ codeVerifier, state, oldUrl });
+    const oldUrl = readLocal(client).oldUrl ?? window.location.toString();
+    saveLocal(client, { codeVerifier, state, oldUrl });
 
-    window.location.assign(`${as
+    window.location.assign(`${authServer
       .authorization_endpoint!}?${new URLSearchParams({
       client_id,
       response_type: "code",
@@ -263,83 +199,15 @@ export function createPublicOauthClient(
     throw new Error("Unable to redirect");
   }
 
-  let refreshTimeout: ReturnType<typeof setTimeout>;
-  function rmTimeout() {
-    if (refreshTimeout) clearTimeout(refreshTimeout);
-  }
-  function restartRefreshTimer(evt: CustomEvent<Token>) {
-    rmTimeout();
-    refreshTimeout = setTimeout(
-      refresh,
-      evt.detail.expires_in * 1000 - 60 * 1000,
-    );
-  }
-
-  const refresh = maybeRefresh.bind(globalThis, true);
-
-  async function signOut() {
-    invariant(token, "not signed in");
-
-    const result = await processRevocationResponse(
-      await revocationRequest(
-        as,
-        client,
-        token.access_token,
-        oauthHttpOptions,
-      ),
-    );
-
-    rmTimeout();
-
-    // Clean up
-    removeLocal();
-    token = undefined;
-    throwIfError(result);
-    eventTarget.dispatchTypedEvent("signOut", new Event("signOut"));
-  }
-
-  let pendingSignIn: Promise<Token> | undefined;
-  async function signIn() {
-    if (pendingSignIn) {
-      return pendingSignIn;
-    }
-    try {
-      pendingSignIn = _signIn();
-      return await pendingSignIn;
-    } finally {
-      pendingSignIn = undefined;
-    }
-  }
   /** Will throw if there is no token! */
   async function _signIn() {
     // 1. Check if we have a refresh token in local storage
-    return token = await maybeRefresh()
+    return await maybeRefresh()
       // 2. If there is no refresh token we are likely trying to perform the callback
       ?? await maybeHandleAuthReturn()
       // 3. If we haven't been able to load the token from one of the two above ways, we need to make the initial auth request
       ?? await initiateLoginRedirect() as unknown as Token;
   }
 
-  eventTarget.addEventListener("signIn", restartRefreshTimer);
-  eventTarget.addEventListener("refresh", restartRefreshTimer);
-
-  let token: Token | undefined;
-  const ret = Object.assign(async function ret() {
-    if (!token || Date.now() >= token.expires_at) {
-      token = await signIn();
-    }
-    return token!.access_token;
-  }, {
-    signIn,
-    refresh,
-    signOut,
-    addEventListener: eventTarget.addEventListener.bind(
-      eventTarget,
-    ) as typeof eventTarget.addEventListener,
-    removeEventListener: eventTarget.removeEventListener.bind(
-      eventTarget,
-    ) as typeof eventTarget.removeEventListener,
-  });
-
-  return ret;
+  return getToken;
 }
diff --git a/packages/oauth/src/index.ts b/packages/oauth/src/index.ts
index 0cfab9691..dee6054fd 100644
--- a/packages/oauth/src/index.ts
+++ b/packages/oauth/src/index.ts
@@ -14,5 +14,6 @@
  * limitations under the License.
  */
 
+export { createConfidentialOauthClient } from "./createConfidentialOauthClient.js";
 export { createPublicOauthClient } from "./createPublicOauthClient.js";
 export type { PublicOauthClient } from "./PublicOauthClient.js";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ef969ebfe..c6b232866 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1282,6 +1282,34 @@ importers:
         specifier: ^5.5.2
         version: 5.5.3
 
+  packages/e2e.sandbox.oauth:
+    dependencies:
+      '@osdk/client':
+        specifier: workspace:~
+        version: link:../client
+      '@osdk/oauth':
+        specifier: workspace:~
+        version: link:../oauth
+      consola:
+        specifier: ^3.2.3
+        version: 3.2.3
+      tiny-invariant:
+        specifier: ^1.3.3
+        version: 1.3.3
+    devDependencies:
+      '@osdk/monorepo.api-extractor':
+        specifier: workspace:~
+        version: link:../monorepo.api-extractor
+      '@osdk/monorepo.tsconfig':
+        specifier: workspace:~
+        version: link:../monorepo.tsconfig
+      '@osdk/monorepo.tsup':
+        specifier: workspace:~
+        version: link:../monorepo.tsup
+      typescript:
+        specifier: ^5.5.2
+        version: 5.5.3
+
   packages/e2e.sandbox.todoapp:
     dependencies:
       '@osdk/api':
@@ -2130,9 +2158,6 @@ importers:
       jest-extended:
         specifier: ^4.0.2
         version: 4.0.2
-      ts-expect:
-        specifier: ^1.3.0
-        version: 1.3.0
       typescript:
         specifier: ^5.5.2
         version: 5.5.2
@@ -8993,7 +9018,7 @@ snapshots:
 
   '@types/ws@8.5.10':
     dependencies:
-      '@types/node': 20.12.12
+      '@types/node': 20.14.10
 
   '@types/yargs-parser@21.0.2': {}
 
@@ -10277,8 +10302,8 @@ snapshots:
       '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.3)
       eslint: 8.57.0
       eslint-import-resolver-node: 0.3.9
-      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0)
       eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
       eslint-plugin-react: 7.34.1(eslint@8.57.0)
       eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@@ -10296,13 +10321,13 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0):
+  eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
     dependencies:
       debug: 4.3.4
       enhanced-resolve: 5.16.1
       eslint: 8.57.0
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+      eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0)
       fast-glob: 3.3.2
       get-tsconfig: 4.7.5
       is-core-module: 2.13.1
@@ -10330,14 +10355,25 @@ snapshots:
       - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
+  eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
     dependencies:
       debug: 3.2.7
     optionalDependencies:
       '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.3)
       eslint: 8.57.0
       eslint-import-resolver-node: 0.3.9
-      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0)
+      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+    transitivePeerDependencies:
+      - supports-color
+
+  eslint-module-utils@2.8.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0):
+    dependencies:
+      debug: 3.2.7
+    optionalDependencies:
+      '@typescript-eslint/parser': 7.16.0(eslint@9.3.0)(typescript@5.5.3)
+      eslint: 8.57.0
+      eslint-import-resolver-node: 0.3.9
+      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -10366,7 +10402,7 @@ snapshots:
     dependencies:
       eslint: 9.3.0
 
-  eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+  eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0):
     dependencies:
       array-includes: 3.1.8
       array.prototype.findlastindex: 1.2.5
@@ -10376,7 +10412,7 @@ snapshots:
       doctrine: 2.1.0
       eslint: 8.57.0
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
+      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.16.0(eslint@9.3.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@9.3.0))(eslint@8.57.0)
       hasown: 2.0.2
       is-core-module: 2.13.1
       is-glob: 4.0.3
@@ -10387,7 +10423,7 @@ snapshots:
       semver: 6.3.1
       tsconfig-paths: 3.15.0
     optionalDependencies:
-      '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.3)
+      '@typescript-eslint/parser': 7.16.0(eslint@9.3.0)(typescript@5.5.3)
     transitivePeerDependencies:
       - eslint-import-resolver-typescript
       - eslint-import-resolver-webpack