@milaboratories/pl-client 3.2.5 → 3.3.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.
Files changed (112) hide show
  1. package/dist/core/client.cjs +24 -56
  2. package/dist/core/client.cjs.map +1 -1
  3. package/dist/core/client.d.ts +12 -8
  4. package/dist/core/client.d.ts.map +1 -1
  5. package/dist/core/client.js +26 -58
  6. package/dist/core/client.js.map +1 -1
  7. package/dist/core/errors.cjs +20 -0
  8. package/dist/core/errors.cjs.map +1 -1
  9. package/dist/core/errors.d.ts +6 -1
  10. package/dist/core/errors.d.ts.map +1 -1
  11. package/dist/core/errors.js +19 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/final.cjs +6 -5
  14. package/dist/core/final.cjs.map +1 -1
  15. package/dist/core/final.d.ts.map +1 -1
  16. package/dist/core/final.js +7 -6
  17. package/dist/core/final.js.map +1 -1
  18. package/dist/core/ll_client.cjs +18 -1
  19. package/dist/core/ll_client.cjs.map +1 -1
  20. package/dist/core/ll_client.d.ts +6 -2
  21. package/dist/core/ll_client.d.ts.map +1 -1
  22. package/dist/core/ll_client.js +19 -2
  23. package/dist/core/ll_client.js.map +1 -1
  24. package/dist/core/transaction.cjs +109 -75
  25. package/dist/core/transaction.cjs.map +1 -1
  26. package/dist/core/transaction.d.ts +30 -22
  27. package/dist/core/transaction.d.ts.map +1 -1
  28. package/dist/core/transaction.js +111 -76
  29. package/dist/core/transaction.js.map +1 -1
  30. package/dist/core/type_conversion.cjs +14 -6
  31. package/dist/core/type_conversion.cjs.map +1 -1
  32. package/dist/core/type_conversion.js +14 -6
  33. package/dist/core/type_conversion.js.map +1 -1
  34. package/dist/core/types.cjs +77 -17
  35. package/dist/core/types.cjs.map +1 -1
  36. package/dist/core/types.d.ts +49 -26
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/core/types.js +66 -14
  39. package/dist/core/types.js.map +1 -1
  40. package/dist/core/user_resources.cjs +181 -0
  41. package/dist/core/user_resources.cjs.map +1 -0
  42. package/dist/core/user_resources.d.ts +75 -0
  43. package/dist/core/user_resources.d.ts.map +1 -0
  44. package/dist/core/user_resources.js +180 -0
  45. package/dist/core/user_resources.js.map +1 -0
  46. package/dist/helpers/poll.cjs +4 -4
  47. package/dist/helpers/poll.cjs.map +1 -1
  48. package/dist/helpers/poll.d.ts +3 -3
  49. package/dist/helpers/poll.d.ts.map +1 -1
  50. package/dist/helpers/poll.js +5 -5
  51. package/dist/helpers/poll.js.map +1 -1
  52. package/dist/helpers/tx_helpers.cjs +1 -1
  53. package/dist/helpers/tx_helpers.cjs.map +1 -1
  54. package/dist/helpers/tx_helpers.d.ts +3 -3
  55. package/dist/helpers/tx_helpers.d.ts.map +1 -1
  56. package/dist/helpers/tx_helpers.js +2 -2
  57. package/dist/helpers/tx_helpers.js.map +1 -1
  58. package/dist/index.cjs +16 -5
  59. package/dist/index.d.ts +5 -4
  60. package/dist/index.js +5 -4
  61. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +724 -188
  62. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
  63. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +34 -9
  64. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
  65. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +37 -5
  66. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
  67. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +34 -9
  68. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
  69. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +326 -136
  70. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
  71. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +724 -188
  72. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
  73. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs +18 -7
  74. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs.map +1 -1
  75. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +11 -7
  76. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
  77. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js +18 -7
  78. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js.map +1 -1
  79. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs +57 -2
  80. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs.map +1 -1
  81. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts +26 -3
  82. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
  83. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js +57 -3
  84. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js.map +1 -1
  85. package/dist/proto-rest/plapi.d.ts +421 -207
  86. package/dist/proto-rest/plapi.d.ts.map +1 -1
  87. package/dist/test/test_config.cjs +6 -3
  88. package/dist/test/test_config.cjs.map +1 -1
  89. package/dist/test/test_config.d.ts.map +1 -1
  90. package/dist/test/test_config.js +7 -4
  91. package/dist/test/test_config.js.map +1 -1
  92. package/package.json +4 -4
  93. package/src/core/client.ts +58 -103
  94. package/src/core/errors.ts +23 -0
  95. package/src/core/final.ts +16 -6
  96. package/src/core/ll_client.ts +39 -3
  97. package/src/core/ll_transaction.test.ts +41 -6
  98. package/src/core/transaction.ts +176 -86
  99. package/src/core/type_conversion.ts +24 -9
  100. package/src/core/types.ts +147 -41
  101. package/src/core/user_resources.ts +332 -0
  102. package/src/helpers/poll.ts +15 -8
  103. package/src/helpers/state_helpers.ts +2 -2
  104. package/src/helpers/tx_helpers.ts +5 -5
  105. package/src/index.ts +1 -0
  106. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +61 -14
  107. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +1045 -379
  108. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +33 -18
  109. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts +75 -6
  110. package/src/proto-grpc/google/protobuf/descriptor.ts +5 -2
  111. package/src/proto-rest/plapi.ts +447 -225
  112. package/src/test/test_config.ts +8 -5
@@ -1,7 +1,6 @@
1
1
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
2
  const require_types = require("./types.cjs");
3
3
  const require_api = require("../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs");
4
- const require_errors = require("./errors.cjs");
5
4
  const require_stat = require("./stat.cjs");
6
5
  const require_transaction = require("./transaction.cjs");
7
6
  const require_pl = require("../helpers/pl.cjs");
@@ -9,14 +8,13 @@ const require_config = require("./config.cjs");
9
8
  const require_ll_client = require("./ll_client.cjs");
10
9
  const require_final = require("./final.cjs");
11
10
  const require_advisory_locks = require("./advisory_locks.cjs");
