@milaboratories/pl-client 3.14.5 → 3.14.6
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/client.cjs +10 -2
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +4 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +10 -2
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.cjs +37 -1
- package/dist/core/config.cjs.map +1 -1
- package/dist/core/config.d.ts +25 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +31 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.cjs +7 -0
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts +5 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/ll_client.cjs +64 -7
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +20 -0
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +66 -9
- package/dist/core/ll_client.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/package.json +5 -5
- package/src/core/client.ts +9 -0
- package/src/core/config.test.ts +70 -1
- package/src/core/config.ts +43 -0
- package/src/core/error.test.ts +23 -1
- package/src/core/errors.ts +7 -0
- package/src/core/ll_client.ts +120 -28
package/dist/core/client.cjs
CHANGED
|
@@ -74,7 +74,8 @@ var PlClient = class PlClient {
|
|
|
74
74
|
initialDelay: conf.retryInitialDelay,
|
|
75
75
|
maxAttempts: conf.retryMaxAttempts,
|
|
76
76
|
backoffMultiplier: conf.retryExponentialBackoffMultiplier,
|
|
77
|
-
jitter: conf.retryJitter
|
|
77
|
+
jitter: conf.retryJitter,
|
|
78
|
+
maxDelay: conf.retryMaxDelay
|
|
78
79
|
};
|
|
79
80
|
break;
|
|
80
81
|
case "linear":
|
|
@@ -83,7 +84,8 @@ var PlClient = class PlClient {
|
|
|
83
84
|
initialDelay: conf.retryInitialDelay,
|
|
84
85
|
maxAttempts: conf.retryMaxAttempts,
|
|
85
86
|
backoffStep: conf.retryLinearBackoffStep,
|
|
86
|
-
jitter: conf.retryJitter
|
|
87
|
+
jitter: conf.retryJitter,
|
|
88
|
+
maxDelay: conf.retryMaxDelay
|
|
87
89
|
};
|
|
88
90
|
break;
|
|
89
91
|
default: (0, _milaboratories_ts_helpers.assertNever)(conf.retryBackoffAlgorithm);
|
|
@@ -108,6 +110,12 @@ var PlClient = class PlClient {
|
|
|
108
110
|
error: this.txErrorStat
|
|
109
111
|
};
|
|
110
112
|
}
|
|
113
|
+
/** Smoothed round-trip estimate in ms from ping timings, or undefined before the first
|
|
114
|
+
* ping. Exposed so latency-sensitive consumers (e.g. the tree-sync poll cadence) can scale
|
|
115
|
+
* their timing to the link instead of assuming a local server. */
|
|
116
|
+
get rttEstimateMs() {
|
|
117
|
+
return this.ll.rttEstimateMs;
|
|
118
|
+
}
|
|
111
119
|
async ping() {
|
|
112
120
|
return await this.ll.ping();
|
|
113
121
|
}
|
package/dist/core/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["initialTxStat","plAddressToConfig","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","addStat","isNullSignedResourceId","ensureSignedResourceIdNotNull","UserResources","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 { Role } 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 AuthAPI_User,\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\";\nimport type { BackendCapability } from \"./capabilities\";\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 _userResources?: UserResources;\n\n /** Cached effective role of the authenticated session, fetched once during\n * init via the GetSessionInfo RPC. `null` for an anonymous (no-auth)\n * connection — mirrors {@link authUser}. Read through {@link currentUserRole}. */\n private _currentUserRole: Role | null = null;\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 // An empty or whitespace-only asUser means \"no impersonation\"; normalize to undefined so a\n // directly-constructed { asUser: \"\" } does not slip through as an empty login.\n if (conf.asUser !== undefined && conf.asUser.trim() === \"\") conf.asUser = undefined;\n\n // asUser (impersonation) and alternativeRoot both repoint the client root, so they cannot be\n // combined. Validate here, before init() touches the network, so a misconfiguration fails fast.\n if (conf.asUser !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"PlClient: asUser and alternativeRoot cannot be combined.\");\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 /** Lists the users known to the server (login only on the wire). See {@link LLPlClient.listUsers}. */\n public async listUsers(): Promise<AuthAPI_User[]> {\n return await this.ll.listUsers();\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(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n public async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: 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._ll!.serverInfo;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n this.checkInitialized();\n return this._ll!.hasCapability(capability);\n }\n\n /** Effective role of the authenticated session, resolved once during init via\n * the GetSessionInfo RPC and cached. `null` for an anonymous (no-auth)\n * connection, or when the backend predates GetSessionInfo. Mirrors\n * {@link authUser}'s null contract. */\n public get currentUserRole(): Role | null {\n this.checkInitialized();\n return this._currentUserRole;\n }\n\n /** Login of the authenticated user, or `null` for an anonymous connection. */\n public get authUser(): string | null {\n this.checkInitialized();\n return this.userResources.authUser;\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules.\n * See {@link LLPlClient.supportsWritableWorkdirFiles} for the full definition.\n */\n public get supportsWritableWorkdirFiles(): boolean {\n this.checkInitialized();\n return this._ll!.supportsWritableWorkdirFiles;\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 // LLPlClient.build() guarantees a successful ping; read the cached response instead of pinging again.\n const info = this._ll.serverInfo;\n if (info.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Resolve the client root. Normally the caller's own root; when `asUser` is set (admin impersonation),\n // the target user's root instead. The backend authorizes impersonation by role and silently returns the\n // caller's own root for non-admins, so no client-side role gate is needed here.\n let userRoot: SignedResourceId;\n if (this.conf.asUser === undefined) {\n userRoot = await this.userResources.getUserRoot({ createIfNotExists: true });\n } else {\n const impersonatedRoot = await this.userResources.getUserRoot({ login: this.conf.asUser });\n if (impersonatedRoot === undefined)\n throw new Error(\n `PlClient: cannot open root of user '${this.conf.asUser}': no root found (the user may have never logged in).`,\n );\n userRoot = impersonatedRoot;\n }\n\n // Resolve the caller's role once; only the publicGrants-gated share-with-everybody check uses it,\n // so skip it for anonymous connections or when publicGrants:v1 is absent. Use _ll.hasCapability,\n // not this.hasCapability: we are mid-init, so this.checkInitialized() would throw.\n if (this.userResources.authUser !== null && this._ll.hasCapability(\"publicGrants:v1\")) {\n this._currentUserRole = (await this._ll.getSessionInfo()).role;\n }\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 // Skip when backend doesn't implement the `set_default_color` TX request.\n if (this.ll.supportsResourceSignatures && !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 /**\n * Runs a write transaction whose default color is `root`'s signature instead of the client\n * root's, and whose `tx.clientRoot` is `root`. For admin cross-root operations (e.g. copying a\n * project into another user's root so the copy is minted in that user's color). The backend\n * still authorizes the write by role.\n */\n public async withWriteTxOnRoot<T>(\n root: SignedResourceId,\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, true, root, 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":";;;;;;;;;;;;;;;;AA4CA,MAAM,eAAe,EACnB,MAAM,MACR;AAEA,SAAS,yBAAyB,iBAAiC;CACjE,OAAO,oBAAoB;AAC7B;;AAGA,IAAa,WAAb,MAAa,SAAS;CACpB,0BAA2B,IAAI,IAAsB;;;CAIrD;;CAGA;;CAGA;CAEA;CAIA;CAEA,IAAY,KAAiB;EAC3B,IAAI,KAAK,QAAQ,KAAA,GACf,MAAM,IAAI,MAAM,4BAA4B;EAE9C,OAAO,KAAK;CACd;;CAGA,cAAQ;CAER;;;;CAKA,mBAAwC;CAExC,mBAAmCA,aAAAA,cAAc;CACjD,kBAAkCA,aAAAA,cAAc;CAChD,eAA+BA,aAAAA,cAAc;;CAO7C;;CAGA;CAEA,YACE,iBACA,MACA,MAII,CAAC,GACL;EACA,MAAM,OACJ,OAAO,oBAAoB,WAAWC,eAAAA,kBAAkB,eAAe,IAAI;EAI7E,IAAI,KAAK,WAAW,KAAA,KAAa,KAAK,OAAO,KAAK,MAAM,IAAI,KAAK,SAAS,KAAA;EAI1E,IAAI,KAAK,WAAW,KAAA,KAAa,KAAK,oBAAoB,KAAA,GACxD,MAAM,IAAI,MAAM,0DAA0D;EAE5E,KAAK,kBAAkB,OACrB,eACA,iBACwB;GACxB,IAAI,cAAc,KAAK,eAAe;GACtC,OAAO,MAAMC,kBAAAA,WAAW,MAAM,MAAM;IAAE;IAAM,GAAG;IAAK;GAAc,CAAC;EACrE;EAEA,KAAK,UAAU,KAAK;EACpB,KAAK,YAAY,KAAK;EACtB,KAAK,iBAAiB,IAAI,kBAAkBC,cAAAA;EAC5C,KAAK,oBAAoB,IAAIC,UAAAA,SAAS;GACpC,SAAS,KAAK;GACd,kBAAkB,OAAO,EAAE,UAAU,MAAM,UAAU,KAAK;EAC5D,CAAC;EACD,QAAQ,KAAK,uBAAb;GACE,KAAK;IACH,KAAK,sBAAsB;KACzB,MAAM;KACN,cAAc,KAAK;KACnB,aAAa,KAAK;KAClB,mBAAmB,KAAK;KACxB,QAAQ,KAAK;IACf;IACA;GACF,KAAK;IACH,KAAK,sBAAsB;KACzB,MAAM;KACN,cAAc,KAAK;KACnB,aAAa,KAAK;KAClB,aAAa,KAAK;KAClB,QAAQ,KAAK;IACf;IACA;GACF,SACE,CAAA,GAAA,2BAAA,YAAA,CAAY,KAAK,qBAAqB;EAC1C;CACF;CAEA,IAAW,kBAA0B;EACnC,OAAO,EAAE,GAAG,KAAK,iBAAiB;CACpC;CAEA,IAAW,iBAAyB;EAClC,OAAO,EAAE,GAAG,KAAK,gBAAgB;CACnC;CAEA,IAAW,cAAsB;EAC/B,OAAO,EAAE,GAAG,KAAK,aAAa;CAChC;CAEA,IAAW,cAAsB;EAC/B,OAAOC,aAAAA,QAAQA,aAAAA,QAAQ,KAAK,kBAAkB,KAAK,eAAe,GAAG,KAAK,YAAY;CACxF;CAEA,IAAW,YAAuB;EAChC,OAAO;GACL,WAAW,KAAK;GAChB,UAAU,KAAK;GACf,OAAO,KAAK;EACd;CACF;CAEA,MAAa,OAA8C;EACzD,OAAO,MAAM,KAAK,GAAG,KAAK;CAC5B;CAEA,MAAa,UAAoD;EAC/D,OAAO,MAAM,KAAK,GAAG,QAAQ;CAC/B;;CAGA,MAAa,YAAqC;EAChD,OAAO,MAAM,KAAK,GAAG,UAAU;CACjC;CAgBA,MAAa,YACX,OAAwD,CAAC,GAClB;EACvC,OAAO,KAAK,cAAc,YAAY,IAAI;CAC5C;CAEA,IAAW,OAAuB;EAChC,OAAO,KAAK,GAAG;CACjB;CAEA,IAAW,iBAA6B;EACtC,OAAO,KAAK,GAAG;CACjB;CAEA,IAAW,iBAAiC;EAC1C,OAAO,KAAK,GAAG;CACjB;CAEA,IAAY,cAAc;EACxB,OAAO,CAACC,cAAAA,uBAAuB,KAAK,WAAW;CACjD;CAEA,mBAA2B;EACzB,IAAI,CAAC,KAAK,aAAa,MAAM,IAAI,MAAM,wBAAwB;CACjE;CAEA,IAAW,aAA+B;EACxC,KAAK,iBAAiB;EACtB,OAAOC,cAAAA,8BAA8B,KAAK,WAAW;CACvD;CAEA,IAAW,aAA2C;EACpD,KAAK,iBAAiB;EACtB,OAAO,KAAK,IAAK;CACnB;CAEA,cAAqB,YAAwC;EAC3D,KAAK,iBAAiB;EACtB,OAAO,KAAK,IAAK,cAAc,UAAU;CAC3C;;;;;CAMA,IAAW,kBAA+B;EACxC,KAAK,iBAAiB;EACtB,OAAO,KAAK;CACd;;CAGA,IAAW,WAA0B;EACnC,KAAK,iBAAiB;EACtB,OAAO,KAAK,cAAc;CAC5B;;;;;CAMA,IAAW,+BAAwC;EACjD,KAAK,iBAAiB;EACtB,OAAO,KAAK,IAAK;CACnB;;CAGA,IAAW,gBAA+B;EACxC,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,MAAM,wBAAwB;EAEvD,IAAI,CAAC,KAAK,gBACR,KAAK,iBAAiB,IAAIC,uBAAAA,cAAc,KAAK,KAAK,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ;EAG9F,OAAO,KAAK;CACd;;;CAIA,MAAc,OAAO;EACnB,IAAI,KAAK,aAAa,MAAM,IAAI,MAAM,qBAAqB;EAQ3D,KAAK,MAAM,MAAM,KAAK,gBAAgB,KAAK;EAC3C,MAAM,eAAe,KAAK,IAAI;EAI9B,IADa,KAAK,IAAI,WACb,gBAAA,GAA+D;GACtE,MAAM,KAAK,IAAI,MAAM;GACrB,KAAK,MAAM,MAAM,KAAK,gBAAgB,MAAM,YAAY;EAC1D;EAKA,IAAI;EACJ,IAAI,KAAK,KAAK,WAAW,KAAA,GACvB,WAAW,MAAM,KAAK,cAAc,YAAY,EAAE,mBAAmB,KAAK,CAAC;OACtE;GACL,MAAM,mBAAmB,MAAM,KAAK,cAAc,YAAY,EAAE,OAAO,KAAK,KAAK,OAAO,CAAC;GACzF,IAAI,qBAAqB,KAAA,GACvB,MAAM,IAAI,MACR,uCAAuC,KAAK,KAAK,OAAO,sDAC1D;GACF,WAAW;EACb;EAKA,IAAI,KAAK,cAAc,aAAa,QAAQ,KAAK,IAAI,cAAc,iBAAiB,GAClF,KAAK,oBAAoB,MAAM,KAAK,IAAI,eAAe,EAAA,CAAG;EAG5D,IAAI,KAAK,KAAK,oBAAoB,KAAA,GAChC,KAAK,cAAc;OAEnB,KAAK,cAAc,MAAM,KAAK,QAAQ,kBAAkB,MAAM,UAAU,OAAO,OAAO;GACpF,MAAM,OAAO;IACX,YAAY;IACZ,WAAW,yBAAyB,KAAK,KAAK,eAAgB;GAChE;GAEA,MAAM,UAAU,GAAG,gBAAgBC,WAAAA,UAAU;GAC7C,GAAG,KAAK,OAAO;GACf,GAAG,YAAY,MAAM,SAAS;GAC9B,GAAG,SAAS,MAAM,OAAO;GACzB,MAAM,GAAG,OAAO;GAEhB,OAAO,MAAM,QAAQ;EACvB,CAAC;CAEL;;CAGA,MAAa,sBAAsB,qBAA+C;EAChF,KAAK,iBAAiB;EACtB,IAAI,KAAK,GAAG,KAAK,oBAAoB,KAAA,GACnC,MAAM,IAAI,MAAM,oCAAoC;EACtD,OAAO,MAAM,KAAK,YAAY,2BAA2B,OAAO,OAAO;GACrE,MAAM,MAAM;IACV,YAAY,GAAG;IACf,WAAW,yBAAyB,mBAAmB;GACzD;GACA,MAAM,SAAS,GAAG,YAAY,GAAG;GACjC,GAAG,YAAY,GAAG;GAClB,MAAM,GAAG,OAAO;GAChB,OAAO,MAAM;EACf,CAAC;CACH;CAEA,MAAc,QACZ,MACA,UACA,YACA,MACA,KACY;EAEZ,IAAI,cAAA,GAAA,2BAAA,iBAAA,CAA8B,KAAK,gBAAgB,KAAK,mBAAmB;EAE/E,OAAO,MAAM;GACX,MAAM,UAAU,KAAK,SAAS,MAAMC,uBAAAA,aAAa,IAAI,MAAM,UAAU,CAAC;GAEtE,IAAI;IAIF,MAAM,KAAK,IAAIC,oBAAAA,cAFF,KAAK,GAAG,SAAS,UAAU,GAGnC,GACH,MACA,UACA,YACA,KAAK,gBACL,KAAK,iBACP;IAIA,IAAI,KAAK,GAAG,8BAA8B,CAACL,cAAAA,uBAAuB,UAAU,KAAK,UAAU;KACzF,MAAM,SAASM,cAAAA,sBAAsB,UAAU;KAC/C,IAAI,OAAO,WACT,GAAG,gBAAgB,OAAO,SAAuB;IAErD;IAEA,IAAI,KAAK;IACT,IAAI,SAAwB,KAAA;IAC5B,IAAI;IAEJ,IAAI;KAEF,SAAS,MAAM,KAAK,EAAE;KAEtB,KAAK,mBAAmBP,aAAAA,QAAQ,KAAK,kBAAkB,GAAG,IAAI;KAC9D,KAAK;IACP,SAAS,GAAY;KAEnB,IAAI,aAAaQ,oBAAAA,kBAGf,KAAK,kBAAkBR,aAAAA,QAAQ,KAAK,iBAAiB,GAAG,IAAI;UACvD;MAEL,KAAK,eAAeA,aAAAA,QAAQ,KAAK,cAAc,GAAG,IAAI;MACtD,MAAM;KACR;IACF,UAAU;KAMR,MAAM,GAAG,SAAS;KAClB,MAAM,GAAG,MAAM;KAEf,OAAO,MAAM,GAAG,cAAc;IAChC;IAEA,IAAI,IAAI;KAEN,IAAI,KAAK,SAAS,KAAA,IAAY,KAAK,YAAY,KAAK,MAAM,MAAM,KAAK,GAAG,OAAO,IAAI;KAGnF,IAAI,YAAY,KAAK,UAAU,GAC7B,MAAMS,qBAAG,WAAW,KAAK,SAAS,KAAA,GAAW,EAAE,QAAQ,KAAK,YAAY,CAAC;KAE3E,OAAO;IACT;GACF,UAAU;IACR,QAAQ;GACV;GAKA,MAAMA,qBAAG,WAAW,WAAW,WAAW,KAAA,GAAW,EAAE,QAAQ,KAAK,YAAY,CAAC;GACjF,cAAA,GAAA,2BAAA,sBAAA,CAAmC,UAAU;EAC/C;CACF;CAEA,MAAc,OACZ,MACA,UACA,MACA,MAAsB,CAAC,GACX;EACZ,KAAK,iBAAiB;EACtB,OAAO,MAAM,KAAK,QAAQ,MAAM,UAAU,KAAK,YAAY,MAAM;GAAE,GAAG;GAAK,GAAG;EAAa,CAAC;CAC9F;CAEA,MAAa,YACX,MACA,MACA,MAAsB,CAAC,GACX;EACZ,OAAO,MAAM,KAAK,OAAO,MAAM,MAAM,MAAM;GAAE,GAAG;GAAK,GAAG;EAAa,CAAC;CACxE;;;;;;;CAQA,MAAa,kBACX,MACA,MACA,MACA,MAAsB,CAAC,GACX;EACZ,KAAK,iBAAiB;EACtB,OAAO,MAAM,KAAK,QAAQ,MAAM,MAAM,MAAM,MAAM;GAAE,GAAG;GAAK,GAAG;EAAa,CAAC;CAC/E;CAEA,MAAa,WACX,MACA,MACA,MAAsB,CAAC,GACX;EACZ,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,MAAM;GAAE,GAAG;GAAK,GAAG;EAAa,CAAC;CACzE;CAEA,UAAuC,YAA0C;EAC/E,MAAM,WAAW,KAAK,QAAQ,IAAI,WAAW,IAAI;EACjD,IAAI,aAAa,KAAA,GAAW,OAAO;EACnC,MAAM,SAAS,WAAW,KAAK,MAAM,KAAK,IAAI,KAAK,cAAc;EACjE,KAAK,QAAQ,IAAI,WAAW,MAAM,MAAM;EACxC,OAAO;CACT;;CAGA,MAAa,QAAQ;EACnB,MAAM,KAAK,GAAG,MAAM;CACtB;CAEA,aAAoB,KAClB,iBACA,MACA,MAGI,CAAC,GACL;EACA,MAAM,KAAK,IAAI,SAAS,iBAAiB,MAAM,GAAG;EAClD,MAAM,GAAG,KAAK;EACd,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["initialTxStat","plAddressToConfig","LLPlClient","DefaultFinalResourceDataPredicate","LRUCache","addStat","isNullSignedResourceId","ensureSignedResourceIdNotNull","UserResources","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 { Role } 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 AuthAPI_User,\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\";\nimport type { BackendCapability } from \"./capabilities\";\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 _userResources?: UserResources;\n\n /** Cached effective role of the authenticated session, fetched once during\n * init via the GetSessionInfo RPC. `null` for an anonymous (no-auth)\n * connection — mirrors {@link authUser}. Read through {@link currentUserRole}. */\n private _currentUserRole: Role | null = null;\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 // An empty or whitespace-only asUser means \"no impersonation\"; normalize to undefined so a\n // directly-constructed { asUser: \"\" } does not slip through as an empty login.\n if (conf.asUser !== undefined && conf.asUser.trim() === \"\") conf.asUser = undefined;\n\n // asUser (impersonation) and alternativeRoot both repoint the client root, so they cannot be\n // combined. Validate here, before init() touches the network, so a misconfiguration fails fast.\n if (conf.asUser !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"PlClient: asUser and alternativeRoot cannot be combined.\");\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 maxDelay: conf.retryMaxDelay,\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 maxDelay: conf.retryMaxDelay,\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 /** Smoothed round-trip estimate in ms from ping timings, or undefined before the first\n * ping. Exposed so latency-sensitive consumers (e.g. the tree-sync poll cadence) can scale\n * their timing to the link instead of assuming a local server. */\n public get rttEstimateMs(): number | undefined {\n return this.ll.rttEstimateMs;\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 /** Lists the users known to the server (login only on the wire). See {@link LLPlClient.listUsers}. */\n public async listUsers(): Promise<AuthAPI_User[]> {\n return await this.ll.listUsers();\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(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n public async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: 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._ll!.serverInfo;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n this.checkInitialized();\n return this._ll!.hasCapability(capability);\n }\n\n /** Effective role of the authenticated session, resolved once during init via\n * the GetSessionInfo RPC and cached. `null` for an anonymous (no-auth)\n * connection, or when the backend predates GetSessionInfo. Mirrors\n * {@link authUser}'s null contract. */\n public get currentUserRole(): Role | null {\n this.checkInitialized();\n return this._currentUserRole;\n }\n\n /** Login of the authenticated user, or `null` for an anonymous connection. */\n public get authUser(): string | null {\n this.checkInitialized();\n return this.userResources.authUser;\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules.\n * See {@link LLPlClient.supportsWritableWorkdirFiles} for the full definition.\n */\n public get supportsWritableWorkdirFiles(): boolean {\n this.checkInitialized();\n return this._ll!.supportsWritableWorkdirFiles;\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 // LLPlClient.build() guarantees a successful ping; read the cached response instead of pinging again.\n const info = this._ll.serverInfo;\n if (info.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Resolve the client root. Normally the caller's own root; when `asUser` is set (admin impersonation),\n // the target user's root instead. The backend authorizes impersonation by role and silently returns the\n // caller's own root for non-admins, so no client-side role gate is needed here.\n let userRoot: SignedResourceId;\n if (this.conf.asUser === undefined) {\n userRoot = await this.userResources.getUserRoot({ createIfNotExists: true });\n } else {\n const impersonatedRoot = await this.userResources.getUserRoot({ login: this.conf.asUser });\n if (impersonatedRoot === undefined)\n throw new Error(\n `PlClient: cannot open root of user '${this.conf.asUser}': no root found (the user may have never logged in).`,\n );\n userRoot = impersonatedRoot;\n }\n\n // Resolve the caller's role once; only the publicGrants-gated share-with-everybody check uses it,\n // so skip it for anonymous connections or when publicGrants:v1 is absent. Use _ll.hasCapability,\n // not this.hasCapability: we are mid-init, so this.checkInitialized() would throw.\n if (this.userResources.authUser !== null && this._ll.hasCapability(\"publicGrants:v1\")) {\n this._currentUserRole = (await this._ll.getSessionInfo()).role;\n }\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 // Skip when backend doesn't implement the `set_default_color` TX request.\n if (this.ll.supportsResourceSignatures && !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 /**\n * Runs a write transaction whose default color is `root`'s signature instead of the client\n * root's, and whose `tx.clientRoot` is `root`. For admin cross-root operations (e.g. copying a\n * project into another user's root so the copy is minted in that user's color). The backend\n * still authorizes the write by role.\n */\n public async withWriteTxOnRoot<T>(\n root: SignedResourceId,\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, true, root, 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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"}
|
package/dist/core/client.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ declare class PlClient {
|
|
|
52
52
|
get txErrorStat(): TxStat;
|
|
53
53
|
get txTotalStat(): TxStat;
|
|
54
54
|
get allTxStat(): AllTxStat;
|
|
55
|
+
/** Smoothed round-trip estimate in ms from ping timings, or undefined before the first
|
|
56
|
+
* ping. Exposed so latency-sensitive consumers (e.g. the tree-sync poll cadence) can scale
|
|
57
|
+
* their timing to the link instead of assuming a local server. */
|
|
58
|
+
get rttEstimateMs(): number | undefined;
|
|
55
59
|
ping(): Promise<MaintenanceAPI_Ping_Response>;
|
|
56
60
|
license(): Promise<MaintenanceAPI_License_Response>;
|
|
57
61
|
/** Lists the users known to the server (login only on the wire). See {@link LLPlClient.listUsers}. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/core/client.ts"],"mappings":";;;;;;;;;;;;;;;KAqCY,KAAA,GAAQ,SAAA;EAClB,IAAA;EACA,YAAA,GAAe,YAAY;EAC3B,IAAA;EACA,MAAA;AAAA;;cAYW,QAAA;EAAA,iBACM,OAAA;EAdjB;;EAAA,iBAkBiB,OAAA;EAjBX;EAAA,iBAoBW,SAAA;EARE;EAAA,iBAWF,mBAAA;EAAA,iBAEA,eAAA;EAAA,QAIT,GAAA;EAAA,YAEI,EAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/core/client.ts"],"mappings":";;;;;;;;;;;;;;;KAqCY,KAAA,GAAQ,SAAA;EAClB,IAAA;EACA,YAAA,GAAe,YAAY;EAC3B,IAAA;EACA,MAAA;AAAA;;cAYW,QAAA;EAAA,iBACM,OAAA;EAdjB;;EAAA,iBAkBiB,OAAA;EAjBX;EAAA,iBAoBW,SAAA;EARE;EAAA,iBAWF,mBAAA;EAAA,iBAEA,eAAA;EAAA,QAIT,GAAA;EAAA,YAEI,EAAA;EAqGc;EAAA,QA7FlB,WAAA;EAAA,QAEA,cAAA;EAkHqB;;;EAAA,QA7GrB,gBAAA;EAAA,QAEA,gBAAA;EAAA,QACA,eAAA;EAAA,QACA,YAAA;EA+HJ;EAAA,SAxHY,cAAA,EAAgB,0BAAA;EA4H5B;EAAA,iBAzHa,iBAAA;EAAA,QAEV,WAAA;EAAA,IA8DI,eAAA,IAAmB,MAAA;EAAA,IAInB,cAAA,IAAkB,MAAA;EAAA,IAIlB,WAAA,IAAe,MAAA;EAAA,IAIf,WAAA,IAAe,MAAA;EAAA,IAIf,SAAA,IAAa,SAAA;EA4GI;;;EAAA,IAjGjB,aAAA;EAIE,IAAA,IAAQ,OAAA,CAAQ,4BAAA;EAIhB,OAAA,IAAW,OAAA,CAAQ,+BAAA;EA0RzB;EArRM,SAAA,IAAa,OAAA,CAAQ,YAAA;EAsR/B;;;;;;EA5QU,WAAA,CAAY,IAAA;IACvB,KAAA;IACA,iBAAA;EAAA,IACE,OAAA,CAAQ,gBAAA;EACC,WAAA,CAAY,IAAA;IACvB,KAAA;IACA,iBAAA;EAAA,IACE,OAAA,CAAQ,gBAAA;EAAA,IAOD,IAAA,IAAQ,cAAA;EAAA,IAIR,cAAA,IAAkB,UAAA;EAAA,IAIlB,cAAA,IAAkB,cAAA;EAAA,YAIjB,WAAA;EAAA,QAIJ,gBAAA;EAAA,IAIG,UAAA,IAAc,gBAAA;EAAA,IAKd,UAAA,IAAc,4BAAA;EAKlB,aAAA,CAAc,UAAA,EAAY,iBAAA;EA2QzB;;;;EAAA,IAlQG,eAAA,IAAmB,IAAA;EAsQtB;EAAA,IAhQG,QAAA;EA5NM;;;;EAAA,IAqON,4BAAA;EArNH;EAAA,IA2NG,aAAA,IAAiB,aAAA;EAjNpB;;EAAA,QA6NM,IAAA;EApNN;EAkRK,qBAAA,CAAsB,mBAAA,WAA8B,OAAA;EAAA,QAgBnD,OAAA;EAAA,QA0FA,MAAA;EAUD,WAAA,IACX,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EA9XM;;;;;;EAwYJ,iBAAA,IACX,IAAA,EAAM,gBAAA,EACN,IAAA,UACA,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,GACrC,GAAA,GAAK,OAAA,CAAQ,KAAA,IACZ,OAAA,CAAQ,CAAA;EAKE,UAAA,IACX,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,EAAU,UAAA,EAAY,kBAAA,CAAmB,GAAA,IAAO,GAAA;EA9UnD;EAuVb,KAAA,IAAK,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"}
|
package/dist/core/client.js
CHANGED
|
@@ -72,7 +72,8 @@ var PlClient = class PlClient {
|
|
|
72
72
|
initialDelay: conf.retryInitialDelay,
|
|
73
73
|
maxAttempts: conf.retryMaxAttempts,
|
|
74
74
|
backoffMultiplier: conf.retryExponentialBackoffMultiplier,
|
|
75
|
-
jitter: conf.retryJitter
|
|
75
|
+
jitter: conf.retryJitter,
|
|
76
|
+
maxDelay: conf.retryMaxDelay
|
|
76
77
|
};
|
|
77
78
|
break;
|
|
78
79
|
case "linear":
|
|
@@ -81,7 +82,8 @@ var PlClient = class PlClient {
|
|
|
81
82
|
initialDelay: conf.retryInitialDelay,
|
|
82
83
|
maxAttempts: conf.retryMaxAttempts,
|
|
83
84
|
backoffStep: conf.retryLinearBackoffStep,
|
|
84
|
-
jitter: conf.retryJitter
|
|
85
|
+
jitter: conf.retryJitter,
|
|
86
|
+
maxDelay: conf.retryMaxDelay
|
|
85
87
|
};
|
|
86
88
|
break;
|
|
87
89
|
default: assertNever(conf.retryBackoffAlgorithm);
|
|
@@ -106,6 +108,12 @@ var PlClient = class PlClient {
|
|
|
106
108
|
error: this.txErrorStat
|
|
107
109
|
};
|
|
108
110
|
}
|
|
111
|
+
/** Smoothed round-trip estimate in ms from ping timings, or undefined before the first
|
|
112
|
+
* ping. Exposed so latency-sensitive consumers (e.g. the tree-sync poll cadence) can scale
|
|
113
|
+
* their timing to the link instead of assuming a local server. */
|
|
114
|
+
get rttEstimateMs() {
|
|
115
|
+
return this.ll.rttEstimateMs;
|
|
116
|
+
}
|
|
109
117
|
async ping() {
|
|
110
118
|
return await this.ll.ping();
|
|
111
119
|
}
|
package/dist/core/client.js.map
CHANGED
|
@@ -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 { PlTransaction, TxCommitConflict } from \"./transaction\";\nimport type { Role } 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 AuthAPI_User,\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\";\nimport type { BackendCapability } from \"./capabilities\";\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 _userResources?: UserResources;\n\n /** Cached effective role of the authenticated session, fetched once during\n * init via the GetSessionInfo RPC. `null` for an anonymous (no-auth)\n * connection — mirrors {@link authUser}. Read through {@link currentUserRole}. */\n private _currentUserRole: Role | null = null;\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 // An empty or whitespace-only asUser means \"no impersonation\"; normalize to undefined so a\n // directly-constructed { asUser: \"\" } does not slip through as an empty login.\n if (conf.asUser !== undefined && conf.asUser.trim() === \"\") conf.asUser = undefined;\n\n // asUser (impersonation) and alternativeRoot both repoint the client root, so they cannot be\n // combined. Validate here, before init() touches the network, so a misconfiguration fails fast.\n if (conf.asUser !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"PlClient: asUser and alternativeRoot cannot be combined.\");\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 /** Lists the users known to the server (login only on the wire). See {@link LLPlClient.listUsers}. */\n public async listUsers(): Promise<AuthAPI_User[]> {\n return await this.ll.listUsers();\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(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n public async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: 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._ll!.serverInfo;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n this.checkInitialized();\n return this._ll!.hasCapability(capability);\n }\n\n /** Effective role of the authenticated session, resolved once during init via\n * the GetSessionInfo RPC and cached. `null` for an anonymous (no-auth)\n * connection, or when the backend predates GetSessionInfo. Mirrors\n * {@link authUser}'s null contract. */\n public get currentUserRole(): Role | null {\n this.checkInitialized();\n return this._currentUserRole;\n }\n\n /** Login of the authenticated user, or `null` for an anonymous connection. */\n public get authUser(): string | null {\n this.checkInitialized();\n return this.userResources.authUser;\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules.\n * See {@link LLPlClient.supportsWritableWorkdirFiles} for the full definition.\n */\n public get supportsWritableWorkdirFiles(): boolean {\n this.checkInitialized();\n return this._ll!.supportsWritableWorkdirFiles;\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 // LLPlClient.build() guarantees a successful ping; read the cached response instead of pinging again.\n const info = this._ll.serverInfo;\n if (info.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Resolve the client root. Normally the caller's own root; when `asUser` is set (admin impersonation),\n // the target user's root instead. The backend authorizes impersonation by role and silently returns the\n // caller's own root for non-admins, so no client-side role gate is needed here.\n let userRoot: SignedResourceId;\n if (this.conf.asUser === undefined) {\n userRoot = await this.userResources.getUserRoot({ createIfNotExists: true });\n } else {\n const impersonatedRoot = await this.userResources.getUserRoot({ login: this.conf.asUser });\n if (impersonatedRoot === undefined)\n throw new Error(\n `PlClient: cannot open root of user '${this.conf.asUser}': no root found (the user may have never logged in).`,\n );\n userRoot = impersonatedRoot;\n }\n\n // Resolve the caller's role once; only the publicGrants-gated share-with-everybody check uses it,\n // so skip it for anonymous connections or when publicGrants:v1 is absent. Use _ll.hasCapability,\n // not this.hasCapability: we are mid-init, so this.checkInitialized() would throw.\n if (this.userResources.authUser !== null && this._ll.hasCapability(\"publicGrants:v1\")) {\n this._currentUserRole = (await this._ll.getSessionInfo()).role;\n }\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 // Skip when backend doesn't implement the `set_default_color` TX request.\n if (this.ll.supportsResourceSignatures && !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 /**\n * Runs a write transaction whose default color is `root`'s signature instead of the client\n * root's, and whose `tx.clientRoot` is `root`. For admin cross-root operations (e.g. copying a\n * project into another user's root so the copy is minted in that user's color). The backend\n * still authorizes the write by role.\n */\n public async withWriteTxOnRoot<T>(\n root: SignedResourceId,\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, true, root, 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 { Role } 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 AuthAPI_User,\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\";\nimport type { BackendCapability } from \"./capabilities\";\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 _userResources?: UserResources;\n\n /** Cached effective role of the authenticated session, fetched once during\n * init via the GetSessionInfo RPC. `null` for an anonymous (no-auth)\n * connection — mirrors {@link authUser}. Read through {@link currentUserRole}. */\n private _currentUserRole: Role | null = null;\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 // An empty or whitespace-only asUser means \"no impersonation\"; normalize to undefined so a\n // directly-constructed { asUser: \"\" } does not slip through as an empty login.\n if (conf.asUser !== undefined && conf.asUser.trim() === \"\") conf.asUser = undefined;\n\n // asUser (impersonation) and alternativeRoot both repoint the client root, so they cannot be\n // combined. Validate here, before init() touches the network, so a misconfiguration fails fast.\n if (conf.asUser !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"PlClient: asUser and alternativeRoot cannot be combined.\");\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 maxDelay: conf.retryMaxDelay,\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 maxDelay: conf.retryMaxDelay,\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 /** Smoothed round-trip estimate in ms from ping timings, or undefined before the first\n * ping. Exposed so latency-sensitive consumers (e.g. the tree-sync poll cadence) can scale\n * their timing to the link instead of assuming a local server. */\n public get rttEstimateMs(): number | undefined {\n return this.ll.rttEstimateMs;\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 /** Lists the users known to the server (login only on the wire). See {@link LLPlClient.listUsers}. */\n public async listUsers(): Promise<AuthAPI_User[]> {\n return await this.ll.listUsers();\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(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n public async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: 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._ll!.serverInfo;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n this.checkInitialized();\n return this._ll!.hasCapability(capability);\n }\n\n /** Effective role of the authenticated session, resolved once during init via\n * the GetSessionInfo RPC and cached. `null` for an anonymous (no-auth)\n * connection, or when the backend predates GetSessionInfo. Mirrors\n * {@link authUser}'s null contract. */\n public get currentUserRole(): Role | null {\n this.checkInitialized();\n return this._currentUserRole;\n }\n\n /** Login of the authenticated user, or `null` for an anonymous connection. */\n public get authUser(): string | null {\n this.checkInitialized();\n return this.userResources.authUser;\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules.\n * See {@link LLPlClient.supportsWritableWorkdirFiles} for the full definition.\n */\n public get supportsWritableWorkdirFiles(): boolean {\n this.checkInitialized();\n return this._ll!.supportsWritableWorkdirFiles;\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 // LLPlClient.build() guarantees a successful ping; read the cached response instead of pinging again.\n const info = this._ll.serverInfo;\n if (info.compression === MaintenanceAPI_Ping_Response_Compression.GZIP) {\n await this._ll.close();\n this._ll = await this.buildLLPlClient(true, wireProtocol);\n }\n\n // Resolve the client root. Normally the caller's own root; when `asUser` is set (admin impersonation),\n // the target user's root instead. The backend authorizes impersonation by role and silently returns the\n // caller's own root for non-admins, so no client-side role gate is needed here.\n let userRoot: SignedResourceId;\n if (this.conf.asUser === undefined) {\n userRoot = await this.userResources.getUserRoot({ createIfNotExists: true });\n } else {\n const impersonatedRoot = await this.userResources.getUserRoot({ login: this.conf.asUser });\n if (impersonatedRoot === undefined)\n throw new Error(\n `PlClient: cannot open root of user '${this.conf.asUser}': no root found (the user may have never logged in).`,\n );\n userRoot = impersonatedRoot;\n }\n\n // Resolve the caller's role once; only the publicGrants-gated share-with-everybody check uses it,\n // so skip it for anonymous connections or when publicGrants:v1 is absent. Use _ll.hasCapability,\n // not this.hasCapability: we are mid-init, so this.checkInitialized() would throw.\n if (this.userResources.authUser !== null && this._ll.hasCapability(\"publicGrants:v1\")) {\n this._currentUserRole = (await this._ll.getSessionInfo()).role;\n }\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 // Skip when backend doesn't implement the `set_default_color` TX request.\n if (this.ll.supportsResourceSignatures && !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 /**\n * Runs a write transaction whose default color is `root`'s signature instead of the client\n * root's, and whose `tx.clientRoot` is `root`. For admin cross-root operations (e.g. copying a\n * project into another user's root so the copy is minted in that user's color). The backend\n * still authorizes the write by role.\n */\n public async withWriteTxOnRoot<T>(\n root: SignedResourceId,\n name: string,\n body: (tx: PlTransaction) => Promise<T>,\n ops: Partial<TxOps> = {},\n ): Promise<T> {\n this.checkInitialized();\n return await this._withTx(name, true, root, 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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"}
|
package/dist/core/config.cjs
CHANGED
|
@@ -12,12 +12,39 @@ const DEFAULT_RETRY_INITIAL_DELAY = 20;
|
|
|
12
12
|
const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 1.5;
|
|
13
13
|
const DEFAULT_RETRY_LINEAR_BACKOFF_STEP = 50;
|
|
14
14
|
const DEFAULT_RETRY_JITTER = .3;
|
|
15
|
+
const DEFAULT_RETRY_MAX_DELAY = 5e3;
|
|
16
|
+
/** Multiplier applied to the observed RTT when deriving the unary deadline. A unary call
|
|
17
|
+
* costs at least one round trip, plus connect/DNS/LB and server work on top, so the
|
|
18
|
+
* deadline needs sizeable headroom over the bare RTT. */
|
|
19
|
+
const RTT_DEADLINE_MULTIPLIER = 8;
|
|
20
|
+
/** Ceiling for the RTT-derived unary deadline. Beyond this a call is stuck rather than
|
|
21
|
+
* slow, and waiting longer only delays the retry. */
|
|
22
|
+
const MAX_ADAPTIVE_REQUEST_TIMEOUT = 6e4;
|
|
23
|
+
/** Weight of the newest RTT sample in the smoothed estimate. Low enough that one unlucky
|
|
24
|
+
* sample cannot swing the deadline, high enough to follow a real change. */
|
|
25
|
+
const RTT_SMOOTHING_ALPHA = .3;
|
|
26
|
+
/** Login runs password hashing (and possibly an external IdP round trip) server side, so
|
|
27
|
+
* it is legitimately slow and gets its own deadline instead of the unary default. */
|
|
28
|
+
const DEFAULT_LOGIN_TIMEOUT = 12e4;
|
|
29
|
+
/** Unary deadline for a given RTT estimate: floored by the configured timeout so a fast
|
|
30
|
+
* link behaves exactly as before, capped by {@link MAX_ADAPTIVE_REQUEST_TIMEOUT}.
|
|
31
|
+
* An undefined `rttMs` (no ping yet) yields the configured value unchanged. */
|
|
32
|
+
function deriveUnaryDeadline(configuredMs, rttMs) {
|
|
33
|
+
if (rttMs === void 0) return configuredMs;
|
|
34
|
+
return Math.min(MAX_ADAPTIVE_REQUEST_TIMEOUT, Math.max(configuredMs, Math.ceil(rttMs * 8)));
|
|
35
|
+
}
|
|
36
|
+
/** Folds a new round-trip sample into an exponentially smoothed estimate. */
|
|
37
|
+
function smoothRtt(previousMs, sampleMs) {
|
|
38
|
+
if (previousMs === void 0) return sampleMs;
|
|
39
|
+
return previousMs * (1 - RTT_SMOOTHING_ALPHA) + sampleMs * RTT_SMOOTHING_ALPHA;
|
|
40
|
+
}
|
|
15
41
|
const DefaultRetryOptions = {
|
|
16
42
|
type: "exponentialBackoff",
|
|
17
43
|
maxAttempts: 21,
|
|
18
44
|
initialDelay: 20,
|
|
19
45
|
backoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,
|
|
20
|
-
jitter: DEFAULT_RETRY_JITTER
|
|
46
|
+
jitter: DEFAULT_RETRY_JITTER,
|
|
47
|
+
maxDelay: DEFAULT_RETRY_MAX_DELAY
|
|
21
48
|
};
|
|
22
49
|
function parseInt(s) {
|
|
23
50
|
if (!s) return void 0;
|
|
@@ -45,6 +72,7 @@ function plAddressToConfig(address, overrides = {}) {
|
|
|
45
72
|
retryExponentialBackoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,
|
|
46
73
|
retryLinearBackoffStep: 50,
|
|
47
74
|
retryJitter: DEFAULT_RETRY_JITTER,
|
|
75
|
+
retryMaxDelay: DEFAULT_RETRY_MAX_DELAY,
|
|
48
76
|
...overrides
|
|
49
77
|
};
|
|
50
78
|
const url = new URL(address);
|
|
@@ -84,6 +112,7 @@ function plAddressToConfig(address, overrides = {}) {
|
|
|
84
112
|
retryExponentialBackoffMultiplier: parseInt(url.searchParams.get("retry-exp-backoff-multiplier")) ?? 1.5,
|
|
85
113
|
retryLinearBackoffStep: parseInt(url.searchParams.get("retry-linear-backoff-step")) ?? 50,
|
|
86
114
|
retryJitter: parseInt(url.searchParams.get("retry-backoff-jitter")) ?? .3,
|
|
115
|
+
retryMaxDelay: parseInt(url.searchParams.get("retry-max-delay")) ?? 5e3,
|
|
87
116
|
...overrides
|
|
88
117
|
};
|
|
89
118
|
}
|
|
@@ -91,6 +120,7 @@ const AnonymousAuthInformation = {};
|
|
|
91
120
|
//#endregion
|
|
92
121
|
exports.AnonymousAuthInformation = AnonymousAuthInformation;
|
|
93
122
|
exports.DEFAULT_AUTH_MAX_REFRESH = DEFAULT_AUTH_MAX_REFRESH;
|
|
123
|
+
exports.DEFAULT_LOGIN_TIMEOUT = DEFAULT_LOGIN_TIMEOUT;
|
|
94
124
|
exports.DEFAULT_MAX_CACHE_BYTES = DEFAULT_MAX_CACHE_BYTES;
|
|
95
125
|
exports.DEFAULT_REQUEST_TIMEOUT = DEFAULT_REQUEST_TIMEOUT;
|
|
96
126
|
exports.DEFAULT_RETRY_BACKOFF_ALGORITHM = DEFAULT_RETRY_BACKOFF_ALGORITHM;
|
|
@@ -99,11 +129,17 @@ exports.DEFAULT_RETRY_INITIAL_DELAY = DEFAULT_RETRY_INITIAL_DELAY;
|
|
|
99
129
|
exports.DEFAULT_RETRY_JITTER = DEFAULT_RETRY_JITTER;
|
|
100
130
|
exports.DEFAULT_RETRY_LINEAR_BACKOFF_STEP = DEFAULT_RETRY_LINEAR_BACKOFF_STEP;
|
|
101
131
|
exports.DEFAULT_RETRY_MAX_ATTEMPTS = DEFAULT_RETRY_MAX_ATTEMPTS;
|
|
132
|
+
exports.DEFAULT_RETRY_MAX_DELAY = DEFAULT_RETRY_MAX_DELAY;
|
|
102
133
|
exports.DEFAULT_RO_TX_TIMEOUT = DEFAULT_RO_TX_TIMEOUT;
|
|
103
134
|
exports.DEFAULT_RW_TX_TIMEOUT = DEFAULT_RW_TX_TIMEOUT;
|
|
104
135
|
exports.DEFAULT_TOKEN_TTL_SECONDS = DEFAULT_TOKEN_TTL_SECONDS;
|
|
105
136
|
exports.DefaultRetryOptions = DefaultRetryOptions;
|
|
137
|
+
exports.MAX_ADAPTIVE_REQUEST_TIMEOUT = MAX_ADAPTIVE_REQUEST_TIMEOUT;
|
|
138
|
+
exports.RTT_DEADLINE_MULTIPLIER = RTT_DEADLINE_MULTIPLIER;
|
|
139
|
+
exports.RTT_SMOOTHING_ALPHA = RTT_SMOOTHING_ALPHA;
|
|
106
140
|
exports.SUPPORTED_WIRE_PROTOCOLS = SUPPORTED_WIRE_PROTOCOLS;
|
|
141
|
+
exports.deriveUnaryDeadline = deriveUnaryDeadline;
|
|
107
142
|
exports.plAddressToConfig = plAddressToConfig;
|
|
143
|
+
exports.smoothRtt = smoothRtt;
|
|
108
144
|
|
|
109
145
|
//# sourceMappingURL=config.cjs.map
|
package/dist/core/config.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/core/config.ts"],"sourcesContent":["import type { ProxySettings } from \"@milaboratories/pl-http\";\nimport type { ExponentialBackoffRetryOptions } from \"@milaboratories/ts-helpers\";\n\nexport const SUPPORTED_WIRE_PROTOCOLS = [\"grpc\", \"rest\"] as const;\nexport type wireProtocol = (typeof SUPPORTED_WIRE_PROTOCOLS)[number];\n\n/** Base configuration structure for PL client */\nexport interface PlClientConfig {\n /** Port and host of remote pl server */\n hostAndPort: string;\n\n /** Type of wire connection (protocol server uses for communication) */\n wireProtocol?: wireProtocol;\n\n /** If set, client will expose a nested object under a field with name `alternative_root_${alternativeRoot}` as a\n * client root. */\n alternativeRoot?: string;\n\n /** Admin-only impersonation: if set, the client opens the root of the user with this login instead of the\n * caller's own root. The backend authorizes this by role and silently falls back to the caller's own root for\n * regular users, so it is safe without a client-side gate. Mutually exclusive with {@link alternativeRoot}. */\n asUser?: string;\n\n /** If true, client will establish tls connection to the server, using default\n * CA of node instance. */\n // Not implementing custom ssl validation logic for now.\n // Implementing it in a correct way is really nontrivial thing,\n // real use-cases should be considered.\n ssl: boolean;\n\n /** Default timeout in milliseconds for unary calls, like ping and login. */\n defaultRequestTimeout: number;\n\n /** Default timeout in milliseconds for read-write transaction, should be\n * adjusted for long round-trip or low bandwidth connections. */\n defaultRWTransactionTimeout: number;\n /** Default timeout in milliseconds for read-only transaction, should be\n * adjusted for long round-trip or low bandwidth connections. */\n defaultROTransactionTimeout: number;\n\n /** Controls what TTL will be requested from the server, when new JWT token\n * is requested. */\n authTTLSeconds: number;\n /** If token is older than this time, it will be refreshed regardless of its\n * expiration time. */\n authMaxRefreshSeconds: number;\n\n /** Proxy server config to use for pl connection. */\n grpcProxy?: string | ProxySettings;\n /** Proxy server config to use for http connections of pl drivers, like file\n * downloading. */\n httpProxy?: string | ProxySettings;\n\n /** Username extracted from pl URL. Ignored by {@link PlClient}, picked up by {@link defaultPlClient}. */\n user?: string;\n /** Password extracted from pl URL. Ignored by {@link PlClient}, picked up by {@link defaultPlClient}. */\n password?: string;\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n forceSync: boolean;\n\n /** Maximal number of bytes of resource state to cache */\n maxCacheBytes: number;\n\n //\n // Retry\n //\n\n /**\n * What type of backoff strategy to use in transaction retries\n * (pl uses optimistic transaction model with regular retries in write transactions)\n */\n retryBackoffAlgorithm: \"exponential\" | \"linear\";\n\n /** Maximal number of attempts in */\n retryMaxAttempts: number;\n\n /** Delay after first failed attempt, in ms. */\n retryInitialDelay: number;\n\n /** Each time delay will be multiplied by this number (1.5 means plus on 50% each attempt) */\n retryExponentialBackoffMultiplier: number;\n\n /** [used only for ] This value will be added to the delay from the previous step, in ms */\n retryLinearBackoffStep: number;\n\n /** Value from 0 to 1, determine level of randomness to introduce to the backoff delays sequence. (0 meaning no randomness) */\n retryJitter: number;\n}\n\nexport const DEFAULT_REQUEST_TIMEOUT = 5_000;\nexport const DEFAULT_RO_TX_TIMEOUT = 300_000;\nexport const DEFAULT_RW_TX_TIMEOUT = 60_000;\nexport const DEFAULT_TOKEN_TTL_SECONDS = 31 * 24 * 60 * 60;\nexport const DEFAULT_AUTH_MAX_REFRESH = 12 * 24 * 60 * 60;\n\nexport const DEFAULT_MAX_CACHE_BYTES = 128_000_000; // 128 Mb\n\nexport const DEFAULT_RETRY_BACKOFF_ALGORITHM = \"exponential\";\nexport const DEFAULT_RETRY_MAX_ATTEMPTS = 21; // 1st attempt + 20 retries\nexport const DEFAULT_RETRY_INITIAL_DELAY = 20; // 20 ms * <jitter> of sleep after first failure\nexport const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 1.5; // + 50% on each round\nexport const DEFAULT_RETRY_LINEAR_BACKOFF_STEP = 50; // + 50 ms\nexport const DEFAULT_RETRY_JITTER = 0.3; // 30%\n\nexport const DefaultRetryOptions: ExponentialBackoffRetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,\n initialDelay: DEFAULT_RETRY_INITIAL_DELAY,\n backoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n jitter: DEFAULT_RETRY_JITTER,\n};\n\ntype PlConfigOverrides = Partial<\n Pick<\n PlClientConfig,\n | \"ssl\"\n | \"defaultRequestTimeout\"\n | \"defaultROTransactionTimeout\"\n | \"defaultRWTransactionTimeout\"\n | \"httpProxy\"\n | \"grpcProxy\"\n >\n>;\n\nfunction parseInt(s: string | null | undefined): number | undefined {\n if (!s) return undefined;\n const num = Number(s);\n if (Number.isNaN(num)) throw new Error(`Can't parse number: ${s}`);\n return num;\n}\n\n/** Parses pl url and creates a config object that can be passed to\n * {@link PlClient} of {@link UnauthenticatedPlClient}. */\nexport function plAddressToConfig(\n address: string,\n overrides: PlConfigOverrides = {},\n): PlClientConfig {\n if (address.indexOf(\"://\") === -1)\n // non-url address\n return {\n hostAndPort: address,\n ssl: false,\n defaultRequestTimeout: DEFAULT_REQUEST_TIMEOUT,\n defaultROTransactionTimeout: DEFAULT_RO_TX_TIMEOUT,\n defaultRWTransactionTimeout: DEFAULT_RW_TX_TIMEOUT,\n authTTLSeconds: DEFAULT_TOKEN_TTL_SECONDS,\n authMaxRefreshSeconds: DEFAULT_AUTH_MAX_REFRESH,\n txDelay: 0,\n forceSync: false,\n\n maxCacheBytes: DEFAULT_MAX_CACHE_BYTES,\n\n retryBackoffAlgorithm: DEFAULT_RETRY_BACKOFF_ALGORITHM,\n retryMaxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,\n retryInitialDelay: DEFAULT_RETRY_INITIAL_DELAY,\n retryExponentialBackoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n retryLinearBackoffStep: DEFAULT_RETRY_LINEAR_BACKOFF_STEP,\n retryJitter: DEFAULT_RETRY_JITTER,\n\n ...overrides,\n };\n\n const url = new URL(address);\n\n if (\n url.protocol !== \"https:\" &&\n url.protocol !== \"http:\" &&\n url.protocol !== \"grpc:\" &&\n url.protocol !== \"tls:\"\n )\n throw new Error(`Unexpected URL schema: ${url.protocol}`);\n\n if (url.pathname !== \"/\" && url.pathname !== \"\")\n throw new Error(`Unexpected URL path: ${url.pathname}`);\n\n let port = url.port;\n if (!port) {\n switch (url.protocol) {\n case \"http:\":\n port = \"80\";\n break;\n case \"https:\":\n port = \"443\";\n break;\n default:\n throw new Error(`Port must be specified explicitly for ${url.protocol} protocol.`);\n }\n }\n\n return {\n hostAndPort: `${url.hostname}:${port}`,\n alternativeRoot: url.searchParams.get(\"alternative-root\") ?? undefined,\n asUser: url.searchParams.get(\"as-user\") || undefined,\n ssl: url.protocol === \"https:\" || url.protocol === \"tls:\",\n\n wireProtocol: (url.searchParams.get(\"wire-protocol\") as wireProtocol) ?? undefined,\n\n defaultRequestTimeout:\n parseInt(url.searchParams.get(\"request-timeout\")) ?? DEFAULT_REQUEST_TIMEOUT,\n defaultROTransactionTimeout:\n parseInt(url.searchParams.get(\"ro-tx-timeout\")) ??\n parseInt(url.searchParams.get(\"tx-timeout\")) ??\n DEFAULT_RO_TX_TIMEOUT,\n defaultRWTransactionTimeout:\n parseInt(url.searchParams.get(\"rw-tx-timeout\")) ??\n parseInt(url.searchParams.get(\"tx-timeout\")) ??\n DEFAULT_RW_TX_TIMEOUT,\n authTTLSeconds: DEFAULT_TOKEN_TTL_SECONDS,\n authMaxRefreshSeconds: DEFAULT_AUTH_MAX_REFRESH,\n grpcProxy: url.searchParams.get(\"grpc-proxy\") ?? undefined,\n httpProxy: url.searchParams.get(\"http-proxy\") ?? undefined,\n user: url.username === \"\" ? undefined : url.username,\n password: url.password === \"\" ? undefined : url.password,\n txDelay: parseInt(url.searchParams.get(\"tx-delay\")) ?? 0,\n forceSync: Boolean(url.searchParams.get(\"force-sync\")),\n\n maxCacheBytes: parseInt(url.searchParams.get(\"max-cache-bytes\")) ?? DEFAULT_MAX_CACHE_BYTES,\n\n retryBackoffAlgorithm: (url.searchParams.get(\"retry-backoff-algorithm\") ??\n DEFAULT_RETRY_BACKOFF_ALGORITHM) as any,\n retryMaxAttempts:\n parseInt(url.searchParams.get(\"retry-max-attempts\")) ?? DEFAULT_RETRY_MAX_ATTEMPTS,\n retryInitialDelay:\n parseInt(url.searchParams.get(\"retry-initial-delay\")) ?? DEFAULT_RETRY_INITIAL_DELAY,\n retryExponentialBackoffMultiplier:\n parseInt(url.searchParams.get(\"retry-exp-backoff-multiplier\")) ??\n DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n retryLinearBackoffStep:\n parseInt(url.searchParams.get(\"retry-linear-backoff-step\")) ??\n DEFAULT_RETRY_LINEAR_BACKOFF_STEP,\n retryJitter: parseInt(url.searchParams.get(\"retry-backoff-jitter\")) ?? DEFAULT_RETRY_JITTER,\n\n ...overrides,\n };\n}\n\n/**\n * Authorization data / JWT Token.\n * Absent JWT Token tells the client to connect as anonymous user.\n */\nexport interface AuthInformation {\n /** Absent token means anonymous access */\n jwtToken?: string;\n}\n\nexport const AnonymousAuthInformation: AuthInformation = {};\n\n/** Authorization related settings to pass to {@link PlClient}. */\nexport interface AuthOps {\n /** Initial authorization information */\n authInformation: AuthInformation;\n /** Will be executed after successful authorization information refresh */\n readonly onUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n readonly onAuthError?: () => void;\n /** Will be executed if error encountered during token update */\n readonly onUpdateError?: (error: unknown) => void;\n}\n\n/** Connection status. */\nexport type PlConnectionStatus = \"OK\" | \"Disconnected\" | \"Unauthenticated\";\n\n/** Listener that will be called each time connection status changes. */\nexport type PlConnectionStatusListener = (status: PlConnectionStatus) => void;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.cjs","names":[],"sources":["../../src/core/config.ts"],"sourcesContent":["import type { ProxySettings } from \"@milaboratories/pl-http\";\nimport type { ExponentialBackoffRetryOptions } from \"@milaboratories/ts-helpers\";\n\nexport const SUPPORTED_WIRE_PROTOCOLS = [\"grpc\", \"rest\"] as const;\nexport type wireProtocol = (typeof SUPPORTED_WIRE_PROTOCOLS)[number];\n\n/** Base configuration structure for PL client */\nexport interface PlClientConfig {\n /** Port and host of remote pl server */\n hostAndPort: string;\n\n /** Type of wire connection (protocol server uses for communication) */\n wireProtocol?: wireProtocol;\n\n /** If set, client will expose a nested object under a field with name `alternative_root_${alternativeRoot}` as a\n * client root. */\n alternativeRoot?: string;\n\n /** Admin-only impersonation: if set, the client opens the root of the user with this login instead of the\n * caller's own root. The backend authorizes this by role and silently falls back to the caller's own root for\n * regular users, so it is safe without a client-side gate. Mutually exclusive with {@link alternativeRoot}. */\n asUser?: string;\n\n /** If true, client will establish tls connection to the server, using default\n * CA of node instance. */\n // Not implementing custom ssl validation logic for now.\n // Implementing it in a correct way is really nontrivial thing,\n // real use-cases should be considered.\n ssl: boolean;\n\n /** Default timeout in milliseconds for unary calls, like ping and login. */\n defaultRequestTimeout: number;\n\n /** Default timeout in milliseconds for read-write transaction, should be\n * adjusted for long round-trip or low bandwidth connections. */\n defaultRWTransactionTimeout: number;\n /** Default timeout in milliseconds for read-only transaction, should be\n * adjusted for long round-trip or low bandwidth connections. */\n defaultROTransactionTimeout: number;\n\n /** Controls what TTL will be requested from the server, when new JWT token\n * is requested. */\n authTTLSeconds: number;\n /** If token is older than this time, it will be refreshed regardless of its\n * expiration time. */\n authMaxRefreshSeconds: number;\n\n /** Proxy server config to use for pl connection. */\n grpcProxy?: string | ProxySettings;\n /** Proxy server config to use for http connections of pl drivers, like file\n * downloading. */\n httpProxy?: string | ProxySettings;\n\n /** Username extracted from pl URL. Ignored by {@link PlClient}, picked up by {@link defaultPlClient}. */\n user?: string;\n /** Password extracted from pl URL. Ignored by {@link PlClient}, picked up by {@link defaultPlClient}. */\n password?: string;\n\n /** Artificial delay introduced after write transactions completion, to\n * somewhat throttle the load on pl. Delay introduced after sync, if requested. */\n txDelay: number;\n\n /** Last resort measure to solve complicated race conditions in pl. */\n forceSync: boolean;\n\n /** Maximal number of bytes of resource state to cache */\n maxCacheBytes: number;\n\n //\n // Retry\n //\n\n /**\n * What type of backoff strategy to use in transaction retries\n * (pl uses optimistic transaction model with regular retries in write transactions)\n */\n retryBackoffAlgorithm: \"exponential\" | \"linear\";\n\n /** Maximal number of attempts in */\n retryMaxAttempts: number;\n\n /** Delay after first failed attempt, in ms. */\n retryInitialDelay: number;\n\n /** Each time delay will be multiplied by this number (1.5 means plus on 50% each attempt) */\n retryExponentialBackoffMultiplier: number;\n\n /** [used only for ] This value will be added to the delay from the previous step, in ms */\n retryLinearBackoffStep: number;\n\n /** Value from 0 to 1, determine level of randomness to introduce to the backoff delays sequence. (0 meaning no randomness) */\n retryJitter: number;\n\n /** Upper bound for a single backoff delay, in ms. Without it the exponential\n * sequence keeps growing across all attempts: at the defaults the last sleep\n * alone reaches ~66s (~150s once jitter compounds). */\n retryMaxDelay: number;\n}\n\nexport const DEFAULT_REQUEST_TIMEOUT = 5_000;\nexport const DEFAULT_RO_TX_TIMEOUT = 300_000;\nexport const DEFAULT_RW_TX_TIMEOUT = 60_000;\nexport const DEFAULT_TOKEN_TTL_SECONDS = 31 * 24 * 60 * 60;\nexport const DEFAULT_AUTH_MAX_REFRESH = 12 * 24 * 60 * 60;\n\nexport const DEFAULT_MAX_CACHE_BYTES = 128_000_000; // 128 Mb\n\nexport const DEFAULT_RETRY_BACKOFF_ALGORITHM = \"exponential\";\nexport const DEFAULT_RETRY_MAX_ATTEMPTS = 21; // 1st attempt + 20 retries\nexport const DEFAULT_RETRY_INITIAL_DELAY = 20; // 20 ms * <jitter> of sleep after first failure\nexport const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 1.5; // + 50% on each round\nexport const DEFAULT_RETRY_LINEAR_BACKOFF_STEP = 50; // + 50 ms\nexport const DEFAULT_RETRY_JITTER = 0.3; // 30%\nexport const DEFAULT_RETRY_MAX_DELAY = 5_000; // 5 seconds\n\n/** Multiplier applied to the observed RTT when deriving the unary deadline. A unary call\n * costs at least one round trip, plus connect/DNS/LB and server work on top, so the\n * deadline needs sizeable headroom over the bare RTT. */\nexport const RTT_DEADLINE_MULTIPLIER = 8;\n\n/** Ceiling for the RTT-derived unary deadline. Beyond this a call is stuck rather than\n * slow, and waiting longer only delays the retry. */\nexport const MAX_ADAPTIVE_REQUEST_TIMEOUT = 60_000;\n\n/** Weight of the newest RTT sample in the smoothed estimate. Low enough that one unlucky\n * sample cannot swing the deadline, high enough to follow a real change. */\nexport const RTT_SMOOTHING_ALPHA = 0.3;\n\n/** Login runs password hashing (and possibly an external IdP round trip) server side, so\n * it is legitimately slow and gets its own deadline instead of the unary default. */\nexport const DEFAULT_LOGIN_TIMEOUT = 120_000;\n\n/** Unary deadline for a given RTT estimate: floored by the configured timeout so a fast\n * link behaves exactly as before, capped by {@link MAX_ADAPTIVE_REQUEST_TIMEOUT}.\n * An undefined `rttMs` (no ping yet) yields the configured value unchanged. */\nexport function deriveUnaryDeadline(configuredMs: number, rttMs: number | undefined): number {\n if (rttMs === undefined) return configuredMs;\n return Math.min(\n MAX_ADAPTIVE_REQUEST_TIMEOUT,\n Math.max(configuredMs, Math.ceil(rttMs * RTT_DEADLINE_MULTIPLIER)),\n );\n}\n\n/** Folds a new round-trip sample into an exponentially smoothed estimate. */\nexport function smoothRtt(previousMs: number | undefined, sampleMs: number): number {\n if (previousMs === undefined) return sampleMs;\n return previousMs * (1 - RTT_SMOOTHING_ALPHA) + sampleMs * RTT_SMOOTHING_ALPHA;\n}\n\nexport const DefaultRetryOptions: ExponentialBackoffRetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,\n initialDelay: DEFAULT_RETRY_INITIAL_DELAY,\n backoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n jitter: DEFAULT_RETRY_JITTER,\n maxDelay: DEFAULT_RETRY_MAX_DELAY,\n};\n\ntype PlConfigOverrides = Partial<\n Pick<\n PlClientConfig,\n | \"ssl\"\n | \"defaultRequestTimeout\"\n | \"defaultROTransactionTimeout\"\n | \"defaultRWTransactionTimeout\"\n | \"httpProxy\"\n | \"grpcProxy\"\n >\n>;\n\nfunction parseInt(s: string | null | undefined): number | undefined {\n if (!s) return undefined;\n const num = Number(s);\n if (Number.isNaN(num)) throw new Error(`Can't parse number: ${s}`);\n return num;\n}\n\n/** Parses pl url and creates a config object that can be passed to\n * {@link PlClient} of {@link UnauthenticatedPlClient}. */\nexport function plAddressToConfig(\n address: string,\n overrides: PlConfigOverrides = {},\n): PlClientConfig {\n if (address.indexOf(\"://\") === -1)\n // non-url address\n return {\n hostAndPort: address,\n ssl: false,\n defaultRequestTimeout: DEFAULT_REQUEST_TIMEOUT,\n defaultROTransactionTimeout: DEFAULT_RO_TX_TIMEOUT,\n defaultRWTransactionTimeout: DEFAULT_RW_TX_TIMEOUT,\n authTTLSeconds: DEFAULT_TOKEN_TTL_SECONDS,\n authMaxRefreshSeconds: DEFAULT_AUTH_MAX_REFRESH,\n txDelay: 0,\n forceSync: false,\n\n maxCacheBytes: DEFAULT_MAX_CACHE_BYTES,\n\n retryBackoffAlgorithm: DEFAULT_RETRY_BACKOFF_ALGORITHM,\n retryMaxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,\n retryInitialDelay: DEFAULT_RETRY_INITIAL_DELAY,\n retryExponentialBackoffMultiplier: DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n retryLinearBackoffStep: DEFAULT_RETRY_LINEAR_BACKOFF_STEP,\n retryJitter: DEFAULT_RETRY_JITTER,\n retryMaxDelay: DEFAULT_RETRY_MAX_DELAY,\n\n ...overrides,\n };\n\n const url = new URL(address);\n\n if (\n url.protocol !== \"https:\" &&\n url.protocol !== \"http:\" &&\n url.protocol !== \"grpc:\" &&\n url.protocol !== \"tls:\"\n )\n throw new Error(`Unexpected URL schema: ${url.protocol}`);\n\n if (url.pathname !== \"/\" && url.pathname !== \"\")\n throw new Error(`Unexpected URL path: ${url.pathname}`);\n\n let port = url.port;\n if (!port) {\n switch (url.protocol) {\n case \"http:\":\n port = \"80\";\n break;\n case \"https:\":\n port = \"443\";\n break;\n default:\n throw new Error(`Port must be specified explicitly for ${url.protocol} protocol.`);\n }\n }\n\n return {\n hostAndPort: `${url.hostname}:${port}`,\n alternativeRoot: url.searchParams.get(\"alternative-root\") ?? undefined,\n asUser: url.searchParams.get(\"as-user\") || undefined,\n ssl: url.protocol === \"https:\" || url.protocol === \"tls:\",\n\n wireProtocol: (url.searchParams.get(\"wire-protocol\") as wireProtocol) ?? undefined,\n\n defaultRequestTimeout:\n parseInt(url.searchParams.get(\"request-timeout\")) ?? DEFAULT_REQUEST_TIMEOUT,\n defaultROTransactionTimeout:\n parseInt(url.searchParams.get(\"ro-tx-timeout\")) ??\n parseInt(url.searchParams.get(\"tx-timeout\")) ??\n DEFAULT_RO_TX_TIMEOUT,\n defaultRWTransactionTimeout:\n parseInt(url.searchParams.get(\"rw-tx-timeout\")) ??\n parseInt(url.searchParams.get(\"tx-timeout\")) ??\n DEFAULT_RW_TX_TIMEOUT,\n authTTLSeconds: DEFAULT_TOKEN_TTL_SECONDS,\n authMaxRefreshSeconds: DEFAULT_AUTH_MAX_REFRESH,\n grpcProxy: url.searchParams.get(\"grpc-proxy\") ?? undefined,\n httpProxy: url.searchParams.get(\"http-proxy\") ?? undefined,\n user: url.username === \"\" ? undefined : url.username,\n password: url.password === \"\" ? undefined : url.password,\n txDelay: parseInt(url.searchParams.get(\"tx-delay\")) ?? 0,\n forceSync: Boolean(url.searchParams.get(\"force-sync\")),\n\n maxCacheBytes: parseInt(url.searchParams.get(\"max-cache-bytes\")) ?? DEFAULT_MAX_CACHE_BYTES,\n\n retryBackoffAlgorithm: (url.searchParams.get(\"retry-backoff-algorithm\") ??\n DEFAULT_RETRY_BACKOFF_ALGORITHM) as any,\n retryMaxAttempts:\n parseInt(url.searchParams.get(\"retry-max-attempts\")) ?? DEFAULT_RETRY_MAX_ATTEMPTS,\n retryInitialDelay:\n parseInt(url.searchParams.get(\"retry-initial-delay\")) ?? DEFAULT_RETRY_INITIAL_DELAY,\n retryExponentialBackoffMultiplier:\n parseInt(url.searchParams.get(\"retry-exp-backoff-multiplier\")) ??\n DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER,\n retryLinearBackoffStep:\n parseInt(url.searchParams.get(\"retry-linear-backoff-step\")) ??\n DEFAULT_RETRY_LINEAR_BACKOFF_STEP,\n retryJitter: parseInt(url.searchParams.get(\"retry-backoff-jitter\")) ?? DEFAULT_RETRY_JITTER,\n retryMaxDelay: parseInt(url.searchParams.get(\"retry-max-delay\")) ?? DEFAULT_RETRY_MAX_DELAY,\n\n ...overrides,\n };\n}\n\n/**\n * Authorization data / JWT Token.\n * Absent JWT Token tells the client to connect as anonymous user.\n */\nexport interface AuthInformation {\n /** Absent token means anonymous access */\n jwtToken?: string;\n}\n\nexport const AnonymousAuthInformation: AuthInformation = {};\n\n/** Authorization related settings to pass to {@link PlClient}. */\nexport interface AuthOps {\n /** Initial authorization information */\n authInformation: AuthInformation;\n /** Will be executed after successful authorization information refresh */\n readonly onUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n readonly onAuthError?: () => void;\n /** Will be executed if error encountered during token update */\n readonly onUpdateError?: (error: unknown) => void;\n}\n\n/** Connection status. */\nexport type PlConnectionStatus = \"OK\" | \"Disconnected\" | \"Unauthenticated\";\n\n/** Listener that will be called each time connection status changes. */\nexport type PlConnectionStatusListener = (status: PlConnectionStatus) => void;\n"],"mappings":"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"}
|
package/dist/core/config.d.ts
CHANGED
|
@@ -65,6 +65,10 @@ interface PlClientConfig {
|
|
|
65
65
|
retryLinearBackoffStep: number;
|
|
66
66
|
/** Value from 0 to 1, determine level of randomness to introduce to the backoff delays sequence. (0 meaning no randomness) */
|
|
67
67
|
retryJitter: number;
|
|
68
|
+
/** Upper bound for a single backoff delay, in ms. Without it the exponential
|
|
69
|
+
* sequence keeps growing across all attempts: at the defaults the last sleep
|
|
70
|
+
* alone reaches ~66s (~150s once jitter compounds). */
|
|
71
|
+
retryMaxDelay: number;
|
|
68
72
|
}
|
|
69
73
|
declare const DEFAULT_REQUEST_TIMEOUT = 5000;
|
|
70
74
|
declare const DEFAULT_RO_TX_TIMEOUT = 300000;
|
|
@@ -78,6 +82,26 @@ declare const DEFAULT_RETRY_INITIAL_DELAY = 20;
|
|
|
78
82
|
declare const DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER = 1.5;
|
|
79
83
|
declare const DEFAULT_RETRY_LINEAR_BACKOFF_STEP = 50;
|
|
80
84
|
declare const DEFAULT_RETRY_JITTER = 0.3;
|
|
85
|
+
declare const DEFAULT_RETRY_MAX_DELAY = 5000;
|
|
86
|
+
/** Multiplier applied to the observed RTT when deriving the unary deadline. A unary call
|
|
87
|
+
* costs at least one round trip, plus connect/DNS/LB and server work on top, so the
|
|
88
|
+
* deadline needs sizeable headroom over the bare RTT. */
|
|
89
|
+
declare const RTT_DEADLINE_MULTIPLIER = 8;
|
|
90
|
+
/** Ceiling for the RTT-derived unary deadline. Beyond this a call is stuck rather than
|
|
91
|
+
* slow, and waiting longer only delays the retry. */
|
|
92
|
+
declare const MAX_ADAPTIVE_REQUEST_TIMEOUT = 60000;
|
|
93
|
+
/** Weight of the newest RTT sample in the smoothed estimate. Low enough that one unlucky
|
|
94
|
+
* sample cannot swing the deadline, high enough to follow a real change. */
|
|
95
|
+
declare const RTT_SMOOTHING_ALPHA = 0.3;
|
|
96
|
+
/** Login runs password hashing (and possibly an external IdP round trip) server side, so
|
|
97
|
+
* it is legitimately slow and gets its own deadline instead of the unary default. */
|
|
98
|
+
declare const DEFAULT_LOGIN_TIMEOUT = 120000;
|
|
99
|
+
/** Unary deadline for a given RTT estimate: floored by the configured timeout so a fast
|
|
100
|
+
* link behaves exactly as before, capped by {@link MAX_ADAPTIVE_REQUEST_TIMEOUT}.
|
|
101
|
+
* An undefined `rttMs` (no ping yet) yields the configured value unchanged. */
|
|
102
|
+
declare function deriveUnaryDeadline(configuredMs: number, rttMs: number | undefined): number;
|
|
103
|
+
/** Folds a new round-trip sample into an exponentially smoothed estimate. */
|
|
104
|
+
declare function smoothRtt(previousMs: number | undefined, sampleMs: number): number;
|
|
81
105
|
declare const DefaultRetryOptions: ExponentialBackoffRetryOptions;
|
|
82
106
|
type PlConfigOverrides = Partial<Pick<PlClientConfig, "ssl" | "defaultRequestTimeout" | "defaultROTransactionTimeout" | "defaultRWTransactionTimeout" | "httpProxy" | "grpcProxy">>;
|
|
83
107
|
/** Parses pl url and creates a config object that can be passed to
|
|
@@ -108,5 +132,5 @@ type PlConnectionStatus = "OK" | "Disconnected" | "Unauthenticated";
|
|
|
108
132
|
/** Listener that will be called each time connection status changes. */
|
|
109
133
|
type PlConnectionStatusListener = (status: PlConnectionStatus) => void;
|
|
110
134
|
//#endregion
|
|
111
|
-
export { AnonymousAuthInformation, AuthInformation, AuthOps, DEFAULT_AUTH_MAX_REFRESH, DEFAULT_MAX_CACHE_BYTES, DEFAULT_REQUEST_TIMEOUT, DEFAULT_RETRY_BACKOFF_ALGORITHM, DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY, DEFAULT_RETRY_JITTER, DEFAULT_RETRY_LINEAR_BACKOFF_STEP, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RO_TX_TIMEOUT, DEFAULT_RW_TX_TIMEOUT, DEFAULT_TOKEN_TTL_SECONDS, DefaultRetryOptions, PlClientConfig, PlConnectionStatus, PlConnectionStatusListener, SUPPORTED_WIRE_PROTOCOLS, plAddressToConfig, wireProtocol };
|
|
135
|
+
export { AnonymousAuthInformation, AuthInformation, AuthOps, DEFAULT_AUTH_MAX_REFRESH, DEFAULT_LOGIN_TIMEOUT, DEFAULT_MAX_CACHE_BYTES, DEFAULT_REQUEST_TIMEOUT, DEFAULT_RETRY_BACKOFF_ALGORITHM, DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY, DEFAULT_RETRY_JITTER, DEFAULT_RETRY_LINEAR_BACKOFF_STEP, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY, DEFAULT_RO_TX_TIMEOUT, DEFAULT_RW_TX_TIMEOUT, DEFAULT_TOKEN_TTL_SECONDS, DefaultRetryOptions, MAX_ADAPTIVE_REQUEST_TIMEOUT, PlClientConfig, PlConnectionStatus, PlConnectionStatusListener, RTT_DEADLINE_MULTIPLIER, RTT_SMOOTHING_ALPHA, SUPPORTED_WIRE_PROTOCOLS, deriveUnaryDeadline, plAddressToConfig, smoothRtt, wireProtocol };
|
|
112
136
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/core/config.ts"],"mappings":";;;;cAGa,wBAAA;AAAA,KACD,YAAA,WAAuB,wBAAwB;AAD3D;AAAA,UAIiB,cAAA;;EAEf,WAAA;EAN+D;EAS/D,YAAA,GAAe,YAAA;EARO;;EAYtB,eAAA;EAZyD;AAG3D;;EAcE,MAAA;EATe;;EAgBf,GAAA;EAuBkC;EApBlC,qBAAA;EAtBA;;EA0BA,2BAAA;EAnBA;;EAsBA,2BAAA;EAPA;;EAWA,cAAA;EAAA;;EAGA,qBAAA;EAGqB;EAArB,SAAA,YAAqB,aAAA;EAGA;;EAArB,SAAA,YAAqB,aAAA;EASrB;EANA,IAAA;EAYA;EAVA,QAAA;EAuBA;;EAnBA,OAAA;EA4BA;EAzBA,SAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../../src/core/config.ts"],"mappings":";;;;cAGa,wBAAA;AAAA,KACD,YAAA,WAAuB,wBAAwB;AAD3D;AAAA,UAIiB,cAAA;;EAEf,WAAA;EAN+D;EAS/D,YAAA,GAAe,YAAA;EARO;;EAYtB,eAAA;EAZyD;AAG3D;;EAcE,MAAA;EATe;;EAgBf,GAAA;EAuBkC;EApBlC,qBAAA;EAtBA;;EA0BA,2BAAA;EAnBA;;EAsBA,2BAAA;EAPA;;EAWA,cAAA;EAAA;;EAGA,qBAAA;EAGqB;EAArB,SAAA,YAAqB,aAAA;EAGA;;EAArB,SAAA,YAAqB,aAAA;EASrB;EANA,IAAA;EAYA;EAVA,QAAA;EAuBA;;EAnBA,OAAA;EA4BA;EAzBA,SAAA;EAiCA;EA9BA,aAAA;EA8Ba;AAGf;;;EAvBE,qBAAA;EAuBkC;EApBlC,gBAAA;EAqBgC;EAlBhC,iBAAA;EAkBgC;EAfhC,iCAAA;EAgBW;EAbX,sBAAA;;EAGA,WAAA;EAUgC;AAClC;;EANE,aAAA;AAAA;AAAA,cAGW,uBAAA;AAAA,cACA,qBAAA;AAAA,cACA,qBAAA;AAAA,cACA,yBAAA;AAAA,cACA,wBAAA;AAAA,cAEA,uBAAA;AAAA,cAEA,+BAAA;AAAA,cACA,0BAAA;AAAA,cACA,2BAAA;AAAA,cACA,4CAAA;AAAA,cACA,iCAAA;AAAA,cACA,oBAAA;AAAA,cACA,uBAAA;;;AAN+B;cAW/B,uBAAA;;;cAIA,4BAAA;AAd0B;AACvC;AADuC,cAkB1B,mBAAA;;;cAIA,qBAAA;AApBb;;;AAAA,iBAyBgB,mBAAA,CAAoB,YAAA,UAAsB,KAAyB;AAzB1B;AAAA,iBAkCzC,SAAA,CAAU,UAAA,sBAAgC,QAAgB;AAAA,cAK7D,mBAAA,EAAqB,8BAOjC;AAAA,KAEI,iBAAA,GAAoB,OAAA,CACvB,IAAA,CACE,cAAA;;AAjD0C;iBAoE9B,iBAAA,CACd,OAAA,UACA,SAAA,GAAW,iBAAA,GACV,cAAc;;;;AAtEgB;UAgLhB,eAAA;EA/KmB;EAiLlC,QAAQ;AAAA;AAAA,cAGG,wBAAA,EAA0B,eAAoB;AA/K3D;AAAA,UAkLiB,OAAA;;EAEf,eAAA,EAAiB,eAAA;EApLiB;EAAA,SAsLzB,QAAA,IAAY,OAAA,EAAS,eAAe;EAlLN;EAAA,SAoL9B,WAAA;EApL8B;EAAA,SAsL9B,aAAA,IAAiB,KAAA;AAAA;;KAIhB,kBAAA;;KAGA,0BAAA,IAA8B,MAA0B,EAAlB,kBAAkB"}
|