@milaboratories/pl-client 2.16.1 → 2.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/__external/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.7.0_typescript@5.6.3/__external/tslib/tslib.es6.cjs +61 -0
  2. package/dist/__external/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.7.0_typescript@5.6.3/__external/tslib/tslib.es6.cjs.map +1 -0
  3. package/dist/__external/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.7.0_typescript@5.6.3/__external/tslib/tslib.es6.js +58 -0
  4. package/dist/__external/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.52.4_tslib@2.7.0_typescript@5.6.3/__external/tslib/tslib.es6.js.map +1 -0
  5. package/dist/core/PromiseTracker.cjs +39 -0
  6. package/dist/core/PromiseTracker.cjs.map +1 -0
  7. package/dist/core/PromiseTracker.d.ts +14 -0
  8. package/dist/core/PromiseTracker.d.ts.map +1 -0
  9. package/dist/core/PromiseTracker.js +37 -0
  10. package/dist/core/PromiseTracker.js.map +1 -0
  11. package/dist/core/StatefulPromise.cjs +65 -0
  12. package/dist/core/StatefulPromise.cjs.map +1 -0
  13. package/dist/core/StatefulPromise.d.ts +39 -0
  14. package/dist/core/StatefulPromise.d.ts.map +1 -0
  15. package/dist/core/StatefulPromise.js +63 -0
  16. package/dist/core/StatefulPromise.js.map +1 -0
  17. package/dist/core/ll_transaction.cjs +3 -2
  18. package/dist/core/ll_transaction.cjs.map +1 -1
  19. package/dist/core/ll_transaction.d.ts.map +1 -1
  20. package/dist/core/ll_transaction.js +3 -2
  21. package/dist/core/ll_transaction.js.map +1 -1
  22. package/dist/core/transaction.cjs +577 -515
  23. package/dist/core/transaction.cjs.map +1 -1
  24. package/dist/core/transaction.d.ts +6 -4
  25. package/dist/core/transaction.d.ts.map +1 -1
  26. package/dist/core/transaction.js +578 -516
  27. package/dist/core/transaction.js.map +1 -1
  28. package/dist/test/tcp-proxy.cjs +129 -0
  29. package/dist/test/tcp-proxy.cjs.map +1 -0
  30. package/dist/test/tcp-proxy.d.ts +17 -0
  31. package/dist/test/tcp-proxy.d.ts.map +1 -0
  32. package/dist/test/tcp-proxy.js +107 -0
  33. package/dist/test/tcp-proxy.js.map +1 -0
  34. package/dist/test/test_config.cjs +54 -7
  35. package/dist/test/test_config.cjs.map +1 -1
  36. package/dist/test/test_config.d.ts +18 -2
  37. package/dist/test/test_config.d.ts.map +1 -1
  38. package/dist/test/test_config.js +54 -7
  39. package/dist/test/test_config.js.map +1 -1
  40. package/package.json +7 -8
  41. package/src/core/PromiseTracker.ts +39 -0
  42. package/src/core/StatefulPromise.ts +92 -0
  43. package/src/core/client.test.ts +1 -1
  44. package/src/core/config.test.ts +1 -1
  45. package/src/core/connectivity.test.ts +70 -0
  46. package/src/core/error.test.ts +1 -1
  47. package/src/core/ll_client.test.ts +1 -1
  48. package/src/core/ll_transaction.test.ts +1 -1
  49. package/src/core/ll_transaction.ts +3 -2
  50. package/src/core/transaction.test.ts +6 -1
  51. package/src/core/transaction.ts +91 -48
  52. package/src/core/types.test.ts +1 -1
  53. package/src/core/unauth_client.test.ts +1 -1
  54. package/src/helpers/rich_resource_types.test.ts +1 -1
  55. package/src/test/tcp-proxy.ts +126 -0
  56. package/src/test/test_config.test.ts +1 -1
  57. package/src/test/test_config.ts +82 -7
  58. package/src/util/util.test.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.cjs","sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport { LLPlClient } from '../core/ll_client';\nimport type { AuthInformation, AuthOps, PlClientConfig } from '../core/config';\nimport { plAddressToConfig } from '../core/config';\nimport { UnauthenticatedPlClient } from '../core/unauth_client';\nimport { PlClient } from '../core/client';\nimport { randomUUID } from 'node:crypto';\nimport type { OptionalResourceId } from '../core/types';\nimport { NullResourceId, resourceIdToString } from '../core/types';\nimport { inferAuthRefreshTime } from '../core/auth';\nimport * as path from 'node:path';\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = 'test_config.json';\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve('.test_auth.json');\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: 'utf-8' })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(tConf: TestConfig): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n 'utf8',\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n console.warn(`Removing: ${getFullAuthDataFilePath()}`);\n fs.rmSync(getFullAuthDataFilePath());\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // try recover from cache\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: 'utf-8' }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address\n && cache.conf.test_user === tConf.test_user\n && cache.conf.test_password === tConf.test_password\n && cache.expiration > Date.now()\n )\n authInformation = cache.authInformation;\n } catch (_e) {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const plConf = plAddressToConfig(tConf.address);\n\n const uClient = new UnauthenticatedPlClient(plConf);\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return new LLPlClient({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(alternativeRoot?: string) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error('test pl address configured with alternative root');\n return await PlClient.init({ ...conf, alternativeRoot }, auth);\n}\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalResourceId = NullResourceId;\n try {\n const client = await getTestClient(alternativeRoot);\n altRootId = client.clientRoot;\n const value = await body(client);\n const rawClient = await getTestClient();\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n return value;\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw new Error(err.message, { cause: err });\n }\n}\n"],"names":["path","fs","randomUUID","inferAuthRefreshTime","plAddressToConfig","UnauthenticatedPlClient","LLPlClient","PlClient","NullResourceId","resourceIdToString"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAM,WAAW,GAAG,kBAAkB;AACtC;AAEA,IAAI,gBAAoC;AAExC,SAAS,uBAAuB,GAAA;IAC9B,IAAI,gBAAgB,KAAK,SAAS;AAAE,QAAA,gBAAgB,GAAGA,eAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC;AACtF,IAAA,OAAO,gBAAgB;AACzB;SAEgB,aAAa,GAAA;IAC3B,IAAI,IAAI,GAAwB,EAAE;AAClC,IAAA,IAAIC,aAAE,CAAC,UAAU,CAAC,WAAW,CAAC;AAC5B,QAAA,IAAI,GAAG,IAAI,CAAC,KAAK,CAACA,aAAE,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAe;AAEtF,IAAA,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,SAAS;QAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU;AAE/E,IAAA,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS;QAAE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY;AAErF,IAAA,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,SAAS;QAAE,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB;AAEjG,IAAA,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,SAAS;QAAE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa;AAExF,IAAA,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;AAC5B,QAAA,MAAM,IAAI,KAAK,CACb,2CAA2C,WAAW,CAAA,qCAAA,CAAuC,CAC9F;AAEH,IAAA,OAAO,IAAkB;AAC3B;AASA,SAAS,oBAAoB,CAAC,KAAiB,EAAA;IAC7C,OAAO,CAAC,eAAe,KAAI;AACzB,QAAA,MAAM,GAAG,GAAG,uBAAuB,EAAE;AACrC,QAAA,MAAM,MAAM,GAAG,uBAAuB,EAAE,GAAGC,sBAAU,EAAE;AACvD,QAAAD,aAAE,CAAC,aAAa,CACd,MAAM,EACN,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,SAAS,CAAC;AACb,YAAA,IAAI,EAAE,KAAK;YACX,eAAe;YACf,UAAU,EAAEE,yBAAoB,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AACnD,SAAA,CAAC,CAChB,EACD,MAAM,CACP;AACD,QAAAF,aAAE,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC;AAC5B,IAAA,CAAC;AACH;AAEA,MAAM,qBAAqB,GAAG,MAAK;IACjC,OAAO,CAAC,IAAI,CAAC,CAAA,UAAA,EAAa,uBAAuB,EAAE,CAAA,CAAE,CAAC;AACtD,IAAAA,aAAE,CAAC,MAAM,CAAC,uBAAuB,EAAE,CAAC;AACtC,CAAC;AAEM,eAAe,iBAAiB,GAAA;AACrC,IAAA,MAAM,KAAK,GAAG,aAAa,EAAE;IAE7B,IAAI,eAAe,GAAgC,SAAS;;IAG5D,IAAIA,aAAE,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC,EAAE;AAC5C,QAAA,IAAI;YACF,MAAM,KAAK,GAAc,IAAI,CAAC,KAAK,CACjCA,aAAE,CAAC,YAAY,CAAC,uBAAuB,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CACrD,CAAC;YACf,IACE,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC;AAC1B,mBAAA,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC/B,mBAAA,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC;AACnC,mBAAA,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE;AAEhC,gBAAA,eAAe,GAAG,KAAK,CAAC,eAAe;QAC3C;QAAE,OAAO,EAAE,EAAE;;AAEX,YAAAA,aAAE,CAAC,MAAM,CAAC,uBAAuB,EAAE,CAAC;QACtC;IACF;IAEA,MAAM,MAAM,GAAGG,wBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC;AAE/C,IAAA,MAAM,OAAO,GAAG,IAAIC,qCAAuB,CAAC,MAAM,CAAC;AAEnD,IAAA,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE;AAE/C,IAAA,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC;AACtF,QAAA,MAAM,IAAI,KAAK,CACb,iFAAiF,WAAW,CAAA,qDAAA,CAAuD,CACpJ;AAEH,IAAA,IAAI,WAAW,KAAK,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC;AACrF,QAAA,MAAM,IAAI,KAAK,CACb,wCAAwC,WAAW,CAAA,qDAAA,CAAuD,CAC3G;AAEH,IAAA,IAAI,eAAe,KAAK,SAAS,EAAE;AACjC,QAAA,IAAI,WAAW;AAAE,YAAA,eAAe,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,SAAU,EAAE,KAAK,CAAC,aAAc,CAAC;;;YAEzF,eAAe,GAAG,EAAE;;AAGzB,QAAA,oBAAoB,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC;IAC9C;IAEA,OAAO;AACL,QAAA,IAAI,EAAE,MAAM;AACZ,QAAA,IAAI,EAAE;YACJ,eAAe;AACf,YAAA,QAAQ,EAAE,oBAAoB,CAAC,KAAK,CAAC;AACrC,YAAA,WAAW,EAAE,qBAAqB;AAClC,YAAA,aAAa,EAAE,qBAAqB;AACrC,SAAA;KACF;AACH;AAEO,eAAe,eAAe,CAAC,gBAAyC,EAAE,EAAA;IAC/E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,iBAAiB,EAAE;AAChD,IAAA,OAAO,IAAIC,oBAAU,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,aAAa,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAChE;AAEO,eAAe,aAAa,CAAC,eAAwB,EAAA;IAC1D,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,iBAAiB,EAAE;IAChD,IAAI,eAAe,KAAK,SAAS,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;AACrE,QAAA,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC;AACrE,IAAA,OAAO,MAAMC,eAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,EAAE,IAAI,CAAC;AAChE;AAEO,eAAe,YAAY,CAAI,IAAkC,EAAA;IACtE,MAAM,eAAe,GAAG,CAAA,KAAA,EAAQ,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAIL,sBAAU,EAAE,CAAA,CAAE;IAC5D,IAAI,SAAS,GAAuBM,oBAAc;AAClD,IAAA,IAAI;AACF,QAAA,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC;AACnD,QAAA,SAAS,GAAG,MAAM,CAAC,UAAU;AAC7B,QAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;AAChC,QAAA,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE;AACvC,QAAA,MAAM,SAAS,CAAC,qBAAqB,CAAC,eAAe,CAAC;AACtD,QAAA,OAAO,KAAK;IACd;IAAE,OAAO,GAAQ,EAAE;AACjB,QAAA,OAAO,CAAC,GAAG,CAAC,CAAA,kBAAA,EAAqB,eAAe,CAAA,EAAA,EAAKC,wBAAkB,CAAC,SAAS,CAAC,CAAA,CAAA,CAAG,CAAC;AACtF,QAAA,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC9C;AACF;;;;;;;;"}
