Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ac699ec
Initial AI generated framework for plugins
tconley1428 Aug 4, 2025
6580484
WIP Plugin shenanigans
tconley1428 Aug 4, 2025
ec44d6c
Merge remote-tracking branch 'origin/main' into plugins/initial
tconley1428 Oct 2, 2025
0f8361c
Updating to bring more in line with current Python implementation. Wi…
tconley1428 Oct 6, 2025
a6f8bfb
Additional plugin work
tconley1428 Oct 8, 2025
748a1c9
Ongoing work on simpleplugin
tconley1428 Oct 13, 2025
6d16100
Ongoing work on SimplePlugin
tconley1428 Oct 14, 2025
03957fd
Merge remote-tracking branch 'origin/main' into plugins/initial
tconley1428 Oct 16, 2025
ba37fd8
Fix plugin composition in connection
tconley1428 Oct 16, 2025
604e65f
PR Feedback
tconley1428 Oct 20, 2025
f85cc33
Linting
tconley1428 Oct 20, 2025
b01a63e
Remove no longer needed apikey test, typing has changed
tconley1428 Oct 20, 2025
118c0b9
Merge branch 'main' into plugins/initial
tconley1428 Oct 20, 2025
7ab11f3
Merge branch 'main' into plugins/initial
tconley1428 Oct 21, 2025
bd2dad0
Minor PR changes
tconley1428 Oct 22, 2025
6e94e0c
Merge branch 'plugins/initial' of https://github.com/temporalio/sdk-t…
tconley1428 Oct 22, 2025
fab3fa4
Add package to additional places
tconley1428 Oct 30, 2025
6502384
Merge remote-tracking branch 'origin/main' into plugins/initial
tconley1428 Oct 30, 2025
baf8bdb
Lint
tconley1428 Oct 30, 2025
1d60fca
PR comments
tconley1428 Nov 3, 2025
84476a0
Add some comments to SimplePlugin
tconley1428 Nov 3, 2025
b3d60e7
Some more documentation
tconley1428 Nov 3, 2025
72d234a
Linting
tconley1428 Nov 3, 2025
32f9332
Merge branch 'main' into plugins/initial
tconley1428 Nov 3, 2025
9beaa9b
Merge branch 'main' into plugins/initial
tconley1428 Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@temporalio/interceptors-opentelemetry": "file:packages/interceptors-opentelemetry",
"@temporalio/nexus": "file:packages/nexus",
"@temporalio/nyc-test-coverage": "file:packages/nyc-test-coverage",
"@temporalio/plugin": "file:packages/plugin",
"@temporalio/proto": "file:packages/proto",
"@temporalio/test": "file:packages/test",
"@temporalio/testing": "file:packages/testing",
Expand Down Expand Up @@ -92,6 +93,7 @@
"packages/interceptors-opentelemetry",
"packages/nexus",
"packages/nyc-test-coverage",
"packages/plugin",
"packages/proto",
"packages/test",
"packages/testing",
Expand Down
46 changes: 45 additions & 1 deletion packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export interface ClientOptions extends BaseClientOptions {
*/
interceptors?: ClientInterceptors;

/**
* List of plugins to register with the client.
*
* Plugins allow you to extend and customize the behavior of Temporal clients.
* They can intercept and modify client creation.
*
* @experimental Plugins is an experimental feature; APIs may change without notice.
*/
plugins?: ClientPlugin[];

workflow?: {
/**
* Should a query be rejected by closed and failed workflows
Expand All @@ -32,6 +42,7 @@ export type LoadedClientOptions = LoadedWithDefaults<ClientOptions>;
*/
export class Client extends BaseClient {
public readonly options: LoadedClientOptions;

/**
* Workflow sub-client - use to start and interact with Workflows
*/
Expand All @@ -52,9 +63,21 @@ export class Client extends BaseClient {
public readonly taskQueue: TaskQueueClient;

constructor(options?: ClientOptions) {
options = options ?? {};

// Add client plugins from the connection
options.plugins = (options.plugins ?? []).concat(options.connection?.plugins ?? []);

// Process plugins first to allow them to modify connect configuration
for (const plugin of options.plugins) {
if (plugin.configureClient !== undefined) {
options = plugin.configureClient(options);
}
}

super(options);

const { interceptors, workflow, ...commonOptions } = options ?? {};
const { interceptors, workflow, plugins, ...commonOptions } = options;

this.workflow = new WorkflowClient({
...commonOptions,
Expand Down Expand Up @@ -95,6 +118,7 @@ export class Client extends BaseClient {
workflow: {
queryRejectCondition: this.workflow.options.queryRejectCondition,
},
plugins: plugins ?? [],
};
}

Expand All @@ -108,3 +132,23 @@ export class Client extends BaseClient {
return this.connection.workflowService;
}
}

/**
* Plugin to control the configuration of a native connection.
*
* @experimental Plugins is an experimental feature; APIs may change without notice.
*/
export interface ClientPlugin {
/**
* Gets the name of this plugin.
*/
get name(): string;

/**
* Hook called when creating a client to allow modification of configuration.
*
* This method is called during client creation and allows plugins to modify
* the client configuration before the client is fully initialized.
*/
configureClient?(options: Omit<ClientOptions, 'plugins'>): Omit<ClientOptions, 'plugins'>;
}
45 changes: 41 additions & 4 deletions packages/client/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ export interface ConnectionOptions {
* @default 10 seconds
*/
connectTimeout?: Duration;

/**
* List of plugins to register with the connection.
*
* Plugins allow you to configure the connection options.
* Any plugins provided will also be passed to any client built from this connection.
*
* @experimental Plugins is an experimental feature; APIs may change without notice.
*/
plugins?: ConnectionPlugin[];
}

export type ConnectionOptionsWithDefaults = Required<
Expand Down Expand Up @@ -172,6 +182,7 @@ function addDefaults(options: ConnectionOptions): ConnectionOptionsWithDefaults
interceptors: interceptors ?? [makeGrpcRetryInterceptor(defaultGrpcRetryOptions())],
metadata: {},
connectTimeoutMs: msOptionalToNumber(connectTimeout) ?? 10_000,
plugins: [],
...filterNullAndUndefined(rest),
};
}
Expand All @@ -182,8 +193,8 @@ function addDefaults(options: ConnectionOptions): ConnectionOptionsWithDefaults
* - Add default port to address if port not specified
* - Set `Authorization` header based on {@link ConnectionOptions.apiKey}
*/
function normalizeGRPCConfig(options?: ConnectionOptions): ConnectionOptions {
const { tls: tlsFromConfig, credentials, callCredentials, ...rest } = options || {};
function normalizeGRPCConfig(options: ConnectionOptions): ConnectionOptions {
const { tls: tlsFromConfig, credentials, callCredentials, ...rest } = options;
if (rest.apiKey) {
if (rest.metadata?.['Authorization']) {
throw new TypeError(
Expand Down Expand Up @@ -325,10 +336,12 @@ export class Connection {
*/
public readonly healthService: HealthService;

public readonly plugins: ConnectionPlugin[];

readonly callContextStorage: AsyncLocalStorage<CallContext>;
private readonly apiKeyFnRef: { fn?: () => string };

protected static createCtorOptions(options?: ConnectionOptions): ConnectionCtorOptions {
protected static createCtorOptions(options: ConnectionOptions): ConnectionCtorOptions {
const normalizedOptions = normalizeGRPCConfig(options);
const apiKeyFnRef: { fn?: () => string } = {};
if (normalizedOptions.apiKey) {
Expand Down Expand Up @@ -444,6 +457,12 @@ export class Connection {
* This method does not verify connectivity with the server. We recommend using {@link connect} instead.
*/
static lazy(options?: ConnectionOptions): Connection {
options = options ?? {};
for (const plugin of options.plugins ?? []) {
if (plugin.configureConnection !== undefined) {
options = plugin.configureConnection(options);
}
}
return new this(this.createCtorOptions(options));
}

Expand Down Expand Up @@ -477,6 +496,7 @@ export class Connection {
this.healthService = healthService;
this.callContextStorage = callContextStorage;
this.apiKeyFnRef = apiKeyFnRef;
this.plugins = options.plugins ?? [];
}

protected static generateRPCImplementation({
Expand Down Expand Up @@ -532,7 +552,7 @@ export class Connection {
* this will locally result in the request call throwing a {@link grpc.ServiceError|ServiceError}
* with code {@link grpc.status.DEADLINE_EXCEEDED|DEADLINE_EXCEEDED}; see {@link isGrpcDeadlineError}.
*
* It is stronly recommended to explicitly set deadlines. If no deadline is set, then it is
* It is strongly recommended to explicitly set deadlines. If no deadline is set, then it is
* possible for the client to end up waiting forever for a response.
*
* @param deadline a point in time after which the request will be considered as failed; either a
Expand Down Expand Up @@ -688,3 +708,20 @@ export class Connection {
return wrapper as WorkflowService;
}
}

/**
* Plugin to control the configuration of a connection.
*
* @experimental Plugins is an experimental feature; APIs may change without notice.
*/
export interface ConnectionPlugin {
/**
* Gets the name of this plugin.
*/
get name(): string;

/**
* Hook called when creating a connection to allow modification of configuration.
*/
configureConnection?(options: ConnectionOptions): ConnectionOptions;
}
8 changes: 7 additions & 1 deletion packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ export * from '@temporalio/common/lib/interfaces';
export * from '@temporalio/common/lib/workflow-handle';
export * from './async-completion-client';
export * from './client';
export { Connection, ConnectionOptions, ConnectionOptionsWithDefaults, LOCAL_TARGET } from './connection';
export {
Connection,
ConnectionOptions,
ConnectionOptionsWithDefaults,
ConnectionPlugin,
LOCAL_TARGET,
} from './connection';
export * from './errors';
export * from './grpc-retry';
export * from './interceptors';
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { TypedSearchAttributes, SearchAttributes, SearchAttributeValue, Pri
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
import * as proto from '@temporalio/proto';
import { Replace } from '@temporalio/common/lib/type-helpers';
import type { ConnectionPlugin } from './connection';

export interface WorkflowExecution {
workflowId: string;
Expand Down Expand Up @@ -122,6 +123,7 @@ export interface CallContext {
*/
export interface ConnectionLike {
workflowService: WorkflowService;
plugins: ConnectionPlugin[];
close(): Promise<void>;
ensureConnected(): Promise<void>;

Expand Down
1 change: 1 addition & 0 deletions packages/meta/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@temporalio/envconfig": "file:../envconfig",
"@temporalio/interceptors-opentelemetry": "file:../interceptors-opentelemetry",
"@temporalio/nexus": "file:../nexus",
"@temporalio/plugin": "file:../plugin",
"@temporalio/proto": "file:../proto",
"@temporalio/testing": "file:../testing",
"@temporalio/worker": "file:../worker",
Expand Down
1 change: 1 addition & 0 deletions packages/meta/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * as nexus from '@temporalio/nexus';
export * as testing from '@temporalio/testing';
export * as opentelemetry from '@temporalio/interceptors-opentelemetry';
export * as envconfig from '@temporalio/envconfig';
export * as plugin from '@temporalio/plugin';
1 change: 1 addition & 0 deletions packages/meta/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
{ "path": "../activity" },
{ "path": "../client" },
{ "path": "../common" },
{ "path": "../plugin" },
{ "path": "../worker" },
{ "path": "../workflow" }
],
Expand Down
41 changes: 41 additions & 0 deletions packages/plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@temporalio/plugin",
"version": "1.13.0",
"description": "Library for plugin creation",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"keywords": [
"temporal",
"workflow",
"worker",
"plugin"
],
"author": "Temporal Technologies Inc. <sdk@temporal.io>",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@temporalio/common": "file:../common",
"@temporalio/client": "file:../client",
"@temporalio/worker": "file:../worker",
"nexus-rpc": "^0.0.1"
},
"bugs": {
"url": "https://github.com/temporalio/sdk-typescript/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/temporalio/sdk-typescript.git",
"directory": "packages/plugin"
},
"homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/plugin",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">= 18.0.0"
},
"files": [
"src",
"lib"
]
}
1 change: 1 addition & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SimplePlugin, SimplePluginOptions } from './plugin';
Loading
Loading