@milaboratories/pl-client 2.16.11 → 2.16.13

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 (54) hide show
  1. package/dist/core/driver.cjs +1 -1
  2. package/dist/core/driver.cjs.map +1 -1
  3. package/dist/core/driver.js +1 -1
  4. package/dist/core/driver.js.map +1 -1
  5. package/dist/core/errors.cjs +2 -0
  6. package/dist/core/errors.cjs.map +1 -1
  7. package/dist/core/errors.d.ts.map +1 -1
  8. package/dist/core/errors.js +2 -0
  9. package/dist/core/errors.js.map +1 -1
  10. package/dist/core/ll_client.cjs +32 -9
  11. package/dist/core/ll_client.cjs.map +1 -1
  12. package/dist/core/ll_client.d.ts.map +1 -1
  13. package/dist/core/ll_client.js +32 -9
  14. package/dist/core/ll_client.js.map +1 -1
  15. package/dist/core/ll_transaction.cjs +10 -0
  16. package/dist/core/ll_transaction.cjs.map +1 -1
  17. package/dist/core/ll_transaction.d.ts +1 -0
  18. package/dist/core/ll_transaction.d.ts.map +1 -1
  19. package/dist/core/ll_transaction.js +10 -0
  20. package/dist/core/ll_transaction.js.map +1 -1
  21. package/dist/core/websocket_stream.cjs +330 -0
  22. package/dist/core/websocket_stream.cjs.map +1 -0
  23. package/dist/core/websocket_stream.d.ts +67 -0
  24. package/dist/core/websocket_stream.d.ts.map +1 -0
  25. package/dist/core/websocket_stream.js +328 -0
  26. package/dist/core/websocket_stream.js.map +1 -0
  27. package/dist/helpers/retry_strategy.cjs +92 -0
  28. package/dist/helpers/retry_strategy.cjs.map +1 -0
  29. package/dist/helpers/retry_strategy.d.ts +24 -0
  30. package/dist/helpers/retry_strategy.d.ts.map +1 -0
  31. package/dist/helpers/retry_strategy.js +89 -0
  32. package/dist/helpers/retry_strategy.js.map +1 -0
  33. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +136 -0
  34. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
  35. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +75 -1
  36. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
  37. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +135 -1
  38. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
  39. package/dist/proto-rest/index.cjs +16 -2
  40. package/dist/proto-rest/index.cjs.map +1 -1
  41. package/dist/proto-rest/index.d.ts.map +1 -1
  42. package/dist/proto-rest/index.js +16 -2
  43. package/dist/proto-rest/index.js.map +1 -1
  44. package/package.json +6 -6
  45. package/src/core/driver.ts +1 -1
  46. package/src/core/errors.ts +1 -0
  47. package/src/core/ll_client.ts +42 -9
  48. package/src/core/ll_transaction.test.ts +18 -0
  49. package/src/core/ll_transaction.ts +12 -0
  50. package/src/core/websocket_stream.test.ts +423 -0
  51. package/src/core/websocket_stream.ts +400 -0
  52. package/src/helpers/retry_strategy.ts +123 -0
  53. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +179 -1
  54. package/src/proto-rest/index.ts +17 -2
