@milaboratories/pl-client 2.12.2 → 2.13.1
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/core/advisory_locks.cjs +55 -0
- package/dist/core/advisory_locks.cjs.map +1 -0
- package/dist/core/advisory_locks.d.ts +10 -0
- package/dist/core/advisory_locks.d.ts.map +1 -0
- package/dist/core/advisory_locks.js +53 -0
- package/dist/core/advisory_locks.js.map +1 -0
- package/dist/core/client.cjs +45 -38
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +45 -38
- package/dist/core/client.js.map +1 -1
- package/package.json +6 -6
- package/src/core/advisory_locks.ts +67 -0
- package/src/core/client.ts +56 -47
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class AwaitLock {
|
|
4
|
+
acquired = false;
|
|
5
|
+
resolvers = [];
|
|
6
|
+
acquireAsync() {
|
|
7
|
+
if (!this.acquired) {
|
|
8
|
+
this.acquired = true;
|
|
9
|
+
return Promise.resolve();
|
|
10
|
+
}
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
this.resolvers.push(resolve);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Releases the lock. Returns true if the lock becomes fully idle (no waiters, not acquired).
|
|
17
|
+
*/
|
|
18
|
+
release() {
|
|
19
|
+
if (!this.acquired) {
|
|
20
|
+
throw new Error('Cannot release an unacquired lock');
|
|
21
|
+
}
|
|
22
|
+
if (this.resolvers.length) {
|
|
23
|
+
this.resolvers.shift()?.();
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.acquired = false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const m = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Acquire a process-local async lock for the given id and return a release function.
|
|
35
|
+
* Ensures only one concurrent operation per id within this process.
|
|
36
|
+
*/
|
|
37
|
+
async function advisoryLock(id) {
|
|
38
|
+
if (!m.has(id)) {
|
|
39
|
+
m.set(id, new AwaitLock());
|
|
40
|
+
}
|
|
41
|
+
const lock = m.get(id);
|
|
42
|
+
await lock.acquireAsync();
|
|
43
|
+
let released = false;
|
|
44
|
+
return () => {
|
|
45
|
+
if (released)
|
|
46
|
+
return;
|
|
47
|
+
released = true;
|
|
48
|
+
const nowIdle = lock.release();
|
|
49
|
+
if (nowIdle)
|
|
50
|
+
m.delete(id);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.advisoryLock = advisoryLock;
|
|
55
|
+
//# sourceMappingURL=advisory_locks.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisory_locks.cjs","sources":["../../src/core/advisory_locks.ts"],"sourcesContent":["class AwaitLock {\n private acquired = false;\n private resolvers: (() => void)[] = [];\n\n acquireAsync(): Promise<void> {\n if (!this.acquired) {\n this.acquired = true;\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n this.resolvers.push(resolve);\n });\n }\n\n /**\n * Releases the lock. Returns true if the lock becomes fully idle (no waiters, not acquired).\n */\n release(): boolean {\n if (!this.acquired) {\n throw new Error('Cannot release an unacquired lock');\n }\n\n if (this.resolvers.length) {\n this.resolvers.shift()?.();\n return false;\n } else {\n this.acquired = false;\n return true;\n }\n }\n}\n\nconst m = new Map<string, AwaitLock>();\n\n/**\n * Acquire a process-local async lock for the given id and return a release function.\n * Ensures only one concurrent operation per id within this process.\n */\nexport async function advisoryLock(id: string) {\n if (!m.has(id)) {\n m.set(id, new AwaitLock());\n }\n\n const lock = m.get(id)!;\n await lock.acquireAsync();\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const nowIdle = lock.release();\n if (nowIdle) m.delete(id);\n };\n}\n\n/**\n * Run the callback under the lock for the given id; releases automatically.\n */\nexport async function advisoryLockCallback<T>(id: string, cb: () => Promise<T>): Promise<T> {\n const release = await advisoryLock(id);\n try {\n return await cb();\n } finally {\n release();\n }\n}\n"],"names":[],"mappings":";;AAAA,MAAM,SAAS,CAAA;IACL,QAAQ,GAAG,KAAK;IAChB,SAAS,GAAmB,EAAE;IAEtC,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI;AACpB,YAAA,OAAO,OAAO,CAAC,OAAO,EAAE;QAC1B;AAEA,QAAA,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAI;AAC7B,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;AAC9B,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;IACH,OAAO,GAAA;AACL,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC;QACtD;AAEA,QAAA,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE;AACzB,YAAA,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI;AAC1B,YAAA,OAAO,KAAK;QACd;aAAO;AACL,YAAA,IAAI,CAAC,QAAQ,GAAG,KAAK;AACrB,YAAA,OAAO,IAAI;QACb;IACF;AACD;AAED,MAAM,CAAC,GAAG,IAAI,GAAG,EAAqB;AAEtC;;;AAGG;AACI,eAAe,YAAY,CAAC,EAAU,EAAA;IAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QACd,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,SAAS,EAAE,CAAC;IAC5B;IAEA,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAE;AACvB,IAAA,MAAM,IAAI,CAAC,YAAY,EAAE;IAEzB,IAAI,QAAQ,GAAG,KAAK;AACpB,IAAA,OAAO,MAAK;AACV,QAAA,IAAI,QAAQ;YAAE;QACd,QAAQ,GAAG,IAAI;AACf,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE;AAC9B,QAAA,IAAI,OAAO;AAAE,YAAA,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AAC3B,IAAA,CAAC;AACH;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire a process-local async lock for the given id and return a release function.
|
|
3
|
+
* Ensures only one concurrent operation per id within this process.
|
|
4
|
+
*/
|
|
5
|
+
export declare function advisoryLock(id: string): Promise<() => void>;
|
|
6
|
+
/**
|
|
7
|
+
* Run the callback under the lock for the given id; releases automatically.
|
|
8
|
+
*/
|
|
9
|
+
export declare function advisoryLockCallback<T>(id: string, cb: () => Promise<T>): Promise<T>;
|
|
10
|
+
//# sourceMappingURL=advisory_locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisory_locks.d.ts","sourceRoot":"","sources":["../../src/core/advisory_locks.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,uBAe5C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO1F"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
class AwaitLock {
|
|
2
|
+
acquired = false;
|
|
3
|
+
resolvers = [];
|
|
4
|
+
acquireAsync() {
|
|
5
|
+
if (!this.acquired) {
|
|
6
|
+
this.acquired = true;
|
|
7
|
+
return Promise.resolve();
|
|
8
|
+
}
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
this.resolvers.push(resolve);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Releases the lock. Returns true if the lock becomes fully idle (no waiters, not acquired).
|
|
15
|
+
*/
|
|
16
|
+
release() {
|
|
17
|
+
if (!this.acquired) {
|
|
18
|
+
throw new Error('Cannot release an unacquired lock');
|
|
19
|
+
}
|
|
20
|
+
if (this.resolvers.length) {
|
|
21
|
+
this.resolvers.shift()?.();
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.acquired = false;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const m = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Acquire a process-local async lock for the given id and return a release function.
|
|
33
|
+
* Ensures only one concurrent operation per id within this process.
|
|
34
|
+
*/
|
|
35
|
+
async function advisoryLock(id) {
|
|
36
|
+
if (!m.has(id)) {
|
|
37
|
+
m.set(id, new AwaitLock());
|
|
38
|
+
}
|
|
39
|
+
const lock = m.get(id);
|
|
40
|
+
await lock.acquireAsync();
|
|
41
|
+
let released = false;
|
|
42
|
+
return () => {
|
|
43
|
+
if (released)
|
|
44
|
+
return;
|
|
45
|
+
released = true;
|
|
46
|
+
const nowIdle = lock.release();
|
|
47
|
+
if (nowIdle)
|
|
48
|
+
m.delete(id);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { advisoryLock };
|
|
53
|
+
//# sourceMappingURL=advisory_locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisory_locks.js","sources":["../../src/core/advisory_locks.ts"],"sourcesContent":["class AwaitLock {\n private acquired = false;\n private resolvers: (() => void)[] = [];\n\n acquireAsync(): Promise<void> {\n if (!this.acquired) {\n this.acquired = true;\n return Promise.resolve();\n }\n\n return new Promise((resolve) => {\n this.resolvers.push(resolve);\n });\n }\n\n /**\n * Releases the lock. Returns true if the lock becomes fully idle (no waiters, not acquired).\n */\n release(): boolean {\n if (!this.acquired) {\n throw new Error('Cannot release an unacquired lock');\n }\n\n if (this.resolvers.length) {\n this.resolvers.shift()?.();\n return false;\n } else {\n this.acquired = false;\n return true;\n }\n }\n}\n\nconst m = new Map<string, AwaitLock>();\n\n/**\n * Acquire a process-local async lock for the given id and return a release function.\n * Ensures only one concurrent operation per id within this process.\n */\nexport async function advisoryLock(id: string) {\n if (!m.has(id)) {\n m.set(id, new AwaitLock());\n }\n\n const lock = m.get(id)!;\n await lock.acquireAsync();\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const nowIdle = lock.release();\n if (nowIdle) m.delete(id);\n };\n}\n\n/**\n * Run the callback under the lock for the given id; releases automatically.\n */\nexport async function advisoryLockCallback<T>(id: string, cb: () => Promise<T>): Promise<T> {\n const release = await advisoryLock(id);\n try {\n return await cb();\n } finally {\n release();\n }\n}\n"],"names":[],"mappings":"AAAA,MAAM,SAAS,CAAA;IACL,QAAQ,GAAG,KAAK;IAChB,SAAS,GAAmB,EAAE;IAEtC,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI;AACpB,YAAA,OAAO,OAAO,CAAC,OAAO,EAAE;QAC1B;AAEA,QAAA,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAI;AAC7B,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;AAC9B,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;IACH,OAAO,GAAA;AACL,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC;QACtD;AAEA,QAAA,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE;AACzB,YAAA,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI;AAC1B,YAAA,OAAO,KAAK;QACd;aAAO;AACL,YAAA,IAAI,CAAC,QAAQ,GAAG,KAAK;AACrB,YAAA,OAAO,IAAI;QACb;IACF;AACD;AAED,MAAM,CAAC,GAAG,IAAI,GAAG,EAAqB;AAEtC;;;AAGG;AACI,eAAe,YAAY,CAAC,EAAU,EAAA;IAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QACd,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,SAAS,EAAE,CAAC;IAC5B;IAEA,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAE;AACvB,IAAA,MAAM,IAAI,CAAC,YAAY,EAAE;IAEzB,IAAI,QAAQ,GAAG,KAAK;AACpB,IAAA,OAAO,MAAK;AACV,QAAA,IAAI,QAAQ;YAAE;QACd,QAAQ,GAAG,IAAI;AACf,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE;AAC9B,QAAA,IAAI,OAAO;AAAE,YAAA,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AAC3B,IAAA,CAAC;AACH;;;;"}
|
package/dist/core/client.cjs
CHANGED
|
@@ -11,6 +11,7 @@ var tp = require('node:timers/promises');
|
|
|
11
11
|
var lruCache = require('lru-cache');
|
|
12
12
|
var final = require('./final.cjs');
|
|
13
13
|
var stat = require('./stat.cjs');
|
|
14
|
+
var advisory_locks = require('./advisory_locks.cjs');
|
|
14
15
|
|
|
15
16
|
function _interopNamespaceDefault(e) {
|
|
16
17
|
var n = Object.create(null);
|
|
@@ -207,50 +208,56 @@ class PlClient {
|
|
|
207
208
|
// for exponential / linear backoff
|
|
208
209
|
let retryState = tsHelpers.createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);
|
|
209
210
|
while (true) {
|
|
210
|
-
|
|
211
|
-
const llTx = this._ll.createTx(writable, ops);
|
|
212
|
-
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
213
|
-
const tx = new transaction.PlTransaction(llTx, name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
|
|
214
|
-
let ok = false;
|
|
215
|
-
let result = undefined;
|
|
216
|
-
let txId;
|
|
211
|
+
const release = ops?.lockId ? await advisory_locks.advisoryLock(ops.lockId) : () => { };
|
|
217
212
|
try {
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
ok =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
213
|
+
// opening low-level tx
|
|
214
|
+
const llTx = this._ll.createTx(writable, ops);
|
|
215
|
+
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
216
|
+
const tx = new transaction.PlTransaction(llTx, name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
|
|
217
|
+
let ok = false;
|
|
218
|
+
let result = undefined;
|
|
219
|
+
let txId;
|
|
220
|
+
try {
|
|
221
|
+
// executing transaction body
|
|
222
|
+
result = await body(tx);
|
|
228
223
|
// collecting stat
|
|
229
|
-
this.
|
|
224
|
+
this._txCommittedStat = stat.addStat(this._txCommittedStat, tx.stat);
|
|
225
|
+
ok = true;
|
|
230
226
|
}
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
catch (e) {
|
|
228
|
+
// the only recoverable
|
|
229
|
+
if (e instanceof transaction.TxCommitConflict) {
|
|
230
|
+
// ignoring
|
|
231
|
+
// collecting stat
|
|
232
|
+
this._txConflictStat = stat.addStat(this._txConflictStat, tx.stat);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// collecting stat
|
|
236
|
+
this._txErrorStat = stat.addStat(this._txErrorStat, tx.stat);
|
|
237
|
+
throw e;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
// close underlying grpc stream, if not yet done
|
|
242
|
+
// even though we can skip two lines below for read-only transactions,
|
|
243
|
+
// we don't do it to simplify reasoning about what is going on in
|
|
244
|
+
// concurrent code, especially in significant latency situations
|
|
245
|
+
await tx.complete();
|
|
246
|
+
await tx.await();
|
|
247
|
+
txId = await tx.getGlobalTxId();
|
|
248
|
+
}
|
|
249
|
+
if (ok) {
|
|
250
|
+
// syncing on transaction if requested
|
|
251
|
+
if (ops?.sync === undefined ? this.forceSync : ops?.sync)
|
|
252
|
+
await this._ll.grpcPl.get().txSync({ txId });
|
|
253
|
+
// introducing artificial delay, if requested
|
|
254
|
+
if (writable && this.txDelay > 0)
|
|
255
|
+
await tp__namespace.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });
|
|
256
|
+
return result;
|
|
235
257
|
}
|
|
236
258
|
}
|
|
237
259
|
finally {
|
|
238
|
-
|
|
239
|
-
// even though we can skip two lines below for read-only transactions,
|
|
240
|
-
// we don't do it to simplify reasoning about what is going on in
|
|
241
|
-
// concurrent code, especially in significant latency situations
|
|
242
|
-
await tx.complete();
|
|
243
|
-
await tx.await();
|
|
244
|
-
txId = await tx.getGlobalTxId();
|
|
245
|
-
}
|
|
246
|
-
if (ok) {
|
|
247
|
-
// syncing on transaction if requested
|
|
248
|
-
if (ops?.sync === undefined ? this.forceSync : ops?.sync)
|
|
249
|
-
await this._ll.grpcPl.get().txSync({ txId });
|
|
250
|
-
// introducing artificial delay, if requested
|
|
251
|
-
if (writable && this.txDelay > 0)
|
|
252
|
-
await tp__namespace.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });
|
|
253
|
-
return result;
|
|
260
|
+
release();
|
|
254
261
|
}
|
|
255
262
|
// we only get here after TxCommitConflict error,
|
|
256
263
|
// all other errors terminate this loop instantly
|
package/dist/core/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener } from './config';\nimport type { PlCallOps } from './ll_client';\nimport { LLPlClient } from './ll_client';\nimport type { AnyResourceRef } from './transaction';\nimport { PlTransaction, toGlobalResourceId, TxCommitConflict } from './transaction';\nimport { createHash } from 'node:crypto';\nimport type { OptionalResourceId, ResourceId } from './types';\nimport { ensureResourceIdNotNull, isNullResourceId, NullResourceId } from './types';\nimport { ClientRoot } from '../helpers/pl';\nimport type { RetryOptions } from '@milaboratories/ts-helpers';\nimport { assertNever, createRetryState, nextRetryStateOrError } from '@milaboratories/ts-helpers';\nimport type { PlDriver, PlDriverDefinition } from './driver';\nimport type { MaintenanceAPI_Ping_Response, MaintenanceAPI_License_Response } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport { MaintenanceAPI_Ping_Response_Compression } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport * as tp from 'node:timers/promises';\nimport type { Dispatcher } from 'undici';\nimport { LRUCache } from 'lru-cache';\nimport type { ResourceDataCacheRecord } from './cache';\nimport type { FinalResourceDataPredicate } from './final';\nimport { DefaultFinalResourceDataPredicate } from './final';\nimport type { AllTxStat, TxStat } from './stat';\nimport { addStat, initialTxStat } from './stat';\nimport type { GrpcTransport } from '@protobuf-ts/grpc-transport';\n\nexport type TxOps = PlCallOps & {\n sync?: boolean;\n retryOptions?: RetryOptions;\n};\n\nconst defaultTxOps = {\n sync: false,\n};\n\nconst AnonymousClientRoot = 'AnonymousRoot';\n\nfunction alternativeRootFieldName(alternativeRoot: string): string {\n return `alternative_root_${alternativeRoot}`;\n}\n\n/** Client to access core PL API. */\nexport class PlClient {\n private readonly drivers = new Map<string, PlDriver>();\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n private readonly txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly forceSync: boolean;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly defaultRetryOptions: RetryOptions;\n\n private readonly buildLLPlClient: (shouldUseGzip: boolean) => LLPlClient;\n private _ll: LLPlClient;\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalResourceId = NullResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _txCommittedStat: TxStat = initialTxStat();\n private _txConflictStat: TxStat = initialTxStat();\n private _txErrorStat: TxStat = initialTxStat();\n\n //\n // Caching\n //\n\n /** This function determines whether resource data can be cached */\n public readonly finalPredicate: FinalResourceDataPredicate;\n\n /** Resource data cache, to minimize redundant data rereading from remote db */\n private readonly resourceDataCache: LRUCache<ResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n } = {},\n ) {\n // Will reinitialize client after getting available feature from server.\n this.buildLLPlClient = (shouldUseGzip: boolean) => {\n return new LLPlClient(configOrAddress, { auth, ...ops, shouldUseGzip });\n };\n this._ll = this.buildLLPlClient(false);\n const conf = this._ll.conf;\n this.txDelay = conf.txDelay;\n this.forceSync = conf.forceSync;\n this.finalPredicate = ops.finalPredicate ?? DefaultFinalResourceDataPredicate;\n this.resourceDataCache = new LRUCache({\n maxSize: conf.maxCacheBytes,\n sizeCalculation: (v) => (v.basicData.data?.length ?? 0) + 64,\n });\n switch (conf.retryBackoffAlgorithm) {\n case 'exponential':\n this.defaultRetryOptions = {\n type: 'exponentialBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffMultiplier: conf.retryExponentialBackoffMultiplier,\n jitter: conf.retryJitter,\n };\n break;\n case 'linear':\n this.defaultRetryOptions = {\n type: 'linearBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffStep: conf.retryLinearBackoffStep,\n jitter: conf.retryJitter,\n };\n break;\n default:\n assertNever(conf.retryBackoffAlgorithm);\n }\n }\n\n public get txCommittedStat(): TxStat {\n return { ...this._txCommittedStat };\n }\n\n public get txConflictStat(): TxStat {\n return { ...this._txConflictStat };\n }\n\n public get txErrorStat(): TxStat {\n return { ...this._txErrorStat };\n }\n\n public get txTotalStat(): TxStat {\n return addStat(addStat(this._txCommittedStat, this._txConflictStat), this._txErrorStat);\n }\n\n public get allTxStat(): AllTxStat {\n return {\n committed: this.txCommittedStat,\n conflict: this.txConflictStat,\n error: this.txErrorStat,\n };\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return (await this._ll.grpcPl.get().ping({})).response;\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return (await this._ll.grpcPl.get().license({})).response;\n }\n\n public get conf(): PlClientConfig {\n return this._ll.conf;\n }\n\n public get httpDispatcher(): Dispatcher {\n return this._ll.httpDispatcher;\n }\n\n public get grpcTransport(): GrpcTransport {\n return this._ll.grpcTransport;\n }\n\n private get initialized() {\n return !isNullResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error('Client not initialized');\n }\n\n public get clientRoot(): ResourceId {\n this.checkInitialized();\n return ensureResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** Currently implements custom logic to emulate future behaviour with single root. */\n public async init() {\n if (this.initialized) throw new Error('Already initialized');\n\n // calculating reproducible root name from the username\n const user = this._ll.authUser;\n const mainRootName\n = user === null ? AnonymousClientRoot : createHash('sha256').update(user).digest('hex');\n\n this._serverInfo = await this.ping();\n if (this._serverInfo.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = this.buildLLPlClient(true);\n }\n\n this._clientRoot = await this._withTx('initialization', true, NullResourceId, async (tx) => {\n let mainRoot: AnyResourceRef;\n\n if (await tx.checkResourceNameExists(mainRootName))\n mainRoot = await tx.getResourceByName(mainRootName);\n else {\n mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n }\n\n if (this.conf.alternativeRoot === undefined) {\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n } else {\n const aFId = {\n resourceId: mainRoot,\n fieldName: alternativeRootFieldName(this.conf.alternativeRoot),\n };\n\n const altRoot = tx.createEphemeral(ClientRoot);\n tx.lock(altRoot);\n tx.createField(aFId, 'Dynamic');\n tx.setField(aFId, altRoot);\n await tx.commit();\n\n return await altRoot.globalId;\n }\n });\n }\n\n /** Returns true if field existed */\n public async deleteAlternativeRoot(alternativeRootName: string): Promise<boolean> {\n this.checkInitialized();\n if (this._ll.conf.alternativeRoot !== undefined)\n throw new Error('Initialized with alternative root.');\n return await this.withWriteTx('delete-alternative-root', async (tx) => {\n const fId = {\n resourceId: tx.clientRoot,\n fieldName: alternativeRootFieldName(alternativeRootName),\n };\n const exists = tx.fieldExists(fId);\n tx.removeField(fId);\n await tx.commit();\n return await exists;\n });\n }\n\n private async _withTx<T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n ops?: TxOps,\n ): Promise<T> {\n // for exponential / linear backoff\n let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);\n\n while (true) {\n // opening low-level tx\n const llTx = this._ll.createTx(writable, ops);\n // wrapping it into high-level tx (this also asynchronously sends initialization message)\n const tx = new PlTransaction(\n llTx,\n name,\n writable,\n clientRoot,\n this.finalPredicate,\n this.resourceDataCache,\n );\n\n let ok = false;\n let result: T | undefined = undefined;\n let txId;\n\n try {\n // executing transaction body\n result = await body(tx);\n // collecting stat\n this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);\n ok = true;\n } catch (e: unknown) {\n // the only recoverable\n if (e instanceof TxCommitConflict) {\n // ignoring\n // collecting stat\n this._txConflictStat = addStat(this._txConflictStat, tx.stat);\n } else {\n // collecting stat\n this._txErrorStat = addStat(this._txErrorStat, tx.stat);\n throw e;\n }\n } finally {\n // close underlying grpc stream, if not yet done\n\n // even though we can skip two lines below for read-only transactions,\n // we don't do it to simplify reasoning about what is going on in\n // concurrent code, especially in significant latency situations\n await tx.complete();\n await tx.await();\n\n txId = await tx.getGlobalTxId();\n }\n\n if (ok) {\n // syncing on transaction if requested\n if (ops?.sync === undefined ? this.forceSync : ops?.sync)\n await this._ll.grpcPl.get().txSync({ txId });\n\n // introducing artificial delay, if requested\n if (writable && this.txDelay > 0)\n await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });\n\n return result!;\n }\n\n // we only get here after TxCommitConflict error,\n // all other errors terminate this loop instantly\n\n await tp.setTimeout(retryState.nextDelay, undefined, { signal: ops?.abortSignal });\n retryState = nextRetryStateOrError(retryState);\n }\n }\n\n private async withTx<T>(\n name: string,\n writable: boolean,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, writable, this.clientRoot, body, { ...ops, ...defaultTxOps });\n }\n\n public async withWriteTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, true, body, { ...ops, ...defaultTxOps });\n }\n\n public async withReadTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, false, body, { ...ops, ...defaultTxOps });\n }\n\n public getDriver<Drv extends PlDriver>(definition: PlDriverDefinition<Drv>): Drv {\n const attached = this.drivers.get(definition.name);\n if (attached !== undefined) return attached as Drv;\n const driver = definition.init(this, this._ll, this.httpDispatcher);\n this.drivers.set(definition.name, driver);\n return driver;\n }\n\n /** Closes underlying transport */\n public async close() {\n await this._ll.close();\n }\n\n public static async init(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"names":["NullResourceId","initialTxStat","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","assertNever","addStat","isNullResourceId","ensureResourceIdNotNull","createHash","MaintenanceAPI_Ping_Response_Compression","ClientRoot","toGlobalResourceId","createRetryState","PlTransaction","TxCommitConflict","tp","nextRetryStateOrError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.cjs","sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener } from './config';\nimport type { PlCallOps } from './ll_client';\nimport { LLPlClient } from './ll_client';\nimport type { AnyResourceRef } from './transaction';\nimport { PlTransaction, toGlobalResourceId, TxCommitConflict } from './transaction';\nimport { createHash } from 'node:crypto';\nimport type { OptionalResourceId, ResourceId } from './types';\nimport { ensureResourceIdNotNull, isNullResourceId, NullResourceId } from './types';\nimport { ClientRoot } from '../helpers/pl';\nimport type { RetryOptions } from '@milaboratories/ts-helpers';\nimport { assertNever, createRetryState, nextRetryStateOrError } from '@milaboratories/ts-helpers';\nimport type { PlDriver, PlDriverDefinition } from './driver';\nimport type { MaintenanceAPI_Ping_Response, MaintenanceAPI_License_Response } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport { MaintenanceAPI_Ping_Response_Compression } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport * as tp from 'node:timers/promises';\nimport type { Dispatcher } from 'undici';\nimport { LRUCache } from 'lru-cache';\nimport type { ResourceDataCacheRecord } from './cache';\nimport type { FinalResourceDataPredicate } from './final';\nimport { DefaultFinalResourceDataPredicate } from './final';\nimport type { AllTxStat, TxStat } from './stat';\nimport { addStat, initialTxStat } from './stat';\nimport type { GrpcTransport } from '@protobuf-ts/grpc-transport';\nimport { advisoryLock } from './advisory_locks';\n\nexport type TxOps = PlCallOps & {\n sync?: boolean;\n retryOptions?: RetryOptions;\n name?: string;\n lockId?: string;\n};\n\nconst defaultTxOps = {\n sync: false,\n};\n\nconst AnonymousClientRoot = 'AnonymousRoot';\n\nfunction alternativeRootFieldName(alternativeRoot: string): string {\n return `alternative_root_${alternativeRoot}`;\n}\n\n/** Client to access core PL API. */\nexport class PlClient {\n private readonly drivers = new Map<string, PlDriver>();\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n private readonly txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly forceSync: boolean;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly defaultRetryOptions: RetryOptions;\n\n private readonly buildLLPlClient: (shouldUseGzip: boolean) => LLPlClient;\n private _ll: LLPlClient;\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalResourceId = NullResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _txCommittedStat: TxStat = initialTxStat();\n private _txConflictStat: TxStat = initialTxStat();\n private _txErrorStat: TxStat = initialTxStat();\n\n //\n // Caching\n //\n\n /** This function determines whether resource data can be cached */\n public readonly finalPredicate: FinalResourceDataPredicate;\n\n /** Resource data cache, to minimize redundant data rereading from remote db */\n private readonly resourceDataCache: LRUCache<ResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n } = {},\n ) {\n // Will reinitialize client after getting available feature from server.\n this.buildLLPlClient = (shouldUseGzip: boolean) => {\n return new LLPlClient(configOrAddress, { auth, ...ops, shouldUseGzip });\n };\n this._ll = this.buildLLPlClient(false);\n const conf = this._ll.conf;\n this.txDelay = conf.txDelay;\n this.forceSync = conf.forceSync;\n this.finalPredicate = ops.finalPredicate ?? DefaultFinalResourceDataPredicate;\n this.resourceDataCache = new LRUCache({\n maxSize: conf.maxCacheBytes,\n sizeCalculation: (v) => (v.basicData.data?.length ?? 0) + 64,\n });\n switch (conf.retryBackoffAlgorithm) {\n case 'exponential':\n this.defaultRetryOptions = {\n type: 'exponentialBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffMultiplier: conf.retryExponentialBackoffMultiplier,\n jitter: conf.retryJitter,\n };\n break;\n case 'linear':\n this.defaultRetryOptions = {\n type: 'linearBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffStep: conf.retryLinearBackoffStep,\n jitter: conf.retryJitter,\n };\n break;\n default:\n assertNever(conf.retryBackoffAlgorithm);\n }\n }\n\n public get txCommittedStat(): TxStat {\n return { ...this._txCommittedStat };\n }\n\n public get txConflictStat(): TxStat {\n return { ...this._txConflictStat };\n }\n\n public get txErrorStat(): TxStat {\n return { ...this._txErrorStat };\n }\n\n public get txTotalStat(): TxStat {\n return addStat(addStat(this._txCommittedStat, this._txConflictStat), this._txErrorStat);\n }\n\n public get allTxStat(): AllTxStat {\n return {\n committed: this.txCommittedStat,\n conflict: this.txConflictStat,\n error: this.txErrorStat,\n };\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return (await this._ll.grpcPl.get().ping({})).response;\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return (await this._ll.grpcPl.get().license({})).response;\n }\n\n public get conf(): PlClientConfig {\n return this._ll.conf;\n }\n\n public get httpDispatcher(): Dispatcher {\n return this._ll.httpDispatcher;\n }\n\n public get grpcTransport(): GrpcTransport {\n return this._ll.grpcTransport;\n }\n\n private get initialized() {\n return !isNullResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error('Client not initialized');\n }\n\n public get clientRoot(): ResourceId {\n this.checkInitialized();\n return ensureResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** Currently implements custom logic to emulate future behaviour with single root. */\n public async init() {\n if (this.initialized) throw new Error('Already initialized');\n\n // calculating reproducible root name from the username\n const user = this._ll.authUser;\n const mainRootName\n = user === null ? AnonymousClientRoot : createHash('sha256').update(user).digest('hex');\n\n this._serverInfo = await this.ping();\n if (this._serverInfo.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = this.buildLLPlClient(true);\n }\n\n this._clientRoot = await this._withTx('initialization', true, NullResourceId, async (tx) => {\n let mainRoot: AnyResourceRef;\n\n if (await tx.checkResourceNameExists(mainRootName))\n mainRoot = await tx.getResourceByName(mainRootName);\n else {\n mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n }\n\n if (this.conf.alternativeRoot === undefined) {\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n } else {\n const aFId = {\n resourceId: mainRoot,\n fieldName: alternativeRootFieldName(this.conf.alternativeRoot),\n };\n\n const altRoot = tx.createEphemeral(ClientRoot);\n tx.lock(altRoot);\n tx.createField(aFId, 'Dynamic');\n tx.setField(aFId, altRoot);\n await tx.commit();\n\n return await altRoot.globalId;\n }\n });\n }\n\n /** Returns true if field existed */\n public async deleteAlternativeRoot(alternativeRootName: string): Promise<boolean> {\n this.checkInitialized();\n if (this._ll.conf.alternativeRoot !== undefined)\n throw new Error('Initialized with alternative root.');\n return await this.withWriteTx('delete-alternative-root', async (tx) => {\n const fId = {\n resourceId: tx.clientRoot,\n fieldName: alternativeRootFieldName(alternativeRootName),\n };\n const exists = tx.fieldExists(fId);\n tx.removeField(fId);\n await tx.commit();\n return await exists;\n });\n }\n\n private async _withTx<T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n ops?: TxOps,\n ): Promise<T> {\n // for exponential / linear backoff\n let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);\n\n while (true) {\n const release = ops?.lockId ? await advisoryLock(ops.lockId) : () => {};\n\n try {\n // opening low-level tx\n const llTx = this._ll.createTx(writable, ops);\n // wrapping it into high-level tx (this also asynchronously sends initialization message)\n const tx = new PlTransaction(\n llTx,\n name,\n writable,\n clientRoot,\n this.finalPredicate,\n this.resourceDataCache,\n );\n\n let ok = false;\n let result: T | undefined = undefined;\n let txId;\n\n try {\n // executing transaction body\n result = await body(tx);\n // collecting stat\n this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);\n ok = true;\n } catch (e: unknown) {\n // the only recoverable\n if (e instanceof TxCommitConflict) {\n // ignoring\n // collecting stat\n this._txConflictStat = addStat(this._txConflictStat, tx.stat);\n } else {\n // collecting stat\n this._txErrorStat = addStat(this._txErrorStat, tx.stat);\n throw e;\n }\n } finally {\n // close underlying grpc stream, if not yet done\n\n // even though we can skip two lines below for read-only transactions,\n // we don't do it to simplify reasoning about what is going on in\n // concurrent code, especially in significant latency situations\n await tx.complete();\n await tx.await();\n\n txId = await tx.getGlobalTxId();\n }\n\n if (ok) {\n // syncing on transaction if requested\n if (ops?.sync === undefined ? this.forceSync : ops?.sync)\n await this._ll.grpcPl.get().txSync({ txId });\n\n // introducing artificial delay, if requested\n if (writable && this.txDelay > 0)\n await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });\n\n return result!;\n }\n } finally {\n release();\n }\n\n // we only get here after TxCommitConflict error,\n // all other errors terminate this loop instantly\n\n await tp.setTimeout(retryState.nextDelay, undefined, { signal: ops?.abortSignal });\n retryState = nextRetryStateOrError(retryState);\n }\n }\n\n private async withTx<T>(\n name: string,\n writable: boolean,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, writable, this.clientRoot, body, { ...ops, ...defaultTxOps });\n }\n\n public async withWriteTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, true, body, { ...ops, ...defaultTxOps });\n }\n\n public async withReadTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, false, body, { ...ops, ...defaultTxOps });\n }\n\n public getDriver<Drv extends PlDriver>(definition: PlDriverDefinition<Drv>): Drv {\n const attached = this.drivers.get(definition.name);\n if (attached !== undefined) return attached as Drv;\n const driver = definition.init(this, this._ll, this.httpDispatcher);\n this.drivers.set(definition.name, driver);\n return driver;\n }\n\n /** Closes underlying transport */\n public async close() {\n await this._ll.close();\n }\n\n public static async init(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"names":["NullResourceId","initialTxStat","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","assertNever","addStat","isNullResourceId","ensureResourceIdNotNull","createHash","MaintenanceAPI_Ping_Response_Compression","ClientRoot","toGlobalResourceId","createRetryState","advisoryLock","PlTransaction","TxCommitConflict","tp","nextRetryStateOrError"],"mappings":"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"}
|
package/dist/core/client.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ import type { GrpcTransport } from '@protobuf-ts/grpc-transport';
|
|
|
12
12
|
export type TxOps = PlCallOps & {
|
|
13
13
|
sync?: boolean;
|
|
14
14
|
retryOptions?: RetryOptions;
|
|
15
|
+
name?: string;
|
|
16
|
+
lockId?: string;
|
|
15
17
|
};
|
|
16
18
|
/** Client to access core PL API. */
|
|
17
19
|
export declare class PlClient {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAwC,MAAM,eAAe,CAAC;AAEpF,OAAO,KAAK,EAAsB,UAAU,EAAE,MAAM,SAAS,CAAC;AAG9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,KAAK,EAAE,4BAA4B,EAAE,+BAA+B,EAAE,MAAM,0DAA0D,CAAC;AAG9I,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"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"}
|
package/dist/core/client.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as tp from 'node:timers/promises';
|
|
|
9
9
|
import { LRUCache } from 'lru-cache';
|
|
10
10
|
import { DefaultFinalResourceDataPredicate } from './final.js';
|
|
11
11
|
import { initialTxStat, addStat } from './stat.js';
|
|
12
|
+
import { advisoryLock } from './advisory_locks.js';
|
|
12
13
|
|
|
13
14
|
const defaultTxOps = {
|
|
14
15
|
sync: false,
|
|
@@ -186,50 +187,56 @@ class PlClient {
|
|
|
186
187
|
// for exponential / linear backoff
|
|
187
188
|
let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);
|
|
188
189
|
while (true) {
|
|
189
|
-
|
|
190
|
-
const llTx = this._ll.createTx(writable, ops);
|
|
191
|
-
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
192
|
-
const tx = new PlTransaction(llTx, name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
|
|
193
|
-
let ok = false;
|
|
194
|
-
let result = undefined;
|
|
195
|
-
let txId;
|
|
190
|
+
const release = ops?.lockId ? await advisoryLock(ops.lockId) : () => { };
|
|
196
191
|
try {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
ok =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
// opening low-level tx
|
|
193
|
+
const llTx = this._ll.createTx(writable, ops);
|
|
194
|
+
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
195
|
+
const tx = new PlTransaction(llTx, name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
|
|
196
|
+
let ok = false;
|
|
197
|
+
let result = undefined;
|
|
198
|
+
let txId;
|
|
199
|
+
try {
|
|
200
|
+
// executing transaction body
|
|
201
|
+
result = await body(tx);
|
|
207
202
|
// collecting stat
|
|
208
|
-
this.
|
|
203
|
+
this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);
|
|
204
|
+
ok = true;
|
|
209
205
|
}
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
206
|
+
catch (e) {
|
|
207
|
+
// the only recoverable
|
|
208
|
+
if (e instanceof TxCommitConflict) {
|
|
209
|
+
// ignoring
|
|
210
|
+
// collecting stat
|
|
211
|
+
this._txConflictStat = addStat(this._txConflictStat, tx.stat);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// collecting stat
|
|
215
|
+
this._txErrorStat = addStat(this._txErrorStat, tx.stat);
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
// close underlying grpc stream, if not yet done
|
|
221
|
+
// even though we can skip two lines below for read-only transactions,
|
|
222
|
+
// we don't do it to simplify reasoning about what is going on in
|
|
223
|
+
// concurrent code, especially in significant latency situations
|
|
224
|
+
await tx.complete();
|
|
225
|
+
await tx.await();
|
|
226
|
+
txId = await tx.getGlobalTxId();
|
|
227
|
+
}
|
|
228
|
+
if (ok) {
|
|
229
|
+
// syncing on transaction if requested
|
|
230
|
+
if (ops?.sync === undefined ? this.forceSync : ops?.sync)
|
|
231
|
+
await this._ll.grpcPl.get().txSync({ txId });
|
|
232
|
+
// introducing artificial delay, if requested
|
|
233
|
+
if (writable && this.txDelay > 0)
|
|
234
|
+
await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });
|
|
235
|
+
return result;
|
|
214
236
|
}
|
|
215
237
|
}
|
|
216
238
|
finally {
|
|
217
|
-
|
|
218
|
-
// even though we can skip two lines below for read-only transactions,
|
|
219
|
-
// we don't do it to simplify reasoning about what is going on in
|
|
220
|
-
// concurrent code, especially in significant latency situations
|
|
221
|
-
await tx.complete();
|
|
222
|
-
await tx.await();
|
|
223
|
-
txId = await tx.getGlobalTxId();
|
|
224
|
-
}
|
|
225
|
-
if (ok) {
|
|
226
|
-
// syncing on transaction if requested
|
|
227
|
-
if (ops?.sync === undefined ? this.forceSync : ops?.sync)
|
|
228
|
-
await this._ll.grpcPl.get().txSync({ txId });
|
|
229
|
-
// introducing artificial delay, if requested
|
|
230
|
-
if (writable && this.txDelay > 0)
|
|
231
|
-
await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });
|
|
232
|
-
return result;
|
|
239
|
+
release();
|
|
233
240
|
}
|
|
234
241
|
// we only get here after TxCommitConflict error,
|
|
235
242
|
// all other errors terminate this loop instantly
|
package/dist/core/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener } from './config';\nimport type { PlCallOps } from './ll_client';\nimport { LLPlClient } from './ll_client';\nimport type { AnyResourceRef } from './transaction';\nimport { PlTransaction, toGlobalResourceId, TxCommitConflict } from './transaction';\nimport { createHash } from 'node:crypto';\nimport type { OptionalResourceId, ResourceId } from './types';\nimport { ensureResourceIdNotNull, isNullResourceId, NullResourceId } from './types';\nimport { ClientRoot } from '../helpers/pl';\nimport type { RetryOptions } from '@milaboratories/ts-helpers';\nimport { assertNever, createRetryState, nextRetryStateOrError } from '@milaboratories/ts-helpers';\nimport type { PlDriver, PlDriverDefinition } from './driver';\nimport type { MaintenanceAPI_Ping_Response, MaintenanceAPI_License_Response } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport { MaintenanceAPI_Ping_Response_Compression } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport * as tp from 'node:timers/promises';\nimport type { Dispatcher } from 'undici';\nimport { LRUCache } from 'lru-cache';\nimport type { ResourceDataCacheRecord } from './cache';\nimport type { FinalResourceDataPredicate } from './final';\nimport { DefaultFinalResourceDataPredicate } from './final';\nimport type { AllTxStat, TxStat } from './stat';\nimport { addStat, initialTxStat } from './stat';\nimport type { GrpcTransport } from '@protobuf-ts/grpc-transport';\n\nexport type TxOps = PlCallOps & {\n sync?: boolean;\n retryOptions?: RetryOptions;\n};\n\nconst defaultTxOps = {\n sync: false,\n};\n\nconst AnonymousClientRoot = 'AnonymousRoot';\n\nfunction alternativeRootFieldName(alternativeRoot: string): string {\n return `alternative_root_${alternativeRoot}`;\n}\n\n/** Client to access core PL API. */\nexport class PlClient {\n private readonly drivers = new Map<string, PlDriver>();\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n private readonly txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly forceSync: boolean;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly defaultRetryOptions: RetryOptions;\n\n private readonly buildLLPlClient: (shouldUseGzip: boolean) => LLPlClient;\n private _ll: LLPlClient;\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalResourceId = NullResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _txCommittedStat: TxStat = initialTxStat();\n private _txConflictStat: TxStat = initialTxStat();\n private _txErrorStat: TxStat = initialTxStat();\n\n //\n // Caching\n //\n\n /** This function determines whether resource data can be cached */\n public readonly finalPredicate: FinalResourceDataPredicate;\n\n /** Resource data cache, to minimize redundant data rereading from remote db */\n private readonly resourceDataCache: LRUCache<ResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n } = {},\n ) {\n // Will reinitialize client after getting available feature from server.\n this.buildLLPlClient = (shouldUseGzip: boolean) => {\n return new LLPlClient(configOrAddress, { auth, ...ops, shouldUseGzip });\n };\n this._ll = this.buildLLPlClient(false);\n const conf = this._ll.conf;\n this.txDelay = conf.txDelay;\n this.forceSync = conf.forceSync;\n this.finalPredicate = ops.finalPredicate ?? DefaultFinalResourceDataPredicate;\n this.resourceDataCache = new LRUCache({\n maxSize: conf.maxCacheBytes,\n sizeCalculation: (v) => (v.basicData.data?.length ?? 0) + 64,\n });\n switch (conf.retryBackoffAlgorithm) {\n case 'exponential':\n this.defaultRetryOptions = {\n type: 'exponentialBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffMultiplier: conf.retryExponentialBackoffMultiplier,\n jitter: conf.retryJitter,\n };\n break;\n case 'linear':\n this.defaultRetryOptions = {\n type: 'linearBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffStep: conf.retryLinearBackoffStep,\n jitter: conf.retryJitter,\n };\n break;\n default:\n assertNever(conf.retryBackoffAlgorithm);\n }\n }\n\n public get txCommittedStat(): TxStat {\n return { ...this._txCommittedStat };\n }\n\n public get txConflictStat(): TxStat {\n return { ...this._txConflictStat };\n }\n\n public get txErrorStat(): TxStat {\n return { ...this._txErrorStat };\n }\n\n public get txTotalStat(): TxStat {\n return addStat(addStat(this._txCommittedStat, this._txConflictStat), this._txErrorStat);\n }\n\n public get allTxStat(): AllTxStat {\n return {\n committed: this.txCommittedStat,\n conflict: this.txConflictStat,\n error: this.txErrorStat,\n };\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return (await this._ll.grpcPl.get().ping({})).response;\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return (await this._ll.grpcPl.get().license({})).response;\n }\n\n public get conf(): PlClientConfig {\n return this._ll.conf;\n }\n\n public get httpDispatcher(): Dispatcher {\n return this._ll.httpDispatcher;\n }\n\n public get grpcTransport(): GrpcTransport {\n return this._ll.grpcTransport;\n }\n\n private get initialized() {\n return !isNullResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error('Client not initialized');\n }\n\n public get clientRoot(): ResourceId {\n this.checkInitialized();\n return ensureResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** Currently implements custom logic to emulate future behaviour with single root. */\n public async init() {\n if (this.initialized) throw new Error('Already initialized');\n\n // calculating reproducible root name from the username\n const user = this._ll.authUser;\n const mainRootName\n = user === null ? AnonymousClientRoot : createHash('sha256').update(user).digest('hex');\n\n this._serverInfo = await this.ping();\n if (this._serverInfo.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = this.buildLLPlClient(true);\n }\n\n this._clientRoot = await this._withTx('initialization', true, NullResourceId, async (tx) => {\n let mainRoot: AnyResourceRef;\n\n if (await tx.checkResourceNameExists(mainRootName))\n mainRoot = await tx.getResourceByName(mainRootName);\n else {\n mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n }\n\n if (this.conf.alternativeRoot === undefined) {\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n } else {\n const aFId = {\n resourceId: mainRoot,\n fieldName: alternativeRootFieldName(this.conf.alternativeRoot),\n };\n\n const altRoot = tx.createEphemeral(ClientRoot);\n tx.lock(altRoot);\n tx.createField(aFId, 'Dynamic');\n tx.setField(aFId, altRoot);\n await tx.commit();\n\n return await altRoot.globalId;\n }\n });\n }\n\n /** Returns true if field existed */\n public async deleteAlternativeRoot(alternativeRootName: string): Promise<boolean> {\n this.checkInitialized();\n if (this._ll.conf.alternativeRoot !== undefined)\n throw new Error('Initialized with alternative root.');\n return await this.withWriteTx('delete-alternative-root', async (tx) => {\n const fId = {\n resourceId: tx.clientRoot,\n fieldName: alternativeRootFieldName(alternativeRootName),\n };\n const exists = tx.fieldExists(fId);\n tx.removeField(fId);\n await tx.commit();\n return await exists;\n });\n }\n\n private async _withTx<T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n ops?: TxOps,\n ): Promise<T> {\n // for exponential / linear backoff\n let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);\n\n while (true) {\n // opening low-level tx\n const llTx = this._ll.createTx(writable, ops);\n // wrapping it into high-level tx (this also asynchronously sends initialization message)\n const tx = new PlTransaction(\n llTx,\n name,\n writable,\n clientRoot,\n this.finalPredicate,\n this.resourceDataCache,\n );\n\n let ok = false;\n let result: T | undefined = undefined;\n let txId;\n\n try {\n // executing transaction body\n result = await body(tx);\n // collecting stat\n this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);\n ok = true;\n } catch (e: unknown) {\n // the only recoverable\n if (e instanceof TxCommitConflict) {\n // ignoring\n // collecting stat\n this._txConflictStat = addStat(this._txConflictStat, tx.stat);\n } else {\n // collecting stat\n this._txErrorStat = addStat(this._txErrorStat, tx.stat);\n throw e;\n }\n } finally {\n // close underlying grpc stream, if not yet done\n\n // even though we can skip two lines below for read-only transactions,\n // we don't do it to simplify reasoning about what is going on in\n // concurrent code, especially in significant latency situations\n await tx.complete();\n await tx.await();\n\n txId = await tx.getGlobalTxId();\n }\n\n if (ok) {\n // syncing on transaction if requested\n if (ops?.sync === undefined ? this.forceSync : ops?.sync)\n await this._ll.grpcPl.get().txSync({ txId });\n\n // introducing artificial delay, if requested\n if (writable && this.txDelay > 0)\n await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });\n\n return result!;\n }\n\n // we only get here after TxCommitConflict error,\n // all other errors terminate this loop instantly\n\n await tp.setTimeout(retryState.nextDelay, undefined, { signal: ops?.abortSignal });\n retryState = nextRetryStateOrError(retryState);\n }\n }\n\n private async withTx<T>(\n name: string,\n writable: boolean,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, writable, this.clientRoot, body, { ...ops, ...defaultTxOps });\n }\n\n public async withWriteTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, true, body, { ...ops, ...defaultTxOps });\n }\n\n public async withReadTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, false, body, { ...ops, ...defaultTxOps });\n }\n\n public getDriver<Drv extends PlDriver>(definition: PlDriverDefinition<Drv>): Drv {\n const attached = this.drivers.get(definition.name);\n if (attached !== undefined) return attached as Drv;\n const driver = definition.init(this, this._ll, this.httpDispatcher);\n this.drivers.set(definition.name, driver);\n return driver;\n }\n\n /** Closes underlying transport */\n public async close() {\n await this._ll.close();\n }\n\n public static async init(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener } from './config';\nimport type { PlCallOps } from './ll_client';\nimport { LLPlClient } from './ll_client';\nimport type { AnyResourceRef } from './transaction';\nimport { PlTransaction, toGlobalResourceId, TxCommitConflict } from './transaction';\nimport { createHash } from 'node:crypto';\nimport type { OptionalResourceId, ResourceId } from './types';\nimport { ensureResourceIdNotNull, isNullResourceId, NullResourceId } from './types';\nimport { ClientRoot } from '../helpers/pl';\nimport type { RetryOptions } from '@milaboratories/ts-helpers';\nimport { assertNever, createRetryState, nextRetryStateOrError } from '@milaboratories/ts-helpers';\nimport type { PlDriver, PlDriverDefinition } from './driver';\nimport type { MaintenanceAPI_Ping_Response, MaintenanceAPI_License_Response } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport { MaintenanceAPI_Ping_Response_Compression } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';\nimport * as tp from 'node:timers/promises';\nimport type { Dispatcher } from 'undici';\nimport { LRUCache } from 'lru-cache';\nimport type { ResourceDataCacheRecord } from './cache';\nimport type { FinalResourceDataPredicate } from './final';\nimport { DefaultFinalResourceDataPredicate } from './final';\nimport type { AllTxStat, TxStat } from './stat';\nimport { addStat, initialTxStat } from './stat';\nimport type { GrpcTransport } from '@protobuf-ts/grpc-transport';\nimport { advisoryLock } from './advisory_locks';\n\nexport type TxOps = PlCallOps & {\n sync?: boolean;\n retryOptions?: RetryOptions;\n name?: string;\n lockId?: string;\n};\n\nconst defaultTxOps = {\n sync: false,\n};\n\nconst AnonymousClientRoot = 'AnonymousRoot';\n\nfunction alternativeRootFieldName(alternativeRoot: string): string {\n return `alternative_root_${alternativeRoot}`;\n}\n\n/** Client to access core PL API. */\nexport class PlClient {\n private readonly drivers = new Map<string, PlDriver>();\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n private readonly txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly forceSync: boolean;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n private readonly defaultRetryOptions: RetryOptions;\n\n private readonly buildLLPlClient: (shouldUseGzip: boolean) => LLPlClient;\n private _ll: LLPlClient;\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalResourceId = NullResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _txCommittedStat: TxStat = initialTxStat();\n private _txConflictStat: TxStat = initialTxStat();\n private _txErrorStat: TxStat = initialTxStat();\n\n //\n // Caching\n //\n\n /** This function determines whether resource data can be cached */\n public readonly finalPredicate: FinalResourceDataPredicate;\n\n /** Resource data cache, to minimize redundant data rereading from remote db */\n private readonly resourceDataCache: LRUCache<ResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n } = {},\n ) {\n // Will reinitialize client after getting available feature from server.\n this.buildLLPlClient = (shouldUseGzip: boolean) => {\n return new LLPlClient(configOrAddress, { auth, ...ops, shouldUseGzip });\n };\n this._ll = this.buildLLPlClient(false);\n const conf = this._ll.conf;\n this.txDelay = conf.txDelay;\n this.forceSync = conf.forceSync;\n this.finalPredicate = ops.finalPredicate ?? DefaultFinalResourceDataPredicate;\n this.resourceDataCache = new LRUCache({\n maxSize: conf.maxCacheBytes,\n sizeCalculation: (v) => (v.basicData.data?.length ?? 0) + 64,\n });\n switch (conf.retryBackoffAlgorithm) {\n case 'exponential':\n this.defaultRetryOptions = {\n type: 'exponentialBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffMultiplier: conf.retryExponentialBackoffMultiplier,\n jitter: conf.retryJitter,\n };\n break;\n case 'linear':\n this.defaultRetryOptions = {\n type: 'linearBackoff',\n initialDelay: conf.retryInitialDelay,\n maxAttempts: conf.retryMaxAttempts,\n backoffStep: conf.retryLinearBackoffStep,\n jitter: conf.retryJitter,\n };\n break;\n default:\n assertNever(conf.retryBackoffAlgorithm);\n }\n }\n\n public get txCommittedStat(): TxStat {\n return { ...this._txCommittedStat };\n }\n\n public get txConflictStat(): TxStat {\n return { ...this._txConflictStat };\n }\n\n public get txErrorStat(): TxStat {\n return { ...this._txErrorStat };\n }\n\n public get txTotalStat(): TxStat {\n return addStat(addStat(this._txCommittedStat, this._txConflictStat), this._txErrorStat);\n }\n\n public get allTxStat(): AllTxStat {\n return {\n committed: this.txCommittedStat,\n conflict: this.txConflictStat,\n error: this.txErrorStat,\n };\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return (await this._ll.grpcPl.get().ping({})).response;\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return (await this._ll.grpcPl.get().license({})).response;\n }\n\n public get conf(): PlClientConfig {\n return this._ll.conf;\n }\n\n public get httpDispatcher(): Dispatcher {\n return this._ll.httpDispatcher;\n }\n\n public get grpcTransport(): GrpcTransport {\n return this._ll.grpcTransport;\n }\n\n private get initialized() {\n return !isNullResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error('Client not initialized');\n }\n\n public get clientRoot(): ResourceId {\n this.checkInitialized();\n return ensureResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** Currently implements custom logic to emulate future behaviour with single root. */\n public async init() {\n if (this.initialized) throw new Error('Already initialized');\n\n // calculating reproducible root name from the username\n const user = this._ll.authUser;\n const mainRootName\n = user === null ? AnonymousClientRoot : createHash('sha256').update(user).digest('hex');\n\n this._serverInfo = await this.ping();\n if (this._serverInfo.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = this.buildLLPlClient(true);\n }\n\n this._clientRoot = await this._withTx('initialization', true, NullResourceId, async (tx) => {\n let mainRoot: AnyResourceRef;\n\n if (await tx.checkResourceNameExists(mainRootName))\n mainRoot = await tx.getResourceByName(mainRootName);\n else {\n mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n }\n\n if (this.conf.alternativeRoot === undefined) {\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n } else {\n const aFId = {\n resourceId: mainRoot,\n fieldName: alternativeRootFieldName(this.conf.alternativeRoot),\n };\n\n const altRoot = tx.createEphemeral(ClientRoot);\n tx.lock(altRoot);\n tx.createField(aFId, 'Dynamic');\n tx.setField(aFId, altRoot);\n await tx.commit();\n\n return await altRoot.globalId;\n }\n });\n }\n\n /** Returns true if field existed */\n public async deleteAlternativeRoot(alternativeRootName: string): Promise<boolean> {\n this.checkInitialized();\n if (this._ll.conf.alternativeRoot !== undefined)\n throw new Error('Initialized with alternative root.');\n return await this.withWriteTx('delete-alternative-root', async (tx) => {\n const fId = {\n resourceId: tx.clientRoot,\n fieldName: alternativeRootFieldName(alternativeRootName),\n };\n const exists = tx.fieldExists(fId);\n tx.removeField(fId);\n await tx.commit();\n return await exists;\n });\n }\n\n private async _withTx<T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n ops?: TxOps,\n ): Promise<T> {\n // for exponential / linear backoff\n let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);\n\n while (true) {\n const release = ops?.lockId ? await advisoryLock(ops.lockId) : () => {};\n\n try {\n // opening low-level tx\n const llTx = this._ll.createTx(writable, ops);\n // wrapping it into high-level tx (this also asynchronously sends initialization message)\n const tx = new PlTransaction(\n llTx,\n name,\n writable,\n clientRoot,\n this.finalPredicate,\n this.resourceDataCache,\n );\n\n let ok = false;\n let result: T | undefined = undefined;\n let txId;\n\n try {\n // executing transaction body\n result = await body(tx);\n // collecting stat\n this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);\n ok = true;\n } catch (e: unknown) {\n // the only recoverable\n if (e instanceof TxCommitConflict) {\n // ignoring\n // collecting stat\n this._txConflictStat = addStat(this._txConflictStat, tx.stat);\n } else {\n // collecting stat\n this._txErrorStat = addStat(this._txErrorStat, tx.stat);\n throw e;\n }\n } finally {\n // close underlying grpc stream, if not yet done\n\n // even though we can skip two lines below for read-only transactions,\n // we don't do it to simplify reasoning about what is going on in\n // concurrent code, especially in significant latency situations\n await tx.complete();\n await tx.await();\n\n txId = await tx.getGlobalTxId();\n }\n\n if (ok) {\n // syncing on transaction if requested\n if (ops?.sync === undefined ? this.forceSync : ops?.sync)\n await this._ll.grpcPl.get().txSync({ txId });\n\n // introducing artificial delay, if requested\n if (writable && this.txDelay > 0)\n await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });\n\n return result!;\n }\n } finally {\n release();\n }\n\n // we only get here after TxCommitConflict error,\n // all other errors terminate this loop instantly\n\n await tp.setTimeout(retryState.nextDelay, undefined, { signal: ops?.abortSignal });\n retryState = nextRetryStateOrError(retryState);\n }\n }\n\n private async withTx<T>(\n name: string,\n writable: boolean,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, writable, this.clientRoot, body, { ...ops, ...defaultTxOps });\n }\n\n public async withWriteTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, true, body, { ...ops, ...defaultTxOps });\n }\n\n public async withReadTx<T>(\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n return await this.withTx(name, false, body, { ...ops, ...defaultTxOps });\n }\n\n public getDriver<Drv extends PlDriver>(definition: PlDriverDefinition<Drv>): Drv {\n const attached = this.drivers.get(definition.name);\n if (attached !== undefined) return attached as Drv;\n const driver = definition.init(this, this._ll, this.httpDispatcher);\n this.drivers.set(definition.name, driver);\n return driver;\n }\n\n /** Closes underlying transport */\n public async close() {\n await this._ll.close();\n }\n\n public static async init(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"engines": {
|
|
5
|
-
"node": ">=
|
|
5
|
+
"node": ">=22.19.0"
|
|
6
6
|
},
|
|
7
7
|
"description": "New TS/JS client for Platform API",
|
|
8
8
|
"type": "module",
|
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
"utility-types": "^3.11.0",
|
|
35
35
|
"yaml": "^2.8.0",
|
|
36
36
|
"@milaboratories/pl-http": "1.1.7",
|
|
37
|
-
"@milaboratories/ts-helpers": "1.
|
|
37
|
+
"@milaboratories/ts-helpers": "1.5.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"typescript": "~5.6.3",
|
|
41
|
-
"@types/node": "~
|
|
41
|
+
"@types/node": "~24.5.2",
|
|
42
42
|
"@protobuf-ts/plugin": "2.11.1",
|
|
43
43
|
"@types/http-proxy": "^1.17.16",
|
|
44
44
|
"@types/jest": "^29.5.14",
|
|
45
45
|
"jest": "^29.7.0",
|
|
46
46
|
"@jest/globals": "^29.7.0",
|
|
47
47
|
"ts-jest": "^29.2.6",
|
|
48
|
+
"@milaboratories/build-configs": "1.0.8",
|
|
48
49
|
"@milaboratories/ts-configs": "1.0.6",
|
|
49
|
-
"@milaboratories/ts-builder": "1.0.5"
|
|
50
|
-
"@milaboratories/build-configs": "1.0.8"
|
|
50
|
+
"@milaboratories/ts-builder": "1.0.5"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"type-check": "ts-builder types --target node",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
class AwaitLock {
|
|
2
|
+
private acquired = false;
|
|
3
|
+
private resolvers: (() => void)[] = [];
|
|
4
|
+
|
|
5
|
+
acquireAsync(): Promise<void> {
|
|
6
|
+
if (!this.acquired) {
|
|
7
|
+
this.acquired = true;
|
|
8
|
+
return Promise.resolve();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
this.resolvers.push(resolve);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Releases the lock. Returns true if the lock becomes fully idle (no waiters, not acquired).
|
|
18
|
+
*/
|
|
19
|
+
release(): boolean {
|
|
20
|
+
if (!this.acquired) {
|
|
21
|
+
throw new Error('Cannot release an unacquired lock');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (this.resolvers.length) {
|
|
25
|
+
this.resolvers.shift()?.();
|
|
26
|
+
return false;
|
|
27
|
+
} else {
|
|
28
|
+
this.acquired = false;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const m = new Map<string, AwaitLock>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Acquire a process-local async lock for the given id and return a release function.
|
|
38
|
+
* Ensures only one concurrent operation per id within this process.
|
|
39
|
+
*/
|
|
40
|
+
export async function advisoryLock(id: string) {
|
|
41
|
+
if (!m.has(id)) {
|
|
42
|
+
m.set(id, new AwaitLock());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lock = m.get(id)!;
|
|
46
|
+
await lock.acquireAsync();
|
|
47
|
+
|
|
48
|
+
let released = false;
|
|
49
|
+
return () => {
|
|
50
|
+
if (released) return;
|
|
51
|
+
released = true;
|
|
52
|
+
const nowIdle = lock.release();
|
|
53
|
+
if (nowIdle) m.delete(id);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run the callback under the lock for the given id; releases automatically.
|
|
59
|
+
*/
|
|
60
|
+
export async function advisoryLockCallback<T>(id: string, cb: () => Promise<T>): Promise<T> {
|
|
61
|
+
const release = await advisoryLock(id);
|
|
62
|
+
try {
|
|
63
|
+
return await cb();
|
|
64
|
+
} finally {
|
|
65
|
+
release();
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/core/client.ts
CHANGED
|
@@ -21,10 +21,13 @@ import { DefaultFinalResourceDataPredicate } from './final';
|
|
|
21
21
|
import type { AllTxStat, TxStat } from './stat';
|
|
22
22
|
import { addStat, initialTxStat } from './stat';
|
|
23
23
|
import type { GrpcTransport } from '@protobuf-ts/grpc-transport';
|
|
24
|
+
import { advisoryLock } from './advisory_locks';
|
|
24
25
|
|
|
25
26
|
export type TxOps = PlCallOps & {
|
|
26
27
|
sync?: boolean;
|
|
27
28
|
retryOptions?: RetryOptions;
|
|
29
|
+
name?: string;
|
|
30
|
+
lockId?: string;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
const defaultTxOps = {
|
|
@@ -252,61 +255,67 @@ export class PlClient {
|
|
|
252
255
|
let retryState = createRetryState(ops?.retryOptions ?? this.defaultRetryOptions);
|
|
253
256
|
|
|
254
257
|
while (true) {
|
|
255
|
-
|
|
256
|
-
const llTx = this._ll.createTx(writable, ops);
|
|
257
|
-
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
258
|
-
const tx = new PlTransaction(
|
|
259
|
-
llTx,
|
|
260
|
-
name,
|
|
261
|
-
writable,
|
|
262
|
-
clientRoot,
|
|
263
|
-
this.finalPredicate,
|
|
264
|
-
this.resourceDataCache,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
let ok = false;
|
|
268
|
-
let result: T | undefined = undefined;
|
|
269
|
-
let txId;
|
|
258
|
+
const release = ops?.lockId ? await advisoryLock(ops.lockId) : () => {};
|
|
270
259
|
|
|
271
260
|
try {
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
261
|
+
// opening low-level tx
|
|
262
|
+
const llTx = this._ll.createTx(writable, ops);
|
|
263
|
+
// wrapping it into high-level tx (this also asynchronously sends initialization message)
|
|
264
|
+
const tx = new PlTransaction(
|
|
265
|
+
llTx,
|
|
266
|
+
name,
|
|
267
|
+
writable,
|
|
268
|
+
clientRoot,
|
|
269
|
+
this.finalPredicate,
|
|
270
|
+
this.resourceDataCache,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
let ok = false;
|
|
274
|
+
let result: T | undefined = undefined;
|
|
275
|
+
let txId;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// executing transaction body
|
|
279
|
+
result = await body(tx);
|
|
281
280
|
// collecting stat
|
|
282
|
-
this.
|
|
283
|
-
|
|
281
|
+
this._txCommittedStat = addStat(this._txCommittedStat, tx.stat);
|
|
282
|
+
ok = true;
|
|
283
|
+
} catch (e: unknown) {
|
|
284
|
+
// the only recoverable
|
|
285
|
+
if (e instanceof TxCommitConflict) {
|
|
286
|
+
// ignoring
|
|
287
|
+
// collecting stat
|
|
288
|
+
this._txConflictStat = addStat(this._txConflictStat, tx.stat);
|
|
289
|
+
} else {
|
|
284
290
|
// collecting stat
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
this._txErrorStat = addStat(this._txErrorStat, tx.stat);
|
|
292
|
+
throw e;
|
|
293
|
+
}
|
|
294
|
+
} finally {
|
|
295
|
+
// close underlying grpc stream, if not yet done
|
|
296
|
+
|
|
297
|
+
// even though we can skip two lines below for read-only transactions,
|
|
298
|
+
// we don't do it to simplify reasoning about what is going on in
|
|
299
|
+
// concurrent code, especially in significant latency situations
|
|
300
|
+
await tx.complete();
|
|
301
|
+
await tx.await();
|
|
302
|
+
|
|
303
|
+
txId = await tx.getGlobalTxId();
|
|
287
304
|
}
|
|
288
|
-
} finally {
|
|
289
|
-
// close underlying grpc stream, if not yet done
|
|
290
|
-
|
|
291
|
-
// even though we can skip two lines below for read-only transactions,
|
|
292
|
-
// we don't do it to simplify reasoning about what is going on in
|
|
293
|
-
// concurrent code, especially in significant latency situations
|
|
294
|
-
await tx.complete();
|
|
295
|
-
await tx.await();
|
|
296
|
-
|
|
297
|
-
txId = await tx.getGlobalTxId();
|
|
298
|
-
}
|
|
299
305
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
if (ok) {
|
|
307
|
+
// syncing on transaction if requested
|
|
308
|
+
if (ops?.sync === undefined ? this.forceSync : ops?.sync)
|
|
309
|
+
await this._ll.grpcPl.get().txSync({ txId });
|
|
304
310
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
// introducing artificial delay, if requested
|
|
312
|
+
if (writable && this.txDelay > 0)
|
|
313
|
+
await tp.setTimeout(this.txDelay, undefined, { signal: ops?.abortSignal });
|
|
308
314
|
|
|
309
|
-
|
|
315
|
+
return result!;
|
|
316
|
+
}
|
|
317
|
+
} finally {
|
|
318
|
+
release();
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
// we only get here after TxCommitConflict error,
|