@milaboratories/pl-client 2.7.1 → 2.7.3

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.7.1",
3
+ "version": "2.7.3",
4
4
  "description": "New TS/JS client for Platform API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "./src/**/*"
18
18
  ],
19
19
  "dependencies": {
20
- "@grpc/grpc-js": "^1.12.4",
20
+ "@grpc/grpc-js": "^1.12.5",
21
21
  "@protobuf-ts/grpc-transport": "^2.9.4",
22
22
  "@protobuf-ts/runtime": "^2.9.4",
23
23
  "@protobuf-ts/runtime-rpc": "^2.9.4",
@@ -26,12 +26,12 @@
26
26
  "lru-cache": "^11.0.2",
27
27
  "https-proxy-agent": "^7.0.6",
28
28
  "cacheable-lookup": "^6.1.0",
29
- "long": "^5.2.3",
30
- "undici": "^7.1.0",
29
+ "long": "^5.2.4",
30
+ "undici": "^7.2.3",
31
31
  "utility-types": "^3.11.0",
32
32
  "yaml": "^2.6.1",
33
- "@milaboratories/ts-helpers": "^1.1.3",
34
- "@milaboratories/pl-http": "^1.0.2"
33
+ "@milaboratories/pl-http": "^1.0.3",
34
+ "@milaboratories/ts-helpers": "^1.1.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "~5.5.4",
@@ -23,6 +23,7 @@ import { Dispatcher } from 'undici';
23
23
  import { LRUCache } from 'lru-cache';
24
24
  import { ResourceDataCacheRecord } from './cache';
25
25
  import { DefaultFinalResourceDataPredicate, FinalResourceDataPredicate } from './final';
26
+ import { addStat, AllTxStat, initialTxStat, TxStat } from './stat';
26
27
 
27
28
  export type TxOps = PlCallOps & {
28
29
  sync?: boolean;
@@ -59,6 +60,10 @@ export class PlClient {
59
60
 
60
61
  private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;
61
62
 
63
+ private _txCommittedStat: TxStat = initialTxStat();
64
+ private _txConflictStat: TxStat = initialTxStat();
65
+ private _txErrorStat: TxStat = initialTxStat();
66
+
62
67
  //
63
68
  // Caching
64
69
  //
@@ -110,6 +115,30 @@ export class PlClient {
110
115
  }
111
116
  }
112
117
 
118
+ public get txCommittedStat(): TxStat {
119
+ return { ...this._txCommittedStat };
120
+ }
121
+
122
+ public get txConflictStat(): TxStat {
123
+ return { ...this._txConflictStat };
124
+ }
125
+
126
+ public get txErrorStat(): TxStat {
127
+ return { ...this._txErrorStat };
128
+ }
129
+
130
+ public get txTotalStat(): TxStat {
131
+ return addStat(addStat(this._txCommittedStat, this._txConflictStat), this._txErrorStat);
132
+ }
133
+
134
+ public get allTxStat(): AllTxStat {
135
+ return {
136
+ committed: this.txCommittedStat,
137
+ conflict: this.txConflictStat,
138
+ error: this.txErrorStat
139
+ };
140
+ }
141
+
113
142
  public async ping(): Promise<MaintenanceAPI_Ping_Response> {
114
143
  return (await this.ll.grpcPl.ping({})).response;
115
144
  }
@@ -234,13 +263,18 @@ export class PlClient {
234
263
  try {
235
264
  // executing transaction body
236
265
  result = await body(tx);
266
+ // collecting stat
267
+ this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);
237
268
  ok = true;
238
269
  } catch (e: unknown) {
239
270
  // the only recoverable
240
271
  if (e instanceof TxCommitConflict) {
241
272
  // ignoring
242
- // TODO collect stats
273
+ // collecting stat
274
+ this._txConflictStat = addStat(this._txConflictStat, tx.stat);
243
275
  } else {
276
+ // collecting stat
277
+ this._txErrorStat = addStat(this._txErrorStat, tx.stat);
244
278
  throw e;
245
279
  }
246
280
  } finally {
@@ -87,9 +87,9 @@ export const DEFAULT_AUTH_MAX_REFRESH = 12 * 24 * 60 * 60;
87
87
  export const DEFAULT_MAX_CACHE_BYTES = 128_000_000; // 128 Mb
88
88
 
89
89
  export const DEFAULT_RETRY_BACKOFF_ALGORITHM = 'exponential';
90
- export const DEFAULT_RETRY_MAX_ATTEMPTS = 10;
91
- export const DEFAULT_RETRY_INITIAL_DELAY = 4; // 4 ms
92
- export const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 2; // + 100% on each round
90
+ export const DEFAULT_RETRY_MAX_ATTEMPTS = 16; // 1st attempt + 15 retries
91
+ export const DEFAULT_RETRY_INITIAL_DELAY = 14; // 14 ms * <jitter> of sleep after first failure
92
+ export const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 1.5; // + 50% on each round
93
93
  export const DEFAULT_RETRY_LINEAR_BACKOFF_STEP = 50; // + 50 ms
94
94
  export const DEFAULT_RETRY_JITTER = 0.3; // 30%
95
95
 
@@ -101,7 +101,7 @@ export class LLPlClient {
101
101
  this.grpcTransport = new GrpcTransport(grpcOptions);
102
102
  this.grpcPl = new PlatformClient(this.grpcTransport);
103
103
 
104
- this.httpDispatcher = defaultHttpDispatcher();
104
+ this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);
105
105
 
106
106
  if (statusListener !== undefined) {
107
107
  this.statusListener = statusListener;
@@ -0,0 +1,102 @@
1
+ export type TxStat = {
2
+ txCount: number;
3
+
4
+ rootsCreated: number;
5
+ structsCreated: number;
6
+ structsCreatedDataBytes: number;
7
+ ephemeralsCreated: number;
8
+ ephemeralsCreatedDataBytes: number;
9
+ valuesCreated: number;
10
+ valuesCreatedDataBytes: number;
11
+
12
+ kvSetRequests: number;
13
+ kvSetBytes: number;
14
+
15
+ inputsLocked: number;
16
+ outputsLocked: number;
17
+ fieldsCreated: number;
18
+ fieldsSet: number;
19
+ fieldsGet: number;
20
+
21
+ rGetDataCacheHits: number;
22
+ rGetDataCacheFields: number;
23
+ rGetDataCacheBytes: number;
24
+ rGetDataNetRequests: number;
25
+ rGetDataNetFields: number;
26
+ rGetDataNetBytes: number;
27
+
28
+ kvListRequests: number;
29
+ kvListEntries: number;
30
+ kvListBytes: number;
31
+
32
+ kvGetRequests: number;
33
+ kvGetBytes: number;
34
+ };
35
+
36
+ export function initialTxStat(): TxStat {
37
+ return {
38
+ txCount: 0,
39
+ rootsCreated: 0,
40
+ structsCreated: 0,
41
+ structsCreatedDataBytes: 0,
42
+ ephemeralsCreated: 0,
43
+ ephemeralsCreatedDataBytes: 0,
44
+ valuesCreated: 0,
45
+ valuesCreatedDataBytes: 0,
46
+ kvSetRequests: 0,
47
+ kvSetBytes: 0,
48
+ inputsLocked: 0,
49
+ outputsLocked: 0,
50
+ fieldsCreated: 0,
51
+ fieldsSet: 0,
52
+ fieldsGet: 0,
53
+ rGetDataCacheHits: 0,
54
+ rGetDataCacheFields: 0,
55
+ rGetDataCacheBytes: 0,
56
+ rGetDataNetRequests: 0,
57
+ rGetDataNetFields: 0,
58
+ rGetDataNetBytes: 0,
59
+ kvListRequests: 0,
60
+ kvListEntries: 0,
61
+ kvListBytes: 0,
62
+ kvGetRequests: 0,
63
+ kvGetBytes: 0
64
+ };
65
+ }
66
+
67
+ export function addStat(a: TxStat, b: TxStat): TxStat {
68
+ return {
69
+ txCount: a.txCount + b.txCount,
70
+ rootsCreated: a.rootsCreated + b.rootsCreated,
71
+ structsCreated: a.structsCreated + b.structsCreated,
72
+ structsCreatedDataBytes: a.structsCreatedDataBytes + b.structsCreatedDataBytes,
73
+ ephemeralsCreated: a.ephemeralsCreated + b.ephemeralsCreated,
74
+ ephemeralsCreatedDataBytes: a.ephemeralsCreatedDataBytes + b.ephemeralsCreatedDataBytes,
75
+ valuesCreated: a.valuesCreated + b.valuesCreated,
76
+ valuesCreatedDataBytes: a.valuesCreatedDataBytes + b.valuesCreatedDataBytes,
77
+ kvSetRequests: a.kvSetRequests + b.kvSetRequests,
78
+ kvSetBytes: a.kvSetBytes + b.kvSetBytes,
79
+ inputsLocked: a.inputsLocked + b.inputsLocked,
80
+ outputsLocked: a.outputsLocked + b.outputsLocked,
81
+ fieldsCreated: a.fieldsCreated + b.fieldsCreated,
82
+ fieldsSet: a.fieldsSet + b.fieldsSet,
83
+ fieldsGet: a.fieldsGet + b.fieldsGet,
84
+ rGetDataCacheHits: a.rGetDataCacheHits + b.rGetDataCacheHits,
85
+ rGetDataCacheFields: a.rGetDataCacheFields + b.rGetDataCacheFields,
86
+ rGetDataCacheBytes: a.rGetDataCacheBytes + b.rGetDataCacheBytes,
87
+ rGetDataNetRequests: a.rGetDataNetRequests + b.rGetDataNetRequests,
88
+ rGetDataNetFields: a.rGetDataNetFields + b.rGetDataNetFields,
89
+ rGetDataNetBytes: a.rGetDataNetBytes + b.rGetDataNetBytes,
90
+ kvListRequests: a.kvListRequests + b.kvListRequests,
91
+ kvListEntries: a.kvListEntries + b.kvListEntries,
92
+ kvListBytes: a.kvListBytes + b.kvListBytes,
93
+ kvGetRequests: a.kvGetRequests + b.kvGetRequests,
94
+ kvGetBytes: a.kvGetBytes + b.kvGetBytes
95
+ };
96
+ }
97
+
98
+ export type AllTxStat = {
99
+ committed: TxStat;
100
+ conflict: TxStat;
101
+ error: TxStat;
102
+ };
@@ -30,6 +30,7 @@ import { isNotFoundError } from './errors';
30
30
  import { FinalResourceDataPredicate } from './final';
31
31
  import { LRUCache } from 'lru-cache';
32
32
  import { ResourceDataCacheRecord } from './cache';
33
+ import { initialTxStat, TxStat } from './stat';
33
34
 
34
35
  /** Reference to resource, used only within transaction */
35
36
  export interface ResourceRef {
@@ -154,6 +155,8 @@ export class PlTransaction {
154
155
 
155
156
  private globalTxIdWasAwaited: boolean = false;
156
157
 
158
+ public readonly stat: TxStat = initialTxStat();
159
+
157
160
  constructor(
158
161
  private readonly ll: LLPlTransaction,
159
162
  public readonly name: string,
@@ -182,6 +185,9 @@ export class PlTransaction {
182
185
  console.warn(err);
183
186
  }
184
187
  });
188
+
189
+ // Adding stats
190
+ this.stat.txCount++;
185
191
  }
186
192
 
187
193
  private async drainAndAwaitPendingOps(): Promise<void> {
@@ -367,6 +373,7 @@ export class PlTransaction {
367
373
  }
368
374
 
369
375
  public createRoot(type: ResourceType): ResourceRef {
376
+ this.stat.rootsCreated++;
370
377
  return this.createResource(
371
378
  true,
372
379
  (localId) => ({ oneofKind: 'resourceCreateRoot', resourceCreateRoot: { type, id: localId } }),
@@ -375,6 +382,8 @@ export class PlTransaction {
375
382
  }
376
383
 
377
384
  public createStruct(type: ResourceType, data?: Uint8Array | string): ResourceRef {
385
+ this.stat.structsCreated++;
386
+ this.stat.structsCreatedDataBytes += data?.length ?? 0;
378
387
  return this.createResource(
379
388
  false,
380
389
  (localId) => ({
@@ -390,6 +399,8 @@ export class PlTransaction {
390
399
  }
391
400
 
392
401
  public createEphemeral(type: ResourceType, data?: Uint8Array | string): ResourceRef {
402
+ this.stat.ephemeralsCreated++;
403
+ this.stat.ephemeralsCreatedDataBytes += data?.length ?? 0;
393
404
  return this.createResource(
394
405
  false,
395
406
  (localId) => ({
@@ -409,6 +420,8 @@ export class PlTransaction {
409
420
  data: Uint8Array | string,
410
421
  errorIfExists: boolean = false
411
422
  ): ResourceRef {
423
+ this.stat.valuesCreated++;
424
+ this.stat.valuesCreatedDataBytes += data?.length ?? 0;
412
425
  return this.createResource(
413
426
  false,
414
427
  (localId) => ({
@@ -496,8 +509,16 @@ export class PlTransaction {
496
509
  // checking if we can return result from cache
497
510
  const fromCache = this.sharedResourceDataCache.get(rId);
498
511
  if (fromCache && fromCache.cacheTxOpenTimestamp < this.txOpenTimestamp) {
499
- if (!loadFields) return fromCache.basicData;
500
- else if (fromCache.data) return fromCache.data;
512
+ if (!loadFields) {
513
+ this.stat.rGetDataCacheHits++;
514
+ this.stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0;
515
+ return fromCache.basicData;
516
+ } else if (fromCache.data) {
517
+ this.stat.rGetDataCacheHits++;
518
+ this.stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0;
519
+ this.stat.rGetDataCacheFields += fromCache.data.fields.length;
520
+ return fromCache.data;
521
+ }
501
522
  }
502
523
  }
503
524
 
@@ -509,6 +530,10 @@ export class PlTransaction {
509
530
  (r) => protoToResource(notEmpty(r.resourceGet.resource))
510
531
  );
511
532
 
533
+ this.stat.rGetDataNetRequests++;
534
+ this.stat.rGetDataNetBytes += result.data?.length ?? 0;
535
+ this.stat.rGetDataNetFields += result.fields.length;
536
+
512
537
  // we will cache only final resource data states
513
538
  // caching result even if we were ignore the cache
514
539
  if (!isResourceRef(rId) && !isLocalResourceId(rId) && this.finalPredicate(result)) {
@@ -575,6 +600,7 @@ export class PlTransaction {
575
600
  * have their values, if inputs list is not locked.
576
601
  */
577
602
  public lockInputs(rId: AnyResourceRef): void {
603
+ this.stat.inputsLocked++;
578
604
  this.sendVoidAsync({
579
605
  oneofKind: 'resourceLockInputs',
580
606
  resourceLockInputs: { resourceId: toResourceId(rId) }
@@ -586,6 +612,7 @@ export class PlTransaction {
586
612
  * This is required for resource to pass deduplication.
587
613
  */
588
614
  public lockOutputs(rId: AnyResourceRef): void {
615
+ this.stat.outputsLocked++;
589
616
  this.sendVoidAsync({
590
617
  oneofKind: 'resourceLockOutputs',
591
618
  resourceLockOutputs: { resourceId: toResourceId(rId) }
@@ -602,6 +629,7 @@ export class PlTransaction {
602
629
  //
603
630
 
604
631
  public createField(fId: AnyFieldRef, fieldType: FieldType, value?: AnyRef): void {
632
+ this.stat.fieldsCreated++;
605
633
  this.sendVoidAsync({
606
634
  oneofKind: 'fieldCreate',
607
635
  fieldCreate: { type: fieldTypeToProto(fieldType), id: toFieldId(fId) }
@@ -620,6 +648,7 @@ export class PlTransaction {
620
648
  }
621
649
 
622
650
  public setField(fId: AnyFieldRef, ref: AnyRef): void {
651
+ this.stat.fieldsSet++;
623
652
  if (isResource(ref))
624
653
  this.sendVoidAsync({
625
654
  oneofKind: 'fieldSet',
@@ -642,6 +671,7 @@ export class PlTransaction {
642
671
  }
643
672
 
644
673
  public setFieldError(fId: AnyFieldRef, ref: AnyResourceRef): void {
674
+ this.stat.fieldsSet++;
645
675
  this.sendVoidAsync({
646
676
  oneofKind: 'fieldSetError',
647
677
  fieldSetError: { field: toFieldId(fId), errResourceId: toResourceId(ref) }
@@ -649,6 +679,7 @@ export class PlTransaction {
649
679
  }
650
680
 
651
681
  public async getField(fId: AnyFieldRef): Promise<FieldData> {
682
+ this.stat.fieldsGet++;
652
683
  return await this.sendSingleAndParse(
653
684
  { oneofKind: 'fieldGet', fieldGet: { field: toFieldId(fId) } },
654
685
  (r) => protoToField(notEmpty(r.fieldGet.field))
@@ -672,13 +703,19 @@ export class PlTransaction {
672
703
  //
673
704
 
674
705
  public async listKeyValues(rId: AnyResourceRef): Promise<KeyValue[]> {
675
- return await this.sendMultiAndParse(
706
+ const result = await this.sendMultiAndParse(
676
707
  {
677
708
  oneofKind: 'resourceKeyValueList',
678
709
  resourceKeyValueList: { resourceId: toResourceId(rId), startFrom: '', limit: 0 }
679
710
  },
680
711
  (r) => r.map((e) => e.resourceKeyValueList.record!)
681
712
  );
713
+
714
+ this.stat.kvListRequests++;
715
+ this.stat.kvListEntries += result.length;
716
+ for (const kv of result) this.stat.kvListBytes += kv.key.length + kv.value.length;
717
+
718
+ return result;
682
719
  }
683
720
 
684
721
  public async listKeyValuesString(rId: AnyResourceRef): Promise<KeyValueString[]> {
@@ -699,6 +736,8 @@ export class PlTransaction {
699
736
  }
700
737
 
701
738
  public setKValue(rId: AnyResourceRef, key: string, value: Uint8Array | string): void {
739
+ this.stat.kvSetRequests++;
740
+ this.stat.kvSetBytes++;
702
741
  this.sendVoidAsync({
703
742
  oneofKind: 'resourceKeyValueSet',
704
743
  resourceKeyValueSet: {
@@ -720,13 +759,18 @@ export class PlTransaction {
720
759
  }
721
760
 
722
761
  public async getKValue(rId: AnyResourceRef, key: string): Promise<Uint8Array> {
723
- return await this.sendSingleAndParse(
762
+ const result = await this.sendSingleAndParse(
724
763
  {
725
764
  oneofKind: 'resourceKeyValueGet',
726
765
  resourceKeyValueGet: { resourceId: toResourceId(rId), key }
727
766
  },
728
767
  (r) => r.resourceKeyValueGet.value
729
768
  );
769
+
770
+ this.stat.kvGetRequests++;
771
+ this.stat.kvGetBytes += result.length;
772
+
773
+ return result;
730
774
  }
731
775
 
732
776
  public async getKValueString(rId: AnyResourceRef, key: string): Promise<string> {
@@ -741,7 +785,7 @@ export class PlTransaction {
741
785
  rId: AnyResourceRef,
742
786
  key: string
743
787
  ): Promise<Uint8Array | undefined> {
744
- return await this.sendSingleAndParse(
788
+ const result = await this.sendSingleAndParse(
745
789
  {
746
790
  oneofKind: 'resourceKeyValueGetIfExists',
747
791
  resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), key }
@@ -749,6 +793,11 @@ export class PlTransaction {
749
793
  (r) =>
750
794
  r.resourceKeyValueGetIfExists.exists ? r.resourceKeyValueGetIfExists.value : undefined
751
795
  );
796
+
797
+ this.stat.kvGetRequests++;
798
+ this.stat.kvGetBytes += result?.length ?? 0;
799
+
800
+ return result;
752
801
  }
753
802
 
754
803
  public async getKValueStringIfExists(