@@ -14,9 +14,23 @@ function createClient(opts) {
14
14
  const client = createOpenApiClient({
15
15
  baseUrl: `${scheme}${opts.hostAndPort}`,
16
16
  fetch: (input) => {
17
- return undici.fetch(input.url, {
18
- ...input,
17
+ // If body has already been consumed, clone the request
18
+ const request = input.bodyUsed ? input.clone() : input;
19
+ return undici.fetch(request.url, {
20
+ body: request.body,
21
+ cache: request.cache,
22
+ credentials: request.credentials,
19
23
  dispatcher: opts.dispatcher,
24
+ duplex: request.duplex,
25
+ headers: request.headers,
26
+ integrity: request.integrity,
27
+ keepalive: request.keepalive,
28
+ method: request.method,
29
+ mode: request.mode,
30
+ redirect: request.redirect,
31
+ referrer: request.referrer,
32
+ referrerPolicy: request.referrerPolicy,
33
+ signal: request.signal,
20
34
  });
21
35
  },
22
36
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/proto-rest/index.ts"],"sourcesContent":["//\n// This file is NOT autogenerated!\n//\n// After generating new clients from openapi specifications, add client types here.\n//\n\nimport type { paths as PlApiPaths } from './plapi';\nimport { default as createOpenApiClient, type Middleware, type Client } from 'openapi-fetch';\nimport { Dispatcher, fetch as undiciFetch } from 'undici';\nimport { rethrowMeaningfulError } from '../core/errors';\nimport { Code } from '../proto-grpc/google/rpc/code';\n\nexport { PlApiPaths };\nexport type PlRestClientType = Client<PlApiPaths>;\n\nexport type RestClientConfig = {\n hostAndPort: string;\n ssl: boolean;\n dispatcher: Dispatcher;\n middlewares: Middleware[];\n}\n\nexport function createClient<Paths extends {}>(opts: RestClientConfig): Client<Paths> {\n const scheme = opts.ssl ? 'https://' : 'http://';\n const client = createOpenApiClient<Paths>({\n baseUrl: `${scheme}${opts.hostAndPort}`,\n fetch: (input: Request): Promise<Response> => {\n return undiciFetch(input.url, {\n ...input,\n dispatcher: opts.dispatcher,\n });\n },\n });\n client.use(errorHandlerMiddleware(), ...opts.middlewares);\n return client;\n}\n\nexport type ErrorResponse = {\n code: Code,\n message: string,\n details: any[],\n}\nexport async function parseResponseError(response: Response): Promise<{\n error?: ErrorResponse | string,\n origBody?: string,\n}>{\n if (response.status < 400) {\n return {};\n }\n\n let error: any = await response.clone().text();\n const origBody = error;\n try {\n error = JSON.parse(error) as ErrorResponse;\n } catch {\n }\n return {\n error: error,\n origBody\n };\n}\n\n/**\n * Parses all API responses and thrown error in case of error response.\n * Allows all callers to not bother about .error response field checking.\n */\nfunction errorHandlerMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n const { body, ...resOptions } = response;\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === 'string') {\n throw new Error(respErr.error);\n }\n\n rethrowMeaningfulError(respErr.error);\n },\n };\n}\n"],"names":["undiciFetch","rethrowMeaningfulError"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AAkBM,SAAU,YAAY,CAAmB,IAAsB,EAAA;AACnE,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,UAAU,GAAG,SAAS;IAChD,MAAM,MAAM,GAAG,mBAAmB,CAAQ;AACxC,QAAA,OAAO,EAAE,CAAA,EAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA,CAAE;AACvC,QAAA,KAAK,EAAE,CAAC,KAAc,KAAuB;AAC3C,YAAA,OAAOA,YAAW,CAAC,KAAK,CAAC,GAAG,EAAE;AAC5B,gBAAA,GAAG,KAAK;gBACR,UAAU,EAAE,IAAI,CAAC,UAAU;AAC5B,aAAA,CAAC;QACJ,CAAC;AACF,KAAA,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;AACzD,IAAA,OAAO,MAAM;AACf;AAOO,eAAe,kBAAkB,CAAC,QAAkB,EAAA;AAIzD,IAAA,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;AACzB,QAAA,OAAO,EAAE;IACX;IAEA,IAAI,KAAK,GAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;IAC9C,MAAM,QAAQ,GAAG,KAAK;AACtB,IAAA,IAAI;AACF,QAAA,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAkB;IAC5C;AAAE,IAAA,MAAM;IACR;IACA,OAAO;AACL,QAAA,KAAK,EAAE,KAAK;QACZ;KACD;AACH;AAEA;;;AAGG;AACH,SAAS,sBAAsB,GAAA;IAC7B,OAAO;AACL,QAAA,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAI;AACvE,YAAA,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC;AAClD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBAClB,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,GAAG,QAAQ;AACxC,gBAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvE;AAEA,YAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;AACrC,gBAAA,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAChC;AAEA,YAAAC,6BAAsB,CAAC,OAAO,CAAC,KAAK,CAAC;QACvC,CAAC;KACF;AACH;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/proto-rest/index.ts"],"sourcesContent":["//\n// This file is NOT autogenerated!\n//\n// After generating new clients from openapi specifications, add client types here.\n//\n\nimport type { paths as PlApiPaths } from './plapi';\nimport { default as createOpenApiClient, type Middleware, type Client } from 'openapi-fetch';\nimport { Dispatcher, fetch as undiciFetch } from 'undici';\nimport { rethrowMeaningfulError } from '../core/errors';\nimport { Code } from '../proto-grpc/google/rpc/code';\n\nexport { PlApiPaths };\nexport type PlRestClientType = Client<PlApiPaths>;\n\nexport type RestClientConfig = {\n hostAndPort: string;\n ssl: boolean;\n dispatcher: Dispatcher;\n middlewares: Middleware[];\n}\n\nexport function createClient<Paths extends {}>(opts: RestClientConfig): Client<Paths> {\n const scheme = opts.ssl ? 'https://' : 'http://';\n const client = createOpenApiClient<Paths>({\n baseUrl: `${scheme}${opts.hostAndPort}`,\n fetch: (input: Request): Promise<Response> => {\n // If body has already been consumed, clone the request\n const request = input.bodyUsed ? input.clone() : input;\n\n return undiciFetch(request.url, {\n body: request.body,\n cache: request.cache,\n credentials: request.credentials,\n dispatcher: opts.dispatcher,\n duplex: request.duplex,\n headers: request.headers,\n integrity: request.integrity,\n keepalive: request.keepalive,\n method: request.method,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: request.signal,\n });\n },\n });\n client.use(errorHandlerMiddleware(), ...opts.middlewares);\n return client;\n}\n\nexport type ErrorResponse = {\n code: Code,\n message: string,\n details: any[],\n}\nexport async function parseResponseError(response: Response): Promise<{\n error?: ErrorResponse | string,\n origBody?: string,\n}>{\n if (response.status < 400) {\n return {};\n }\n\n let error: any = await response.clone().text();\n const origBody = error;\n try {\n error = JSON.parse(error) as ErrorResponse;\n } catch {\n }\n return {\n error: error,\n origBody\n };\n}\n\n/**\n * Parses all API responses and thrown error in case of error response.\n * Allows all callers to not bother about .error response field checking.\n */\nfunction errorHandlerMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n const { body, ...resOptions } = response;\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === 'string') {\n throw new Error(respErr.error);\n }\n\n rethrowMeaningfulError(respErr.error);\n },\n };\n}\n"],"names":["undiciFetch","rethrowMeaningfulError"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AAkBM,SAAU,YAAY,CAAmB,IAAsB,EAAA;AACnE,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,UAAU,GAAG,SAAS;IAChD,MAAM,MAAM,GAAG,mBAAmB,CAAQ;AACxC,QAAA,OAAO,EAAE,CAAA,EAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA,CAAE;AACvC,QAAA,KAAK,EAAE,CAAC,KAAc,KAAuB;;AAE3C,YAAA,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK;AAEtD,YAAA,OAAOA,YAAW,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;AACvB,aAAA,CAAC;QACJ,CAAC;AACF,KAAA,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;AACzD,IAAA,OAAO,MAAM;AACf;AAOO,eAAe,kBAAkB,CAAC,QAAkB,EAAA;AAIzD,IAAA,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;AACzB,QAAA,OAAO,EAAE;IACX;IAEA,IAAI,KAAK,GAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;IAC9C,MAAM,QAAQ,GAAG,KAAK;AACtB,IAAA,IAAI;AACF,QAAA,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAkB;IAC5C;AAAE,IAAA,MAAM;IACR;IACA,OAAO;AACL,QAAA,KAAK,EAAE,KAAK;QACZ;KACD;AACH;AAEA;;;AAGG;AACH,SAAS,sBAAsB,GAAA;IAC7B,OAAO;AACL,QAAA,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAI;AACvE,YAAA,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC;AAClD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBAClB,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,GAAG,QAAQ;AACxC,gBAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvE;AAEA,YAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;AACrC,gBAAA,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAChC;AAEA,YAAAC,6BAAsB,CAAC,OAAO,CAAC,KAAK,CAAC;QACvC,CAAC;KACF;AACH;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proto-rest/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAkC,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAwB,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAA;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,EAAE,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,CAapF;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC;CAChB,CAAA;AACD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC;IACpE,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAeD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proto-rest/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAkC,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAwB,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAA;AAED,wBAAgB,YAAY,CAAC,KAAK,SAAS,EAAE,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,CA4BpF;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC;CAChB,CAAA;AACD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC;IACpE,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAeD"}
@@ -12,9 +12,23 @@ function createClient(opts) {
12
12
  const client = createOpenApiClient({
13
13
  baseUrl: `${scheme}${opts.hostAndPort}`,
14
14
  fetch: (input) => {
15
- return fetch(input.url, {
16
- ...input,
15
+ // If body has already been consumed, clone the request
16
+ const request = input.bodyUsed ? input.clone() : input;
17
+ return fetch(request.url, {
18
+ body: request.body,
19
+ cache: request.cache,
20
+ credentials: request.credentials,
17
21
  dispatcher: opts.dispatcher,
22
+ duplex: request.duplex,
23
+ headers: request.headers,
24
+ integrity: request.integrity,
25
+ keepalive: request.keepalive,
26
+ method: request.method,
27
+ mode: request.mode,
28
+ redirect: request.redirect,
29
+ referrer: request.referrer,
30
+ referrerPolicy: request.referrerPolicy,
31
+ signal: request.signal,
18
32
  });
19
33
  },
20
34
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/proto-rest/index.ts"],"sourcesContent":["//\n// This file is NOT autogenerated!\n//\n// After generating new clients from openapi specifications, add client types here.\n//\n\nimport type { paths as PlApiPaths } from './plapi';\nimport { default as createOpenApiClient, type Middleware, type Client } from 'openapi-fetch';\nimport { Dispatcher, fetch as undiciFetch } from 'undici';\nimport { rethrowMeaningfulError } from '../core/errors';\nimport { Code } from '../proto-grpc/google/rpc/code';\n\nexport { PlApiPaths };\nexport type PlRestClientType = Client<PlApiPaths>;\n\nexport type RestClientConfig = {\n hostAndPort: string;\n ssl: boolean;\n dispatcher: Dispatcher;\n middlewares: Middleware[];\n}\n\nexport function createClient<Paths extends {}>(opts: RestClientConfig): Client<Paths> {\n const scheme = opts.ssl ? 'https://' : 'http://';\n const client = createOpenApiClient<Paths>({\n baseUrl: `${scheme}${opts.hostAndPort}`,\n fetch: (input: Request): Promise<Response> => {\n return undiciFetch(input.url, {\n ...input,\n dispatcher: opts.dispatcher,\n });\n },\n });\n client.use(errorHandlerMiddleware(), ...opts.middlewares);\n return client;\n}\n\nexport type ErrorResponse = {\n code: Code,\n message: string,\n details: any[],\n}\nexport async function parseResponseError(response: Response): Promise<{\n error?: ErrorResponse | string,\n origBody?: string,\n}>{\n if (response.status < 400) {\n return {};\n }\n\n let error: any = await response.clone().text();\n const origBody = error;\n try {\n error = JSON.parse(error) as ErrorResponse;\n } catch {\n }\n return {\n error: error,\n origBody\n };\n}\n\n/**\n * Parses all API responses and thrown error in case of error response.\n * Allows all callers to not bother about .error response field checking.\n */\nfunction errorHandlerMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n const { body, ...resOptions } = response;\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === 'string') {\n throw new Error(respErr.error);\n }\n\n rethrowMeaningfulError(respErr.error);\n },\n };\n}\n"],"names":["undiciFetch"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AAkBM,SAAU,YAAY,CAAmB,IAAsB,EAAA;AACnE,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,UAAU,GAAG,SAAS;IAChD,MAAM,MAAM,GAAG,mBAAmB,CAAQ;AACxC,QAAA,OAAO,EAAE,CAAA,EAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA,CAAE;AACvC,QAAA,KAAK,EAAE,CAAC,KAAc,KAAuB;AAC3C,YAAA,OAAOA,KAAW,CAAC,KAAK,CAAC,GAAG,EAAE;AAC5B,gBAAA,GAAG,KAAK;gBACR,UAAU,EAAE,IAAI,CAAC,UAAU;AAC5B,aAAA,CAAC;QACJ,CAAC;AACF,KAAA,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;AACzD,IAAA,OAAO,MAAM;AACf;AAOO,eAAe,kBAAkB,CAAC,QAAkB,EAAA;AAIzD,IAAA,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;AACzB,QAAA,OAAO,EAAE;IACX;IAEA,IAAI,KAAK,GAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;IAC9C,MAAM,QAAQ,GAAG,KAAK;AACtB,IAAA,IAAI;AACF,QAAA,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAkB;IAC5C;AAAE,IAAA,MAAM;IACR;IACA,OAAO;AACL,QAAA,KAAK,EAAE,KAAK;QACZ;KACD;AACH;AAEA;;;AAGG;AACH,SAAS,sBAAsB,GAAA;IAC7B,OAAO;AACL,QAAA,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAI;AACvE,YAAA,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC;AAClD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBAClB,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,GAAG,QAAQ;AACxC,gBAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvE;AAEA,YAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;AACrC,gBAAA,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAChC;AAEA,YAAA,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC;QACvC,CAAC;KACF;AACH;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/proto-rest/index.ts"],"sourcesContent":["//\n// This file is NOT autogenerated!\n//\n// After generating new clients from openapi specifications, add client types here.\n//\n\nimport type { paths as PlApiPaths } from './plapi';\nimport { default as createOpenApiClient, type Middleware, type Client } from 'openapi-fetch';\nimport { Dispatcher, fetch as undiciFetch } from 'undici';\nimport { rethrowMeaningfulError } from '../core/errors';\nimport { Code } from '../proto-grpc/google/rpc/code';\n\nexport { PlApiPaths };\nexport type PlRestClientType = Client<PlApiPaths>;\n\nexport type RestClientConfig = {\n hostAndPort: string;\n ssl: boolean;\n dispatcher: Dispatcher;\n middlewares: Middleware[];\n}\n\nexport function createClient<Paths extends {}>(opts: RestClientConfig): Client<Paths> {\n const scheme = opts.ssl ? 'https://' : 'http://';\n const client = createOpenApiClient<Paths>({\n baseUrl: `${scheme}${opts.hostAndPort}`,\n fetch: (input: Request): Promise<Response> => {\n // If body has already been consumed, clone the request\n const request = input.bodyUsed ? input.clone() : input;\n\n return undiciFetch(request.url, {\n body: request.body,\n cache: request.cache,\n credentials: request.credentials,\n dispatcher: opts.dispatcher,\n duplex: request.duplex,\n headers: request.headers,\n integrity: request.integrity,\n keepalive: request.keepalive,\n method: request.method,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: request.signal,\n });\n },\n });\n client.use(errorHandlerMiddleware(), ...opts.middlewares);\n return client;\n}\n\nexport type ErrorResponse = {\n code: Code,\n message: string,\n details: any[],\n}\nexport async function parseResponseError(response: Response): Promise<{\n error?: ErrorResponse | string,\n origBody?: string,\n}>{\n if (response.status < 400) {\n return {};\n }\n\n let error: any = await response.clone().text();\n const origBody = error;\n try {\n error = JSON.parse(error) as ErrorResponse;\n } catch {\n }\n return {\n error: error,\n origBody\n };\n}\n\n/**\n * Parses all API responses and thrown error in case of error response.\n * Allows all callers to not bother about .error response field checking.\n */\nfunction errorHandlerMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n const { body, ...resOptions } = response;\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === 'string') {\n throw new Error(respErr.error);\n }\n\n rethrowMeaningfulError(respErr.error);\n },\n };\n}\n"],"names":["undiciFetch"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AAkBM,SAAU,YAAY,CAAmB,IAAsB,EAAA;AACnE,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,UAAU,GAAG,SAAS;IAChD,MAAM,MAAM,GAAG,mBAAmB,CAAQ;AACxC,QAAA,OAAO,EAAE,CAAA,EAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA,CAAE;AACvC,QAAA,KAAK,EAAE,CAAC,KAAc,KAAuB;;AAE3C,YAAA,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK;AAEtD,YAAA,OAAOA,KAAW,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;AACvB,aAAA,CAAC;QACJ,CAAC;AACF,KAAA,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;AACzD,IAAA,OAAO,MAAM;AACf;AAOO,eAAe,kBAAkB,CAAC,QAAkB,EAAA;AAIzD,IAAA,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;AACzB,QAAA,OAAO,EAAE;IACX;IAEA,IAAI,KAAK,GAAQ,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;IAC9C,MAAM,QAAQ,GAAG,KAAK;AACtB,IAAA,IAAI;AACF,QAAA,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAkB;IAC5C;AAAE,IAAA,MAAM;IACR;IACA,OAAO;AACL,QAAA,KAAK,EAAE,KAAK;QACZ;KACD;AACH;AAEA;;;AAGG;AACH,SAAS,sBAAsB,GAAA;IAC7B,OAAO;AACL,QAAA,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAI;AACvE,YAAA,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC;AAClD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBAClB,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,GAAG,QAAQ;AACxC,gBAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvE;AAEA,YAAA,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE;AACrC,gBAAA,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAChC;AAEA,YAAA,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC;QACvC,CAAC;KACF;AACH;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-client",
3
- "version": "2.16.11",
3
+ "version": "2.16.13",
4
4
  "engines": {
5
5
  "node": ">=22.19.0"
6
6
  },
@@ -36,8 +36,8 @@
36
36
  "utility-types": "^3.11.0",
37
37
  "yaml": "^2.8.0",
38
38
  "@milaboratories/pl-http": "1.2.0",
39
- "@milaboratories/pl-model-common": "1.21.9",
40
- "@milaboratories/ts-helpers": "1.5.4"
39
+ "@milaboratories/ts-helpers": "1.5.4",
40
+ "@milaboratories/pl-model-common": "1.21.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@protobuf-ts/plugin": "2.11.1",
@@ -47,9 +47,9 @@
47
47
  "@vitest/coverage-v8": "^4.0.7",
48
48
  "vitest": "^4.0.7",
49
49
  "typescript": "~5.6.3",
50
- "@milaboratories/build-configs": "1.0.8",
51
- "@milaboratories/ts-configs": "1.0.6",
52
- "@milaboratories/ts-builder": "1.0.5"
50
+ "@milaboratories/ts-builder": "1.0.6",
51
+ "@milaboratories/build-configs": "1.0.9",
52
+ "@milaboratories/ts-configs": "1.0.6"
53
53
  },
54
54
  "scripts": {
55
55
  "type-check": "ts-builder types --target node",
@@ -36,6 +36,6 @@ export function addRTypeToMetadata(rType: ResourceType, options?: RpcOptions) {
36
36
  */
37
37
  export function createRTypeRoutingHeader(rType: ResourceType): Record<string, string> {
38
38
  return {
39
- resourcetype: `${rType.name}:${rType.version}`,
39
+ resourceType: `${rType.name}:${rType.version}`,
40
40
  };
41
41
  }
@@ -24,6 +24,7 @@ export function isUnauthenticated(err: unknown, nested: boolean = false): boolea
24
24
 
25
25
  export function isTimeoutOrCancelError(err: unknown, nested: boolean = false): boolean {
26
26
  if (err instanceof Aborted || (err as any).name == 'AbortError') return true;
27
+ if ((err as any).name == 'TimeoutError') return true;
27
28
  if ((err as any).code == 'ABORT_ERR') return true;
28
29
  if ((err as any).code == Code.ABORTED) return true;
29
30
  if (
@@ -28,6 +28,8 @@ import type * as grpcTypes from '../proto-grpc/github.com/milaboratory/pl/plapi/
28
28
  import { type PlApiPaths, type PlRestClientType, createClient, parseResponseError } from '../proto-rest';
29
29
  import { notEmpty } from '@milaboratories/ts-helpers';
30
30
  import { Code } from '../proto-grpc/google/rpc/code';
31
+ import { WebSocketBiDiStream } from './websocket_stream';
32
+ import { TxAPI_ClientMessage, TxAPI_ServerMessage } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
31
33
 
32
34
  export interface PlCallOps {
33
35
  timeout?: number;
@@ -161,7 +163,7 @@ export class LLPlClient implements WireClientProviderFactory {
161
163
  }
162
164
 
163
165
  private initRestConnection(): void {
164
- const dispatcher = defaultHttpDispatcher(this.conf.httpProxy, this._restInterceptors);
166
+ const dispatcher = defaultHttpDispatcher(this.conf.grpcProxy, this._restInterceptors);
165
167
  this._replaceWireConnection({ type: 'rest', Config: this.conf, Dispatcher: dispatcher, Middlewares: this._restMiddlewares });
166
168
  }
167
169
 
@@ -479,17 +481,48 @@ export class LLPlClient implements WireClientProviderFactory {
479
481
  let totalAbortSignal = abortSignal;
480
482
  if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);
481
483
 
484
+ const timeout = ops.timeout ?? (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout);
485
+
482
486
  const cl = this.clientProvider.get();
483
- if (!(cl instanceof GrpcPlApiClient)) {
484
- // TODO: add WebSockets
485
- throw new Error('tx is not supported for REST client');
487
+ if (cl instanceof GrpcPlApiClient) {
488
+ return cl.tx({
489
+ abort: totalAbortSignal,
490
+ timeout,
491
+ });
486
492
  }
487
493
 
488
- return cl.tx({
489
- abort: totalAbortSignal,
490
- timeout: ops.timeout
491
- ?? (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout),
492
- });
494
+ const wireConn = this.wireConnection;
495
+ if (wireConn.type === 'rest') {
496
+ // For REST/WebSocket protocol, timeout needs to be converted to AbortSignal
497
+ if (timeout !== undefined) {
498
+ totalAbortSignal = AbortSignal.any([totalAbortSignal, AbortSignal.timeout(timeout)]);
499
+ }
500
+
501
+ // The gRPC transport has the auth interceptor that already handles it, but here we need to refresh the auth information to be safe.
502
+ this.refreshAuthInformationIfNeeded();
503
+
504
+ const wsUrl = this.conf.ssl
505
+ ? `wss://${this.conf.hostAndPort}/v1/ws/tx`
506
+ : `ws://${this.conf.hostAndPort}/v1/ws/tx`;
507
+
508
+ return new WebSocketBiDiStream(wsUrl,
509
+ (msg) => TxAPI_ClientMessage.toBinary(msg),
510
+ (data) => TxAPI_ServerMessage.fromBinary(new Uint8Array(data)),
511
+ {
512
+ abortSignal: totalAbortSignal,
513
+ jwtToken: this.authInformation?.jwtToken,
514
+ dispatcher: wireConn.Dispatcher,
515
+
516
+ onComplete: async (stream) => stream.requests.send({
517
+ // Ask server to gracefully close the stream on its side, if not done yet.
518
+ requestId: 0,
519
+ request: { oneofKind: 'streamClose', streamClose: {} },
520
+ }),
521
+ },
522
+ );
523
+ }
524
+
525
+ throw new Error(`transactions are not supported for wire protocol ${this._wireProto}`);
493
526
  });
494
527
  }
495
528
 
@@ -6,6 +6,24 @@ import { test, expect } from 'vitest';
6
6
  import { isTimeoutOrCancelError } from './errors';
7
7
  import { Aborted } from '@milaboratories/ts-helpers';
8
8
 
9
+ test('check successful transaction', async () => {
10
+ const client = await getTestLLClient();
11
+ const tx = client.createTx(true);
12
+
13
+ const openResp = await tx.send({
14
+ oneofKind: 'txOpen',
15
+ txOpen: { name: 'test', writable: TxAPI_Open_Request_WritableTx.WRITABLE, enableFormattedErrors: false }
16
+ }, false);
17
+ const commitResp = await tx.send({
18
+ oneofKind: 'txCommit',
19
+ txCommit: {}
20
+ }, false);
21
+
22
+ expect(openResp.txOpen.tx?.isValid).toBeTruthy();
23
+ expect(commitResp.txCommit.success).toBeTruthy();
24
+ await tx.await();
25
+ });
26
+
9
27
  test('transaction timeout test', async () => {
10
28
  const client = await getTestLLClient();
11
29
  const tx = client.createTx(true, { timeout: 500 });
@@ -236,6 +236,13 @@ export class LLPlTransaction {
236
236
  (currentHandler as AnySingleResponseHandler).resolve(message.response);
237
237
  currentHandler = undefined;
238
238
  }
239
+
240
+ // After receiving a terminal response (txCommit or txDiscard), we proactively close the client stream.
241
+ // This ensures consistent behavior between the gRPC and WebSocket transports,
242
+ // since the server closes the connection automatically upon transaction completion in both cases.
243
+ if (this.isTerminalResponse(message) && this.responseHandlerQueue.length === 0) {
244
+ await this.stream.requests.complete();
245
+ }
239
246
  }
240
247
  } catch (e: any) {
241
248
  return this.assignErrorFactoryIfNotSet(() => {
@@ -332,4 +339,9 @@ export class LLPlTransaction {
332
339
  this._completed = true;
333
340
  await this.stream.requests.complete();
334
341
  }
342
+
343
+ private isTerminalResponse(message: TxAPI_ServerMessage): boolean {
344
+ const kind = message.response.oneofKind;
345
+ return kind === 'txCommit' || kind === 'txDiscard';
346
+ }
335
347
  }