11
+ const require_user_resources = require("./user_resources.cjs");
12
12
  let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
13
- let node_crypto = require("node:crypto");
14
13
  let node_timers_promises = require("node:timers/promises");
15
14
  node_timers_promises = require_runtime.__toESM(node_timers_promises);
16
15
  let lru_cache = require("lru-cache");
17
16
  //#region src/core/client.ts
18
17
  const defaultTxOps = { sync: false };
19
- const AnonymousClientRoot = "AnonymousRoot";
20
18
  function alternativeRootFieldName(alternativeRoot) {
21
19
  return `alternative_root_${alternativeRoot}`;
22
20
  }
@@ -37,8 +35,9 @@ var PlClient = class PlClient {
37
35
  return this._ll;
38
36
  }
39
37
  /** Stores client root (this abstraction is intended for future implementation of the security model) */
40
- _clientRoot = require_types.NullResourceId;
38
+ _clientRoot = "";
41
39
  _serverInfo = void 0;
40
+ _userResources;
42
41
  _txCommittedStat = require_stat.initialTxStat();
43
42
  _txConflictStat = require_stat.initialTxStat();
44
43
  _txErrorStat = require_stat.initialTxStat();
@@ -111,17 +110,13 @@ var PlClient = class PlClient {
111
110
  return await this.ll.license();
112
111
  }
113
112
  /**
114
- * Returns the user root ResourceId via ListUserResources.
113
+ * Returns the user root SignedResourceId via ListUserResources.
115
114
  * @param opts.login - target user login; omit for the authenticated user.
116
- * @returns ResourceId of the user root, or undefined if the server returned no userRoot entry.
115
+ * @returns SignedResourceId of the user root, or undefined if the server returned no userRoot entry.
117
116
  * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).
118
117
  */
119
118
  async getUserRoot(opts = {}) {
120
- const responses = await this.ll.listUserResources({
121
- login: opts.login,
122
- limit: 1
123
- });
124
- for (const msg of responses) if (msg.entry.oneofKind === "userRoot") return require_types.bigintToResourceId(msg.entry.userRoot.resourceId);
119
+ return this.userResources.getUserRoot(opts);
125
120
  }
