stream.cjs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.pipeGeneratorWithSetup = exports.AsyncGeneratorWithSetup = exports.concat = exports.atee = exports.IterableReadableStream = void 0;
  4. const config_js_1 = require("../runnables/config.cjs");
  5. const index_js_1 = require("../singletons/index.cjs");
  6. const signal_js_1 = require("./signal.cjs");
  7. /*
  8. * Support async iterator syntax for ReadableStreams in all environments.
  9. * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
  10. */
  11. class IterableReadableStream extends ReadableStream {
  12. constructor() {
  13. super(...arguments);
  14. Object.defineProperty(this, "reader", {
  15. enumerable: true,
  16. configurable: true,
  17. writable: true,
  18. value: void 0
  19. });
  20. }
  21. ensureReader() {
  22. if (!this.reader) {
  23. this.reader = this.getReader();
  24. }
  25. }
  26. async next() {
  27. this.ensureReader();
  28. try {
  29. const result = await this.reader.read();
  30. if (result.done) {
  31. this.reader.releaseLock(); // release lock when stream becomes closed
  32. return {
  33. done: true,
  34. value: undefined,
  35. };
  36. }
  37. else {
  38. return {
  39. done: false,
  40. value: result.value,
  41. };
  42. }
  43. }
  44. catch (e) {
  45. this.reader.releaseLock(); // release lock when stream becomes errored
  46. throw e;
  47. }
  48. }
  49. async return() {
  50. this.ensureReader();
  51. // If wrapped in a Node stream, cancel is already called.
  52. if (this.locked) {
  53. const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
  54. this.reader.releaseLock(); // release lock first
  55. await cancelPromise; // now await it
  56. }
  57. return { done: true, value: undefined };
  58. }
  59. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  60. async throw(e) {
  61. this.ensureReader();
  62. if (this.locked) {
  63. const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
  64. this.reader.releaseLock(); // release lock first
  65. await cancelPromise; // now await it
  66. }
  67. throw e;
  68. }
  69. [Symbol.asyncIterator]() {
  70. return this;
  71. }
  72. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  73. // @ts-ignore Not present in Node 18 types, required in latest Node 22
  74. async [Symbol.asyncDispose]() {
  75. await this.return();
  76. }
  77. static fromReadableStream(stream) {
  78. // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream
  79. const reader = stream.getReader();
  80. return new IterableReadableStream({
  81. start(controller) {
  82. return pump();
  83. function pump() {
  84. return reader.read().then(({ done, value }) => {
  85. // When no more data needs to be consumed, close the stream
  86. if (done) {
  87. controller.close();
  88. return;
  89. }
  90. // Enqueue the next data chunk into our target stream
  91. controller.enqueue(value);
  92. return pump();
  93. });
  94. }
  95. },
  96. cancel() {
  97. reader.releaseLock();
  98. },
  99. });
  100. }
  101. static fromAsyncGenerator(generator) {
  102. return new IterableReadableStream({
  103. async pull(controller) {
  104. const { value, done } = await generator.next();
  105. // When no more data needs to be consumed, close the stream
  106. if (done) {
  107. controller.close();
  108. }
  109. // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled
  110. controller.enqueue(value);
  111. },
  112. async cancel(reason) {
  113. await generator.return(reason);
  114. },
  115. });
  116. }
  117. }
  118. exports.IterableReadableStream = IterableReadableStream;
  119. function atee(iter, length = 2) {
  120. const buffers = Array.from({ length }, () => []);
  121. return buffers.map(async function* makeIter(buffer) {
  122. while (true) {
  123. if (buffer.length === 0) {
  124. const result = await iter.next();
  125. for (const buffer of buffers) {
  126. buffer.push(result);
  127. }
  128. }
  129. else if (buffer[0].done) {
  130. return;
  131. }
  132. else {
  133. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  134. yield buffer.shift().value;
  135. }
  136. }
  137. });
  138. }
  139. exports.atee = atee;
  140. function concat(first, second) {
  141. if (Array.isArray(first) && Array.isArray(second)) {
  142. return first.concat(second);
  143. }
  144. else if (typeof first === "string" && typeof second === "string") {
  145. return (first + second);
  146. }
  147. else if (typeof first === "number" && typeof second === "number") {
  148. return (first + second);
  149. }
  150. else if (
  151. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  152. "concat" in first &&
  153. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  154. typeof first.concat === "function") {
  155. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  156. return first.concat(second);
  157. }
  158. else if (typeof first === "object" && typeof second === "object") {
  159. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  160. const chunk = { ...first };
  161. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  162. for (const [key, value] of Object.entries(second)) {
  163. if (key in chunk && !Array.isArray(chunk[key])) {
  164. chunk[key] = concat(chunk[key], value);
  165. }
  166. else {
  167. chunk[key] = value;
  168. }
  169. }
  170. return chunk;
  171. }
  172. else {
  173. throw new Error(`Cannot concat ${typeof first} and ${typeof second}`);
  174. }
  175. }
  176. exports.concat = concat;
  177. class AsyncGeneratorWithSetup {
  178. constructor(params) {
  179. Object.defineProperty(this, "generator", {
  180. enumerable: true,
  181. configurable: true,
  182. writable: true,
  183. value: void 0
  184. });
  185. Object.defineProperty(this, "setup", {
  186. enumerable: true,
  187. configurable: true,
  188. writable: true,
  189. value: void 0
  190. });
  191. Object.defineProperty(this, "config", {
  192. enumerable: true,
  193. configurable: true,
  194. writable: true,
  195. value: void 0
  196. });
  197. Object.defineProperty(this, "signal", {
  198. enumerable: true,
  199. configurable: true,
  200. writable: true,
  201. value: void 0
  202. });
  203. Object.defineProperty(this, "firstResult", {
  204. enumerable: true,
  205. configurable: true,
  206. writable: true,
  207. value: void 0
  208. });
  209. Object.defineProperty(this, "firstResultUsed", {
  210. enumerable: true,
  211. configurable: true,
  212. writable: true,
  213. value: false
  214. });
  215. this.generator = params.generator;
  216. this.config = params.config;
  217. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  218. this.signal = params.signal ?? this.config?.signal;
  219. // setup is a promise that resolves only after the first iterator value
  220. // is available. this is useful when setup of several piped generators
  221. // needs to happen in logical order, ie. in the order in which input to
  222. // to each generator is available.
  223. this.setup = new Promise((resolve, reject) => {
  224. void index_js_1.AsyncLocalStorageProviderSingleton.runWithConfig((0, config_js_1.pickRunnableConfigKeys)(params.config), async () => {
  225. this.firstResult = params.generator.next();
  226. if (params.startSetup) {
  227. this.firstResult.then(params.startSetup).then(resolve, reject);
  228. }
  229. else {
  230. this.firstResult.then((_result) => resolve(undefined), reject);
  231. }
  232. }, true);
  233. });
  234. }
  235. async next(...args) {
  236. this.signal?.throwIfAborted();
  237. if (!this.firstResultUsed) {
  238. this.firstResultUsed = true;
  239. return this.firstResult;
  240. }
  241. return index_js_1.AsyncLocalStorageProviderSingleton.runWithConfig((0, config_js_1.pickRunnableConfigKeys)(this.config), this.signal
  242. ? async () => {
  243. return (0, signal_js_1.raceWithSignal)(this.generator.next(...args), this.signal);
  244. }
  245. : async () => {
  246. return this.generator.next(...args);
  247. }, true);
  248. }
  249. async return(value) {
  250. return this.generator.return(value);
  251. }
  252. async throw(e) {
  253. return this.generator.throw(e);
  254. }
  255. [Symbol.asyncIterator]() {
  256. return this;
  257. }
  258. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  259. // @ts-ignore Not present in Node 18 types, required in latest Node 22
  260. async [Symbol.asyncDispose]() {
  261. await this.return();
  262. }
  263. }
  264. exports.AsyncGeneratorWithSetup = AsyncGeneratorWithSetup;
  265. async function pipeGeneratorWithSetup(to, generator, startSetup, signal, ...args) {
  266. const gen = new AsyncGeneratorWithSetup({
  267. generator,
  268. startSetup,
  269. signal,
  270. });
  271. const setup = await gen.setup;
  272. return { output: to(gen, setup, ...args), setup };
  273. }
  274. exports.pipeGeneratorWithSetup = pipeGeneratorWithSetup;