From 99cdae3f5bc2d9f0484723e161804f7fa1e7fe1c Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 22 Oct 2025 14:19:34 +0200 Subject: [PATCH] feat: add types v1 --- .../services/sdk/chainable-promise.ts | 77 +++++++++++++++++++ .../services/sdk/remote-sdk.service.ts | 11 +++ .../src/platform/services/sdk/remote.ts | 21 +++++ 3 files changed, 109 insertions(+) create mode 100644 libs/common/src/platform/services/sdk/chainable-promise.ts create mode 100644 libs/common/src/platform/services/sdk/remote-sdk.service.ts create mode 100644 libs/common/src/platform/services/sdk/remote.ts diff --git a/libs/common/src/platform/services/sdk/chainable-promise.ts b/libs/common/src/platform/services/sdk/chainable-promise.ts new file mode 100644 index 00000000000..184b779b126 --- /dev/null +++ b/libs/common/src/platform/services/sdk/chainable-promise.ts @@ -0,0 +1,77 @@ +type AsChainable = T extends object + ? T extends Promise + ? T & { await: AwaitProxy } + : Promise & { await: AwaitProxy } + : Promise; + +type AwaitProxy = { + // Methods that return Promise -> (...args) => Promise (and if R is object, re-wrap to Chainable) + [K in keyof T]: T[K] extends (...args: infer A) => Promise + ? (...args: A) => AsChainable + : // Sync methods -> (...args) => Promise (and re-wrap objects) + T[K] extends (...args: infer A) => infer R + ? (...args: A) => AsChainable + : // Properties -> Promise + Promise; +}; + +export type ChainablePromise = T extends object + ? T extends Promise + ? T & { await: AwaitProxy } + : Promise & { await: AwaitProxy } + : Promise; + +export function chain(p: Promise): ChainablePromise { + const promise: any = p; + + if (!promise.await) { + const wrapIfObject = (x: U): any => + typeof x === "object" && x !== null ? chain(Promise.resolve(x as any)) : x; + + promise.await = new Proxy( + {}, + { + get(_t, prop: string | symbol) { + return (...args: any[]) => + Promise.resolve(p).then(async (obj) => { + const member = (obj as any)[prop]; + if (typeof member === "function") { + const result = await member.apply(obj, args); + return wrapIfObject(result); + } + // property access + return wrapIfObject(member); + }); + }, + }, + ); + } + + return promise; +} + +class A { + method(): ChainablePromise { + return chain(B.createB()); + } +} + +class B { + static async createB(): Promise { + return new B(); + } + + async asyncMethod(): Promise { + return new B(); + } + + syncMethod(): number { + return 42; + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +async function testChainable() { + const objectA = new A(); + return objectA.method().await.asyncMethod().await.syncMethod(); +} diff --git a/libs/common/src/platform/services/sdk/remote-sdk.service.ts b/libs/common/src/platform/services/sdk/remote-sdk.service.ts new file mode 100644 index 00000000000..cf2af4baf5c --- /dev/null +++ b/libs/common/src/platform/services/sdk/remote-sdk.service.ts @@ -0,0 +1,11 @@ +import { BitwardenClient, FolderView } from "@bitwarden/sdk-internal"; + +import { Remote } from "./remote"; + +export type RemoteSdk = Remote; + +const remoteClient: RemoteSdk = {} as RemoteSdk; + +export async function test(): Promise { + return await remoteClient.vault().await.folders().await.list(); +} diff --git a/libs/common/src/platform/services/sdk/remote.ts b/libs/common/src/platform/services/sdk/remote.ts new file mode 100644 index 00000000000..bc7d25337aa --- /dev/null +++ b/libs/common/src/platform/services/sdk/remote.ts @@ -0,0 +1,21 @@ +import { ChainablePromise } from "./chainable-promise"; + +export type Remote = { + [K in keyof T]: RemoteProperty; +}; + +export type RemoteProperty = T extends (...args: any[]) => any + ? RemoteFunction + : RemoteValue; + +export type RemoteReference = Remote; + +export type RemoteValue = T extends { free(): void } + ? ChainablePromise> + : T extends Promise + ? Promise + : Promise; + +export type RemoteFunction any> = ( + ...args: Parameters +) => RemoteValue>;