1
+ {"version":3,"file":"test_config.cjs","sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport { LLPlClient } from '../core/ll_client';\nimport type { AuthInformation, AuthOps, PlClientConfig } from '../core/config';\nimport { plAddressToConfig } from '../core/config';\nimport { UnauthenticatedPlClient } from '../core/unauth_client';\nimport { PlClient } from '../core/client';\nimport { randomUUID } from 'node:crypto';\nimport type { OptionalResourceId } from '../core/types';\nimport { NullResourceId, resourceIdToString } from '../core/types';\nimport { inferAuthRefreshTime } from '../core/auth';\nimport * as path from 'node:path';\nimport type { TestTcpProxy } from './tcp-proxy';\nimport { startTcpProxy } from './tcp-proxy';\n\nexport {\n TestTcpProxy,\n};\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = 'test_config.json';\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve('.test_auth.json');\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: 'utf-8' })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(tConf: TestConfig): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n 'utf8',\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n console.warn(`Removing: ${getFullAuthDataFilePath()}`);\n fs.rmSync(getFullAuthDataFilePath());\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // try recover from cache\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: 'utf-8' }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address\n && cache.conf.test_user === tConf.test_user\n && cache.conf.test_password === tConf.test_password\n && cache.expiration > Date.now()\n )\n authInformation = cache.authInformation;\n } catch (_e) {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const plConf = plAddressToConfig(tConf.address);\n\n const uClient = new UnauthenticatedPlClient(plConf);\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return new LLPlClient({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error('test pl address configured with alternative root');\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions = {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n} | {\n viaTcpProxy?: undefined;\n};\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient) => Promise<T>\n): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalResourceId = NullResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === 'http:';\n const isLocal = url.hostname === '127.0.0.1' || url.hostname === 'localhost';\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn('*** skipping proxy-based test, PL_ADDRESS is not localhost', process.env.PL_ADDRESS);\n return;\n }\n } catch (_e) {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n console.log('altRootId', altRootId, altRootId.toString(16));\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n }\n return value;\n } catch (err: any) {\n console.log('ERROR stack trace:', err.stack);\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch (_e) { /* ignore */ }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch (_e) { /* ignore */ }\n }\n }\n}\n"],"names":["path","fs","randomUUID","inferAuthRefreshTime","plAddressToConfig","UnauthenticatedPlClient","LLPlClient","PlClient","NullResourceId","startTcpProxy","resourceIdToString"],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  import { LLPlClient } from '../core/ll_client';
2
2
  import type { AuthOps, PlClientConfig } from '../core/config';
