@milaboratories/pl-client 2.11.0 → 2.11.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-client",
3
- "version": "2.11.0",
3
+ "version": "2.11.2",
4
4
  "engines": {
5
5
  "node": ">=20.3.0"
6
6
  },
@@ -32,8 +32,8 @@
32
32
  "undici": "~7.10.0",
33
33
  "utility-types": "^3.11.0",
34
34
  "yaml": "^2.7.0",
35
- "@milaboratories/pl-http": "^1.1.3",
36
- "@milaboratories/ts-helpers": "^1.4.0"
35
+ "@milaboratories/ts-helpers": "^1.4.1",
36
+ "@milaboratories/pl-http": "^1.1.4"
37
37
  },
38
38
  "devDependencies": {
39
39
  "typescript": "~5.5.4",
@@ -45,7 +45,7 @@
45
45
  "jest": "^29.7.0",
46
46
  "@jest/globals": "^29.7.0",
47
47
  "ts-jest": "^29.2.6",
48
- "@milaboratories/platforma-build-configs": "1.0.3"
48
+ "@milaboratories/build-configs": "1.0.4"
49
49
  },
50
50
  "scripts": {
51
51
  "type-check": "tsc --noEmit --composite false",
@@ -1,8 +1,8 @@
1
1
  import { getTestClient, getTestClientConf } from '../test/test_config';
2
2
  import { PlClient } from './client';
3
3
  import { PlDriver, PlDriverDefinition } from './driver';
4
- import { GrpcTransport } from '@protobuf-ts/grpc-transport';
5
4
  import { Dispatcher, request } from 'undici';
5
+ import { GrpcClientProviderFactory } from './grpc';
6
6
 
7
7
  test('test client init', async () => {
8
8
  const client = await getTestClient(undefined);
@@ -27,7 +27,7 @@ interface SimpleDriver extends PlDriver {
27
27
 
28
28
  const SimpleDriverDefinition: PlDriverDefinition<SimpleDriver> = {
29
29
  name: 'SimpleDriver',
30
- init(pl: PlClient, grpcTransport: GrpcTransport, httpDispatcher: Dispatcher): SimpleDriver {
30
+ init(pl: PlClient, grpcClientProviderFactory: GrpcClientProviderFactory, httpDispatcher: Dispatcher): SimpleDriver {
31
31
  return {
32
32
  async ping(): Promise<string> {
33
33
  const response = await request('https://cdn.milaboratory.com/ping', {
@@ -142,7 +142,7 @@ export class PlClient {
142
142
  }
143
143
 
144
144
  public async ping(): Promise<MaintenanceAPI_Ping_Response> {
145
- return (await this._ll.grpcPl.ping({})).response;
145
+ return (await this._ll.grpcPl.get().ping({})).response;
146
146
  }
147
147
 
148
148
  public get conf(): PlClientConfig {
@@ -296,7 +296,7 @@ export class PlClient {
296
296
  if (ok) {
297
297
  // syncing on transaction if requested
298
298
  if (ops?.sync === undefined ? this.forceSync : ops?.sync)
299
- await this._ll.grpcPl.txSync({ txId });
299
+ await this._ll.grpcPl.get().txSync({ txId });
300
300
 
301
301
  // introducing artificial delay, if requested
302
302
  if (writable && this.txDelay > 0)
@@ -342,7 +342,7 @@ export class PlClient {
342
342
  public getDriver<Drv extends PlDriver>(definition: PlDriverDefinition<Drv>): Drv {
343
343
  const attached = this.drivers.get(definition.name);
344
344
  if (attached !== undefined) return attached as Drv;
345
- const driver = definition.init(this, this.grpcTransport, this.httpDispatcher);
345
+ const driver = definition.init(this, this._ll, this.httpDispatcher);
346
346
  this.drivers.set(definition.name, driver);
347
347
  return driver;
348
348
  }
@@ -1,8 +1,8 @@
1
1
  import type { PlClient } from './client';
2
- import type { GrpcTransport } from '@protobuf-ts/grpc-transport';
3
2
  import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
4
3
  import type { Dispatcher } from 'undici';
5
4
  import type { ResourceType } from './types';
5
+ import type { GrpcClientProviderFactory } from './grpc';
6
6
 
7
7
  /** Drivers must implement this interface */
8
8
  export interface PlDriver {
@@ -15,7 +15,7 @@ export interface PlDriverDefinition<Drv extends PlDriver> {
15
15
  readonly name: string;
16
16
 
17
17
  /** Initialization routine, will be executed only once for each driver in a specific client */
18
- init(pl: PlClient, grpcTransport: GrpcTransport, httpDispatcher: Dispatcher): Drv;
18
+ init(pl: PlClient, grpcClientProviderFactory: GrpcClientProviderFactory, httpDispatcher: Dispatcher): Drv;
19
19
  }
20
20
 
21
21
  // addRTypeToMetadata adds a metadata with resource type
@@ -0,0 +1,17 @@
1
+ import type { GrpcTransport } from '@protobuf-ts/grpc-transport';
2
+
3
+ /**
4
+ * A provider for a grpc client.
5
+ * The client is created on demand, and is reset when the transport is reset.
6
+ * This is useful for cases where the client is used in a loop, and the transport is reset after each iteration.
7
+ */
8
+ export interface GrpcClientProvider<Client> {
9
+ get(): Client;
10
+ }
11
+
12
+ /**
13
+ * A factory for grpc client providers.
14
+ */
15
+ export interface GrpcClientProviderFactory {
16
+ createGrpcClientProvider<Client>(clientConstructor: (transport: GrpcTransport) => Client): GrpcClientProvider<Client>;
17
+ }
@@ -21,14 +21,31 @@ import { parsePlJwt } from '../util/pl';
21
21
  import type { Dispatcher } from 'undici';
22
22
  import { inferAuthRefreshTime } from './auth';
23
23
  import { defaultHttpDispatcher } from '@milaboratories/pl-http';
24
+ import type { GrpcClientProvider, GrpcClientProviderFactory } from './grpc';
24
25
 
25
26
  export interface PlCallOps {
26
27
  timeout?: number;
27
28
  abortSignal?: AbortSignal;
28
29
  }
29
30
 
31
+ class GrpcClientProviderImpl<Client> implements GrpcClientProvider<Client> {
32
+ private client: Client | undefined = undefined;
33
+
34
+ constructor(private readonly grpcTransport: () => GrpcTransport, private readonly clientConstructor: (transport: GrpcTransport) => Client) {}
35
+
36
+ public reset(): void {
37
+ this.client = undefined;
38
+ }
39
+
40
+ public get(): Client {
41
+ if (this.client === undefined)
42
+ this.client = this.clientConstructor(this.grpcTransport());
43
+ return this.client;
44
+ }
45
+ }
46
+
30
47
  /** Abstract out low level networking and authorization details */
31
- export class LLPlClient {
48
+ export class LLPlClient implements GrpcClientProviderFactory {
32
49
  public readonly conf: PlClientConfig;
33
50
 
34
51
  /** Initial authorization information */
@@ -47,7 +64,9 @@ export class LLPlClient {
47
64
 
48
65
  private readonly grpcInterceptors: Interceptor[];
49
66
  private _grpcTransport!: GrpcTransport;
50
- private _grpcPl!: PlatformClient;
67
+ private readonly providers: WeakRef<GrpcClientProviderImpl<any>>[] = [];
68
+
69
+ public readonly grpcPl: GrpcClientProvider<PlatformClient>;
51
70
 
52
71
  public readonly httpDispatcher: Dispatcher;
53
72
 
@@ -89,6 +108,8 @@ export class LLPlClient {
89
108
  this.statusListener = statusListener;
90
109
  statusListener(this._status);
91
110
  }
111
+
112
+ this.grpcPl = this.createGrpcClientProvider((transport) => new PlatformClient(transport));
92
113
  }
93
114
 
94
115
  /**
@@ -125,17 +146,52 @@ export class LLPlClient {
125
146
  const oldTransport = this._grpcTransport;
126
147
 
127
148
  this._grpcTransport = new GrpcTransport(grpcOptions);
128
- this._grpcPl = new PlatformClient(this._grpcTransport);
149
+
150
+ // Reset all providers to let them reinitialize their clients
151
+ for (let i = 0; i < this.providers.length; i++) {
152
+ const provider = this.providers[i].deref();
153
+ if (provider === undefined) {
154
+ // at the same time we need to remove providers that are no longer valid
155
+ this.providers.splice(i, 1);
156
+ i--;
157
+ } else {
158
+ provider.reset();
159
+ }
160
+ }
129
161
 
130
162
  if (oldTransport !== undefined) oldTransport.close();
131
163
  }
132
164
 
133
- public get grpcTransport(): GrpcTransport {
134
- return this._grpcTransport;
165
+ private providerCleanupCounter = 0;
166
+
167
+ /**
168
+ * Creates a provider for a grpc client. Returned provider will create fresh client whenever the underlying transport is reset.
169
+ *
170
+ * @param clientConstructor - a factory function that creates a grpc client
171
+ */
172
+ public createGrpcClientProvider<Client>(clientConstructor: (transport: GrpcTransport) => Client): GrpcClientProvider<Client> {
173
+ // We need to cleanup providers periodically to avoid memory leaks.
174
+ // This is a simple heuristic to avoid memory leaks.
175
+ // We could use a more sophisticated algorithm, but this is good enough for now.
176
+ this.providerCleanupCounter++;
177
+ if (this.providerCleanupCounter >= 16) {
178
+ for (let i = 0; i < this.providers.length; i++) {
179
+ const provider = this.providers[i].deref();
180
+ if (provider === undefined) {
181
+ this.providers.splice(i, 1);
182
+ i--;
183
+ }
184
+ }
185
+ this.providerCleanupCounter = 0;
186
+ }
187
+
188
+ const provider = new GrpcClientProviderImpl<Client>(() => this._grpcTransport, clientConstructor);
189
+ this.providers.push(new WeakRef(provider));
190
+ return provider;
135
191
  }
136
192
 
137
- public get grpcPl(): PlatformClient {
138
- return this._grpcPl;
193
+ public get grpcTransport(): GrpcTransport {
194
+ return this._grpcTransport;
139
195
  }
140
196
 
141
197
  /** Returns true if client is authenticated. Even with anonymous auth information
@@ -182,7 +238,7 @@ export class LLPlClient {
182
238
  this.authRefreshInProgress = true;
183
239
  void (async () => {
184
240
  try {
185
- const response = await this.grpcPl.getJWTToken({
241
+ const response = await this.grpcPl.get().getJWTToken({
186
242
  expiration: {
187
243
  seconds: BigInt(this.conf.authTTLSeconds),
188
244
  nanos: 0,
@@ -244,7 +300,7 @@ export class LLPlClient {
244
300
  return new LLPlTransaction((abortSignal) => {
245
301
  let totalAbortSignal = abortSignal;
246
302
  if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);
247
- return this.grpcPl.tx({
303
+ return this.grpcPl.get().tx({
248
304
  abort: totalAbortSignal,
249
305
  timeout:
250
306
  ops.timeout
@@ -16,11 +16,11 @@ export class UnauthenticatedPlClient {
16
16
  }
17
17
 
18
18
  public async ping(): Promise<MaintenanceAPI_Ping_Response> {
19
- return (await this.ll.grpcPl.ping({})).response;
19
+ return (await this.ll.grpcPl.get().ping({})).response;
20
20
  }
21
21
 
22
22
  public async authMethods(): Promise<AuthAPI_ListMethods_Response> {
23
- return (await this.ll.grpcPl.authMethods({})).response;
23
+ return (await this.ll.grpcPl.get().authMethods({})).response;
24
24
  }
25
25
 
26
26
  public async requireAuth(): Promise<boolean> {
@@ -29,7 +29,7 @@ export class UnauthenticatedPlClient {
29
29
 
30
30
  public async login(user: string, password: string): Promise<AuthInformation> {
31
31
  try {
32
- const response = await this.ll.grpcPl.getJWTToken(
32
+ const response = await this.ll.grpcPl.get().getJWTToken(
33
33
  { expiration: { seconds: BigInt(this.ll.conf.authTTLSeconds), nanos: 0 } },
34
34
  {
35
35
  meta: {
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export * from './core/default_client';
9
9
  export * from './core/unauth_client';
10
10
  export * from './core/auth';
11
11
  export * from './core/final';
12
+ export * from './core/grpc';
12
13
  export * from './helpers/tx_helpers';
13
14
  export * from './helpers/poll';
14
15