126
121
  get conf() {
127
122
  return this.ll.conf;
@@ -133,22 +128,27 @@ var PlClient = class PlClient {
133
128
  return this.ll.wireConnection;
134
129
  }
135
130
  get initialized() {
136
- return !require_types.isNullResourceId(this._clientRoot);
131
+ return !require_types.isNullSignedResourceId(this._clientRoot);
137
132
  }
138
133
  checkInitialized() {
139
134
  if (!this.initialized) throw new Error("Client not initialized");
140
135
  }
141
136
  get clientRoot() {
142
137
  this.checkInitialized();
143
- return require_types.ensureResourceIdNotNull(this._clientRoot);
138
+ return require_types.ensureSignedResourceIdNotNull(this._clientRoot);
144
139
  }
145
140
  get serverInfo() {
146
141
  this.checkInitialized();
147
142
  return this._serverInfo;
148
143
  }
149
- /** Discovers or creates the user's root resource.
150
- * Tries ListUserResources RPC first (new backend), falls back to
151
- * legacy named-resource lookup for older backends. */
144
+ /** User resources index for discovering data libraries and other shared resources. */
145
+ get userResources() {
146
+ if (!this._ll) throw new Error("Client not initialized");
147
+ if (!this._userResources) this._userResources = new require_user_resources.UserResources(this._ll, this._withTx.bind(this), this._ll.authUser);
148
+ return this._userResources;
149
+ }
150
+ /** Discovers or creates the user's root resource via UserResources,
151
+ * then handles alternativeRoot if configured. */
152
152
  async init() {
153
153
  if (this.initialized) throw new Error("Already initialized");
154
154
  this._ll = await this.buildLLPlClient(false);
@@ -158,20 +158,11 @@ var PlClient = class PlClient {
158
158
  await this._ll.close();
159
159
  this._ll = await this.buildLLPlClient(true, wireProtocol);
160
160
  }
161
- let rootFromServer;
162
- try {
163
- const responses = await this._ll.listUserResources({ limit: 1 });
164
- for (const msg of responses) if (msg.entry.oneofKind === "userRoot") {
165
- rootFromServer = require_types.bigintToResourceId(msg.entry.userRoot.resourceId);
166
- break;
167
- }
168
- } catch (err) {
169
- if (!require_errors.isUnimplementedError(err)) throw err;
170
- }
171
- if (rootFromServer !== void 0) if (this.conf.alternativeRoot === void 0) this._clientRoot = rootFromServer;
172
- else this._clientRoot = await this._withTx("initialization", true, rootFromServer, async (tx) => {
161
+ const userRoot = await this.userResources.getUserRoot();
162
+ if (this.conf.alternativeRoot === void 0) this._clientRoot = userRoot;
163
+ else this._clientRoot = await this._withTx("initialization", true, userRoot, async (tx) => {
173
164
  const aFId = {
174
- resourceId: tx.clientRoot,
165
+ resourceId: userRoot,
175
166
  fieldName: alternativeRootFieldName(this.conf.alternativeRoot)
176
167
  };
177
168
  const altRoot = tx.createEphemeral(require_pl.ClientRoot);
@@ -181,33 +172,6 @@ var PlClient = class PlClient {
181
172
  await tx.commit();
182
173
  return await altRoot.globalId;
183
174
  });
184
- else {
185
- const user = this._ll.authUser;
186
- const mainRootName = user === null ? AnonymousClientRoot : (0, node_crypto.createHash)("sha256").update(user).digest("hex");
187
- this._clientRoot = await this._withTx("initialization", true, require_types.NullResourceId, async (tx) => {
188
- let mainRoot;
189
- if (await tx.checkResourceNameExists(mainRootName)) mainRoot = await tx.getResourceByName(mainRootName);
190
- else {
191
- mainRoot = tx.createRoot(require_pl.ClientRoot);
192
- tx.setResourceName(mainRootName, mainRoot);
193
- }
194
- if (this.conf.alternativeRoot === void 0) {
195
- await tx.commit();
196
- return await require_transaction.toGlobalResourceId(mainRoot);
197
- } else {
198
- const aFId = {
199
- resourceId: mainRoot,
200
- fieldName: alternativeRootFieldName(this.conf.alternativeRoot)
201
- };
202
- const altRoot = tx.createEphemeral(require_pl.ClientRoot);
203
- tx.lock(altRoot);
204
- tx.createField(aFId, "Dynamic");
205
- tx.setField(aFId, altRoot);
206
- await tx.commit();
207
- return await altRoot.globalId;
208
- }
209
- });
210
- }
211
175
  }
212
176
  /** Returns true if field existed */
213
177
  async deleteAlternativeRoot(alternativeRootName) {
@@ -230,6 +194,10 @@ var PlClient = class PlClient {
230
194
  const release = ops?.lockId ? await require_advisory_locks.advisoryLock(ops.lockId) : () => {};
231
195
  try {
232
196
  const tx = new require_transaction.PlTransaction(this.ll.createTx(writable, ops), name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
197
+ if (!require_types.isNullSignedResourceId(clientRoot) && writable) {
198
+ const parsed = require_types.parseSignedResourceId(clientRoot);
199
+ if (parsed.signature) tx.setDefaultColor(parsed.signature);
200
+ }
233
201
  let ok = false;
234
202
  let result = void 0;
235
203
  let txId;
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["NullResourceId","initialTxStat","plAddressToConfig","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","addStat","bigintToResourceId","isNullResourceId","ensureResourceIdNotNull","MaintenanceAPI_Ping_Response_Compression","isUnimplementedError","ClientRoot","toGlobalResourceId","advisoryLock","PlTransaction","TxCommitConflict","tp"],"sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener, wireProtocol } 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 {\n bigintToResourceId,\n ensureResourceIdNotNull,\n isNullResourceId,\n NullResourceId,\n} from \"./types\";\nimport { ClientRoot } from \"../helpers/pl\";\nimport { isUnimplementedError } from \"./errors\";\nimport type { MiLogger, RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { assertNever, createRetryState, nextRetryStateOrError } from \"@milaboratories/ts-helpers\";\nimport type { PlDriver, PlDriverDefinition } from \"./driver\";\nimport type {\n MaintenanceAPI_Ping_Response,\n MaintenanceAPI_License_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { MaintenanceAPI_Ping_Response_Compression } from \"../proto-grpc/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 { WireConnection } from \"./wire\";\nimport { advisoryLock } from \"./advisory_locks\";\nimport { plAddressToConfig } from \"./config\";\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: (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ) => Promise<LLPlClient>;\n private _ll?: LLPlClient;\n\n private get ll(): LLPlClient {\n if (this._ll === undefined) {\n throw new Error(\"LLPlClient not initialized\");\n }\n return this._ll;\n }\n\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 logger?: MiLogger;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n this.buildLLPlClient = async (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ): Promise<LLPlClient> => {\n if (wireProtocol) conf.wireProtocol = wireProtocol;\n return await LLPlClient.build(conf, { auth, ...ops, shouldUseGzip });\n };\n\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.ping();\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return await this.ll.license();\n }\n\n /**\n * Returns the user root ResourceId via ListUserResources.\n * @param opts.login - target user login; omit for the authenticated user.\n * @returns ResourceId of the user root, or undefined if the server returned no userRoot entry.\n * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).\n */\n public async getUserRoot(opts: { login?: string } = {}): Promise<ResourceId | undefined> {\n const responses = await this.ll.listUserResources({\n login: opts.login,\n limit: 1,\n });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return bigintToResourceId(msg.entry.userRoot.resourceId);\n }\n }\n return undefined;\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 connectionOpts(): WireConnection {\n return this.ll.wireConnection;\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 /** Discovers or creates the user's root resource.\n * Tries ListUserResources RPC first (new backend), falls back to\n * legacy named-resource lookup for older backends. */\n private async init() {\n if (this.initialized) throw new Error(\"Already initialized\");\n\n // Initial client is created without gzip to perform server ping and detect optimal wire protocol.\n // LLPlClient.build() internally calls detectOptimalWireProtocol() which starts with default 'grpc',\n // then retries with 'rest' if ping fails, alternating until a working protocol is found.\n // We save the detected wireProtocol here because if the server supports gzip compression,\n // we'll need to reinitialize the client with gzip enabled - passing the already-detected\n // wireProtocol avoids redundant protocol detection on reinit.\n this._ll = await this.buildLLPlClient(false);\n const wireProtocol = this._ll.wireProtocol;\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 = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Try ListUserResources first (new backend, gRPC only)\n let rootFromServer: ResourceId | undefined;\n try {\n const responses = await this._ll.listUserResources({ limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId);\n break;\n }\n }\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n // Backend doesn't support ListUserResources — fall through to legacy\n }\n\n if (rootFromServer !== undefined) {\n // New path: server created/returned the root\n if (this.conf.alternativeRoot === undefined) {\n this._clientRoot = rootFromServer;\n } else {\n this._clientRoot = await this._withTx(\n \"initialization\",\n true,\n rootFromServer,\n async (tx) => {\n const aFId = {\n resourceId: tx.clientRoot,\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 } else {\n // Legacy path: named resource lookup\n const user = this._ll.authUser;\n const mainRootName =\n user === null ? AnonymousClientRoot : createHash(\"sha256\").update(user).digest(\"hex\");\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\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) await this.ll.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 logger?: MiLogger;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.cjs","names":["initialTxStat","plAddressToConfig","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","addStat","isNullSignedResourceId","ensureSignedResourceIdNotNull","UserResources","MaintenanceAPI_Ping_Response_Compression","ClientRoot","advisoryLock","PlTransaction","parseSignedResourceId","TxCommitConflict","tp"],"sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener, wireProtocol } from \"./config\";\nimport type { PlCallOps } from \"./ll_client\";\nimport { LLPlClient } from \"./ll_client\";\nimport { PlTransaction, TxCommitConflict } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId } from \"./types\";\nimport {\n ensureSignedResourceIdNotNull,\n isNullSignedResourceId,\n NullSignedResourceId,\n parseSignedResourceId,\n} from \"./types\";\nimport type { ColorProof } from \"./types\";\nimport { ClientRoot } from \"../helpers/pl\";\nimport type { MiLogger, RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { assertNever, createRetryState, nextRetryStateOrError } from \"@milaboratories/ts-helpers\";\nimport type { PlDriver, PlDriverDefinition } from \"./driver\";\nimport type {\n MaintenanceAPI_Ping_Response,\n MaintenanceAPI_License_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { MaintenanceAPI_Ping_Response_Compression } from \"../proto-grpc/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 { WireConnection } from \"./wire\";\nimport { advisoryLock } from \"./advisory_locks\";\nimport { plAddressToConfig } from \"./config\";\nimport { UserResources } from \"./user_resources\";\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\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: (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ) => Promise<LLPlClient>;\n private _ll?: LLPlClient;\n\n private get ll(): LLPlClient {\n if (this._ll === undefined) {\n throw new Error(\"LLPlClient not initialized\");\n }\n return this._ll;\n }\n\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalSignedResourceId = NullSignedResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _userResources?: UserResources;\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<SignedResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n logger?: MiLogger;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n this.buildLLPlClient = async (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ): Promise<LLPlClient> => {\n if (wireProtocol) conf.wireProtocol = wireProtocol;\n return await LLPlClient.build(conf, { auth, ...ops, shouldUseGzip });\n };\n\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.ping();\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return await this.ll.license();\n }\n\n /**\n * Returns the user root SignedResourceId via ListUserResources.\n * @param opts.login - target user login; omit for the authenticated user.\n * @returns SignedResourceId of the user root, or undefined if the server returned no userRoot entry.\n * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).\n */\n public async getUserRoot(\n opts: { login?: string; doNotCreate?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n return this.userResources.getUserRoot(opts);\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 connectionOpts(): WireConnection {\n return this.ll.wireConnection;\n }\n\n private get initialized() {\n return !isNullSignedResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error(\"Client not initialized\");\n }\n\n public get clientRoot(): SignedResourceId {\n this.checkInitialized();\n return ensureSignedResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** User resources index for discovering data libraries and other shared resources. */\n public get userResources(): UserResources {\n if (!this._ll) throw new Error(\"Client not initialized\");\n\n if (!this._userResources) {\n this._userResources = new UserResources(this._ll, this._withTx.bind(this), this._ll.authUser);\n }\n\n return this._userResources;\n }\n\n /** Discovers or creates the user's root resource via UserResources,\n * then handles alternativeRoot if configured. */\n private async init() {\n if (this.initialized) throw new Error(\"Already initialized\");\n\n // Initial client is created without gzip to perform server ping and detect optimal wire protocol.\n // LLPlClient.build() internally calls detectOptimalWireProtocol() which starts with default 'grpc',\n // then retries with 'rest' if ping fails, alternating until a working protocol is found.\n // We save the detected wireProtocol here because if the server supports gzip compression,\n // we'll need to reinitialize the client with gzip enabled - passing the already-detected\n // wireProtocol avoids redundant protocol detection on reinit.\n this._ll = await this.buildLLPlClient(false);\n const wireProtocol = this._ll.wireProtocol;\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 = await this.buildLLPlClient(true, wireProtocol);\n }\n\n const userRoot = await this.userResources.getUserRoot();\n\n if (this.conf.alternativeRoot === undefined) {\n this._clientRoot = userRoot;\n } else {\n this._clientRoot = await this._withTx(\"initialization\", true, userRoot, async (tx) => {\n const aFId = {\n resourceId: userRoot,\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: OptionalSignedResourceId,\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 // Auto-set default color proof from the client root's signature\n if (!isNullSignedResourceId(clientRoot) && writable) {\n const parsed = parseSignedResourceId(clientRoot);\n if (parsed.signature) {\n tx.setDefaultColor(parsed.signature as ColorProof);\n }\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) await this.ll.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 logger?: MiLogger;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ResourceId } from "./types.js";
1
+ import { SignedResourceId } from "./types.js";
2
2
  import { MaintenanceAPI_License_Response, MaintenanceAPI_Ping_Response } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js";
3
3
  import { FinalResourceDataPredicate } from "./final.js";
4
4
  import { AllTxStat, TxStat } from "./stat.js";
@@ -7,6 +7,7 @@ import { AuthOps, PlClientConfig, PlConnectionStatusListener } from "./config.js
7
7
  import { WireConnection } from "./wire.js";
8
8
  import { PlCallOps } from "./ll_client.js";
9
9
  import { PlDriver, PlDriverDefinition } from "./driver.js";
10
+ import { UserResources } from "./user_resources.js";
10
11
  import { MiLogger, RetryOptions } from "@milaboratories/ts-helpers";
11
12
  import { Dispatcher } from "undici";
12
13
 
@@ -33,6 +34,7 @@ declare class PlClient {
33
34
  /** Stores client root (this abstraction is intended for future implementation of the security model) */
34
35
  private _clientRoot;
35
36
  private _serverInfo;
37
+ private _userResources?;
36
38
  private _txCommittedStat;
37
39
  private _txConflictStat;
38
40
  private _txErrorStat;
@@ -49,24 +51,26 @@ declare class PlClient {
49
51
  ping(): Promise<MaintenanceAPI_Ping_Response>;
50
52
  license(): Promise<MaintenanceAPI_License_Response>;
51
53
  /**
52
- * Returns the user root ResourceId via ListUserResources.
54
+ * Returns the user root SignedResourceId via ListUserResources.
53
55
  * @param opts.login - target user login; omit for the authenticated user.
54
- * @returns ResourceId of the user root, or undefined if the server returned no userRoot entry.
56
+ * @returns SignedResourceId of the user root, or undefined if the server returned no userRoot entry.
55
57
  * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).
56
58
  */
57
59
  getUserRoot(opts?: {
58
60
  login?: string;
59
- }): Promise<ResourceId | undefined>;
61
+ doNotCreate?: boolean;
62
+ }): Promise<SignedResourceId | undefined>;
60
63
  get conf(): PlClientConfig;
61
64
  get httpDispatcher(): Dispatcher;
62
65
  get connectionOpts(): WireConnection;
63
66
  private get initialized();
64
67
  private checkInitialized;
65
- get clientRoot(): ResourceId;
68
+ get clientRoot(): SignedResourceId;
66
69
  get serverInfo(): MaintenanceAPI_Ping_Response;
67
- /** Discovers or creates the user's root resource.
68
- * Tries ListUserResources RPC first (new backend), falls back to
69
- * legacy named-resource lookup for older backends. */
70
+ /** User resources index for discovering data libraries and other shared resources. */
71
+ get userResources(): UserResources;
72
+ /** Discovers or creates the user's root resource via UserResources,
73
+ * then handles alternativeRoot if configured. */
70
74
  private init;
71
75
  /** Returns true if field existed */
72
76
  deleteAlternativeRoot(alternativeRootName: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/core/client.ts"],"mappings":";;;;;;;;;;;;;KAmCY,KAAA,GAAQ,SAAA;EAClB,IAAA;EACA,YAAA,GAAe,YAAA;EACf,IAAA;EACA,MAAA;AAAA;;cAcW,QAAA;EAAA,iBACM,OAAA;EAjBF;;EAAA,iBAqBE,OAAA;EAnBX;EAAA,iBAsBW,SAAA;EARN;EAAA,iBAWM,mBAAA;EAAA,iBAEA,eAAA;EAAA,QAIT,GAAA;EAAA,YAEI,EAAA,CAAA;EAiFiB;EAAA,QAzErB,WAAA;EAAA,QAEA,WAAA;EAAA,QAEA,gBAAA;EAAA,QACA,eAAA;EAAA,QACA,YAAA;EA2FwB;EAAA,SApFhB,cAAA,EAAgB,0BAAA;EA8FiC;EAAA,iBA3FhD,iBAAA;EAAA,QAEV,WAAA,CAAA;EAAA,IAmDI,eAAA,CAAA,GAAmB,MAAA;EAAA,IAInB,cAAA,CAAA,GAAkB,MAAA;EAAA,IAIlB,WAAA,CAAA,GAAe,MAAA;EAAA,IAIf,WAAA,CAAA,GAAe,MAAA;EAAA,IAIf,SAAA,CAAA,GAAa,SAAA;EAQX,IAAA,CAAA,GAAQ,OAAA,CAAQ,4BAAA;EAIhB,OAAA,CAAA,GAAW,OAAA,CAAQ,+BAAA;EAqQD;;;;;;EA3PlB,WAAA,CAAY,IAAA;IAAQ,KAAA;EAAA,IAAwB,OAAA,CAAQ,UAAA;EAAA,IAatD,IAAA,CAAA,GAAQ,cAAA;EAAA,IAIR,cAAA,CAAA,GAAkB,UAAA;EAAA,IAIlB,cAAA,CAAA,GAAkB,cAAA;EAAA,YAIjB,WAAA,CAAA;EAAA,QAIJ,gBAAA;EAAA,IAIG,UAAA,CAAA,GAAc,UAAA;EAAA,IAKd,UAAA,CAAA,GAAc,4BAAA;EA4OP;;;EAAA,QApOJ,IAAA;EA6OD;EA7IA,qBAAA,CAAsB,mBAAA,WAA8B,OAAA;EAAA,QAgBnD,OAAA;EAAA,QAiFA,MAAA;EAUD,WAAA,GAAA,CACX,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EAIE,UAAA,GAAA,CACX,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EAIJ,SAAA,aAAsB,QAAA,CAAA,CAAU,UAAA,EAAY,kBAAA,CAAmB,GAAA,IAAO,GAAA;EAvY5D;EAgZJ,KAAA,CAAA,GAAK,OAAA;EAAA,OAIE,IAAA,CAClB,eAAA,EAAiB,cAAA,WACjB,IAAA,EAAM,OAAA,EACN,GAAA;IACE,cAAA,GAAiB,0BAAA;IACjB,MAAA,GAAS,QAAA;EAAA,IACL,OAAA,CAAA,QAAA;AAAA"}
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/core/client.ts"],"mappings":";;;;;;;;;;;;;;KAkCY,KAAA,GAAQ,SAAA;EAClB,IAAA;EACA,YAAA,GAAe,YAAA;EACf,IAAA;EACA,MAAA;AAAA;;cAYW,QAAA;EAAA,iBACM,OAAA;EAfF;;EAAA,iBAmBE,OAAA;EAjBX;EAAA,iBAoBW,SAAA;EARN;EAAA,iBAWM,mBAAA;EAAA,iBAEA,eAAA;EAAA,QAIT,GAAA;EAAA,YAEI,EAAA,CAAA;EAmFiB;EAAA,QA3ErB,WAAA;EAAA,QAEA,WAAA;EAAA,QAEA,cAAA;EAAA,QAEA,gBAAA;EAAA,QACA,eAAA;EAAA,QACA,YAAA;EA2FgB;EAAA,SApFR,cAAA,EAAgB,0BAAA;EAgG7B;EAAA,iBA7Fc,iBAAA;EAAA,QAEV,WAAA,CAAA;EAAA,IAmDI,eAAA,CAAA,GAAmB,MAAA;EAAA,IAInB,cAAA,CAAA,GAAkB,MAAA;EAAA,IAIlB,WAAA,CAAA,GAAe,MAAA;EAAA,IAIf,WAAA,CAAA,GAAe,MAAA;EAAA,IAIf,SAAA,CAAA,GAAa,SAAA;EAQX,IAAA,CAAA,GAAQ,OAAA,CAAQ,4BAAA;EAIhB,OAAA,CAAA,GAAW,OAAA,CAAQ,+BAAA;EAyND;;;;;;EA/MlB,WAAA,CACX,IAAA;IAAQ,KAAA;IAAgB,WAAA;EAAA,IACvB,OAAA,CAAQ,gBAAA;EAAA,IAIA,IAAA,CAAA,GAAQ,cAAA;EAAA,IAIR,cAAA,CAAA,GAAkB,UAAA;EAAA,IAIlB,cAAA,CAAA,GAAkB,cAAA;EAAA,YAIjB,WAAA,CAAA;EAAA,QAIJ,gBAAA;EAAA,IAIG,UAAA,CAAA,GAAc,gBAAA;EAAA,IAKd,UAAA,CAAA,GAAc,4BAAA;EA4MN;EAAA,IAtMR,aAAA,CAAA,GAAiB,aAAA;EAyMP;;EAAA,QA7LP,IAAA;EA+LN;EAtJK,qBAAA,CAAsB,mBAAA,WAA8B,OAAA;EAAA,QAgBnD,OAAA;EAAA,QAyFA,MAAA;EAUD,WAAA,GAAA,CACX,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EAIE,UAAA,GAAA,CACX,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EAIJ,SAAA,aAAsB,QAAA,CAAA,CAAU,UAAA,EAAY,kBAAA,CAAmB,GAAA,IAAO,GAAA;EAxV5D;EAiWJ,KAAA,CAAA,GAAK,OAAA;EAAA,OAIE,IAAA,CAClB,eAAA,EAAiB,cAAA,WACjB,IAAA,EAAM,OAAA,EACN,GAAA;IACE,cAAA,GAAiB,0BAAA;IACjB,MAAA,GAAS,QAAA;EAAA,IACL,OAAA,CAAA,QAAA;AAAA"}
@@ -1,20 +1,18 @@
1
- import { NullResourceId, bigintToResourceId, ensureResourceIdNotNull, isNullResourceId } from "./types.js";
1
+ import { ensureSignedResourceIdNotNull, isNullSignedResourceId, parseSignedResourceId } from "./types.js";
2
2
  import { MaintenanceAPI_Ping_Response_Compression } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js";
3
- import { isUnimplementedError } from "./errors.js";
4
3
  import { addStat, initialTxStat } from "./stat.js";
5
- import { PlTransaction, TxCommitConflict, toGlobalResourceId } from "./transaction.js";
4
+ import { PlTransaction, TxCommitConflict } from "./transaction.js";
6
5
  import { ClientRoot } from "../helpers/pl.js";
7
6
  import { plAddressToConfig } from "./config.js";
8
7
  import { LLPlClient } from "./ll_client.js";
9
8
  import { DefaultFinalResourceDataPredicate } from "./final.js";
10
9
  import { advisoryLock } from "./advisory_locks.js";
10
+ import { UserResources } from "./user_resources.js";
11
11
  import { assertNever, createRetryState, nextRetryStateOrError } from "@milaboratories/ts-helpers";
12
- import { createHash } from "node:crypto";
13
12
  import * as tp from "node:timers/promises";
14
13
  import { LRUCache } from "lru-cache";
15
14
  //#region src/core/client.ts
16
15
  const defaultTxOps = { sync: false };
17
- const AnonymousClientRoot = "AnonymousRoot";
18
16
  function alternativeRootFieldName(alternativeRoot) {
19
17
  return `alternative_root_${alternativeRoot}`;
20
18
  }
@@ -35,8 +33,9 @@ var PlClient = class PlClient {
35
33
  return this._ll;
36
34
  }
37
35
  /** Stores client root (this abstraction is intended for future implementation of the security model) */
38
- _clientRoot = NullResourceId;
36
+ _clientRoot = "";
39
37
  _serverInfo = void 0;
38
+ _userResources;
40
39
  _txCommittedStat = initialTxStat();
41
40
  _txConflictStat = initialTxStat();
42
41
  _txErrorStat = initialTxStat();
@@ -109,17 +108,13 @@ var PlClient = class PlClient {
109
108
  return await this.ll.license();
110
109
  }
111
110
  /**
112
- * Returns the user root ResourceId via ListUserResources.
111
+ * Returns the user root SignedResourceId via ListUserResources.
113
112
  * @param opts.login - target user login; omit for the authenticated user.
114
- * @returns ResourceId of the user root, or undefined if the server returned no userRoot entry.
113
+ * @returns SignedResourceId of the user root, or undefined if the server returned no userRoot entry.
115
114
  * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).
116
115
  */
117
116
  async getUserRoot(opts = {}) {
118
- const responses = await this.ll.listUserResources({
119
- login: opts.login,
120
- limit: 1
121
- });
122
- for (const msg of responses) if (msg.entry.oneofKind === "userRoot") return bigintToResourceId(msg.entry.userRoot.resourceId);
117
+ return this.userResources.getUserRoot(opts);
123
118
  }
124
119
  get conf() {
125
120
  return this.ll.conf;
@@ -131,22 +126,27 @@ var PlClient = class PlClient {
131
126
  return this.ll.wireConnection;
132
127
  }
133
128
  get initialized() {
134
- return !isNullResourceId(this._clientRoot);
129
+ return !isNullSignedResourceId(this._clientRoot);
135
130
  }
136
131
  checkInitialized() {
137
132
  if (!this.initialized) throw new Error("Client not initialized");
138
133
  }
139
134
  get clientRoot() {
140
135
  this.checkInitialized();
141
- return ensureResourceIdNotNull(this._clientRoot);
136
+ return ensureSignedResourceIdNotNull(this._clientRoot);
142
137
  }
143
138
  get serverInfo() {
144
139
  this.checkInitialized();
145
140
  return this._serverInfo;
146
141
  }
147
- /** Discovers or creates the user's root resource.
148
- * Tries ListUserResources RPC first (new backend), falls back to
149
- * legacy named-resource lookup for older backends. */
142
+ /** User resources index for discovering data libraries and other shared resources. */
143
+ get userResources() {
144
+ if (!this._ll) throw new Error("Client not initialized");
145
+ if (!this._userResources) this._userResources = new UserResources(this._ll, this._withTx.bind(this), this._ll.authUser);
146
+ return this._userResources;
147
+ }
148
+ /** Discovers or creates the user's root resource via UserResources,
149
+ * then handles alternativeRoot if configured. */
150
150
  async init() {
151
151
  if (this.initialized) throw new Error("Already initialized");
152
152
  this._ll = await this.buildLLPlClient(false);
@@ -156,20 +156,11 @@ var PlClient = class PlClient {
156
156
  await this._ll.close();
157
157
  this._ll = await this.buildLLPlClient(true, wireProtocol);
158
158
  }
159
- let rootFromServer;
160
- try {
161
- const responses = await this._ll.listUserResources({ limit: 1 });
162
- for (const msg of responses) if (msg.entry.oneofKind === "userRoot") {
163
- rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId);
164
- break;
165
- }
166
- } catch (err) {
167
- if (!isUnimplementedError(err)) throw err;
168
- }
169
- if (rootFromServer !== void 0) if (this.conf.alternativeRoot === void 0) this._clientRoot = rootFromServer;
170
- else this._clientRoot = await this._withTx("initialization", true, rootFromServer, async (tx) => {
159
+ const userRoot = await this.userResources.getUserRoot();
160
+ if (this.conf.alternativeRoot === void 0) this._clientRoot = userRoot;
161
+ else this._clientRoot = await this._withTx("initialization", true, userRoot, async (tx) => {
171
162
  const aFId = {
172
- resourceId: tx.clientRoot,
163
+ resourceId: userRoot,
173
164
  fieldName: alternativeRootFieldName(this.conf.alternativeRoot)
174
165
  };
175
166
  const altRoot = tx.createEphemeral(ClientRoot);
@@ -179,33 +170,6 @@ var PlClient = class PlClient {
179
170
  await tx.commit();
180
171
  return await altRoot.globalId;
181
172
  });
182
- else {
183
- const user = this._ll.authUser;
184
- const mainRootName = user === null ? AnonymousClientRoot : createHash("sha256").update(user).digest("hex");
185
- this._clientRoot = await this._withTx("initialization", true, NullResourceId, async (tx) => {
186
- let mainRoot;
187
- if (await tx.checkResourceNameExists(mainRootName)) mainRoot = await tx.getResourceByName(mainRootName);
188
- else {
189
- mainRoot = tx.createRoot(ClientRoot);
190
- tx.setResourceName(mainRootName, mainRoot);
191
- }
192
- if (this.conf.alternativeRoot === void 0) {
193
- await tx.commit();
194
- return await toGlobalResourceId(mainRoot);
195
- } else {
196
- const aFId = {
197
- resourceId: mainRoot,
198
- fieldName: alternativeRootFieldName(this.conf.alternativeRoot)
199
- };
200
- const altRoot = tx.createEphemeral(ClientRoot);
201
- tx.lock(altRoot);
202
- tx.createField(aFId, "Dynamic");
203
- tx.setField(aFId, altRoot);
204
- await tx.commit();
205
- return await altRoot.globalId;
206
- }
207
- });
208
- }
209
173
  }
210
174
  /** Returns true if field existed */
211
175
  async deleteAlternativeRoot(alternativeRootName) {
@@ -228,6 +192,10 @@ var PlClient = class PlClient {
228
192
  const release = ops?.lockId ? await advisoryLock(ops.lockId) : () => {};
229
193
  try {
230
194
  const tx = new PlTransaction(this.ll.createTx(writable, ops), name, writable, clientRoot, this.finalPredicate, this.resourceDataCache);
195
+ if (!isNullSignedResourceId(clientRoot) && writable) {
196
+ const parsed = parseSignedResourceId(clientRoot);
197
+ if (parsed.signature) tx.setDefaultColor(parsed.signature);
198
+ }
231
199
  let ok = false;
232
200
  let result = void 0;
233
201
  let txId;
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener, wireProtocol } 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 {\n bigintToResourceId,\n ensureResourceIdNotNull,\n isNullResourceId,\n NullResourceId,\n} from \"./types\";\nimport { ClientRoot } from \"../helpers/pl\";\nimport { isUnimplementedError } from \"./errors\";\nimport type { MiLogger, RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { assertNever, createRetryState, nextRetryStateOrError } from \"@milaboratories/ts-helpers\";\nimport type { PlDriver, PlDriverDefinition } from \"./driver\";\nimport type {\n MaintenanceAPI_Ping_Response,\n MaintenanceAPI_License_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { MaintenanceAPI_Ping_Response_Compression } from \"../proto-grpc/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 { WireConnection } from \"./wire\";\nimport { advisoryLock } from \"./advisory_locks\";\nimport { plAddressToConfig } from \"./config\";\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: (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ) => Promise<LLPlClient>;\n private _ll?: LLPlClient;\n\n private get ll(): LLPlClient {\n if (this._ll === undefined) {\n throw new Error(\"LLPlClient not initialized\");\n }\n return this._ll;\n }\n\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 logger?: MiLogger;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n this.buildLLPlClient = async (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ): Promise<LLPlClient> => {\n if (wireProtocol) conf.wireProtocol = wireProtocol;\n return await LLPlClient.build(conf, { auth, ...ops, shouldUseGzip });\n };\n\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.ping();\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return await this.ll.license();\n }\n\n /**\n * Returns the user root ResourceId via ListUserResources.\n * @param opts.login - target user login; omit for the authenticated user.\n * @returns ResourceId of the user root, or undefined if the server returned no userRoot entry.\n * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).\n */\n public async getUserRoot(opts: { login?: string } = {}): Promise<ResourceId | undefined> {\n const responses = await this.ll.listUserResources({\n login: opts.login,\n limit: 1,\n });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return bigintToResourceId(msg.entry.userRoot.resourceId);\n }\n }\n return undefined;\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 connectionOpts(): WireConnection {\n return this.ll.wireConnection;\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 /** Discovers or creates the user's root resource.\n * Tries ListUserResources RPC first (new backend), falls back to\n * legacy named-resource lookup for older backends. */\n private async init() {\n if (this.initialized) throw new Error(\"Already initialized\");\n\n // Initial client is created without gzip to perform server ping and detect optimal wire protocol.\n // LLPlClient.build() internally calls detectOptimalWireProtocol() which starts with default 'grpc',\n // then retries with 'rest' if ping fails, alternating until a working protocol is found.\n // We save the detected wireProtocol here because if the server supports gzip compression,\n // we'll need to reinitialize the client with gzip enabled - passing the already-detected\n // wireProtocol avoids redundant protocol detection on reinit.\n this._ll = await this.buildLLPlClient(false);\n const wireProtocol = this._ll.wireProtocol;\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 = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Try ListUserResources first (new backend, gRPC only)\n let rootFromServer: ResourceId | undefined;\n try {\n const responses = await this._ll.listUserResources({ limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId);\n break;\n }\n }\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n // Backend doesn't support ListUserResources — fall through to legacy\n }\n\n if (rootFromServer !== undefined) {\n // New path: server created/returned the root\n if (this.conf.alternativeRoot === undefined) {\n this._clientRoot = rootFromServer;\n } else {\n this._clientRoot = await this._withTx(\n \"initialization\",\n true,\n rootFromServer,\n async (tx) => {\n const aFId = {\n resourceId: tx.clientRoot,\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 } else {\n // Legacy path: named resource lookup\n const user = this._ll.authUser;\n const mainRootName =\n user === null ? AnonymousClientRoot : createHash(\"sha256\").update(user).digest(\"hex\");\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\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) await this.ll.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 logger?: MiLogger;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/core/client.ts"],"sourcesContent":["import type { AuthOps, PlClientConfig, PlConnectionStatusListener, wireProtocol } from \"./config\";\nimport type { PlCallOps } from \"./ll_client\";\nimport { LLPlClient } from \"./ll_client\";\nimport { PlTransaction, TxCommitConflict } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId } from \"./types\";\nimport {\n ensureSignedResourceIdNotNull,\n isNullSignedResourceId,\n NullSignedResourceId,\n parseSignedResourceId,\n} from \"./types\";\nimport type { ColorProof } from \"./types\";\nimport { ClientRoot } from \"../helpers/pl\";\nimport type { MiLogger, RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { assertNever, createRetryState, nextRetryStateOrError } from \"@milaboratories/ts-helpers\";\nimport type { PlDriver, PlDriverDefinition } from \"./driver\";\nimport type {\n MaintenanceAPI_Ping_Response,\n MaintenanceAPI_License_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { MaintenanceAPI_Ping_Response_Compression } from \"../proto-grpc/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 { WireConnection } from \"./wire\";\nimport { advisoryLock } from \"./advisory_locks\";\nimport { plAddressToConfig } from \"./config\";\nimport { UserResources } from \"./user_resources\";\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\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: (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ) => Promise<LLPlClient>;\n private _ll?: LLPlClient;\n\n private get ll(): LLPlClient {\n if (this._ll === undefined) {\n throw new Error(\"LLPlClient not initialized\");\n }\n return this._ll;\n }\n\n /** Stores client root (this abstraction is intended for future implementation of the security model) */\n private _clientRoot: OptionalSignedResourceId = NullSignedResourceId;\n\n private _serverInfo: MaintenanceAPI_Ping_Response | undefined = undefined;\n\n private _userResources?: UserResources;\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<SignedResourceId, ResourceDataCacheRecord>;\n\n private constructor(\n configOrAddress: PlClientConfig | string,\n auth: AuthOps,\n ops: {\n statusListener?: PlConnectionStatusListener;\n finalPredicate?: FinalResourceDataPredicate;\n logger?: MiLogger;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n this.buildLLPlClient = async (\n shouldUseGzip: boolean,\n wireProtocol?: wireProtocol,\n ): Promise<LLPlClient> => {\n if (wireProtocol) conf.wireProtocol = wireProtocol;\n return await LLPlClient.build(conf, { auth, ...ops, shouldUseGzip });\n };\n\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.ping();\n }\n\n public async license(): Promise<MaintenanceAPI_License_Response> {\n return await this.ll.license();\n }\n\n /**\n * Returns the user root SignedResourceId via ListUserResources.\n * @param opts.login - target user login; omit for the authenticated user.\n * @returns SignedResourceId of the user root, or undefined if the server returned no userRoot entry.\n * @throws if the backend does not implement ListUserResources (callers should catch with isUnimplementedError).\n */\n public async getUserRoot(\n opts: { login?: string; doNotCreate?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n return this.userResources.getUserRoot(opts);\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 connectionOpts(): WireConnection {\n return this.ll.wireConnection;\n }\n\n private get initialized() {\n return !isNullSignedResourceId(this._clientRoot);\n }\n\n private checkInitialized() {\n if (!this.initialized) throw new Error(\"Client not initialized\");\n }\n\n public get clientRoot(): SignedResourceId {\n this.checkInitialized();\n return ensureSignedResourceIdNotNull(this._clientRoot);\n }\n\n public get serverInfo(): MaintenanceAPI_Ping_Response {\n this.checkInitialized();\n return this._serverInfo!;\n }\n\n /** User resources index for discovering data libraries and other shared resources. */\n public get userResources(): UserResources {\n if (!this._ll) throw new Error(\"Client not initialized\");\n\n if (!this._userResources) {\n this._userResources = new UserResources(this._ll, this._withTx.bind(this), this._ll.authUser);\n }\n\n return this._userResources;\n }\n\n /** Discovers or creates the user's root resource via UserResources,\n * then handles alternativeRoot if configured. */\n private async init() {\n if (this.initialized) throw new Error(\"Already initialized\");\n\n // Initial client is created without gzip to perform server ping and detect optimal wire protocol.\n // LLPlClient.build() internally calls detectOptimalWireProtocol() which starts with default 'grpc',\n // then retries with 'rest' if ping fails, alternating until a working protocol is found.\n // We save the detected wireProtocol here because if the server supports gzip compression,\n // we'll need to reinitialize the client with gzip enabled - passing the already-detected\n // wireProtocol avoids redundant protocol detection on reinit.\n this._ll = await this.buildLLPlClient(false);\n const wireProtocol = this._ll.wireProtocol;\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 = await this.buildLLPlClient(true, wireProtocol);\n }\n\n const userRoot = await this.userResources.getUserRoot();\n\n if (this.conf.alternativeRoot === undefined) {\n this._clientRoot = userRoot;\n } else {\n this._clientRoot = await this._withTx(\"initialization\", true, userRoot, async (tx) => {\n const aFId = {\n resourceId: userRoot,\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: OptionalSignedResourceId,\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 // Auto-set default color proof from the client root's signature\n if (!isNullSignedResourceId(clientRoot) && writable) {\n const parsed = parseSignedResourceId(clientRoot);\n if (parsed.signature) {\n tx.setDefaultColor(parsed.signature as ColorProof);\n }\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) await this.ll.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 logger?: MiLogger;\n } = {},\n ) {\n const pl = new PlClient(configOrAddress, auth, ops);\n await pl.init();\n return pl;\n }\n}\n"],"mappings":"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"}