3
3
  import { PlClient } from '../core/client';
4
+ import type { TestTcpProxy } from './tcp-proxy';
5
+ import { startTcpProxy } from './tcp-proxy';
6
+ export { TestTcpProxy, };
4
7
  export interface TestConfig {
5
8
  address: string;
6
9
  test_proxy?: string;
@@ -13,6 +16,19 @@ export declare function getTestClientConf(): Promise<{
13
16
  auth: AuthOps;
14
17
  }>;
15
18
  export declare function getTestLLClient(confOverrides?: Partial<PlClientConfig>): Promise<LLPlClient>;
16
- export declare function getTestClient(alternativeRoot?: string): Promise<PlClient>;
17
- export declare function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T>;
19
+ export declare function getTestClient(alternativeRoot?: string, confOverrides?: Partial<PlClientConfig>): Promise<PlClient>;
20
+ export type WithTempRootOptions = {
21
+ /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,
22
+ * a TCP proxy will be started and PL client will connect through it. */
23
+ viaTcpProxy: true;
24
+ /** Artificial latency for proxy (ms). Default 0 */
25
+ proxyLatencyMs?: number;
26
+ } | {
27
+ viaTcpProxy?: undefined;
28
+ };
29
+ export declare function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T | void>;
30
+ export declare function withTempRoot<T>(body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>, options: {
31
+ viaTcpProxy: true;
32
+ proxyLatencyMs?: number;
33
+ }): Promise<T>;
18
34
  //# sourceMappingURL=test_config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.d.ts","sourceRoot":"","sources":["../../src/test/test_config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAmB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG/E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAO1C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD,wBAAgB,aAAa,IAAI,UAAU,CAmB1C;AAiCD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CA0D1F;AAED,wBAAsB,eAAe,CAAC,aAAa,GAAE,OAAO,CAAC,cAAc,CAAM,uBAGhF;AAED,wBAAsB,aAAa,CAAC,eAAe,CAAC,EAAE,MAAM,qBAK3D;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAcpF"}
1
+ {"version":3,"file":"test_config.d.ts","sourceRoot":"","sources":["../../src/test/test_config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAmB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG/E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAM1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,YAAY,GACb,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD,wBAAgB,aAAa,IAAI,UAAU,CAmB1C;AAiCD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CA0D1F;AAED,wBAAsB,eAAe,CAAC,aAAa,GAAE,OAAO,CAAC,cAAc,CAAM,uBAGhF;AAED,wBAAsB,aAAa,CACjC,eAAe,CAAC,EAAE,MAAM,EACxB,aAAa,GAAE,OAAO,CAAC,cAAc,CAAM,qBAM5C;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC;4EACwE;IACxE,WAAW,EAAE,IAAI,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG;IACF,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB,CAAC;AAEF,wBAAsB,YAAY,CAAC,CAAC,EAClC,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GACjC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAErB,wBAAsB,YAAY,CAAC,CAAC,EAClC,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACpF,OAAO,EAAE;IACP,WAAW,EAAE,IAAI,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,OAAO,CAAC,CAAC,CAAC,CAAC"}
@@ -7,6 +7,7 @@ import { randomUUID } from 'node:crypto';
7
7
  import { NullResourceId, resourceIdToString } from '../core/types.js';
8
8
  import { inferAuthRefreshTime } from '../core/auth.js';
9
9
  import * as path from 'node:path';
10
+ import { startTcpProxy } from './tcp-proxy.js';
10
11
 
11
12
  const CONFIG_FILE = 'test_config.json';
12
13
  // const AUTH_DATA_FILE = '.test_auth.json';
@@ -96,26 +97,72 @@ async function getTestLLClient(confOverrides = {}) {
96
97
  const { conf, auth } = await getTestClientConf();
97
98
  return new LLPlClient({ ...conf, ...confOverrides }, { auth });
98
99
  }
99
- async function getTestClient(alternativeRoot) {
100
+ async function getTestClient(alternativeRoot, confOverrides = {}) {
100
101
  const { conf, auth } = await getTestClientConf();
101
102
  if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)
102
103
  throw new Error('test pl address configured with alternative root');
103
- return await PlClient.init({ ...conf, alternativeRoot }, auth);
104
+ return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);
104
105
  }
105
- async function withTempRoot(body) {
106
+ async function withTempRoot(body, options = {}) {
106
107
  const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;
107
108
  let altRootId = NullResourceId;
109
+ // Proxy management
110
+ let proxy;
111
+ let confOverrides = {};
108
112
  try {
109
- const client = await getTestClient(alternativeRoot);
113
+ // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy
114
+ if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {
115
+ try {
116
+ const url = new URL(process.env.PL_ADDRESS);
117
+ const isHttp = url.protocol === 'http:';
118
+ const isLocal = url.hostname === '127.0.0.1' || url.hostname === 'localhost';
119
+ const port = parseInt(url.port);
120
+ if (isHttp && isLocal && Number.isFinite(port)) {
121
+ proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });
122
+ // Override client connection host:port to proxy
123
+ confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` };
124
+ }
125
+ else {
126
+ console.warn('*** skipping proxy-based test, PL_ADDRESS is not localhost', process.env.PL_ADDRESS);
127
+ return;
128
+ }
129
+ }
130
+ catch (_e) {
131
+ // ignore proxy setup errors; tests will run against original address
132
+ }
133
+ }
134
+ const client = await getTestClient(alternativeRoot, confOverrides);
110
135
  altRootId = client.clientRoot;
111
- const value = await body(client);
136
+ console.log('altRootId', altRootId, altRootId.toString(16));
137
+ const value = await body(client, proxy);
112
138
  const rawClient = await getTestClient();
113
- await rawClient.deleteAlternativeRoot(alternativeRoot);
139
+ try {
140
+ await rawClient.deleteAlternativeRoot(alternativeRoot);
141
+ }
142
+ catch (cleanupErr) {
143
+ // Cleanup may fail if test intentionally deleted resources
144
+ console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);
145
+ }
114
146
  return value;
115
147
  }
116
148
  catch (err) {
149
+ console.log('ERROR stack trace:', err.stack);
117
150
  console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);
118
- throw new Error(err.message, { cause: err });
151
+ throw err;
152
+ // throw new Error('withTempRoot error: ' + err.message, { cause: err });
153
+ }
154
+ finally {
155
+ // Stop proxy if started
156
+ if (proxy) {
157
+ try {
158
+ await proxy.disconnectAll();
159
+ }
160
+ catch (_e) { /* ignore */ }
161
+ try {
162
+ await new Promise((resolve) => proxy.server.close(() => resolve()));
163
+ }
164
+ catch (_e) { /* ignore */ }
165
+ }
119
166
  }
120
167
  }
121
168
 
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.js","sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport { LLPlClient } from '../core/ll_client';\nimport type { AuthInformation, AuthOps, PlClientConfig } from '../core/config';\nimport { plAddressToConfig } from '../core/config';\nimport { UnauthenticatedPlClient } from '../core/unauth_client';\nimport { PlClient } from '../core/client';\nimport { randomUUID } from 'node:crypto';\nimport type { OptionalResourceId } from '../core/types';\nimport { NullResourceId, resourceIdToString } from '../core/types';\nimport { inferAuthRefreshTime } from '../core/auth';\nimport * as path from 'node:path';\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = 'test_config.json';\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve('.test_auth.json');\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: 'utf-8' })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(tConf: TestConfig): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n 'utf8',\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n console.warn(`Removing: ${getFullAuthDataFilePath()}`);\n fs.rmSync(getFullAuthDataFilePath());\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // try recover from cache\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: 'utf-8' }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address\n && cache.conf.test_user === tConf.test_user\n && cache.conf.test_password === tConf.test_password\n && cache.expiration > Date.now()\n )\n authInformation = cache.authInformation;\n } catch (_e) {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const plConf = plAddressToConfig(tConf.address);\n\n const uClient = new UnauthenticatedPlClient(plConf);\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return new LLPlClient({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(alternativeRoot?: string) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error('test pl address configured with alternative root');\n return await PlClient.init({ ...conf, alternativeRoot }, auth);\n}\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalResourceId = NullResourceId;\n try {\n const client = await getTestClient(alternativeRoot);\n altRootId = client.clientRoot;\n const value = await body(client);\n const rawClient = await getTestClient();\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n return value;\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw new Error(err.message, { cause: err });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test_config.js","sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport { LLPlClient } from '../core/ll_client';\nimport type { AuthInformation, AuthOps, PlClientConfig } from '../core/config';\nimport { plAddressToConfig } from '../core/config';\nimport { UnauthenticatedPlClient } from '../core/unauth_client';\nimport { PlClient } from '../core/client';\nimport { randomUUID } from 'node:crypto';\nimport type { OptionalResourceId } from '../core/types';\nimport { NullResourceId, resourceIdToString } from '../core/types';\nimport { inferAuthRefreshTime } from '../core/auth';\nimport * as path from 'node:path';\nimport type { TestTcpProxy } from './tcp-proxy';\nimport { startTcpProxy } from './tcp-proxy';\n\nexport {\n TestTcpProxy,\n};\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = 'test_config.json';\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve('.test_auth.json');\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: 'utf-8' })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(tConf: TestConfig): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n 'utf8',\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n console.warn(`Removing: ${getFullAuthDataFilePath()}`);\n fs.rmSync(getFullAuthDataFilePath());\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // try recover from cache\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: 'utf-8' }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address\n && cache.conf.test_user === tConf.test_user\n && cache.conf.test_password === tConf.test_password\n && cache.expiration > Date.now()\n )\n authInformation = cache.authInformation;\n } catch (_e) {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const plConf = plAddressToConfig(tConf.address);\n\n const uClient = new UnauthenticatedPlClient(plConf);\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return new LLPlClient({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error('test pl address configured with alternative root');\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions = {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n} | {\n viaTcpProxy?: undefined;\n};\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient) => Promise<T>\n): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalResourceId = NullResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === 'http:';\n const isLocal = url.hostname === '127.0.0.1' || url.hostname === 'localhost';\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn('*** skipping proxy-based test, PL_ADDRESS is not localhost', process.env.PL_ADDRESS);\n return;\n }\n } catch (_e) {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n console.log('altRootId', altRootId, altRootId.toString(16));\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n }\n return value;\n } catch (err: any) {\n console.log('ERROR stack trace:', err.stack);\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch (_e) { /* ignore */ }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch (_e) { /* ignore */ }\n }\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-client",
3
- "version": "2.16.1",
3
+ "version": "2.16.3",
4
4
  "engines": {
5
5
  "node": ">=22.19.0"
6
6
  },
@@ -30,21 +30,20 @@
30
30
  "https-proxy-agent": "^7.0.6",
31
31
  "long": "^5.3.2",
32
32
  "lru-cache": "^11.2.2",
33
+ "tslib": "~2.7.0",
33
34
  "undici": "~7.16.0",
34
35
  "utility-types": "^3.11.0",
35
36
  "yaml": "^2.8.0",
36
37
  "@milaboratories/pl-http": "1.2.0",
37
- "@milaboratories/pl-model-common": "1.21.4",
38
- "@milaboratories/ts-helpers": "1.5.1"
38
+ "@milaboratories/pl-model-common": "1.21.5",
39
+ "@milaboratories/ts-helpers": "1.5.2"
39
40
  },
40
41
  "devDependencies": {
41
- "@jest/globals": "^29.7.0",
42
42
  "@protobuf-ts/plugin": "2.11.1",
43
43
  "@types/http-proxy": "^1.17.16",
44
- "@types/jest": "^29.5.14",
45
44
  "@types/node": "~24.5.2",
46
- "jest": "^29.7.0",
47
- "ts-jest": "^29.2.6",
45
+ "@vitest/coverage-v8": "^4.0.7",
46
+ "vitest": "^4.0.7",
48
47
  "typescript": "~5.6.3",
49
48
  "@milaboratories/build-configs": "1.0.8",
50
49
  "@milaboratories/ts-builder": "1.0.5",
@@ -55,7 +54,7 @@
55
54
  "build": "ts-builder build --target node",
56
55
  "watch": "ts-builder build --target node --watch",
57
56
  "lint": "eslint .",
58
- "test": "jest --runInBand",
57
+ "test": "vitest run --coverage",
59
58
  "do-pack": "rm -f *.tgz && pnpm pack && mv *.tgz package.tgz"
60
59
  }
61
60
  }
@@ -0,0 +1,39 @@
1
+ import { StatefulPromise } from './StatefulPromise';
2
+
3
+ /**
4
+ * Tracks pending promises to ensure they are all awaited before proceeding.
5
+ * Useful for coordinating async operations, preventing premature completion,
6
+ * and avoiding unhandled promise rejections.
7
+ */
8
+ export class PromiseTracker {
9
+ private promises: Set<Promise<unknown>> = new Set();
10
+ private _draining = false;
11
+
12
+ get draining(): boolean {
13
+ return this._draining;
14
+ }
15
+
16
+ get size(): number {
17
+ return this.promises.size;
18
+ }
19
+
20
+ track<T>(promiseOrCallback: Promise<T> | (() => Promise<T>)): Promise<T> {
21
+ const _promise = typeof promiseOrCallback === 'function'
22
+ ? promiseOrCallback()
23
+ : promiseOrCallback;
24
+
25
+ const promise = StatefulPromise.fromDeferredReject(_promise, (p) => {
26
+ this.promises.delete(p);
27
+ });
28
+ this.promises.add(promise);
29
+ return promise;
30
+ }
31
+
32
+ async awaitAll() {
33
+ const toAwait = Array.from(this.promises);
34
+ this._draining = true;
35
+ return await Promise.all(toAwait).finally(() => {
36
+ this._draining = false;
37
+ });
38
+ }
39
+ }
@@ -0,0 +1,92 @@
1
+ type PromiseState = 'pending' | 'fulfilled' | 'rejected';
2
+
3
+ type StatefulPromiseOptions<T> = {
4
+ /**
5
+ * If true, rejections that happen inside the promise are *recorded* but
6
+ * not rethrown from the constructor chain. They will only surface when you
7
+ * call unwrap()/then()/await.
8
+ */
9
+ deferReject?: true;
10
+ /**
11
+ * Callback invoked when the promise is accessed (via then/catch/finally/await).
12
+ * Useful for cleanup or tracking when a promise result is consumed.
13
+ */
14
+ onUnwrap?: (promise: StatefulPromise<T>) => void;
15
+ };
16
+
17
+ /**
18
+ * A Promise wrapper that tracks its state (pending/fulfilled/rejected) and provides
19
+ * optional deferred rejection handling to prevent unhandled promise rejections.
20
+ */
21
+ export class StatefulPromise<T> implements Promise<T> {
22
+ private _state: PromiseState;
23
+ private _id: bigint;
24
+
25
+ private static idCounter = 0n;
26
+
27
+ public static debug = false;
28
+
29
+ static from<T>(promise: Promise<T>, options: StatefulPromiseOptions<T> = {}): StatefulPromise<T> {
30
+ return new StatefulPromise(promise, options);
31
+ }
32
+
33
+ static fromDeferredReject<T>(promise: Promise<T>, onUnwrap?: (promise: StatefulPromise<T>) => void): StatefulPromise<T> {
34
+ return new StatefulPromise(promise, { deferReject: true, onUnwrap });
35
+ }
36
+
37
+ static fromDeferredRejectCallback<T>(asyncFn: () => Promise<T>, onUnwrap?: (promise: StatefulPromise<T>) => void): StatefulPromise<T> {
38
+ return new StatefulPromise(asyncFn(), { deferReject: true, onUnwrap });
39
+ }
40
+
41
+ private constructor(private readonly promise: Promise<T>, private readonly options: StatefulPromiseOptions<T> = {}) {
42
+ this._state = 'pending';
43
+ this._id = StatefulPromise.idCounter++;
44
+
45
+ this.promise
46
+ .then((value) => {
47
+ this._state = 'fulfilled';
48
+ return value;
49
+ })
50
+ .catch((err) => {
51
+ this._state = 'rejected';
52
+ if (!options.deferReject) {
53
+ throw err;
54
+ }
55
+ });
56
+ }
57
+
58
+ readonly [Symbol.toStringTag] = 'Promise';
59
+
60
+ get id(): bigint {
61
+ return this._id;
62
+ }
63
+
64
+ get state(): PromiseState {
65
+ return this._state;
66
+ }
67
+
68
+ then<TResult1 = T, TResult2 = never>(
69
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
70
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
71
+ ): Promise<TResult1 | TResult2> {
72
+ return this.unwrap().then(onfulfilled, onrejected);
73
+ }
74
+
75
+ catch<TResult = never>(
76
+ onrejected?: (reason: unknown) => TResult | PromiseLike<TResult>,
77
+ ): Promise<T | TResult> {
78
+ return this.unwrap().catch(onrejected);
79
+ }
80
+
81
+ finally(onfinally?: () => void): Promise<T> {
82
+ return this.unwrap().finally(onfinally);
83
+ }
84
+
85
+ async unwrap(): Promise<T> {
86
+ if (this.options.onUnwrap) {
87
+ this.options.onUnwrap(this);
88
+ }
89
+
90
+ return this.promise;
91
+ }
92
+ }
@@ -3,7 +3,7 @@ import { PlClient } from './client';
3
3
  import { PlDriver, PlDriverDefinition } from './driver';
4
4
  import { Dispatcher, request } from 'undici';
5
5
  import { GrpcClientProviderFactory } from './grpc';
6
- import { test, expect } from '@jest/globals';
6
+ import { test, expect } from 'vitest';
7
7
 
8
8
  test('test client init', async () => {
9
9
  const client = await getTestClient(undefined);
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_RO_TX_TIMEOUT, DEFAULT_RW_TX_TIMEOUT, plAddressToConfig } from './config';
2
- import { test, expect } from '@jest/globals';
2
+ import { test, expect } from 'vitest';
3
3
 
4
4
  test('config form url no auth', () => {
5
5
  const conf = plAddressToConfig('http://127.0.0.1:6345');
@@ -0,0 +1,70 @@
1
+ import { withTempRoot } from '../test/test_config';
2
+ import { StructTestResource } from '../helpers/pl';
3
+ import { toGlobalFieldId } from './transaction';
4
+ import { test, expect } from 'vitest';
5
+ import { sleep } from '@milaboratories/ts-helpers';
6
+ import { DisconnectedError } from './errors';
7
+
8
+ test('connectivity: disconnect', async () => {
9
+ await withTempRoot(async (pl, proxy) => {
10
+ await expect(async () => {
11
+ await pl.withWriteTx('resource1', async (tx) => {
12
+ const r0 = tx.createStruct(StructTestResource);
13
+ const r1 = tx.createStruct(StructTestResource);
14
+ const f0 = { resourceId: tx.clientRoot, fieldName: 'test0' };
15
+ const f1 = { resourceId: tx.clientRoot, fieldName: 'test1' };
16
+
17
+ tx.createField(f0, 'Dynamic');
18
+ tx.createField(f1, 'Dynamic');
19
+ tx.setField(f0, r0);
20
+ tx.setField(f1, r1);
21
+
22
+ await proxy?.disconnectAll();
23
+
24
+ await sleep(100);
25
+
26
+ const theField1 = { resourceId: r1, fieldName: 'theField' };
27
+ tx.createField(theField1, 'Input');
28
+ tx.lock(r1);
29
+ tx.setField(theField1, tx.getFutureFieldValue(r0, 'theField', 'Input'));
30
+
31
+ await tx.commit();
32
+
33
+ return [await r0.globalId, await toGlobalFieldId(theField1)];
34
+ });
35
+ }).rejects.toThrow(DisconnectedError);
36
+ }, { viaTcpProxy: true });
37
+ });
38
+
39
+ test.skip('connectivity: latency', async () => {
40
+ await withTempRoot(async (pl, proxy) => {
41
+ proxy?.setLatency(10_000);
42
+ await expect(async () => {
43
+ const result = await pl.withWriteTx('resource1', async (tx) => {
44
+ const r0 = tx.createStruct(StructTestResource);
45
+ const r1 = tx.createStruct(StructTestResource);
46
+ const f0 = { resourceId: tx.clientRoot, fieldName: 'test0' };
47
+ const f1 = { resourceId: tx.clientRoot, fieldName: 'test1' };
48
+
49
+ tx.createField(f0, 'Dynamic');
50
+ tx.createField(f1, 'Dynamic');
51
+ tx.setField(f0, r0);
52
+ tx.setField(f1, r1);
53
+
54
+ const theField1 = { resourceId: r1, fieldName: 'theField' };
55
+ tx.createField(theField1, 'Input');
56
+ tx.lock(r1);
57
+ tx.setField(theField1, tx.getFutureFieldValue(r0, 'theField', 'Input'));
58
+
59
+ await tx.commit().catch((err) => console.log('error committing tx', err));
60
+
61
+ return [await r0.globalId, await toGlobalFieldId(theField1)];
62
+ });
63
+
64
+ console.log('result', result);
65
+
66
+ return result;
67
+ }).rejects.toThrow(Error);
68
+ await new Promise(resolve => setTimeout(resolve, 1000));
69
+ }, { viaTcpProxy: true });
70
+ }, 60_000);
@@ -1,6 +1,6 @@
1
1
  import * as tp from 'node:timers/promises';
2
2
  import { isTimeoutOrCancelError } from './errors';
3
- import { test, expect } from '@jest/globals';
3
+ import { test, expect } from 'vitest';
4
4
 
5
5
  test('timeout of sleep error type detection', async () => {
6
6
  let noError = false;
@@ -3,7 +3,7 @@ import { getTestConfig, getTestLLClient, getTestClientConf } from '../test/test_
3
3
  import { TxAPI_Open_Request_WritableTx } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';
4
4
  import { request } from 'undici';
5
5
  import * as tp from 'node:timers/promises';
6
- import { test, expect } from '@jest/globals';
6
+ import { test, expect } from 'vitest';
7
7
 
8
8
  import { UnauthenticatedError } from './errors';
9
9
 
@@ -1,7 +1,7 @@
1
1
  import { getTestLLClient } from '../test/test_config';
2
2
  import { TxAPI_Open_Request_WritableTx } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';
3
3
  import { createLocalResourceId } from './types';
4
- import { test, expect } from '@jest/globals';
4
+ import { test, expect } from 'vitest';
5
5
 
6
6
  import { isTimeoutOrCancelError } from './errors';
7
7
  import { Aborted } from '@milaboratories/ts-helpers';
@@ -11,6 +11,7 @@ import {
11
11
  rethrowMeaningfulError,
12
12
  UnrecoverablePlError,
13
13
  } from './errors';
14
+ import { StatefulPromise } from './StatefulPromise';
14
15
 
15
16
  export type ClientMessageRequest = TxAPI_ClientMessage['request'];
16
17
 
@@ -302,11 +303,11 @@ export class LLPlTransaction {
302
303
  if (this.closed) return Promise.reject(new Error('Transaction already closed'));
303
304
 
304
305
  // Note: Promise synchronously executes a callback passed to a constructor
305
- const result = new Promise<OneOfKind<ServerMessageResponse, Kind>>((resolve, reject) => {
306
+ const result = StatefulPromise.fromDeferredReject(new Promise<OneOfKind<ServerMessageResponse, Kind>>((resolve, reject) => {
306
307
  this.responseHandlerQueue.push(
307
308
  createResponseHandler(r.oneofKind, expectMultiResponse, resolve, reject),
308
309
  );
309
- });
310
+ }));
310
311
 
311
312
  // Awaiting message dispatch to catch any associated errors.
312
313
  // There is no hurry, we are not going to receive a response until message is sent.
@@ -3,7 +3,8 @@ import { StructTestResource, ValueTestResource } from '../helpers/pl';
3
3
  import { field, toGlobalFieldId, toGlobalResourceId } from './transaction';
4
4
  import { RecoverablePlError } from './errors';
5
5
  import * as tp from 'node:timers/promises';
6
- import { test, expect } from '@jest/globals';
6
+ import { test, expect } from 'vitest';
7
+ import { StatefulPromise } from './StatefulPromise';
7
8
 
8
9
  test('get field', async () => {
9
10
  await withTempRoot(async (pl) => {
@@ -75,6 +76,8 @@ test('handle absent resource error', async () => {
75
76
  { sync: true }
76
77
  );
77
78
 
79
+ StatefulPromise.debug = true;
80
+
78
81
  let rState = await pl.withReadTx('testRetrieveResource', async (tx) => {
79
82
  await expect(async () => {
80
83
  await tx.getResourceData(rr0, true);
@@ -82,6 +85,8 @@ test('handle absent resource error', async () => {
82
85
  return await tx.getResourceData(tx.clientRoot, true);
83
86
  });
84
87
 
88
+ StatefulPromise.debug = false;
89
+
85
90
  expect(rState.fields).toHaveLength(0);
86
91
 
87
92
  rState = await pl.withReadTx('testRetrieveResource', async (tx) => {