@solana/plugin-core 7.0.0 → 7.1.0-canary-20260812160611
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.cjs +51 -2
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.mjs +51 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.native.mjs +51 -2
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.node.cjs +51 -2
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +51 -2
- package/dist/index.node.mjs.map +1 -1
- package/dist/types/client.d.ts +22 -2
- package/dist/types/client.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +110 -6
package/dist/index.browser.cjs
CHANGED
|
@@ -67,7 +67,7 @@ function addCleanupToClientWithExistingStack(client, cleanup) {
|
|
|
67
67
|
return client;
|
|
68
68
|
}
|
|
69
69
|
function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
70
|
-
const stack =
|
|
70
|
+
const stack = createCleanupStack();
|
|
71
71
|
if (Symbol.dispose in client) {
|
|
72
72
|
const existingDispose = client[Symbol.dispose];
|
|
73
73
|
stack.defer(() => existingDispose.call(client));
|
|
@@ -76,11 +76,60 @@ function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
|
76
76
|
const additions = {
|
|
77
77
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
78
78
|
[Symbol.dispose]() {
|
|
79
|
-
stack
|
|
79
|
+
stack.dispose();
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
return extendClient(client, additions);
|
|
83
83
|
}
|
|
84
|
+
function createCleanupStack() {
|
|
85
|
+
return typeof globalThis.DisposableStack === "function" ? new globalThis.DisposableStack() : createFallbackCleanupStack();
|
|
86
|
+
}
|
|
87
|
+
function createFallbackCleanupStack() {
|
|
88
|
+
const cleanups = [];
|
|
89
|
+
let disposed = false;
|
|
90
|
+
return {
|
|
91
|
+
defer(cleanup) {
|
|
92
|
+
if (disposed) {
|
|
93
|
+
throw new ReferenceError("Cannot add values to a disposed stack");
|
|
94
|
+
}
|
|
95
|
+
if (typeof cleanup !== "function") {
|
|
96
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
97
|
+
}
|
|
98
|
+
cleanups.push(cleanup);
|
|
99
|
+
},
|
|
100
|
+
dispose() {
|
|
101
|
+
if (disposed) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
disposed = true;
|
|
105
|
+
let error;
|
|
106
|
+
let hasError = false;
|
|
107
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
108
|
+
try {
|
|
109
|
+
cleanups[ii]();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
112
|
+
hasError = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
cleanups.length = 0;
|
|
116
|
+
if (hasError) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function createSuppressedError(error, suppressed) {
|
|
123
|
+
const message = "An error was suppressed during disposal";
|
|
124
|
+
if (typeof globalThis.SuppressedError === "function") {
|
|
125
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
126
|
+
}
|
|
127
|
+
return Object.defineProperties(new Error(message), {
|
|
128
|
+
error: { configurable: true, value: error, writable: true },
|
|
129
|
+
name: { configurable: true, value: "SuppressedError", writable: true },
|
|
130
|
+
suppressed: { configurable: true, value: suppressed, writable: true }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
84
133
|
|
|
85
134
|
exports.createClient = createClient;
|
|
86
135
|
exports.extendClient = extendClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.browser.cjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Later, when the client is no longer needed:\n * using client = createClient().use(myPlugin();\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = new DisposableStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack[Symbol.dispose]();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.browser.cjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * Cleanups run in reverse order of registration, disposal is idempotent, and if more than one\n * cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have\n * not shipped explicit resource management are supported too, though a `using` declaration needs a\n * `Symbol.dispose` polyfill there.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Build the client in the scope that should own it:\n * using client = createClient().use(myPlugin());\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @example Disposing without a using declaration\n * `using` requires explicit resource management, which Safari has not shipped as of Safari 27.\n * Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the\n * polyfill before any client is created, since a client registers its dispose method under\n * whatever `Symbol.dispose` was at the time.\n * ```ts\n * const client = createClient().use(myPlugin());\n *\n * // Later, when the client is no longer needed:\n * client[Symbol.dispose]();\n * // `socket.close()` has now been called.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n * @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform\n * availability of `DisposableStack`.\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = createCleanupStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack.dispose();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n\n/**\n * The slice of `DisposableStack` that {@link withCleanup} relies on.\n *\n * Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be\n * satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on\n * runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`\n * because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.\n */\ntype CleanupStack = {\n defer(cleanup: () => void): void;\n dispose(): void;\n};\n\nfunction createCleanupStack(): CleanupStack {\n // Always prefer the runtime's own implementation. The fallback exists only for runtimes that\n // have not shipped explicit resource management — most notably Safari, which as of Safari 27\n // provides neither `DisposableStack` nor `Symbol.dispose`.\n return typeof globalThis.DisposableStack === 'function'\n ? new globalThis.DisposableStack()\n : createFallbackCleanupStack();\n}\n\nfunction createFallbackCleanupStack(): CleanupStack {\n const cleanups: (() => void)[] = [];\n let disposed = false;\n return {\n defer(cleanup) {\n if (disposed) {\n // Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that\n // both stacks refuse a late cleanup the same way, rather than silently accepting\n // one that will never run.\n throw new ReferenceError('Cannot add values to a disposed stack');\n }\n if (typeof cleanup !== 'function') {\n // `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that\n // failure to disposal would surface it far from its cause, and would tangle it up in\n // the error aggregation of unrelated cleanups.\n throw new TypeError(`${String(cleanup)} is not a function`);\n }\n cleanups.push(cleanup);\n },\n dispose() {\n if (disposed) {\n return;\n }\n disposed = true;\n let error: unknown;\n let hasError = false;\n // Cleanups run in reverse order of registration, and one that throws must not stop the\n // rest from running; their errors are aggregated into a `SuppressedError` chain instead.\n for (let ii = cleanups.length - 1; ii >= 0; ii--) {\n try {\n cleanups[ii]();\n } catch (e) {\n error = hasError ? createSuppressedError(e, error) : e;\n hasError = true;\n }\n }\n cleanups.length = 0;\n if (hasError) {\n throw error;\n }\n },\n };\n}\n\nfunction createSuppressedError(error: unknown, suppressed: unknown): Error {\n // The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument\n // form of `SuppressedError` leaves the message empty, which would make an aggregated error read\n // differently depending on which stack produced it.\n const message = 'An error was suppressed during disposal';\n if (typeof globalThis.SuppressedError === 'function') {\n return new globalThis.SuppressedError(error, suppressed, message);\n }\n // A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce\n // its shape on a plain error — including the non-enumerability of every property, so that\n // serializing or spreading the error does not depend on which runtime built it.\n return Object.defineProperties(new Error(message), {\n error: { configurable: true, value: error, writable: true },\n name: { configurable: true, value: 'SuppressedError', writable: true },\n suppressed: { configurable: true, value: suppressed, writable: true },\n });\n}\n"]}
|
package/dist/index.browser.mjs
CHANGED
|
@@ -65,7 +65,7 @@ function addCleanupToClientWithExistingStack(client, cleanup) {
|
|
|
65
65
|
return client;
|
|
66
66
|
}
|
|
67
67
|
function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
68
|
-
const stack =
|
|
68
|
+
const stack = createCleanupStack();
|
|
69
69
|
if (Symbol.dispose in client) {
|
|
70
70
|
const existingDispose = client[Symbol.dispose];
|
|
71
71
|
stack.defer(() => existingDispose.call(client));
|
|
@@ -74,11 +74,60 @@ function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
|
74
74
|
const additions = {
|
|
75
75
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
76
76
|
[Symbol.dispose]() {
|
|
77
|
-
stack
|
|
77
|
+
stack.dispose();
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
return extendClient(client, additions);
|
|
81
81
|
}
|
|
82
|
+
function createCleanupStack() {
|
|
83
|
+
return typeof globalThis.DisposableStack === "function" ? new globalThis.DisposableStack() : createFallbackCleanupStack();
|
|
84
|
+
}
|
|
85
|
+
function createFallbackCleanupStack() {
|
|
86
|
+
const cleanups = [];
|
|
87
|
+
let disposed = false;
|
|
88
|
+
return {
|
|
89
|
+
defer(cleanup) {
|
|
90
|
+
if (disposed) {
|
|
91
|
+
throw new ReferenceError("Cannot add values to a disposed stack");
|
|
92
|
+
}
|
|
93
|
+
if (typeof cleanup !== "function") {
|
|
94
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
95
|
+
}
|
|
96
|
+
cleanups.push(cleanup);
|
|
97
|
+
},
|
|
98
|
+
dispose() {
|
|
99
|
+
if (disposed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
disposed = true;
|
|
103
|
+
let error;
|
|
104
|
+
let hasError = false;
|
|
105
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
106
|
+
try {
|
|
107
|
+
cleanups[ii]();
|
|
108
|
+
} catch (e) {
|
|
109
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
110
|
+
hasError = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
cleanups.length = 0;
|
|
114
|
+
if (hasError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function createSuppressedError(error, suppressed) {
|
|
121
|
+
const message = "An error was suppressed during disposal";
|
|
122
|
+
if (typeof globalThis.SuppressedError === "function") {
|
|
123
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
124
|
+
}
|
|
125
|
+
return Object.defineProperties(new Error(message), {
|
|
126
|
+
error: { configurable: true, value: error, writable: true },
|
|
127
|
+
name: { configurable: true, value: "SuppressedError", writable: true },
|
|
128
|
+
suppressed: { configurable: true, value: suppressed, writable: true }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
82
131
|
|
|
83
132
|
export { createClient, extendClient, withCleanup };
|
|
84
133
|
//# sourceMappingURL=index.browser.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.browser.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Later, when the client is no longer needed:\n * using client = createClient().use(myPlugin();\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = new DisposableStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack[Symbol.dispose]();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.browser.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * Cleanups run in reverse order of registration, disposal is idempotent, and if more than one\n * cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have\n * not shipped explicit resource management are supported too, though a `using` declaration needs a\n * `Symbol.dispose` polyfill there.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Build the client in the scope that should own it:\n * using client = createClient().use(myPlugin());\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @example Disposing without a using declaration\n * `using` requires explicit resource management, which Safari has not shipped as of Safari 27.\n * Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the\n * polyfill before any client is created, since a client registers its dispose method under\n * whatever `Symbol.dispose` was at the time.\n * ```ts\n * const client = createClient().use(myPlugin());\n *\n * // Later, when the client is no longer needed:\n * client[Symbol.dispose]();\n * // `socket.close()` has now been called.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n * @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform\n * availability of `DisposableStack`.\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = createCleanupStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack.dispose();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n\n/**\n * The slice of `DisposableStack` that {@link withCleanup} relies on.\n *\n * Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be\n * satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on\n * runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`\n * because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.\n */\ntype CleanupStack = {\n defer(cleanup: () => void): void;\n dispose(): void;\n};\n\nfunction createCleanupStack(): CleanupStack {\n // Always prefer the runtime's own implementation. The fallback exists only for runtimes that\n // have not shipped explicit resource management — most notably Safari, which as of Safari 27\n // provides neither `DisposableStack` nor `Symbol.dispose`.\n return typeof globalThis.DisposableStack === 'function'\n ? new globalThis.DisposableStack()\n : createFallbackCleanupStack();\n}\n\nfunction createFallbackCleanupStack(): CleanupStack {\n const cleanups: (() => void)[] = [];\n let disposed = false;\n return {\n defer(cleanup) {\n if (disposed) {\n // Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that\n // both stacks refuse a late cleanup the same way, rather than silently accepting\n // one that will never run.\n throw new ReferenceError('Cannot add values to a disposed stack');\n }\n if (typeof cleanup !== 'function') {\n // `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that\n // failure to disposal would surface it far from its cause, and would tangle it up in\n // the error aggregation of unrelated cleanups.\n throw new TypeError(`${String(cleanup)} is not a function`);\n }\n cleanups.push(cleanup);\n },\n dispose() {\n if (disposed) {\n return;\n }\n disposed = true;\n let error: unknown;\n let hasError = false;\n // Cleanups run in reverse order of registration, and one that throws must not stop the\n // rest from running; their errors are aggregated into a `SuppressedError` chain instead.\n for (let ii = cleanups.length - 1; ii >= 0; ii--) {\n try {\n cleanups[ii]();\n } catch (e) {\n error = hasError ? createSuppressedError(e, error) : e;\n hasError = true;\n }\n }\n cleanups.length = 0;\n if (hasError) {\n throw error;\n }\n },\n };\n}\n\nfunction createSuppressedError(error: unknown, suppressed: unknown): Error {\n // The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument\n // form of `SuppressedError` leaves the message empty, which would make an aggregated error read\n // differently depending on which stack produced it.\n const message = 'An error was suppressed during disposal';\n if (typeof globalThis.SuppressedError === 'function') {\n return new globalThis.SuppressedError(error, suppressed, message);\n }\n // A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce\n // its shape on a plain error — including the non-enumerability of every property, so that\n // serializing or spreading the error does not depend on which runtime built it.\n return Object.defineProperties(new Error(message), {\n error: { configurable: true, value: error, writable: true },\n name: { configurable: true, value: 'SuppressedError', writable: true },\n suppressed: { configurable: true, value: suppressed, writable: true },\n });\n}\n"]}
|
package/dist/index.native.mjs
CHANGED
|
@@ -65,7 +65,7 @@ function addCleanupToClientWithExistingStack(client, cleanup) {
|
|
|
65
65
|
return client;
|
|
66
66
|
}
|
|
67
67
|
function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
68
|
-
const stack =
|
|
68
|
+
const stack = createCleanupStack();
|
|
69
69
|
if (Symbol.dispose in client) {
|
|
70
70
|
const existingDispose = client[Symbol.dispose];
|
|
71
71
|
stack.defer(() => existingDispose.call(client));
|
|
@@ -74,11 +74,60 @@ function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
|
74
74
|
const additions = {
|
|
75
75
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
76
76
|
[Symbol.dispose]() {
|
|
77
|
-
stack
|
|
77
|
+
stack.dispose();
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
return extendClient(client, additions);
|
|
81
81
|
}
|
|
82
|
+
function createCleanupStack() {
|
|
83
|
+
return typeof globalThis.DisposableStack === "function" ? new globalThis.DisposableStack() : createFallbackCleanupStack();
|
|
84
|
+
}
|
|
85
|
+
function createFallbackCleanupStack() {
|
|
86
|
+
const cleanups = [];
|
|
87
|
+
let disposed = false;
|
|
88
|
+
return {
|
|
89
|
+
defer(cleanup) {
|
|
90
|
+
if (disposed) {
|
|
91
|
+
throw new ReferenceError("Cannot add values to a disposed stack");
|
|
92
|
+
}
|
|
93
|
+
if (typeof cleanup !== "function") {
|
|
94
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
95
|
+
}
|
|
96
|
+
cleanups.push(cleanup);
|
|
97
|
+
},
|
|
98
|
+
dispose() {
|
|
99
|
+
if (disposed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
disposed = true;
|
|
103
|
+
let error;
|
|
104
|
+
let hasError = false;
|
|
105
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
106
|
+
try {
|
|
107
|
+
cleanups[ii]();
|
|
108
|
+
} catch (e) {
|
|
109
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
110
|
+
hasError = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
cleanups.length = 0;
|
|
114
|
+
if (hasError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function createSuppressedError(error, suppressed) {
|
|
121
|
+
const message = "An error was suppressed during disposal";
|
|
122
|
+
if (typeof globalThis.SuppressedError === "function") {
|
|
123
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
124
|
+
}
|
|
125
|
+
return Object.defineProperties(new Error(message), {
|
|
126
|
+
error: { configurable: true, value: error, writable: true },
|
|
127
|
+
name: { configurable: true, value: "SuppressedError", writable: true },
|
|
128
|
+
suppressed: { configurable: true, value: suppressed, writable: true }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
82
131
|
|
|
83
132
|
export { createClient, extendClient, withCleanup };
|
|
84
133
|
//# sourceMappingURL=index.native.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.native.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Later, when the client is no longer needed:\n * using client = createClient().use(myPlugin();\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = new DisposableStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack[Symbol.dispose]();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.native.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * Cleanups run in reverse order of registration, disposal is idempotent, and if more than one\n * cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have\n * not shipped explicit resource management are supported too, though a `using` declaration needs a\n * `Symbol.dispose` polyfill there.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Build the client in the scope that should own it:\n * using client = createClient().use(myPlugin());\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @example Disposing without a using declaration\n * `using` requires explicit resource management, which Safari has not shipped as of Safari 27.\n * Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the\n * polyfill before any client is created, since a client registers its dispose method under\n * whatever `Symbol.dispose` was at the time.\n * ```ts\n * const client = createClient().use(myPlugin());\n *\n * // Later, when the client is no longer needed:\n * client[Symbol.dispose]();\n * // `socket.close()` has now been called.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n * @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform\n * availability of `DisposableStack`.\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = createCleanupStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack.dispose();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n\n/**\n * The slice of `DisposableStack` that {@link withCleanup} relies on.\n *\n * Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be\n * satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on\n * runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`\n * because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.\n */\ntype CleanupStack = {\n defer(cleanup: () => void): void;\n dispose(): void;\n};\n\nfunction createCleanupStack(): CleanupStack {\n // Always prefer the runtime's own implementation. The fallback exists only for runtimes that\n // have not shipped explicit resource management — most notably Safari, which as of Safari 27\n // provides neither `DisposableStack` nor `Symbol.dispose`.\n return typeof globalThis.DisposableStack === 'function'\n ? new globalThis.DisposableStack()\n : createFallbackCleanupStack();\n}\n\nfunction createFallbackCleanupStack(): CleanupStack {\n const cleanups: (() => void)[] = [];\n let disposed = false;\n return {\n defer(cleanup) {\n if (disposed) {\n // Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that\n // both stacks refuse a late cleanup the same way, rather than silently accepting\n // one that will never run.\n throw new ReferenceError('Cannot add values to a disposed stack');\n }\n if (typeof cleanup !== 'function') {\n // `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that\n // failure to disposal would surface it far from its cause, and would tangle it up in\n // the error aggregation of unrelated cleanups.\n throw new TypeError(`${String(cleanup)} is not a function`);\n }\n cleanups.push(cleanup);\n },\n dispose() {\n if (disposed) {\n return;\n }\n disposed = true;\n let error: unknown;\n let hasError = false;\n // Cleanups run in reverse order of registration, and one that throws must not stop the\n // rest from running; their errors are aggregated into a `SuppressedError` chain instead.\n for (let ii = cleanups.length - 1; ii >= 0; ii--) {\n try {\n cleanups[ii]();\n } catch (e) {\n error = hasError ? createSuppressedError(e, error) : e;\n hasError = true;\n }\n }\n cleanups.length = 0;\n if (hasError) {\n throw error;\n }\n },\n };\n}\n\nfunction createSuppressedError(error: unknown, suppressed: unknown): Error {\n // The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument\n // form of `SuppressedError` leaves the message empty, which would make an aggregated error read\n // differently depending on which stack produced it.\n const message = 'An error was suppressed during disposal';\n if (typeof globalThis.SuppressedError === 'function') {\n return new globalThis.SuppressedError(error, suppressed, message);\n }\n // A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce\n // its shape on a plain error — including the non-enumerability of every property, so that\n // serializing or spreading the error does not depend on which runtime built it.\n return Object.defineProperties(new Error(message), {\n error: { configurable: true, value: error, writable: true },\n name: { configurable: true, value: 'SuppressedError', writable: true },\n suppressed: { configurable: true, value: suppressed, writable: true },\n });\n}\n"]}
|
package/dist/index.node.cjs
CHANGED
|
@@ -67,7 +67,7 @@ function addCleanupToClientWithExistingStack(client, cleanup) {
|
|
|
67
67
|
return client;
|
|
68
68
|
}
|
|
69
69
|
function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
70
|
-
const stack =
|
|
70
|
+
const stack = createCleanupStack();
|
|
71
71
|
if (Symbol.dispose in client) {
|
|
72
72
|
const existingDispose = client[Symbol.dispose];
|
|
73
73
|
stack.defer(() => existingDispose.call(client));
|
|
@@ -76,11 +76,60 @@ function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
|
76
76
|
const additions = {
|
|
77
77
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
78
78
|
[Symbol.dispose]() {
|
|
79
|
-
stack
|
|
79
|
+
stack.dispose();
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
return extendClient(client, additions);
|
|
83
83
|
}
|
|
84
|
+
function createCleanupStack() {
|
|
85
|
+
return typeof globalThis.DisposableStack === "function" ? new globalThis.DisposableStack() : createFallbackCleanupStack();
|
|
86
|
+
}
|
|
87
|
+
function createFallbackCleanupStack() {
|
|
88
|
+
const cleanups = [];
|
|
89
|
+
let disposed = false;
|
|
90
|
+
return {
|
|
91
|
+
defer(cleanup) {
|
|
92
|
+
if (disposed) {
|
|
93
|
+
throw new ReferenceError("Cannot add values to a disposed stack");
|
|
94
|
+
}
|
|
95
|
+
if (typeof cleanup !== "function") {
|
|
96
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
97
|
+
}
|
|
98
|
+
cleanups.push(cleanup);
|
|
99
|
+
},
|
|
100
|
+
dispose() {
|
|
101
|
+
if (disposed) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
disposed = true;
|
|
105
|
+
let error;
|
|
106
|
+
let hasError = false;
|
|
107
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
108
|
+
try {
|
|
109
|
+
cleanups[ii]();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
112
|
+
hasError = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
cleanups.length = 0;
|
|
116
|
+
if (hasError) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function createSuppressedError(error, suppressed) {
|
|
123
|
+
const message = "An error was suppressed during disposal";
|
|
124
|
+
if (typeof globalThis.SuppressedError === "function") {
|
|
125
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
126
|
+
}
|
|
127
|
+
return Object.defineProperties(new Error(message), {
|
|
128
|
+
error: { configurable: true, value: error, writable: true },
|
|
129
|
+
name: { configurable: true, value: "SuppressedError", writable: true },
|
|
130
|
+
suppressed: { configurable: true, value: suppressed, writable: true }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
84
133
|
|
|
85
134
|
exports.createClient = createClient;
|
|
86
135
|
exports.extendClient = extendClient;
|
package/dist/index.node.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.node.cjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Later, when the client is no longer needed:\n * using client = createClient().use(myPlugin();\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = new DisposableStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack[Symbol.dispose]();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.node.cjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * Cleanups run in reverse order of registration, disposal is idempotent, and if more than one\n * cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have\n * not shipped explicit resource management are supported too, though a `using` declaration needs a\n * `Symbol.dispose` polyfill there.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Build the client in the scope that should own it:\n * using client = createClient().use(myPlugin());\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @example Disposing without a using declaration\n * `using` requires explicit resource management, which Safari has not shipped as of Safari 27.\n * Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the\n * polyfill before any client is created, since a client registers its dispose method under\n * whatever `Symbol.dispose` was at the time.\n * ```ts\n * const client = createClient().use(myPlugin());\n *\n * // Later, when the client is no longer needed:\n * client[Symbol.dispose]();\n * // `socket.close()` has now been called.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n * @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform\n * availability of `DisposableStack`.\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = createCleanupStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack.dispose();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n\n/**\n * The slice of `DisposableStack` that {@link withCleanup} relies on.\n *\n * Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be\n * satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on\n * runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`\n * because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.\n */\ntype CleanupStack = {\n defer(cleanup: () => void): void;\n dispose(): void;\n};\n\nfunction createCleanupStack(): CleanupStack {\n // Always prefer the runtime's own implementation. The fallback exists only for runtimes that\n // have not shipped explicit resource management — most notably Safari, which as of Safari 27\n // provides neither `DisposableStack` nor `Symbol.dispose`.\n return typeof globalThis.DisposableStack === 'function'\n ? new globalThis.DisposableStack()\n : createFallbackCleanupStack();\n}\n\nfunction createFallbackCleanupStack(): CleanupStack {\n const cleanups: (() => void)[] = [];\n let disposed = false;\n return {\n defer(cleanup) {\n if (disposed) {\n // Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that\n // both stacks refuse a late cleanup the same way, rather than silently accepting\n // one that will never run.\n throw new ReferenceError('Cannot add values to a disposed stack');\n }\n if (typeof cleanup !== 'function') {\n // `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that\n // failure to disposal would surface it far from its cause, and would tangle it up in\n // the error aggregation of unrelated cleanups.\n throw new TypeError(`${String(cleanup)} is not a function`);\n }\n cleanups.push(cleanup);\n },\n dispose() {\n if (disposed) {\n return;\n }\n disposed = true;\n let error: unknown;\n let hasError = false;\n // Cleanups run in reverse order of registration, and one that throws must not stop the\n // rest from running; their errors are aggregated into a `SuppressedError` chain instead.\n for (let ii = cleanups.length - 1; ii >= 0; ii--) {\n try {\n cleanups[ii]();\n } catch (e) {\n error = hasError ? createSuppressedError(e, error) : e;\n hasError = true;\n }\n }\n cleanups.length = 0;\n if (hasError) {\n throw error;\n }\n },\n };\n}\n\nfunction createSuppressedError(error: unknown, suppressed: unknown): Error {\n // The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument\n // form of `SuppressedError` leaves the message empty, which would make an aggregated error read\n // differently depending on which stack produced it.\n const message = 'An error was suppressed during disposal';\n if (typeof globalThis.SuppressedError === 'function') {\n return new globalThis.SuppressedError(error, suppressed, message);\n }\n // A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce\n // its shape on a plain error — including the non-enumerability of every property, so that\n // serializing or spreading the error does not depend on which runtime built it.\n return Object.defineProperties(new Error(message), {\n error: { configurable: true, value: error, writable: true },\n name: { configurable: true, value: 'SuppressedError', writable: true },\n suppressed: { configurable: true, value: suppressed, writable: true },\n });\n}\n"]}
|
package/dist/index.node.mjs
CHANGED
|
@@ -65,7 +65,7 @@ function addCleanupToClientWithExistingStack(client, cleanup) {
|
|
|
65
65
|
return client;
|
|
66
66
|
}
|
|
67
67
|
function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
68
|
-
const stack =
|
|
68
|
+
const stack = createCleanupStack();
|
|
69
69
|
if (Symbol.dispose in client) {
|
|
70
70
|
const existingDispose = client[Symbol.dispose];
|
|
71
71
|
stack.defer(() => existingDispose.call(client));
|
|
@@ -74,11 +74,60 @@ function addCleanupToClientWithoutExistingStack(client, cleanup) {
|
|
|
74
74
|
const additions = {
|
|
75
75
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
76
76
|
[Symbol.dispose]() {
|
|
77
|
-
stack
|
|
77
|
+
stack.dispose();
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
return extendClient(client, additions);
|
|
81
81
|
}
|
|
82
|
+
function createCleanupStack() {
|
|
83
|
+
return typeof globalThis.DisposableStack === "function" ? new globalThis.DisposableStack() : createFallbackCleanupStack();
|
|
84
|
+
}
|
|
85
|
+
function createFallbackCleanupStack() {
|
|
86
|
+
const cleanups = [];
|
|
87
|
+
let disposed = false;
|
|
88
|
+
return {
|
|
89
|
+
defer(cleanup) {
|
|
90
|
+
if (disposed) {
|
|
91
|
+
throw new ReferenceError("Cannot add values to a disposed stack");
|
|
92
|
+
}
|
|
93
|
+
if (typeof cleanup !== "function") {
|
|
94
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
95
|
+
}
|
|
96
|
+
cleanups.push(cleanup);
|
|
97
|
+
},
|
|
98
|
+
dispose() {
|
|
99
|
+
if (disposed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
disposed = true;
|
|
103
|
+
let error;
|
|
104
|
+
let hasError = false;
|
|
105
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
106
|
+
try {
|
|
107
|
+
cleanups[ii]();
|
|
108
|
+
} catch (e) {
|
|
109
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
110
|
+
hasError = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
cleanups.length = 0;
|
|
114
|
+
if (hasError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function createSuppressedError(error, suppressed) {
|
|
121
|
+
const message = "An error was suppressed during disposal";
|
|
122
|
+
if (typeof globalThis.SuppressedError === "function") {
|
|
123
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
124
|
+
}
|
|
125
|
+
return Object.defineProperties(new Error(message), {
|
|
126
|
+
error: { configurable: true, value: error, writable: true },
|
|
127
|
+
name: { configurable: true, value: "SuppressedError", writable: true },
|
|
128
|
+
suppressed: { configurable: true, value: suppressed, writable: true }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
82
131
|
|
|
83
132
|
export { createClient, extendClient, withCleanup };
|
|
84
133
|
//# sourceMappingURL=index.node.mjs.map
|
package/dist/index.node.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";AA2KO,SAAS,aAA4C,KAAA,EAA8B;AACtF,EAAA,OAAO,MAAA,CAAO,KAAA,IAAU,EAAY,CAAA;AACxC;AAEA,SAAS,OAA6B,KAAA,EAA6B;AAC/D,EAAA,OAAO,MAAA,CAAO,MAAA;AAAA,IACV,MAAA,CAAO,gBAAA;AAAA,MACH,EAAC;AAAA,MACD;AAAA,QACI,GAAG,MAAA,CAAO,yBAAA,CAA0B,KAAK,CAAA;AAAA,QACzC,GAAA,EAAK;AAAA,UACD,YAAA,EAAc,KAAA;AAAA,UACd,UAAA,EAAY,IAAA;AAAA,UACZ,KAAA,EAAO,SAAoD,MAAA,EAAsC;AAC7F,YAAA,MAAM,MAAA,GAAS,OAAO,KAAK,CAAA;AAC3B,YAAA,OAAO,kBAAkB,OAAA,GAAU,iBAAA,CAAkB,MAAM,CAAA,GAAI,OAAO,MAAM,CAAA;AAAA,UAChF,CAAA;AAAA,UACA,QAAA,EAAU;AAAA;AACd;AACJ;AACJ,GACJ;AACJ;AAEA,SAAS,kBAAwC,OAAA,EAA6C;AAC1F,EAAA,OAAO,OAAO,MAAA,CAAO;AAAA,IACjB,MAAM,UAAA,EAAY;AACd,MAAA,OAAO,OAAA,CAAQ,KAAK,CAAA,CAAA,KAAK,MAAA,CAAO,CAAC,CAAC,CAAA,CAAE,MAAM,UAAU,CAAA;AAAA,IACxD,CAAA;AAAA,IACA,QAAQ,SAAA,EAAW;AACf,MAAA,OAAO,OAAA,CAAQ,KAAK,CAAA,CAAA,KAAK,MAAA,CAAO,CAAC,CAAC,CAAA,CAAE,QAAQ,SAAS,CAAA;AAAA,IACzD,CAAA;AAAA,IACA,IAAA,CAAK,aAAa,UAAA,EAAY;AAC1B,MAAA,OAAO,OAAA,CAAQ,KAAK,CAAA,CAAA,KAAK,MAAA,CAAO,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,WAAA,EAAa,UAAU,CAAA;AAAA,IACpE,CAAA;AAAA,IACA,IAA8C,MAAA,EAAsC;AAChF,MAAA,OAAO,iBAAA,CAAkB,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,IACjD;AAAA,GACmB,CAAA;AAC3B;AA2DO,SAAS,YAAA,CACZ,QACA,SAAA,EACmC;AACnC,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,gBAAA,CAAiB,EAAC,EAAG,0BAA0B,MAAA,CAAO,yBAAA,CAA0B,MAAM,CAAC,CAAC,CAAA;AAC9G,EAAA,MAAA,CAAO,gBAAA,CAAiB,MAAA,EAAQ,MAAA,CAAO,yBAAA,CAA0B,SAAS,CAAC,CAAA;AAC3E,EAAA,OAAO,MAAA,CAAO,OAAO,MAAM,CAAA;AAC/B;AAEA,SAAS,0BAA2D,WAAA,EAAmB;AACnF,EAAA,MAAM,SAAS,EAAC;AAChB,EAAA,KAAA,MAAW,GAAA,IAAO,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC5C,IAAA,MAAA,CAAO,GAAG,IAAI,EAAE,GAAG,YAAY,GAAc,CAAA,EAAG,cAAc,IAAA,EAAK;AAAA,EACvE;AACA,EAAA,OAAO,MAAA;AACX;AAwCO,SAAS,WAAA,CACZ,QACA,OAAA,EACmC;AACnC,EAAA,IAAI,6BAA6B,MAAA,EAAQ;AACrC,IAAA,OAAO,mCAAA;AAAA,MACH,MAAA;AAAA,MACA;AAAA,KACJ;AAAA,EACJ,CAAA,MAAO;AACH,IAAA,OAAO,sCAAA,CAAuC,QAAQ,OAAO,CAAA;AAAA,EACjE;AACJ;AAEA,IAAM,yBAAA,GAA4B,6BAAA;AAElC,SAAS,mCAAA,CACL,QACA,OAAA,EACmC;AAEnC,EAAA,MAAA,CAAO,yBAAyB,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA;AAE/C,EAAA,OAAO,MAAA;AACX;AAEA,SAAS,sCAAA,CACL,QACA,OAAA,EACmC;AACnC,EAAA,MAAM,KAAA,GAAQ,IAAI,eAAA,EAAgB;AAGlC,EAAA,IAAI,MAAA,CAAO,WAAW,MAAA,EAAQ;AAC1B,IAAA,MAAM,eAAA,GAAmB,MAAA,CAAsB,MAAA,CAAO,OAAO,CAAA;AAC7D,IAAA,KAAA,CAAM,KAAA,CAAM,MAAM,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,EAClD;AAGA,EAAA,KAAA,CAAM,MAAM,OAAO,CAAA;AAGnB,EAAA,MAAM,SAAA,GAAY;AAAA,IACd,CAAC,yBAAyB,GAAG,KAAA;AAAA,IAC7B,CAAC,MAAA,CAAO,OAAO,CAAA,GAAI;AACf,MAAA,KAAA,CAAM,MAAA,CAAO,OAAO,CAAA,EAAE;AAAA,IAC1B;AAAA,GACJ;AAEA,EAAA,OAAO,YAAA,CAAa,QAAQ,SAAS,CAAA;AACzC","file":"index.node.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Later, when the client is no longer needed:\n * using client = createClient().use(myPlugin();\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, DisposableStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = new DisposableStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack[Symbol.dispose]();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"index.node.mjs","sourcesContent":["/**\n * Defines a plugin that transforms or extends a client with additional functionality.\n *\n * For instance, plugins may add RPC capabilities, wallet integration, transaction building,\n * or other features necessary for interacting with the Solana blockchain.\n *\n * Plugins are functions that take a client object as input and return a new client object\n * or a promise that resolves to a new client object. This allows for both synchronous\n * and asynchronous transformations and extensions of the client.\n *\n * Plugins are usually applied using the `use` method on a {@link Client} or {@link AsyncClient}\n * instance, which {@link createClient} provides as a starting point.\n *\n * @typeParam TInput - The input client object type that this plugin accepts.\n * @typeParam TOutput - The output type. Either a new client object or a promise resolving to one.\n *\n * @example Basic RPC plugin\n * Given an RPC endpoint, this plugin adds an `rpc` property to the client.\n *\n * ```ts\n * import { createClient, createSolanaRpc } from '@solana/kit';\n *\n * // Define a simple RPC plugin.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n *\n * // Use the plugin.\n * const client = createClient().use(rpcPlugin('https://api.mainnet-beta.solana.com'));\n * await client.rpc.getLatestBlockhash().send();\n * ```\n *\n * @example Async plugin that generates a payer wallet\n * The following plugin shows how to create an asynchronous plugin that generates a new keypair signer.\n *\n * ```ts\n * import { createClient, generateKeypairSigner } from '@solana/kit';\n *\n * // Define a plugin that generates a new keypair signer.\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n *\n * // Use the plugin.\n * const client = await createClient().use(generatedPayerPlugin());\n * console.log(client.payer.address);\n * ```\n *\n * @example Plugins with input requirements\n * A plugin can specify required properties on the input client. The example below requires the\n * client to already have a `payer` signer attached to the client in order to perform an airdrop.\n *\n * ```ts\n * import { createClient, TransactionSigner, Lamports, lamports } from '@solana/kit';\n *\n * // Define a plugin that airdrops lamports to the payer set on the client.\n * function airdropPayerPlugin(lamports: Lamports) {\n * return async <T extends { payer: TransactionSigner }>(client: T) => {\n * await myAirdropFunction(client.payer, lamports);\n * return client;\n * };\n * }\n *\n * // Use the plugins.\n * const client = await createClient()\n * .use(generatedPayerPlugin()) // This is required before using the airdrop plugin.\n * .use(airdropPayerPlugin(lamports(1_000_000_000n)));\n * ```\n *\n * @example Chaining plugins\n * Multiple plugins — asynchronous or not — can be chained together to build up complex clients.\n * The example below demonstrates how to gradually build a client with multiple plugins.\n * Notice how, despite having multiple asynchronous plugins, we only need to `await` the final result.\n * This is because the `use` method on `AsyncClient` returns another `AsyncClient`, allowing for seamless chaining.\n *\n * ```ts\n * import { createClient, createSolanaRpc, createSolanaRpcSubscriptions, generateKeypairSigner } from '@solana/kit';\n *\n * // Define multiple plugins.\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpc(endpoint) });\n * }\n * function rpcSubscriptionsPlugin(endpoint: string) {\n * return <T extends object>(client: T) => ({...client, rpc: createSolanaRpcSubscriptions(endpoint) });\n * }\n * function generatedPayerPlugin() {\n * return async <T extends object>(client: T) => ({...client, payer: await generateKeypairSigner() });\n * }\n * function generatedAuthorityPlugin() {\n * return async <T extends object>(client: T) => ({...client, authority: await generateKeypairSigner() });\n * }\n *\n * // Chain plugins together.\n * const client = await createClient()\n * .use(rpcPlugin('https://api.mainnet-beta.solana.com'))\n * .use(rpcSubscriptionsPlugin('wss://api.mainnet-beta.solana.com'))\n * .use(generatedPayerPlugin())\n * .use(generatedAuthorityPlugin());\n * ```\n */\nexport type ClientPlugin<TInput extends object, TOutput extends Promise<object> | object> = (input: TInput) => TOutput;\n\n/**\n * A client that can be extended with plugins.\n *\n * The `Client` type represents a client object that can be built up through\n * the application of one or more plugins. It provides a `use` method to\n * apply plugins, either synchronously (returning a new `Client`) or\n * asynchronously (returning an {@link AsyncClient}).\n *\n * @typeParam TSelf - The current shape of the client object including all applied plugins.\n */\nexport type Client<TSelf extends object> = TSelf & {\n /**\n * Applies a plugin to extend or transform the client.\n *\n * @param plugin The plugin function to apply to this client.\n * @returns Either a new `Client` (for sync plugins) or {@link AsyncClient} (for async plugins).\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => TOutput extends Promise<infer U> ? AsyncClient<U extends object ? U : never> : Client<TOutput>;\n};\n\n/**\n * An asynchronous wrapper that represents a promise of a client.\n *\n * The `AsyncClient` type is returned when an async plugin is applied to a client.\n * It behaves like a `Promise<Client<TSelf>>` but with an additional `use` method\n * that allows chaining more plugins before the promise resolves.\n *\n * This enables fluent chaining of both synchronous and asynchronous plugins\n * without having to await intermediate promises.\n *\n * @typeParam TSelf - The shape of the client object that this async client will resolve to.\n */\nexport type AsyncClient<TSelf extends object> = Promise<Client<TSelf>> & {\n /**\n * Applies a plugin to the client once it resolves.\n *\n * @param plugin The plugin function to apply to the resolved client.\n * @returns A new `AsyncClient` representing the result of applying the plugin.\n */\n readonly use: <TOutput extends Promise<object> | object>(\n plugin: ClientPlugin<TSelf, TOutput>,\n ) => AsyncClient<TOutput extends Promise<infer U> ? (U extends object ? U : never) : TOutput>;\n};\n\n/**\n * Creates a new empty client that can be extended with plugins.\n *\n * This serves as an entry point for building Solana clients.\n * Start with an empty client and chain the `.use()` method\n * to apply plugins that add various functionalities such as RPC\n * connectivity, wallet integration, transaction building, and more.\n *\n * See {@link ClientPlugin} for detailed examples on creating and using plugins.\n *\n * @returns An empty client object with only the `use` method available.\n *\n * @example Basic client setup\n * ```ts\n * import { createClient } from '@solana/client';\n * import { generatedPayer } from '@solana/kit-plugin-payer';\n * import { rpc } from '@solana/kit-plugin-rpc';\n *\n * const client = await createClient()\n * .use(generatedPayer())\n * .use(rpc('https://api.mainnet-beta.solana.com'));\n * ```\n */\nexport function createClient<TSelf extends object = object>(value?: TSelf): Client<TSelf> {\n return addUse(value ?? ({} as TSelf));\n}\n\nfunction addUse<TSelf extends object>(value: TSelf): Client<TSelf> {\n return Object.freeze(\n Object.defineProperties(\n {},\n {\n ...Object.getOwnPropertyDescriptors(value),\n use: {\n configurable: false,\n enumerable: true,\n value: function <TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n const result = plugin(value);\n return result instanceof Promise ? createAsyncClient(result) : addUse(result);\n },\n writable: false,\n },\n },\n ),\n ) as Client<TSelf>;\n}\n\nfunction createAsyncClient<TSelf extends object>(promise: Promise<TSelf>): AsyncClient<TSelf> {\n return Object.freeze({\n catch(onrejected) {\n return promise.then(v => addUse(v)).catch(onrejected);\n },\n finally(onfinally) {\n return promise.then(v => addUse(v)).finally(onfinally);\n },\n then(onfulfilled, onrejected) {\n return promise.then(v => addUse(v)).then(onfulfilled, onrejected);\n },\n use<TOutput extends Promise<object> | object>(plugin: ClientPlugin<TSelf, TOutput>) {\n return createAsyncClient(promise.then(plugin));\n },\n } as AsyncClient<TSelf>);\n}\n\n/**\n * The result of extending a client of type `TClient` with additional properties of type `TAdditions`.\n *\n * Structurally equivalent to `Omit<TClient, keyof TAdditions> & TAdditions` — keys present\n * on both `TClient` and `TAdditions` are replaced by the `TAdditions` version — but expressed\n * as a single homomorphic mapped type so the inferred type displays as a flat object literal\n * rather than a deeply nested chain of `Omit<...>` intersections. Optional (`?`) and `readonly`\n * modifiers from both sides are preserved.\n *\n * Plugin authors who write their own merging helpers can reuse this type to keep the\n * inferred shape of their plugin's output legible in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the client being extended.\n * @typeParam TAdditions - The type of the properties being merged in.\n *\n * @example\n * ```ts\n * function withRpc<TClient extends object>(client: TClient, endpoint: string): ExtendedClient<TClient, { rpc: Rpc }> {\n * return extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link extendClient}\n */\nexport type ExtendedClient<TClient extends object, TAdditions extends object> = {\n [K in keyof (Omit<TClient, keyof TAdditions> & TAdditions)]: (Omit<TClient, keyof TAdditions> & TAdditions)[K];\n} & {};\n\n/**\n * Extends a client object with additional properties, preserving property descriptors\n * (getters, symbol-keyed properties, and non-enumerable properties) from both objects.\n *\n * Use this inside plugins instead of plain object spread (`{...client, ...additions}`)\n * when the client may carry getters or symbol-keyed properties that spread would flatten or lose.\n * When the same key exists on both, `additions` wins.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a single\n * object literal so chained `extendClient` calls do not accumulate nested `Omit<...>` wrappers\n * in editor tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @typeParam TAdditions - The type of the properties being added.\n * @param client - The original client object to extend.\n * @param additions - The properties to add or override on the client.\n * @returns A new object combining both, with `additions` taking precedence on conflicts.\n *\n * @example\n * ```ts\n * function rpcPlugin(endpoint: string) {\n * return <T extends object>(client: T) =>\n * extendClient(client, { rpc: createSolanaRpc(endpoint) });\n * }\n * ```\n *\n * @see {@link ClientPlugin}\n * @see {@link ExtendedClient}\n */\nexport function extendClient<TClient extends object, TAdditions extends object>(\n client: TClient,\n additions: TAdditions,\n): ExtendedClient<TClient, TAdditions> {\n const result = Object.defineProperties({}, toConfigurableDescriptors(Object.getOwnPropertyDescriptors(client)));\n Object.defineProperties(result, Object.getOwnPropertyDescriptors(additions));\n return Object.freeze(result) as ExtendedClient<TClient, TAdditions>;\n}\n\nfunction toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors: T): T {\n const result = {} as Record<string | symbol, PropertyDescriptor>;\n for (const key of Reflect.ownKeys(descriptors)) {\n result[key] = { ...descriptors[key as keyof T], configurable: true };\n }\n return result as T;\n}\n\n/**\n * Wraps a client with a cleanup function, making it {@link Disposable}.\n *\n * Plugin authors can use this to register teardown logic (e.g. closing\n * connections or clearing timers) that runs when the client is disposed.\n * If the client already implements `Symbol.dispose`, the existing dispose\n * logic is chained so that it runs after the new `cleanup` function.\n *\n * Cleanups run in reverse order of registration, disposal is idempotent, and if more than one\n * cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have\n * not shipped explicit resource management are supported too, though a `using` declaration needs a\n * `Symbol.dispose` polyfill there.\n *\n * The return type is an {@link ExtendedClient}, which flattens the merged shape into a\n * single object literal so chained calls do not accumulate nested intersections in editor\n * tooltips and error messages.\n *\n * @typeParam TClient - The type of the original client.\n * @param client - The client to wrap.\n * @param cleanup - The cleanup function to run when the client is disposed.\n * @return A new client that extends `TClient` and implements `Disposable`.\n *\n * @example\n * Register a cleanup function in a plugin that opens a WebSocket connection.\n * ```ts\n * function myPlugin() {\n * return <T extends object>(client: T) => {\n * const socket = new WebSocket('wss://api.example.com');\n * return withCleanup(\n * extendClient(client, { socket }),\n * () => socket.close(),\n * );\n * };\n * }\n *\n * // Build the client in the scope that should own it:\n * using client = createClient().use(myPlugin());\n * // `socket.close()` is called automatically when `client` goes out of scope.\n * ```\n *\n * @example Disposing without a using declaration\n * `using` requires explicit resource management, which Safari has not shipped as of Safari 27.\n * Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the\n * polyfill before any client is created, since a client registers its dispose method under\n * whatever `Symbol.dispose` was at the time.\n * ```ts\n * const client = createClient().use(myPlugin());\n *\n * // Later, when the client is no longer needed:\n * client[Symbol.dispose]();\n * // `socket.close()` has now been called.\n * ```\n *\n * @see {@link extendClient}\n * @see {@link ExtendedClient}\n * @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform\n * availability of `DisposableStack`.\n */\nexport function withCleanup<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n if (DISPOSABLE_STACK_PROPERTY in client) {\n return addCleanupToClientWithExistingStack(\n client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,\n cleanup,\n );\n } else {\n return addCleanupToClientWithoutExistingStack(client, cleanup);\n }\n}\n\nconst DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;\n\nfunction addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n // If we already have the stack, add the new cleanup to it\n client[DISPOSABLE_STACK_PROPERTY].defer(cleanup);\n // We assume we already added a dispose method when we added the stack\n return client as unknown as ExtendedClient<TClient, Disposable>;\n}\n\nfunction addCleanupToClientWithoutExistingStack<TClient extends object>(\n client: TClient,\n cleanup: () => void,\n): ExtendedClient<TClient, Disposable> {\n const stack = createCleanupStack();\n\n // If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack\n if (Symbol.dispose in client) {\n const existingDispose = (client as Disposable)[Symbol.dispose];\n stack.defer(() => existingDispose.call(client));\n }\n\n // Add the new cleanup to the stack\n stack.defer(cleanup);\n\n // We add our stack to the client, and replace any existing dispose method with our stack dispose\n const additions = {\n [DISPOSABLE_STACK_PROPERTY]: stack,\n [Symbol.dispose]() {\n stack.dispose();\n },\n };\n\n return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;\n}\n\n/**\n * The slice of `DisposableStack` that {@link withCleanup} relies on.\n *\n * Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be\n * satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on\n * runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`\n * because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.\n */\ntype CleanupStack = {\n defer(cleanup: () => void): void;\n dispose(): void;\n};\n\nfunction createCleanupStack(): CleanupStack {\n // Always prefer the runtime's own implementation. The fallback exists only for runtimes that\n // have not shipped explicit resource management — most notably Safari, which as of Safari 27\n // provides neither `DisposableStack` nor `Symbol.dispose`.\n return typeof globalThis.DisposableStack === 'function'\n ? new globalThis.DisposableStack()\n : createFallbackCleanupStack();\n}\n\nfunction createFallbackCleanupStack(): CleanupStack {\n const cleanups: (() => void)[] = [];\n let disposed = false;\n return {\n defer(cleanup) {\n if (disposed) {\n // Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that\n // both stacks refuse a late cleanup the same way, rather than silently accepting\n // one that will never run.\n throw new ReferenceError('Cannot add values to a disposed stack');\n }\n if (typeof cleanup !== 'function') {\n // `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that\n // failure to disposal would surface it far from its cause, and would tangle it up in\n // the error aggregation of unrelated cleanups.\n throw new TypeError(`${String(cleanup)} is not a function`);\n }\n cleanups.push(cleanup);\n },\n dispose() {\n if (disposed) {\n return;\n }\n disposed = true;\n let error: unknown;\n let hasError = false;\n // Cleanups run in reverse order of registration, and one that throws must not stop the\n // rest from running; their errors are aggregated into a `SuppressedError` chain instead.\n for (let ii = cleanups.length - 1; ii >= 0; ii--) {\n try {\n cleanups[ii]();\n } catch (e) {\n error = hasError ? createSuppressedError(e, error) : e;\n hasError = true;\n }\n }\n cleanups.length = 0;\n if (hasError) {\n throw error;\n }\n },\n };\n}\n\nfunction createSuppressedError(error: unknown, suppressed: unknown): Error {\n // The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument\n // form of `SuppressedError` leaves the message empty, which would make an aggregated error read\n // differently depending on which stack produced it.\n const message = 'An error was suppressed during disposal';\n if (typeof globalThis.SuppressedError === 'function') {\n return new globalThis.SuppressedError(error, suppressed, message);\n }\n // A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce\n // its shape on a plain error — including the non-enumerability of every property, so that\n // serializing or spreading the error does not depend on which runtime built it.\n return Object.defineProperties(new Error(message), {\n error: { configurable: true, value: error, writable: true },\n name: { configurable: true, value: 'SuppressedError', writable: true },\n suppressed: { configurable: true, value: suppressed, writable: true },\n });\n}\n"]}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -228,6 +228,11 @@ export declare function extendClient<TClient extends object, TAdditions extends
|
|
|
228
228
|
* If the client already implements `Symbol.dispose`, the existing dispose
|
|
229
229
|
* logic is chained so that it runs after the new `cleanup` function.
|
|
230
230
|
*
|
|
231
|
+
* Cleanups run in reverse order of registration, disposal is idempotent, and if more than one
|
|
232
|
+
* cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have
|
|
233
|
+
* not shipped explicit resource management are supported too, though a `using` declaration needs a
|
|
234
|
+
* `Symbol.dispose` polyfill there.
|
|
235
|
+
*
|
|
231
236
|
* The return type is an {@link ExtendedClient}, which flattens the merged shape into a
|
|
232
237
|
* single object literal so chained calls do not accumulate nested intersections in editor
|
|
233
238
|
* tooltips and error messages.
|
|
@@ -250,13 +255,28 @@ export declare function extendClient<TClient extends object, TAdditions extends
|
|
|
250
255
|
* };
|
|
251
256
|
* }
|
|
252
257
|
*
|
|
253
|
-
* //
|
|
254
|
-
* using client = createClient().use(myPlugin();
|
|
258
|
+
* // Build the client in the scope that should own it:
|
|
259
|
+
* using client = createClient().use(myPlugin());
|
|
255
260
|
* // `socket.close()` is called automatically when `client` goes out of scope.
|
|
256
261
|
* ```
|
|
257
262
|
*
|
|
263
|
+
* @example Disposing without a using declaration
|
|
264
|
+
* `using` requires explicit resource management, which Safari has not shipped as of Safari 27.
|
|
265
|
+
* Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the
|
|
266
|
+
* polyfill before any client is created, since a client registers its dispose method under
|
|
267
|
+
* whatever `Symbol.dispose` was at the time.
|
|
268
|
+
* ```ts
|
|
269
|
+
* const client = createClient().use(myPlugin());
|
|
270
|
+
*
|
|
271
|
+
* // Later, when the client is no longer needed:
|
|
272
|
+
* client[Symbol.dispose]();
|
|
273
|
+
* // `socket.close()` has now been called.
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
258
276
|
* @see {@link extendClient}
|
|
259
277
|
* @see {@link ExtendedClient}
|
|
278
|
+
* @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform
|
|
279
|
+
* availability of `DisposableStack`.
|
|
260
280
|
*/
|
|
261
281
|
export declare function withCleanup<TClient extends object>(client: TClient, cleanup: () => void): ExtendedClient<TClient, Disposable>;
|
|
262
282
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,MAAM,MAAM,YAAY,CAAC,MAAM,SAAS,MAAM,EAAE,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvH;;;;;;;;;GASG;AACH,MAAM,MAAM,MAAM,CAAC,KAAK,SAAS,MAAM,IAAI,KAAK,GAAG;IAC/C;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EACnD,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KACnC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CACvG,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG;IACrE;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EACnD,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KACnC,WAAW,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC;CACjG,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAExF;AAuCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,IAAI;KAC3E,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,UAAU,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;CACjH,GAAG,EAAE,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,EAC1E,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,UAAU,GACtB,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CAIrC;AAUD
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,MAAM,MAAM,YAAY,CAAC,MAAM,SAAS,MAAM,EAAE,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvH;;;;;;;;;GASG;AACH,MAAM,MAAM,MAAM,CAAC,KAAK,SAAS,MAAM,IAAI,KAAK,GAAG;IAC/C;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EACnD,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KACnC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;CACvG,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG;IACrE;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EACnD,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KACnC,WAAW,CAAC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC;CACjG,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAExF;AAuCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,IAAI;KAC3E,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,UAAU,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;CACjH,GAAG,EAAE,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,EAAE,UAAU,SAAS,MAAM,EAC1E,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,UAAU,GACtB,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CAIrC;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,EAC9C,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,IAAI,GACpB,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CASrC"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -292,6 +292,11 @@ function toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors:
|
|
|
292
292
|
* If the client already implements `Symbol.dispose`, the existing dispose
|
|
293
293
|
* logic is chained so that it runs after the new `cleanup` function.
|
|
294
294
|
*
|
|
295
|
+
* Cleanups run in reverse order of registration, disposal is idempotent, and if more than one
|
|
296
|
+
* cleanup throws then the errors are aggregated into a `SuppressedError` chain. Runtimes that have
|
|
297
|
+
* not shipped explicit resource management are supported too, though a `using` declaration needs a
|
|
298
|
+
* `Symbol.dispose` polyfill there.
|
|
299
|
+
*
|
|
295
300
|
* The return type is an {@link ExtendedClient}, which flattens the merged shape into a
|
|
296
301
|
* single object literal so chained calls do not accumulate nested intersections in editor
|
|
297
302
|
* tooltips and error messages.
|
|
@@ -314,13 +319,28 @@ function toConfigurableDescriptors<T extends PropertyDescriptorMap>(descriptors:
|
|
|
314
319
|
* };
|
|
315
320
|
* }
|
|
316
321
|
*
|
|
317
|
-
* //
|
|
318
|
-
* using client = createClient().use(myPlugin();
|
|
322
|
+
* // Build the client in the scope that should own it:
|
|
323
|
+
* using client = createClient().use(myPlugin());
|
|
319
324
|
* // `socket.close()` is called automatically when `client` goes out of scope.
|
|
320
325
|
* ```
|
|
321
326
|
*
|
|
327
|
+
* @example Disposing without a using declaration
|
|
328
|
+
* `using` requires explicit resource management, which Safari has not shipped as of Safari 27.
|
|
329
|
+
* Either dispose the client yourself, as below, or polyfill `Symbol.dispose` — installing the
|
|
330
|
+
* polyfill before any client is created, since a client registers its dispose method under
|
|
331
|
+
* whatever `Symbol.dispose` was at the time.
|
|
332
|
+
* ```ts
|
|
333
|
+
* const client = createClient().use(myPlugin());
|
|
334
|
+
*
|
|
335
|
+
* // Later, when the client is no longer needed:
|
|
336
|
+
* client[Symbol.dispose]();
|
|
337
|
+
* // `socket.close()` has now been called.
|
|
338
|
+
* ```
|
|
339
|
+
*
|
|
322
340
|
* @see {@link extendClient}
|
|
323
341
|
* @see {@link ExtendedClient}
|
|
342
|
+
* @remarks See https://caniuse.com/mdn-javascript_builtins_disposablestack for platform
|
|
343
|
+
* availability of `DisposableStack`.
|
|
324
344
|
*/
|
|
325
345
|
export function withCleanup<TClient extends object>(
|
|
326
346
|
client: TClient,
|
|
@@ -328,7 +348,7 @@ export function withCleanup<TClient extends object>(
|
|
|
328
348
|
): ExtendedClient<TClient, Disposable> {
|
|
329
349
|
if (DISPOSABLE_STACK_PROPERTY in client) {
|
|
330
350
|
return addCleanupToClientWithExistingStack(
|
|
331
|
-
client as Record<typeof DISPOSABLE_STACK_PROPERTY,
|
|
351
|
+
client as Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack> & TClient,
|
|
332
352
|
cleanup,
|
|
333
353
|
);
|
|
334
354
|
} else {
|
|
@@ -338,7 +358,7 @@ export function withCleanup<TClient extends object>(
|
|
|
338
358
|
|
|
339
359
|
const DISPOSABLE_STACK_PROPERTY = '__PRIVATE__DISPOSABLE_STACK' as const;
|
|
340
360
|
|
|
341
|
-
function addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY,
|
|
361
|
+
function addCleanupToClientWithExistingStack<TClient extends Record<typeof DISPOSABLE_STACK_PROPERTY, CleanupStack>>(
|
|
342
362
|
client: TClient,
|
|
343
363
|
cleanup: () => void,
|
|
344
364
|
): ExtendedClient<TClient, Disposable> {
|
|
@@ -352,7 +372,7 @@ function addCleanupToClientWithoutExistingStack<TClient extends object>(
|
|
|
352
372
|
client: TClient,
|
|
353
373
|
cleanup: () => void,
|
|
354
374
|
): ExtendedClient<TClient, Disposable> {
|
|
355
|
-
const stack =
|
|
375
|
+
const stack = createCleanupStack();
|
|
356
376
|
|
|
357
377
|
// If the client has an existing dispose method but not our stack, we maintain this existing cleanup by deferring it to the new stack
|
|
358
378
|
if (Symbol.dispose in client) {
|
|
@@ -367,9 +387,93 @@ function addCleanupToClientWithoutExistingStack<TClient extends object>(
|
|
|
367
387
|
const additions = {
|
|
368
388
|
[DISPOSABLE_STACK_PROPERTY]: stack,
|
|
369
389
|
[Symbol.dispose]() {
|
|
370
|
-
stack
|
|
390
|
+
stack.dispose();
|
|
371
391
|
},
|
|
372
392
|
};
|
|
373
393
|
|
|
374
394
|
return extendClient(client, additions) as unknown as ExtendedClient<TClient, Disposable>;
|
|
375
395
|
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The slice of `DisposableStack` that {@link withCleanup} relies on.
|
|
399
|
+
*
|
|
400
|
+
* Deliberately narrow — `defer()` and the plain `dispose()` method only — so that it can be
|
|
401
|
+
* satisfied both by the platform's `DisposableStack` and by {@link createFallbackCleanupStack} on
|
|
402
|
+
* runtimes that lack one. Note that `dispose()` is used in preference to `[Symbol.dispose]()`
|
|
403
|
+
* because runtimes missing `DisposableStack` are missing `Symbol.dispose` as well.
|
|
404
|
+
*/
|
|
405
|
+
type CleanupStack = {
|
|
406
|
+
defer(cleanup: () => void): void;
|
|
407
|
+
dispose(): void;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
function createCleanupStack(): CleanupStack {
|
|
411
|
+
// Always prefer the runtime's own implementation. The fallback exists only for runtimes that
|
|
412
|
+
// have not shipped explicit resource management — most notably Safari, which as of Safari 27
|
|
413
|
+
// provides neither `DisposableStack` nor `Symbol.dispose`.
|
|
414
|
+
return typeof globalThis.DisposableStack === 'function'
|
|
415
|
+
? new globalThis.DisposableStack()
|
|
416
|
+
: createFallbackCleanupStack();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function createFallbackCleanupStack(): CleanupStack {
|
|
420
|
+
const cleanups: (() => void)[] = [];
|
|
421
|
+
let disposed = false;
|
|
422
|
+
return {
|
|
423
|
+
defer(cleanup) {
|
|
424
|
+
if (disposed) {
|
|
425
|
+
// Mirrors the `ReferenceError` thrown by `DisposableStack.prototype.defer` so that
|
|
426
|
+
// both stacks refuse a late cleanup the same way, rather than silently accepting
|
|
427
|
+
// one that will never run.
|
|
428
|
+
throw new ReferenceError('Cannot add values to a disposed stack');
|
|
429
|
+
}
|
|
430
|
+
if (typeof cleanup !== 'function') {
|
|
431
|
+
// `DisposableStack.prototype.defer` rejects a non-callable up front. Deferring that
|
|
432
|
+
// failure to disposal would surface it far from its cause, and would tangle it up in
|
|
433
|
+
// the error aggregation of unrelated cleanups.
|
|
434
|
+
throw new TypeError(`${String(cleanup)} is not a function`);
|
|
435
|
+
}
|
|
436
|
+
cleanups.push(cleanup);
|
|
437
|
+
},
|
|
438
|
+
dispose() {
|
|
439
|
+
if (disposed) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
disposed = true;
|
|
443
|
+
let error: unknown;
|
|
444
|
+
let hasError = false;
|
|
445
|
+
// Cleanups run in reverse order of registration, and one that throws must not stop the
|
|
446
|
+
// rest from running; their errors are aggregated into a `SuppressedError` chain instead.
|
|
447
|
+
for (let ii = cleanups.length - 1; ii >= 0; ii--) {
|
|
448
|
+
try {
|
|
449
|
+
cleanups[ii]();
|
|
450
|
+
} catch (e) {
|
|
451
|
+
error = hasError ? createSuppressedError(e, error) : e;
|
|
452
|
+
hasError = true;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
cleanups.length = 0;
|
|
456
|
+
if (hasError) {
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function createSuppressedError(error: unknown, suppressed: unknown): Error {
|
|
464
|
+
// The message `DisposableStack` disposal produces. Passing it explicitly matters: the two-argument
|
|
465
|
+
// form of `SuppressedError` leaves the message empty, which would make an aggregated error read
|
|
466
|
+
// differently depending on which stack produced it.
|
|
467
|
+
const message = 'An error was suppressed during disposal';
|
|
468
|
+
if (typeof globalThis.SuppressedError === 'function') {
|
|
469
|
+
return new globalThis.SuppressedError(error, suppressed, message);
|
|
470
|
+
}
|
|
471
|
+
// A runtime without `DisposableStack` has no `SuppressedError` constructor either, so reproduce
|
|
472
|
+
// its shape on a plain error — including the non-enumerability of every property, so that
|
|
473
|
+
// serializing or spreading the error does not depend on which runtime built it.
|
|
474
|
+
return Object.defineProperties(new Error(message), {
|
|
475
|
+
error: { configurable: true, value: error, writable: true },
|
|
476
|
+
name: { configurable: true, value: 'SuppressedError', writable: true },
|
|
477
|
+
suppressed: { configurable: true, value: suppressed, writable: true },
|
|
478
|
+
});
|
|
479
|
+
}
|