@workflow/world-local 4.1.2 → 4.1.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.
package/dist/fs.d.ts CHANGED
@@ -112,6 +112,7 @@ export declare function writeJSON(filePath: string, data: any, opts?: WriteOptio
112
112
  export declare function write(filePath: string, data: string | Buffer, opts?: WriteOptions): Promise<void>;
113
113
  export declare function readJSON<T>(filePath: string, decoder: z.ZodType<T>): Promise<T | null>;
114
114
  export declare function readBuffer(filePath: string): Promise<Buffer>;
115
+ export declare function readFirstByte(filePath: string): Promise<number | undefined>;
115
116
  export declare function deleteJSON(filePath: string): Promise<void>;
116
117
  /**
117
118
  * Atomically create a file using O_CREAT | O_EXCL flags.
package/dist/fs.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAEA,OAAO,EAAuB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,kBAAkB;gBAC7C,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAOvC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB;CAGxD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAWpE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,GAAG,QAAQ,EAAE,MAAM,EAAE,GACpB,MAAM,CAOR;AAuCD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAW7C;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,GACX,MAAM,CAKR;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EACpB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAcnB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,EAAE,CAAC,CASnB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CASnB;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9D;AAED,UAAU,YAAY;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAQlE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAUjE;AAED,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CACzB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAQnB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAEtE;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB;AAED,UAAU,8BAA8B,CAAC,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;CACzB;AAqBD,wBAAsB,wBAAwB,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,EAC1E,MAAM,EAAE,8BAA8B,CAAC,CAAC,CAAC,GACxC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CA4J/B"}
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
package/dist/fs.js CHANGED
@@ -303,6 +303,17 @@ export async function readBuffer(filePath) {
303
303
  const content = await fs.readFile(filePath);
304
304
  return content;
305
305
  }
306
+ export async function readFirstByte(filePath) {
307
+ const file = await fs.open(filePath, 'r');
308
+ try {
309
+ const byte = Buffer.allocUnsafe(1);
310
+ const { bytesRead } = await file.read(byte, 0, 1, 0);
311
+ return bytesRead === 0 ? undefined : byte[0];
312
+ }
313
+ finally {
314
+ await file.close();
315
+ }
316
+ }
306
317
  export async function deleteJSON(filePath) {
307
318
  try {
308
319
  await fs.unlink(filePath);
package/dist/fs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAc;IACtC,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC;IACf,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,mBAAoB,SAAQ,kBAAkB;IACzD,YAAY,IAAY,EAAE,KAAa;QACrC,KAAK,CACH,UAAU,IAAI,KAAK,gBAAgB,CAAC,KAAK,CAAC,6DAA6D,CACxG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,EAAE,CAAC,KAAc;QACtB,OAAO,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,CAAC;IACxE,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,KAAa;IAC5D,IACE,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACrB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QACnB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EACnB,CAAC;QACD,MAAM,IAAI,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,GAAG,QAAkB;IAErB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;IAClD,IAAI,MAAM,KAAK,YAAY,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAE/C;;;;;GAKG;AACH,KAAK,UAAU,gBAAgB,CAC7B,EAAoB,EACpB,UAAU,GAAG,CAAC;IAEd,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,CAAC;IAE5B,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,EAAE,CAAC;IAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,MAAM,WAAW,GACf,OAAO,GAAG,UAAU,IAAI,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/D,IAAI,CAAC,WAAW;gBAAE,MAAM,KAAK,CAAC;YAC9B,kCAAkC;YAClC,MAAM,KAAK,GAAG,WAAW,GAAG,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC;YACvE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,qDAAqD;IACrD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;AACpD,CAAC;AAED,wEAAwE;AACxE,8FAA8F;AAC9F,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,MAAc,EAAE,GAAW;IAChD,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,SAAiB,EACjB,MAAc,EACd,GAAY;IAEZ,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS;QAAE,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC;IAClE,OAAO,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,SAAiB,EACjB,MAAc,EACd,MAAoB,EACpB,GAAY;IAEZ,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,SAAS;QAAE,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtD,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,EAC9D,MAAM,CACP,CAAC;QACF,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IACD,OAAO,QAAQ,CACb,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,EACvD,MAAM,CACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAe,EACf,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAAa,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAe,EACf,GAAW,EACX,SAAiB;IAEjB,MAAM,MAAM,GAAG,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAAa,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,2BAA2B;IAC7B,CAAC;AACH,CAAC;AAMD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAc;IACvD,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO;YACL,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC5C,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAc;IACtD,IACE,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACxB,KAAa,CAAC,MAAM,KAAK,YAAY;QACtC,OAAQ,KAAa,CAAC,IAAI,KAAK,QAAQ,EACvC,CAAC;QACD,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAE,KAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAChB,IAAS,EACT,IAAmB;IAEnB,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,QAAgB,EAChB,IAAqB,EACrB,IAAmB;IAEnB,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;QACrB,8EAA8E;QAC9E,6EAA6E;QAC7E,IAAI,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,mBAAmB,CAC3B,QAAQ,QAAQ,0CAA0C,CAC3D,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC1B,sDAAsD;YACtD,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAChC,MAAM,IAAI,mBAAmB,CAC3B,QAAQ,QAAQ,0CAA0C,CAC3D,CAAC;QACJ,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,sDAAsD;YACtD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,QAAQ,QAAQ,IAAI,EAAE,EAAE,CAAC;IAC7C,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACxC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACnC,eAAe,GAAG,IAAI,CAAC;QACvB,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC5D,2DAA2D;QAC3D,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4DAA4D;QAC5D,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,QAAgB,EAChB,OAAqB;IAErB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAAa,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAAa,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IACpD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,IAAY;IAEZ,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,OAAO,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,SAAiB;IAEjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,OAAO,KAAK;aACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;aACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAAa,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAoBD,SAAS,WAAW,CAAC,MAA0B;IAC7C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO;QACL,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI;KACrB,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,SAAe,EAAE,EAAsB;IAC3D,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAyC;IAEzC,MAAM,EACJ,SAAS,EACT,MAAM,EACN,UAAU,EACV,YAAY,EACZ,MAAM,EACN,SAAS,GAAG,MAAM,EAClB,KAAK,GAAG,EAAE,EACV,MAAM,EACN,YAAY,EACZ,KAAK,GACN,GAAG,MAAM,CAAC;IAEX,0EAA0E;IAC1E,0EAA0E;IAC1E,sEAAsE;IACtE,oEAAoE;IACpE,qEAAqE;IACrE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,kBAAkB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC/C,CAAC;IAED,qCAAqC;IACrC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,CAAC;IAE/C,kCAAkC;IAClC,MAAM,eAAe,GAAG,UAAU;QAChC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC3D,CAAC,CAAC,OAAO,CAAC;IACZ,MAAM,eAAe,GAAG,YAAY;QAClC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC;QACtC,CAAC,CAAC,eAAe,CAAC;IAEpB,uFAAuF;IACvF,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,gBAAgB,GAAG,eAAe,CAAC;IAEvC,IAAI,YAAY,EAAE,CAAC;QACjB,gBAAgB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;YACnD,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC;YACpD,IAAI,YAAY,EAAE,CAAC;gBACjB,8CAA8C;gBAC9C,4EAA4E;gBAC5E,iFAAiF;gBACjF,2DAA2D;gBAC3D,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;gBACpD,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;gBAExC,IAAI,YAAY,CAAC,EAAE,EAAE,CAAC;oBACpB,+DAA+D;oBAC/D,OAAO,SAAS,KAAK,MAAM;wBACzB,CAAC,CAAC,QAAQ,IAAI,UAAU;wBACxB,CAAC,CAAC,QAAQ,IAAI,UAAU,CAAC;gBAC7B,CAAC;qBAAM,CAAC;oBACN,qCAAqC;oBACrC,OAAO,SAAS,KAAK,MAAM;wBACzB,CAAC,CAAC,QAAQ,GAAG,UAAU;wBACvB,CAAC,CAAC,QAAQ,GAAG,UAAU,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,0EAA0E;YAC1E,4DAA4D;YAC5D,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,qDAAqD;IACrD,MAAM,UAAU,GAAQ,EAAE,CAAC;IAE3B,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;QACxD,IAAI,IAAI,GAAa,IAAI,CAAC;QAC1B,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,4EAA4E;YAC5E,4EAA4E;YAC5E,mFAAmF;YACnF,2CAA2C;YAC3C,IAAI,KAAK,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAChC,OAAO,CAAC,IAAI,CACV,iBAAiB,MAAM,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAClE,CAAC;gBACF,SAAS;YACX,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,wCAAwC;YACxC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEtC,gEAAgE;YAChE,yDAAyD;YACzD,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;gBAC1C,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;gBAEpD,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;oBACzB,6CAA6C;oBAC7C,IAAI,QAAQ,GAAG,UAAU;wBAAE,SAAS;oBACpC,4EAA4E;oBAC5E,IAAI,QAAQ,KAAK,UAAU,IAAI,YAAY,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC;wBACxD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;wBAC3B,IAAI,MAAM,IAAI,YAAY,CAAC,EAAE;4BAAE,SAAS;oBAC1C,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,4CAA4C;oBAC5C,IAAI,QAAQ,GAAG,UAAU;wBAAE,SAAS;oBACpC,4EAA4E;oBAC5E,IAAI,QAAQ,KAAK,UAAU,IAAI,YAAY,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC;wBACxD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;wBAC3B,IAAI,MAAM,IAAI,YAAY,CAAC,EAAE;4BAAE,SAAS;oBAC1C,CAAC;gBACH,CAAC;YACH,CAAC;YAED,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,cAAc,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC;QAE3E,uEAAuE;QACvE,IAAI,cAAc,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,OAAO,SAAS,KAAK,KAAK;gBACxB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC;gBACxB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAED,OAAO,cAAc,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,sBAAsB;IACtB,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAChE,MAAM,UAAU,GACd,KAAK,CAAC,MAAM,GAAG,CAAC;QACd,CAAC,CAAC,YAAY,CACV,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,EACjC,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CACjC;QACH,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO;QACL,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,UAAU;QAClB,OAAO;KACR,CAAC;AACJ,CAAC","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { EntityConflictError, WorkflowWorldError } from '@workflow/errors';\nimport type { PaginatedResponse } from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\n\nconst ulid = monotonicFactory(() => Math.random());\n\n/**\n * Truncate a possibly-untrusted value for inclusion in an error message.\n * Keeps error output actionable for developers in dev mode while limiting\n * the amount of attacker-controlled data reflected back if a `world-local`\n * backend is ever run in a context that surfaces these messages to clients.\n */\nfunction truncateForError(value: unknown): string {\n const s = typeof value === 'string' ? value : String(value);\n const MAX = 48;\n return s.length > MAX ? `${s.slice(0, MAX)}…` : s;\n}\n\n/**\n * Thrown when a caller-supplied entity ID contains characters that would\n * escape the storage root (path traversal) or otherwise produce an unsafe\n * filename. Extends {@link WorkflowWorldError} so the platform's standard\n * error-to-HTTP mapping handles it alongside other world-layer errors.\n */\nexport class UnsafeEntityIdError extends WorkflowWorldError {\n constructor(kind: string, value: string) {\n super(\n `Unsafe ${kind} \"${truncateForError(value)}\": must not be empty, contain \".\", \"/\", \"\\\\\", or null bytes`\n );\n this.name = 'UnsafeEntityIdError';\n }\n\n static is(value: unknown): value is UnsafeEntityIdError {\n return value instanceof Error && value.name === 'UnsafeEntityIdError';\n }\n}\n\n/**\n * Validate that a string is safe to embed in a filesystem path as a single\n * path component. Rejects values that are:\n * - empty\n * - starting with `.` (blocks `.`, `..`, `.locks`, `.tmp`, and other\n * hidden or reserved filenames)\n * - containing `.` (would collide with the `.tag` / `.json` suffix the\n * tagging logic strips from filenames; see {@link stripTag} /\n * {@link getObjectCreatedAt})\n * - containing `/`, `\\`, or a NUL byte\n *\n * This is the primary defense against path-traversal attacks where a\n * request body supplies a `runId` / `stepId` / `correlationId` containing\n * `../` sequences to read or write files outside the storage root.\n * {@link resolveWithinBase} provides a belt-and-suspenders containment\n * check at the point of `path.join` for defense in depth.\n *\n * @param kind - Human-readable label used in the error message (e.g. \"runId\")\n * @param value - The value to validate; throws {@link UnsafeEntityIdError}\n * if the value is not safe.\n */\nexport function assertSafeEntityId(kind: string, value: string): void {\n if (\n value.length === 0 ||\n value.startsWith('.') ||\n value.includes('/') ||\n value.includes('\\\\') ||\n value.includes('\\0') ||\n value.includes('.')\n ) {\n throw new UnsafeEntityIdError(kind, value);\n }\n}\n\n/**\n * Join `basedir` with an entity-relative path and assert the result stays\n * inside `basedir`. Complements {@link assertSafeEntityId}: per-entry-point\n * validation is the primary defense, and this final check catches any path\n * escape that slipped past (e.g. a new call site that forgot to validate,\n * or an unusual character combination on a future filesystem). Throws\n * {@link UnsafeEntityIdError} if the joined path escapes `basedir`.\n */\nexport function resolveWithinBase(\n basedir: string,\n ...segments: string[]\n): string {\n const resolvedBase = path.resolve(basedir);\n const joined = path.resolve(basedir, ...segments);\n if (joined !== resolvedBase && !joined.startsWith(resolvedBase + path.sep)) {\n throw new UnsafeEntityIdError('path', segments.join('/'));\n }\n return joined;\n}\n\nconst isWindows = process.platform === 'win32';\n\n/**\n * Execute a filesystem operation with retry logic on Windows.\n * On Windows, file operations can fail with EPERM/EBUSY/EACCES when files\n * are briefly locked by another process or antivirus. This wrapper adds\n * exponential backoff retry logic. On non-Windows platforms, executes directly.\n */\nasync function withWindowsRetry<T>(\n fn: () => Promise<T>,\n maxRetries = 5\n): Promise<T> {\n if (!isWindows) return fn();\n\n const retryableErrors = ['EPERM', 'EBUSY', 'EACCES'];\n const baseDelayMs = 10;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error: any) {\n const isRetryable =\n attempt < maxRetries && retryableErrors.includes(error.code);\n if (!isRetryable) throw error;\n // Exponential backoff with jitter\n const delay = baseDelayMs * 2 ** attempt + Math.random() * baseDelayMs;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n // TypeScript: unreachable, but satisfies return type\n throw new Error('Retry loop exited unexpectedly');\n}\n\n// In-memory cache of created files to avoid expensive fs.access() calls\n// This is safe because we only write once per file path (no overwrites without explicit flag)\nconst createdFilesCache = new Set<string>();\n\n/**\n * Clear the created files cache. Useful for testing or when files are deleted externally.\n */\nexport function clearCreatedFilesCache(): void {\n createdFilesCache.clear();\n}\n\nexport { ulidToDate } from '@workflow/world';\n\n/**\n * Regex matching a tag suffix on a fileId (after `.json` has been stripped).\n * E.g., `wrun_ABC.vitest-0` → the `.vitest-0` part.\n * Tags start with a letter and contain alphanumeric chars and hyphens.\n * Entity IDs (ULIDs, step_N, etc.) never contain dots, so the first dot\n * always marks the tag boundary.\n */\nconst TAG_PATTERN = /\\.[a-zA-Z][a-zA-Z0-9-]*$/;\n\n/**\n * Strip a tag suffix from a fileId.\n * `wrun_ABC.vitest-0` → `wrun_ABC`\n * `wrun_ABC` → `wrun_ABC` (no-op if no tag)\n */\nexport function stripTag(fileId: string): string {\n return fileId.replace(TAG_PATTERN, '');\n}\n\n/**\n * Check whether a fileId belongs to a specific tag.\n * `wrun_ABC.vitest-0` belongs to `vitest-0`.\n * Untagged fileIds never match a tag-scoped lookup.\n */\nexport function hasTag(fileId: string, tag: string): boolean {\n return fileId.endsWith(`.${tag}`);\n}\n\n/**\n * Build the file path for an entity, with optional tag embedded in the filename.\n * `taggedPath('/data', 'runs', 'wrun_ABC', 'vitest-0')` → `/data/runs/wrun_ABC.vitest-0.json`\n * `taggedPath('/data', 'runs', 'wrun_ABC')` → `/data/runs/wrun_ABC.json`\n *\n * The `fileId` and `tag` are validated with {@link assertSafeEntityId} and\n * the result is containment-checked with {@link resolveWithinBase} to\n * prevent path-traversal attacks when values are derived from untrusted\n * request input.\n */\nexport function taggedPath(\n basedir: string,\n entityDir: string,\n fileId: string,\n tag?: string\n): string {\n assertSafeEntityId('fileId', fileId);\n if (tag !== undefined) assertSafeEntityId('tag', tag);\n const filename = tag ? `${fileId}.${tag}.json` : `${fileId}.json`;\n return resolveWithinBase(basedir, entityDir, filename);\n}\n\n/**\n * Read a JSON entity with tagged fallback.\n * When a tag is set, tries the tagged path first, then falls back to the\n * untagged path (so a tagged world can read entities written without a tag).\n */\nexport async function readJSONWithFallback<T>(\n basedir: string,\n entityDir: string,\n fileId: string,\n schema: z.ZodType<T>,\n tag?: string\n): Promise<T | null> {\n assertSafeEntityId('fileId', fileId);\n if (tag !== undefined) assertSafeEntityId('tag', tag);\n if (tag) {\n const result = await readJSON(\n resolveWithinBase(basedir, entityDir, `${fileId}.${tag}.json`),\n schema\n );\n if (result !== null) return result;\n }\n return readJSON(\n resolveWithinBase(basedir, entityDir, `${fileId}.json`),\n schema\n );\n}\n\n/**\n * List all filenames in a directory that have a specific tag.\n * Returns full filenames (e.g., `wrun_ABC.vitest-0.json`).\n */\nexport async function listTaggedFiles(\n dirPath: string,\n tag: string\n): Promise<string[]> {\n const suffix = `.${tag}.json`;\n try {\n const files = await fs.readdir(dirPath);\n return files.filter((f) => f.endsWith(suffix));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\n/**\n * List all filenames in a directory that have a specific tag and extension.\n * Returns full filenames (e.g., `stream-chnk_ABC.vitest-0.bin`).\n */\nexport async function listTaggedFilesByExtension(\n dirPath: string,\n tag: string,\n extension: string\n): Promise<string[]> {\n const suffix = `.${tag}${extension}`;\n try {\n const files = await fs.readdir(dirPath);\n return files.filter((f) => f.endsWith(suffix));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n try {\n await fs.mkdir(dirPath, { recursive: true });\n } catch (_error) {\n // Ignore if already exists\n }\n}\n\ninterface WriteOptions {\n overwrite?: boolean;\n}\n\n/**\n * Custom JSON replacer that encodes Uint8Array as base64 strings.\n * Format: { __type: 'Uint8Array', data: '<base64>' }\n */\nexport function jsonReplacer(_key: string, value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n}\n\n/**\n * Custom JSON reviver that decodes base64 strings back to Uint8Array.\n */\nexport function jsonReviver(_key: string, value: unknown): unknown {\n if (\n value !== null &&\n typeof value === 'object' &&\n (value as any).__type === 'Uint8Array' &&\n typeof (value as any).data === 'string'\n ) {\n return new Uint8Array(Buffer.from((value as any).data, 'base64'));\n }\n return value;\n}\n\nexport async function writeJSON(\n filePath: string,\n data: any,\n opts?: WriteOptions\n): Promise<void> {\n return write(filePath, JSON.stringify(data, jsonReplacer, 2), opts);\n}\n\n/**\n * Writes data to a file using atomic write-rename pattern.\n *\n * Note: While this function uses temp files to avoid partial writes,\n * it does not provide protection against concurrent writes from multiple\n * processes. In a multi-writer scenario, the last writer wins.\n * For production use with multiple writers, consider using a proper\n * database or locking mechanism.\n */\nexport async function write(\n filePath: string,\n data: string | Buffer,\n opts?: WriteOptions\n): Promise<void> {\n if (!opts?.overwrite) {\n // Fast path: check in-memory cache first to avoid expensive fs.access() calls\n // This provides significant performance improvement when creating many files\n if (createdFilesCache.has(filePath)) {\n throw new EntityConflictError(\n `File ${filePath} already exists and 'overwrite' is false`\n );\n }\n\n // Slow path: check filesystem for files created before this process started\n try {\n await fs.access(filePath);\n // File exists on disk, add to cache for future checks\n createdFilesCache.add(filePath);\n throw new EntityConflictError(\n `File ${filePath} already exists and 'overwrite' is false`\n );\n } catch (error: any) {\n // If file doesn't exist (ENOENT), continue with write\n if (error.code !== 'ENOENT') {\n throw error;\n }\n }\n }\n\n const tempPath = `${filePath}.tmp.${ulid()}`;\n let tempFileCreated = false;\n try {\n await ensureDir(path.dirname(filePath));\n await fs.writeFile(tempPath, data);\n tempFileCreated = true;\n await withWindowsRetry(() => fs.rename(tempPath, filePath));\n // Track this file in cache so future writes know it exists\n createdFilesCache.add(filePath);\n } catch (error) {\n // Only try to clean up temp file if it was actually created\n if (tempFileCreated) {\n await withWindowsRetry(() => fs.unlink(tempPath), 3).catch(() => {});\n }\n throw error;\n }\n}\n\nexport async function readJSON<T>(\n filePath: string,\n decoder: z.ZodType<T>\n): Promise<T | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n return decoder.parse(JSON.parse(content, jsonReviver));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return null;\n throw error;\n }\n}\n\nexport async function readBuffer(filePath: string): Promise<Buffer> {\n const content = await fs.readFile(filePath);\n return content;\n}\n\nexport async function deleteJSON(filePath: string): Promise<void> {\n try {\n await fs.unlink(filePath);\n } catch (error) {\n if ((error as any).code !== 'ENOENT') throw error;\n }\n}\n\n/**\n * Atomically create a file using O_CREAT | O_EXCL flags.\n * Returns true if the file was created, false if it already exists.\n * This is atomic at the OS level, safe for concurrent access.\n */\nexport async function writeExclusive(\n filePath: string,\n data: string\n): Promise<boolean> {\n await ensureDir(path.dirname(filePath));\n try {\n await fs.writeFile(filePath, data, { flag: 'wx' });\n return true;\n } catch (error: any) {\n if (error.code === 'EEXIST') {\n return false;\n }\n throw error;\n }\n}\n\nexport async function listJSONFiles(dirPath: string): Promise<string[]> {\n return listFilesByExtension(dirPath, '.json');\n}\n\nexport async function listFilesByExtension(\n dirPath: string,\n extension: string\n): Promise<string[]> {\n try {\n const files = await fs.readdir(dirPath);\n return files\n .filter((f) => f.endsWith(extension))\n .map((f) => f.slice(0, -extension.length));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\ninterface PaginatedFileSystemQueryConfig<T> {\n directory: string;\n schema: z.ZodType<T>;\n filePrefix?: string;\n fileIdFilter?: (fileId: string) => boolean;\n filter?: (item: T) => boolean;\n sortOrder?: 'asc' | 'desc';\n limit?: number;\n cursor?: string;\n getCreatedAt(filename: string): Date | null;\n getId?(item: T): string;\n}\n// Cursor format: \"timestamp|id\" for tie-breaking\ninterface ParsedCursor {\n timestamp: Date;\n id: string | null;\n}\n\nfunction parseCursor(cursor: string | undefined): ParsedCursor | null {\n if (!cursor) return null;\n\n const parts = cursor.split('|');\n return {\n timestamp: new Date(parts[0]),\n id: parts[1] || null,\n };\n}\n\nfunction createCursor(timestamp: Date, id: string | undefined): string {\n return id ? `${timestamp.toISOString()}|${id}` : timestamp.toISOString();\n}\n\nexport async function paginatedFileSystemQuery<T extends { createdAt: Date }>(\n config: PaginatedFileSystemQueryConfig<T>\n): Promise<PaginatedResponse<T>> {\n const {\n directory,\n schema,\n filePrefix,\n fileIdFilter,\n filter,\n sortOrder = 'desc',\n limit = 20,\n cursor,\n getCreatedAt,\n getId,\n } = config;\n\n // Validate filePrefix (typically `${runId}-`) so request-derived prefixes\n // consistently reject unsafe characters. filePrefix is only used below to\n // filter readdir() results by prefix — it doesn't participate in path\n // construction — but keeping the validation rule uniform across the\n // storage layer avoids special cases and catches bad values earlier.\n if (filePrefix !== undefined) {\n assertSafeEntityId('filePrefix', filePrefix);\n }\n\n // 1. Get all JSON files in directory\n const fileIds = await listJSONFiles(directory);\n\n // 2. Filter by prefix if provided\n const relevantFileIds = filePrefix\n ? fileIds.filter((fileId) => fileId.startsWith(filePrefix))\n : fileIds;\n const filteredFileIds = fileIdFilter\n ? relevantFileIds.filter(fileIdFilter)\n : relevantFileIds;\n\n // 3. ULID Optimization: Filter by cursor using filename timestamps before loading JSON\n const parsedCursor = parseCursor(cursor);\n let candidateFileIds = filteredFileIds;\n\n if (parsedCursor) {\n candidateFileIds = filteredFileIds.filter((fileId) => {\n const filenameDate = getCreatedAt(`${fileId}.json`);\n if (filenameDate) {\n // Use filename timestamp for cursor filtering\n // We need to be careful here: if parsedCursor has an ID (for tie-breaking),\n // we need to include items with the same timestamp for later ID-based filtering.\n // If no ID, we can use strict inequality for optimization.\n const cursorTime = parsedCursor.timestamp.getTime();\n const fileTime = filenameDate.getTime();\n\n if (parsedCursor.id) {\n // Tie-breaking mode: include items at or near cursor timestamp\n return sortOrder === 'desc'\n ? fileTime <= cursorTime\n : fileTime >= cursorTime;\n } else {\n // No tie-breaking: strict inequality\n return sortOrder === 'desc'\n ? fileTime < cursorTime\n : fileTime > cursorTime;\n }\n }\n // Can't extract timestamp from filename (e.g., steps use sequential IDs).\n // Include the file and defer to JSON-based filtering below.\n return true;\n });\n }\n\n // 4. Load files individually and collect valid items\n const validItems: T[] = [];\n\n for (const fileId of candidateFileIds) {\n const filePath = path.join(directory, `${fileId}.json`);\n let item: T | null = null;\n try {\n item = await readJSON(filePath, schema);\n } catch (error: unknown) {\n // We don't expect zod errors to happen, but if the JSON does get malformed,\n // we skip the item. Preferably, we'd have a way to mark items as malformed,\n // so that the UI can display them as such, with richer messaging. In the meantime,\n // we just log a warning and skip the item.\n if (error instanceof z.ZodError) {\n console.warn(\n `Skipping item ${fileId} due to malformed JSON: ${error.message}`\n );\n continue;\n }\n throw error;\n }\n\n if (item) {\n // Apply custom filter early if provided\n if (filter && !filter(item)) continue;\n\n // Double-check cursor filtering with actual createdAt from JSON\n // (in case ULID timestamp differs from stored createdAt)\n if (parsedCursor) {\n const itemTime = item.createdAt.getTime();\n const cursorTime = parsedCursor.timestamp.getTime();\n\n if (sortOrder === 'desc') {\n // For descending order, skip items >= cursor\n if (itemTime > cursorTime) continue;\n // If timestamps are equal, use ID for tie-breaking (skip if ID >= cursorId)\n if (itemTime === cursorTime && parsedCursor.id && getId) {\n const itemId = getId(item);\n if (itemId >= parsedCursor.id) continue;\n }\n } else {\n // For ascending order, skip items <= cursor\n if (itemTime < cursorTime) continue;\n // If timestamps are equal, use ID for tie-breaking (skip if ID <= cursorId)\n if (itemTime === cursorTime && parsedCursor.id && getId) {\n const itemId = getId(item);\n if (itemId <= parsedCursor.id) continue;\n }\n }\n }\n\n validItems.push(item);\n }\n }\n\n // 5. Sort by createdAt (and by ID for tie-breaking if getId is provided)\n validItems.sort((a, b) => {\n const aTime = a.createdAt.getTime();\n const bTime = b.createdAt.getTime();\n const timeComparison = sortOrder === 'asc' ? aTime - bTime : bTime - aTime;\n\n // If timestamps are equal and we have getId, use ID for stable sorting\n if (timeComparison === 0 && getId) {\n const aId = getId(a);\n const bId = getId(b);\n return sortOrder === 'asc'\n ? aId.localeCompare(bId)\n : bId.localeCompare(aId);\n }\n\n return timeComparison;\n });\n\n // 6. Apply pagination\n const hasMore = validItems.length > limit;\n const items = hasMore ? validItems.slice(0, limit) : validItems;\n const nextCursor =\n items.length > 0\n ? createCursor(\n items[items.length - 1].createdAt,\n getId?.(items[items.length - 1])\n )\n : null;\n\n return {\n data: items,\n cursor: nextCursor,\n hasMore,\n };\n}\n"]}
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { EntityConflictError, WorkflowWorldError } from '@workflow/errors';\nimport type { PaginatedResponse } from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\n\nconst ulid = monotonicFactory(() => Math.random());\n\n/**\n * Truncate a possibly-untrusted value for inclusion in an error message.\n * Keeps error output actionable for developers in dev mode while limiting\n * the amount of attacker-controlled data reflected back if a `world-local`\n * backend is ever run in a context that surfaces these messages to clients.\n */\nfunction truncateForError(value: unknown): string {\n const s = typeof value === 'string' ? value : String(value);\n const MAX = 48;\n return s.length > MAX ? `${s.slice(0, MAX)}…` : s;\n}\n\n/**\n * Thrown when a caller-supplied entity ID contains characters that would\n * escape the storage root (path traversal) or otherwise produce an unsafe\n * filename. Extends {@link WorkflowWorldError} so the platform's standard\n * error-to-HTTP mapping handles it alongside other world-layer errors.\n */\nexport class UnsafeEntityIdError extends WorkflowWorldError {\n constructor(kind: string, value: string) {\n super(\n `Unsafe ${kind} \"${truncateForError(value)}\": must not be empty, contain \".\", \"/\", \"\\\\\", or null bytes`\n );\n this.name = 'UnsafeEntityIdError';\n }\n\n static is(value: unknown): value is UnsafeEntityIdError {\n return value instanceof Error && value.name === 'UnsafeEntityIdError';\n }\n}\n\n/**\n * Validate that a string is safe to embed in a filesystem path as a single\n * path component. Rejects values that are:\n * - empty\n * - starting with `.` (blocks `.`, `..`, `.locks`, `.tmp`, and other\n * hidden or reserved filenames)\n * - containing `.` (would collide with the `.tag` / `.json` suffix the\n * tagging logic strips from filenames; see {@link stripTag} /\n * {@link getObjectCreatedAt})\n * - containing `/`, `\\`, or a NUL byte\n *\n * This is the primary defense against path-traversal attacks where a\n * request body supplies a `runId` / `stepId` / `correlationId` containing\n * `../` sequences to read or write files outside the storage root.\n * {@link resolveWithinBase} provides a belt-and-suspenders containment\n * check at the point of `path.join` for defense in depth.\n *\n * @param kind - Human-readable label used in the error message (e.g. \"runId\")\n * @param value - The value to validate; throws {@link UnsafeEntityIdError}\n * if the value is not safe.\n */\nexport function assertSafeEntityId(kind: string, value: string): void {\n if (\n value.length === 0 ||\n value.startsWith('.') ||\n value.includes('/') ||\n value.includes('\\\\') ||\n value.includes('\\0') ||\n value.includes('.')\n ) {\n throw new UnsafeEntityIdError(kind, value);\n }\n}\n\n/**\n * Join `basedir` with an entity-relative path and assert the result stays\n * inside `basedir`. Complements {@link assertSafeEntityId}: per-entry-point\n * validation is the primary defense, and this final check catches any path\n * escape that slipped past (e.g. a new call site that forgot to validate,\n * or an unusual character combination on a future filesystem). Throws\n * {@link UnsafeEntityIdError} if the joined path escapes `basedir`.\n */\nexport function resolveWithinBase(\n basedir: string,\n ...segments: string[]\n): string {\n const resolvedBase = path.resolve(basedir);\n const joined = path.resolve(basedir, ...segments);\n if (joined !== resolvedBase && !joined.startsWith(resolvedBase + path.sep)) {\n throw new UnsafeEntityIdError('path', segments.join('/'));\n }\n return joined;\n}\n\nconst isWindows = process.platform === 'win32';\n\n/**\n * Execute a filesystem operation with retry logic on Windows.\n * On Windows, file operations can fail with EPERM/EBUSY/EACCES when files\n * are briefly locked by another process or antivirus. This wrapper adds\n * exponential backoff retry logic. On non-Windows platforms, executes directly.\n */\nasync function withWindowsRetry<T>(\n fn: () => Promise<T>,\n maxRetries = 5\n): Promise<T> {\n if (!isWindows) return fn();\n\n const retryableErrors = ['EPERM', 'EBUSY', 'EACCES'];\n const baseDelayMs = 10;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error: any) {\n const isRetryable =\n attempt < maxRetries && retryableErrors.includes(error.code);\n if (!isRetryable) throw error;\n // Exponential backoff with jitter\n const delay = baseDelayMs * 2 ** attempt + Math.random() * baseDelayMs;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n // TypeScript: unreachable, but satisfies return type\n throw new Error('Retry loop exited unexpectedly');\n}\n\n// In-memory cache of created files to avoid expensive fs.access() calls\n// This is safe because we only write once per file path (no overwrites without explicit flag)\nconst createdFilesCache = new Set<string>();\n\n/**\n * Clear the created files cache. Useful for testing or when files are deleted externally.\n */\nexport function clearCreatedFilesCache(): void {\n createdFilesCache.clear();\n}\n\nexport { ulidToDate } from '@workflow/world';\n\n/**\n * Regex matching a tag suffix on a fileId (after `.json` has been stripped).\n * E.g., `wrun_ABC.vitest-0` → the `.vitest-0` part.\n * Tags start with a letter and contain alphanumeric chars and hyphens.\n * Entity IDs (ULIDs, step_N, etc.) never contain dots, so the first dot\n * always marks the tag boundary.\n */\nconst TAG_PATTERN = /\\.[a-zA-Z][a-zA-Z0-9-]*$/;\n\n/**\n * Strip a tag suffix from a fileId.\n * `wrun_ABC.vitest-0` → `wrun_ABC`\n * `wrun_ABC` → `wrun_ABC` (no-op if no tag)\n */\nexport function stripTag(fileId: string): string {\n return fileId.replace(TAG_PATTERN, '');\n}\n\n/**\n * Check whether a fileId belongs to a specific tag.\n * `wrun_ABC.vitest-0` belongs to `vitest-0`.\n * Untagged fileIds never match a tag-scoped lookup.\n */\nexport function hasTag(fileId: string, tag: string): boolean {\n return fileId.endsWith(`.${tag}`);\n}\n\n/**\n * Build the file path for an entity, with optional tag embedded in the filename.\n * `taggedPath('/data', 'runs', 'wrun_ABC', 'vitest-0')` → `/data/runs/wrun_ABC.vitest-0.json`\n * `taggedPath('/data', 'runs', 'wrun_ABC')` → `/data/runs/wrun_ABC.json`\n *\n * The `fileId` and `tag` are validated with {@link assertSafeEntityId} and\n * the result is containment-checked with {@link resolveWithinBase} to\n * prevent path-traversal attacks when values are derived from untrusted\n * request input.\n */\nexport function taggedPath(\n basedir: string,\n entityDir: string,\n fileId: string,\n tag?: string\n): string {\n assertSafeEntityId('fileId', fileId);\n if (tag !== undefined) assertSafeEntityId('tag', tag);\n const filename = tag ? `${fileId}.${tag}.json` : `${fileId}.json`;\n return resolveWithinBase(basedir, entityDir, filename);\n}\n\n/**\n * Read a JSON entity with tagged fallback.\n * When a tag is set, tries the tagged path first, then falls back to the\n * untagged path (so a tagged world can read entities written without a tag).\n */\nexport async function readJSONWithFallback<T>(\n basedir: string,\n entityDir: string,\n fileId: string,\n schema: z.ZodType<T>,\n tag?: string\n): Promise<T | null> {\n assertSafeEntityId('fileId', fileId);\n if (tag !== undefined) assertSafeEntityId('tag', tag);\n if (tag) {\n const result = await readJSON(\n resolveWithinBase(basedir, entityDir, `${fileId}.${tag}.json`),\n schema\n );\n if (result !== null) return result;\n }\n return readJSON(\n resolveWithinBase(basedir, entityDir, `${fileId}.json`),\n schema\n );\n}\n\n/**\n * List all filenames in a directory that have a specific tag.\n * Returns full filenames (e.g., `wrun_ABC.vitest-0.json`).\n */\nexport async function listTaggedFiles(\n dirPath: string,\n tag: string\n): Promise<string[]> {\n const suffix = `.${tag}.json`;\n try {\n const files = await fs.readdir(dirPath);\n return files.filter((f) => f.endsWith(suffix));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\n/**\n * List all filenames in a directory that have a specific tag and extension.\n * Returns full filenames (e.g., `stream-chnk_ABC.vitest-0.bin`).\n */\nexport async function listTaggedFilesByExtension(\n dirPath: string,\n tag: string,\n extension: string\n): Promise<string[]> {\n const suffix = `.${tag}${extension}`;\n try {\n const files = await fs.readdir(dirPath);\n return files.filter((f) => f.endsWith(suffix));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n try {\n await fs.mkdir(dirPath, { recursive: true });\n } catch (_error) {\n // Ignore if already exists\n }\n}\n\ninterface WriteOptions {\n overwrite?: boolean;\n}\n\n/**\n * Custom JSON replacer that encodes Uint8Array as base64 strings.\n * Format: { __type: 'Uint8Array', data: '<base64>' }\n */\nexport function jsonReplacer(_key: string, value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n}\n\n/**\n * Custom JSON reviver that decodes base64 strings back to Uint8Array.\n */\nexport function jsonReviver(_key: string, value: unknown): unknown {\n if (\n value !== null &&\n typeof value === 'object' &&\n (value as any).__type === 'Uint8Array' &&\n typeof (value as any).data === 'string'\n ) {\n return new Uint8Array(Buffer.from((value as any).data, 'base64'));\n }\n return value;\n}\n\nexport async function writeJSON(\n filePath: string,\n data: any,\n opts?: WriteOptions\n): Promise<void> {\n return write(filePath, JSON.stringify(data, jsonReplacer, 2), opts);\n}\n\n/**\n * Writes data to a file using atomic write-rename pattern.\n *\n * Note: While this function uses temp files to avoid partial writes,\n * it does not provide protection against concurrent writes from multiple\n * processes. In a multi-writer scenario, the last writer wins.\n * For production use with multiple writers, consider using a proper\n * database or locking mechanism.\n */\nexport async function write(\n filePath: string,\n data: string | Buffer,\n opts?: WriteOptions\n): Promise<void> {\n if (!opts?.overwrite) {\n // Fast path: check in-memory cache first to avoid expensive fs.access() calls\n // This provides significant performance improvement when creating many files\n if (createdFilesCache.has(filePath)) {\n throw new EntityConflictError(\n `File ${filePath} already exists and 'overwrite' is false`\n );\n }\n\n // Slow path: check filesystem for files created before this process started\n try {\n await fs.access(filePath);\n // File exists on disk, add to cache for future checks\n createdFilesCache.add(filePath);\n throw new EntityConflictError(\n `File ${filePath} already exists and 'overwrite' is false`\n );\n } catch (error: any) {\n // If file doesn't exist (ENOENT), continue with write\n if (error.code !== 'ENOENT') {\n throw error;\n }\n }\n }\n\n const tempPath = `${filePath}.tmp.${ulid()}`;\n let tempFileCreated = false;\n try {\n await ensureDir(path.dirname(filePath));\n await fs.writeFile(tempPath, data);\n tempFileCreated = true;\n await withWindowsRetry(() => fs.rename(tempPath, filePath));\n // Track this file in cache so future writes know it exists\n createdFilesCache.add(filePath);\n } catch (error) {\n // Only try to clean up temp file if it was actually created\n if (tempFileCreated) {\n await withWindowsRetry(() => fs.unlink(tempPath), 3).catch(() => {});\n }\n throw error;\n }\n}\n\nexport async function readJSON<T>(\n filePath: string,\n decoder: z.ZodType<T>\n): Promise<T | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n return decoder.parse(JSON.parse(content, jsonReviver));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return null;\n throw error;\n }\n}\n\nexport async function readBuffer(filePath: string): Promise<Buffer> {\n const content = await fs.readFile(filePath);\n return content;\n}\n\nexport async function readFirstByte(\n filePath: string\n): Promise<number | undefined> {\n const file = await fs.open(filePath, 'r');\n try {\n const byte = Buffer.allocUnsafe(1);\n const { bytesRead } = await file.read(byte, 0, 1, 0);\n return bytesRead === 0 ? undefined : byte[0];\n } finally {\n await file.close();\n }\n}\n\nexport async function deleteJSON(filePath: string): Promise<void> {\n try {\n await fs.unlink(filePath);\n } catch (error) {\n if ((error as any).code !== 'ENOENT') throw error;\n }\n}\n\n/**\n * Atomically create a file using O_CREAT | O_EXCL flags.\n * Returns true if the file was created, false if it already exists.\n * This is atomic at the OS level, safe for concurrent access.\n */\nexport async function writeExclusive(\n filePath: string,\n data: string\n): Promise<boolean> {\n await ensureDir(path.dirname(filePath));\n try {\n await fs.writeFile(filePath, data, { flag: 'wx' });\n return true;\n } catch (error: any) {\n if (error.code === 'EEXIST') {\n return false;\n }\n throw error;\n }\n}\n\nexport async function listJSONFiles(dirPath: string): Promise<string[]> {\n return listFilesByExtension(dirPath, '.json');\n}\n\nexport async function listFilesByExtension(\n dirPath: string,\n extension: string\n): Promise<string[]> {\n try {\n const files = await fs.readdir(dirPath);\n return files\n .filter((f) => f.endsWith(extension))\n .map((f) => f.slice(0, -extension.length));\n } catch (error) {\n if ((error as any).code === 'ENOENT') return [];\n throw error;\n }\n}\n\ninterface PaginatedFileSystemQueryConfig<T> {\n directory: string;\n schema: z.ZodType<T>;\n filePrefix?: string;\n fileIdFilter?: (fileId: string) => boolean;\n filter?: (item: T) => boolean;\n sortOrder?: 'asc' | 'desc';\n limit?: number;\n cursor?: string;\n getCreatedAt(filename: string): Date | null;\n getId?(item: T): string;\n}\n// Cursor format: \"timestamp|id\" for tie-breaking\ninterface ParsedCursor {\n timestamp: Date;\n id: string | null;\n}\n\nfunction parseCursor(cursor: string | undefined): ParsedCursor | null {\n if (!cursor) return null;\n\n const parts = cursor.split('|');\n return {\n timestamp: new Date(parts[0]),\n id: parts[1] || null,\n };\n}\n\nfunction createCursor(timestamp: Date, id: string | undefined): string {\n return id ? `${timestamp.toISOString()}|${id}` : timestamp.toISOString();\n}\n\nexport async function paginatedFileSystemQuery<T extends { createdAt: Date }>(\n config: PaginatedFileSystemQueryConfig<T>\n): Promise<PaginatedResponse<T>> {\n const {\n directory,\n schema,\n filePrefix,\n fileIdFilter,\n filter,\n sortOrder = 'desc',\n limit = 20,\n cursor,\n getCreatedAt,\n getId,\n } = config;\n\n // Validate filePrefix (typically `${runId}-`) so request-derived prefixes\n // consistently reject unsafe characters. filePrefix is only used below to\n // filter readdir() results by prefix — it doesn't participate in path\n // construction — but keeping the validation rule uniform across the\n // storage layer avoids special cases and catches bad values earlier.\n if (filePrefix !== undefined) {\n assertSafeEntityId('filePrefix', filePrefix);\n }\n\n // 1. Get all JSON files in directory\n const fileIds = await listJSONFiles(directory);\n\n // 2. Filter by prefix if provided\n const relevantFileIds = filePrefix\n ? fileIds.filter((fileId) => fileId.startsWith(filePrefix))\n : fileIds;\n const filteredFileIds = fileIdFilter\n ? relevantFileIds.filter(fileIdFilter)\n : relevantFileIds;\n\n // 3. ULID Optimization: Filter by cursor using filename timestamps before loading JSON\n const parsedCursor = parseCursor(cursor);\n let candidateFileIds = filteredFileIds;\n\n if (parsedCursor) {\n candidateFileIds = filteredFileIds.filter((fileId) => {\n const filenameDate = getCreatedAt(`${fileId}.json`);\n if (filenameDate) {\n // Use filename timestamp for cursor filtering\n // We need to be careful here: if parsedCursor has an ID (for tie-breaking),\n // we need to include items with the same timestamp for later ID-based filtering.\n // If no ID, we can use strict inequality for optimization.\n const cursorTime = parsedCursor.timestamp.getTime();\n const fileTime = filenameDate.getTime();\n\n if (parsedCursor.id) {\n // Tie-breaking mode: include items at or near cursor timestamp\n return sortOrder === 'desc'\n ? fileTime <= cursorTime\n : fileTime >= cursorTime;\n } else {\n // No tie-breaking: strict inequality\n return sortOrder === 'desc'\n ? fileTime < cursorTime\n : fileTime > cursorTime;\n }\n }\n // Can't extract timestamp from filename (e.g., steps use sequential IDs).\n // Include the file and defer to JSON-based filtering below.\n return true;\n });\n }\n\n // 4. Load files individually and collect valid items\n const validItems: T[] = [];\n\n for (const fileId of candidateFileIds) {\n const filePath = path.join(directory, `${fileId}.json`);\n let item: T | null = null;\n try {\n item = await readJSON(filePath, schema);\n } catch (error: unknown) {\n // We don't expect zod errors to happen, but if the JSON does get malformed,\n // we skip the item. Preferably, we'd have a way to mark items as malformed,\n // so that the UI can display them as such, with richer messaging. In the meantime,\n // we just log a warning and skip the item.\n if (error instanceof z.ZodError) {\n console.warn(\n `Skipping item ${fileId} due to malformed JSON: ${error.message}`\n );\n continue;\n }\n throw error;\n }\n\n if (item) {\n // Apply custom filter early if provided\n if (filter && !filter(item)) continue;\n\n // Double-check cursor filtering with actual createdAt from JSON\n // (in case ULID timestamp differs from stored createdAt)\n if (parsedCursor) {\n const itemTime = item.createdAt.getTime();\n const cursorTime = parsedCursor.timestamp.getTime();\n\n if (sortOrder === 'desc') {\n // For descending order, skip items >= cursor\n if (itemTime > cursorTime) continue;\n // If timestamps are equal, use ID for tie-breaking (skip if ID >= cursorId)\n if (itemTime === cursorTime && parsedCursor.id && getId) {\n const itemId = getId(item);\n if (itemId >= parsedCursor.id) continue;\n }\n } else {\n // For ascending order, skip items <= cursor\n if (itemTime < cursorTime) continue;\n // If timestamps are equal, use ID for tie-breaking (skip if ID <= cursorId)\n if (itemTime === cursorTime && parsedCursor.id && getId) {\n const itemId = getId(item);\n if (itemId <= parsedCursor.id) continue;\n }\n }\n }\n\n validItems.push(item);\n }\n }\n\n // 5. Sort by createdAt (and by ID for tie-breaking if getId is provided)\n validItems.sort((a, b) => {\n const aTime = a.createdAt.getTime();\n const bTime = b.createdAt.getTime();\n const timeComparison = sortOrder === 'asc' ? aTime - bTime : bTime - aTime;\n\n // If timestamps are equal and we have getId, use ID for stable sorting\n if (timeComparison === 0 && getId) {\n const aId = getId(a);\n const bId = getId(b);\n return sortOrder === 'asc'\n ? aId.localeCompare(bId)\n : bId.localeCompare(aId);\n }\n\n return timeComparison;\n });\n\n // 6. Apply pagination\n const hasMore = validItems.length > limit;\n const items = hasMore ? validItems.slice(0, limit) : validItems;\n const nextCursor =\n items.length > 0\n ? createCursor(\n items[items.length - 1].createdAt,\n getId?.(items[items.length - 1])\n )\n : null;\n\n return {\n data: items,\n cursor: nextCursor,\n hasMore,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAsBzB;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,uBAG1C;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM;;;EAKlD;AAyCD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAujBtE"}
1
+ {"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAsBzB;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,uBAG1C;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM;;;EAKlD;AA4DD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAujBtE"}
package/dist/streamer.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { EventEmitter } from 'node:events';
2
+ import fs from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { monotonicFactory } from 'ulid';
4
5
  import { z } from 'zod';
5
- import { assertSafeEntityId, listFilesByExtension, readBuffer, readJSONWithFallback, taggedPath, write, writeJSON, } from './fs.js';
6
+ import { assertSafeEntityId, readBuffer, readFirstByte, readJSONWithFallback, taggedPath, write, writeJSON, } from './fs.js';
6
7
  // Create a monotonic ULID factory that ensures ULIDs are always increasing
7
8
  // even when generated within the same millisecond
8
9
  const monotonicUlid = monotonicFactory(() => Math.random());
@@ -10,20 +11,43 @@ const monotonicUlid = monotonicFactory(() => Math.random());
10
11
  const RunStreamsSchema = z.object({
11
12
  streams: z.array(z.string()),
12
13
  });
14
+ const EOF_MARKER = 1;
15
+ function isEofByte(byte) {
16
+ return byte === EOF_MARKER;
17
+ }
13
18
  export function serializeChunk(chunk) {
14
- const eofByte = Buffer.from([chunk.eof ? 1 : 0]);
19
+ const eofByte = Buffer.from([chunk.eof ? EOF_MARKER : 0]);
15
20
  return Buffer.concat([eofByte, chunk.chunk]);
16
21
  }
17
22
  /** Check only the EOF flag byte without copying chunk payload. */
18
23
  export function isEofChunk(serialized) {
19
- return serialized[0] === 1;
24
+ return isEofByte(serialized[0]);
20
25
  }
21
26
  export function deserializeChunk(serialized) {
22
- const eof = serialized[0] === 1;
27
+ const eof = isEofChunk(serialized);
23
28
  // Create a copy instead of a view to prevent ArrayBuffer detachment
24
29
  const chunk = Buffer.from(serialized.subarray(1));
25
30
  return { eof, chunk };
26
31
  }
32
+ async function listChunkEntries(chunksDir) {
33
+ try {
34
+ return await fs.readdir(chunksDir);
35
+ }
36
+ catch (error) {
37
+ if (error.code === 'ENOENT')
38
+ return [];
39
+ throw error;
40
+ }
41
+ }
42
+ function addChunkFilesByExtension(extMap, entries, sourceExtension, fileExtension = sourceExtension, include = () => true) {
43
+ for (const entry of entries) {
44
+ if (!entry.endsWith(sourceExtension))
45
+ continue;
46
+ const file = entry.slice(0, -sourceExtension.length);
47
+ if (include(file))
48
+ extMap.set(file, fileExtension);
49
+ }
50
+ }
27
51
  /**
28
52
  * List chunk files for a stream, sorted chronologically (ULID order).
29
53
  * Returns both the sorted file names and a map of file → extension for
@@ -32,26 +56,14 @@ export function deserializeChunk(serialized) {
32
56
  async function listChunkFilesForStream(chunksDir, name, tag) {
33
57
  // Name is used as a filename prefix below; validate it can't escape chunksDir.
34
58
  assertSafeEntityId('streamName', name);
35
- const listPromises = [
36
- listFilesByExtension(chunksDir, '.bin'),
37
- listFilesByExtension(chunksDir, '.json'),
38
- ];
39
- if (tag) {
40
- listPromises.push(listFilesByExtension(chunksDir, `.${tag}.bin`));
41
- }
42
- const [binFiles, jsonFiles, ...taggedResults] = await Promise.all(listPromises);
43
- const taggedBinFiles = taggedResults[0] ?? [];
59
+ const entries = await listChunkEntries(chunksDir);
44
60
  const extMap = new Map();
45
- for (const f of jsonFiles)
46
- extMap.set(f, '.json');
47
- const tagSfx = tag ? `.${tag}` : '';
48
- for (const f of binFiles) {
49
- if (tag && f.endsWith(tagSfx))
50
- continue;
51
- extMap.set(f, '.bin');
61
+ addChunkFilesByExtension(extMap, entries, '.json');
62
+ addChunkFilesByExtension(extMap, entries, '.bin', '.bin', tag ? (file) => !file.endsWith(`.${tag}`) : undefined);
63
+ if (tag) {
64
+ const taggedExtension = `.${tag}.bin`;
65
+ addChunkFilesByExtension(extMap, entries, taggedExtension);
52
66
  }
53
- for (const f of taggedBinFiles)
54
- extMap.set(f, `.${tag}.bin`);
55
67
  const files = [...extMap.keys()]
56
68
  .filter((file) => file.startsWith(`${name}-`))
57
69
  .sort();
@@ -198,7 +210,7 @@ export function createStreamer(basedir, tag) {
198
210
  const filePath = path.join(chunksDir, `${file}${ext}`);
199
211
  // Before the cursor: only need to check EOF (1 byte), skip content
200
212
  if (dataIndex < startIndex) {
201
- if (isEofChunk(await readBuffer(filePath))) {
213
+ if (isEofByte(await readFirstByte(filePath))) {
202
214
  streamDone = true;
203
215
  break;
204
216
  }
@@ -207,7 +219,7 @@ export function createStreamer(basedir, tag) {
207
219
  }
208
220
  // Collected enough data chunks — peek at the next file for EOF/hasMore
209
221
  if (resultChunks.length >= limit) {
210
- if (isEofChunk(await readBuffer(filePath))) {
222
+ if (isEofByte(await readFirstByte(filePath))) {
211
223
  streamDone = true;
212
224
  }
213
225
  else {
@@ -244,13 +256,12 @@ export function createStreamer(basedir, tag) {
244
256
  async getStreamInfo(name, _runId) {
245
257
  const chunksDir = path.join(basedir, 'streams', 'chunks');
246
258
  const { files: chunkFiles, extMap: fileExtMap } = await listChunkFilesForStream(chunksDir, name, tag);
247
- // Only read the first byte of each file to check EOF — no full
248
- // deserialization needed since we just need a count.
259
+ // Read only the EOF marker byte because metadata never needs payloads.
249
260
  let streamDone = false;
250
261
  let dataCount = 0;
251
262
  for (const file of chunkFiles) {
252
263
  const ext = fileExtMap.get(file) ?? '.bin';
253
- if (isEofChunk(await readBuffer(path.join(chunksDir, `${file}${ext}`)))) {
264
+ if (isEofByte(await readFirstByte(path.join(chunksDir, `${file}${ext}`)))) {
254
265
  streamDone = true;
255
266
  break;
256
267
  }
@@ -333,10 +344,7 @@ export function createStreamer(basedir, tag) {
333
344
  chunkFiles.length > 0) {
334
345
  const lastFile = chunkFiles[chunkFiles.length - 1];
335
346
  const lastExt = fileExtMap.get(lastFile) ?? '.bin';
336
- // Note: this incurs an extra disk read to check the EOF marker.
337
- // Acceptable since negative startIndex is not a hot path.
338
- const lastChunk = deserializeChunk(await readBuffer(path.join(chunksDir, `${lastFile}${lastExt}`)));
339
- if (lastChunk?.eof === true) {
347
+ if (isEofByte(await readFirstByte(path.join(chunksDir, `${lastFile}${lastExt}`)))) {
340
348
  dataChunkCount--;
341
349
  }
342
350
  }
@@ -1 +1 @@
1
- {"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport path from 'node:path';\nimport type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\nimport {\n assertSafeEntityId,\n listFilesByExtension,\n readBuffer,\n readJSONWithFallback,\n taggedPath,\n write,\n writeJSON,\n} from './fs.js';\n\n// Create a monotonic ULID factory that ensures ULIDs are always increasing\n// even when generated within the same millisecond\nconst monotonicUlid = monotonicFactory(() => Math.random());\n\n// Schema for the run-to-streams mapping file\nconst RunStreamsSchema = z.object({\n streams: z.array(z.string()),\n});\n\n/**\n * A chunk consists of a boolean `eof` indicating if it's the last chunk,\n * and a `chunk` which is a Buffer of data.\n * The serialized format is:\n * - 1 byte for `eof` (0 or 1)\n * - and the rest is the chunk data.\n */\nexport interface Chunk {\n eof: boolean;\n chunk: Buffer;\n}\n\nexport function serializeChunk(chunk: Chunk) {\n const eofByte = Buffer.from([chunk.eof ? 1 : 0]);\n return Buffer.concat([eofByte, chunk.chunk]);\n}\n\n/** Check only the EOF flag byte without copying chunk payload. */\nexport function isEofChunk(serialized: Buffer): boolean {\n return serialized[0] === 1;\n}\n\nexport function deserializeChunk(serialized: Buffer) {\n const eof = serialized[0] === 1;\n // Create a copy instead of a view to prevent ArrayBuffer detachment\n const chunk = Buffer.from(serialized.subarray(1));\n return { eof, chunk };\n}\n\n/**\n * List chunk files for a stream, sorted chronologically (ULID order).\n * Returns both the sorted file names and a map of file → extension for\n * resolving the full path. Handles tagged and legacy (.json) formats.\n */\nasync function listChunkFilesForStream(\n chunksDir: string,\n name: string,\n tag?: string\n): Promise<{ files: string[]; extMap: Map<string, string> }> {\n // Name is used as a filename prefix below; validate it can't escape chunksDir.\n assertSafeEntityId('streamName', name);\n const listPromises: Promise<string[]>[] = [\n listFilesByExtension(chunksDir, '.bin'),\n listFilesByExtension(chunksDir, '.json'),\n ];\n if (tag) {\n listPromises.push(listFilesByExtension(chunksDir, `.${tag}.bin`));\n }\n const [binFiles, jsonFiles, ...taggedResults] =\n await Promise.all(listPromises);\n const taggedBinFiles = taggedResults[0] ?? [];\n\n const extMap = new Map<string, string>();\n for (const f of jsonFiles) extMap.set(f, '.json');\n const tagSfx = tag ? `.${tag}` : '';\n for (const f of binFiles) {\n if (tag && f.endsWith(tagSfx)) continue;\n extMap.set(f, '.bin');\n }\n for (const f of taggedBinFiles) extMap.set(f, `.${tag}.bin`);\n\n const files = [...extMap.keys()]\n .filter((file) => file.startsWith(`${name}-`))\n .sort();\n\n return { files, extMap };\n}\n\nexport function createStreamer(basedir: string, tag?: string): Streamer {\n const tagSuffix = tag ? `.${tag}` : '';\n const streamEmitter = new EventEmitter<{\n [key: `chunk:${string}`]: [\n {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n },\n ];\n [key: `close:${string}`]: [\n {\n streamName: string;\n },\n ];\n }>();\n\n // Track which streams have already been registered for a run (in-memory cache)\n const registeredStreams = new Set<string>();\n\n // Helper to record the runId <> streamId association\n async function registerStreamForRun(\n runId: string,\n streamName: string\n ): Promise<void> {\n assertSafeEntityId('runId', runId);\n assertSafeEntityId('streamName', streamName);\n const cacheKey = `${runId}:${streamName}`;\n if (registeredStreams.has(cacheKey)) {\n return; // Already registered in this session\n }\n\n const runStreamsPath = taggedPath(basedir, 'streams/runs', runId, tag);\n\n // Read existing streams for this run (try tagged first, fall back to untagged)\n const existing = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n const streams = existing?.streams ?? [];\n\n // Add stream if not already present\n if (!streams.includes(streamName)) {\n streams.push(streamName);\n await writeJSON(runStreamsPath, { streams }, { overwrite: true });\n }\n\n registeredStreams.add(cacheKey);\n }\n\n // Helper to convert a chunk to a Buffer\n function toBuffer(chunk: string | Uint8Array): Buffer {\n if (typeof chunk === 'string') {\n return Buffer.from(new TextEncoder().encode(chunk));\n } else if (chunk instanceof Buffer) {\n return chunk;\n } else {\n return Buffer.from(chunk);\n }\n }\n\n return {\n async writeToStream(\n name: string,\n _runId: string | Promise<string>,\n chunk: string | Uint8Array\n ) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n // This ensures that chunks written in sequence maintain their order even\n // when runId is a promise that multiple writes are waiting on.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Convert chunk to buffer for serialization\n const chunkBuffer = toBuffer(chunk);\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Emit real-time event with Uint8Array (create copy to prevent ArrayBuffer detachment)\n const chunkData = Uint8Array.from(chunkBuffer);\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n },\n\n async writeToStreamMulti(\n name: string,\n _runId: string | Promise<string>,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Generate all ULIDs synchronously BEFORE any await to preserve call order.\n // This ensures that chunks maintain their order even when runId is a promise.\n const chunkIds = chunks.map(() => `chnk_${monotonicUlid()}`);\n\n // Await runId if it's a promise\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Prepare chunk data for parallel writes\n const chunkBuffers = chunks.map((chunk) => toBuffer(chunk));\n\n // Write all chunks in parallel for efficiency, but track individual completion\n const writePromises = chunkBuffers.map(async (chunkBuffer, i) => {\n const chunkId = chunkIds[i];\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Return data needed for event emission\n return {\n chunkId,\n chunkData: Uint8Array.from(chunkBuffer),\n };\n });\n\n // Emit events in order, waiting for each chunk's write to complete\n // This ensures events are emitted in order while writes happen in parallel\n for (const writePromise of writePromises) {\n const { chunkId, chunkData } = await writePromise;\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n }\n },\n\n async closeStream(name: string, _runId: string | Promise<string>) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run (in case writeToStream wasn't called)\n await registerStreamForRun(runId, name);\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(\n chunkPath,\n serializeChunk({ chunk: Buffer.from([]), eof: true })\n );\n\n streamEmitter.emit(`close:${name}` as const, { streamName: name });\n },\n\n async listStreamsByRunId(runId: string) {\n assertSafeEntityId('runId', runId);\n const data = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n return data?.streams ?? [];\n },\n\n async getStreamChunks(\n name: string,\n _runId: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const limit = options?.limit ?? 100;\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Decode cursor\n let startIndex = 0;\n if (options?.cursor) {\n try {\n const decoded = JSON.parse(\n Buffer.from(options.cursor, 'base64').toString('utf-8')\n );\n startIndex = decoded.i;\n } catch {\n startIndex = 0;\n }\n }\n\n // Walk from startIndex, reading only the files we need.\n // Files before the cursor are skipped entirely.\n let streamDone = false;\n const resultChunks: { index: number; data: Uint8Array }[] = [];\n let dataIndex = 0; // running count of data (non-EOF) files seen\n\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n const filePath = path.join(chunksDir, `${file}${ext}`);\n\n // Before the cursor: only need to check EOF (1 byte), skip content\n if (dataIndex < startIndex) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n break;\n }\n dataIndex++;\n continue;\n }\n\n // Collected enough data chunks — peek at the next file for EOF/hasMore\n if (resultChunks.length >= limit) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n } else {\n // More data files exist beyond this page\n dataIndex++;\n }\n break;\n }\n\n // In the page window: deserialize fully\n const chunk = deserializeChunk(await readBuffer(filePath));\n if (chunk.eof) {\n streamDone = true;\n break;\n }\n resultChunks.push({\n index: dataIndex,\n data: Uint8Array.from(chunk.chunk),\n });\n dataIndex++;\n }\n\n // hasMore = we know there are data files beyond this page\n const hasMore =\n !streamDone && dataIndex > startIndex + resultChunks.length;\n const nextIndex = startIndex + resultChunks.length;\n const nextCursor = hasMore\n ? Buffer.from(JSON.stringify({ i: nextIndex })).toString('base64')\n : null;\n\n return {\n data: resultChunks,\n cursor: nextCursor,\n hasMore,\n done: streamDone,\n };\n },\n\n async getStreamInfo(\n name: string,\n _runId: string\n ): Promise<StreamInfoResponse> {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Only read the first byte of each file to check EOF — no full\n // deserialization needed since we just need a count.\n let streamDone = false;\n let dataCount = 0;\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n if (\n isEofChunk(await readBuffer(path.join(chunksDir, `${file}${ext}`)))\n ) {\n streamDone = true;\n break;\n }\n dataCount++;\n }\n\n return { tailIndex: dataCount - 1, done: streamDone };\n },\n\n async readFromStream(name: string, startIndex = 0) {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n let removeListeners = () => {};\n let pollInterval: ReturnType<typeof setInterval> | null = null;\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n // Track chunks delivered via events to prevent duplicates and maintain order.\n const deliveredChunkIds = new Set<string>();\n // Buffer for chunks that arrive via events during disk reading\n const bufferedEventChunks: Array<{\n chunkId: string;\n chunkData: Uint8Array;\n }> = [];\n let isReadingFromDisk = true;\n // Buffer close event if it arrives during disk reading\n let pendingClose = false;\n // Set when the controller is closed; guards against enqueue-after-close\n // in the polling callback when closeListener fires mid-iteration.\n let streamClosed = false;\n\n const chunkListener = (event: {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n }) => {\n // Skip empty chunks to maintain consistency with disk reading behavior\n if (event.chunkData.byteLength === 0) {\n deliveredChunkIds.add(event.chunkId);\n return;\n }\n\n if (isReadingFromDisk) {\n deliveredChunkIds.add(event.chunkId);\n // Buffer chunks that arrive during disk reading to maintain order\n // Create a copy to prevent ArrayBuffer detachment when enqueued later\n bufferedEventChunks.push({\n chunkId: event.chunkId,\n chunkData: Uint8Array.from(event.chunkData),\n });\n } else if (!deliveredChunkIds.has(event.chunkId)) {\n // Guard against duplicates: polling may have already claimed this\n // chunk between its has() check and readBuffer() yield.\n deliveredChunkIds.add(event.chunkId);\n // After disk reading is complete, deliver chunks immediately\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(event.chunkData));\n }\n };\n\n const closeListener = () => {\n // Buffer close event if disk reading is still in progress\n if (isReadingFromDisk) {\n pendingClose = true;\n return;\n }\n // Remove listeners before closing\n streamClosed = true;\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from cancel() or EOF)\n }\n };\n removeListeners = closeListener;\n\n // Set up listeners FIRST to avoid missing events\n streamEmitter.on(`chunk:${name}` as const, chunkListener);\n streamEmitter.on(`close:${name}` as const, closeListener);\n\n // Now load existing chunks from disk.\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Resolve negative startIndex relative to the number of data chunks\n // (excluding the trailing EOF marker chunk, if present).\n let dataChunkCount = chunkFiles.length;\n if (\n typeof startIndex === 'number' &&\n startIndex < 0 &&\n chunkFiles.length > 0\n ) {\n const lastFile = chunkFiles[chunkFiles.length - 1];\n const lastExt = fileExtMap.get(lastFile) ?? '.bin';\n // Note: this incurs an extra disk read to check the EOF marker.\n // Acceptable since negative startIndex is not a hot path.\n const lastChunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${lastFile}${lastExt}`))\n );\n if (lastChunk?.eof === true) {\n dataChunkCount--;\n }\n }\n const resolvedStartIndex =\n typeof startIndex === 'number' && startIndex < 0\n ? Math.max(0, dataChunkCount + startIndex)\n : startIndex;\n\n // Process existing chunks, skipping any already delivered via events\n let isComplete = false;\n for (let i = resolvedStartIndex; i < chunkFiles.length; i++) {\n const file = chunkFiles[i];\n // Extract chunk ID from filename: \"streamName-chunkId\" or \"streamName-chunkId.tag\"\n const rawChunkId = file.substring(name.length + 1);\n // Strip tag suffix (e.g., \"chnk_ULID.vitest-0\" → \"chnk_ULID\")\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n // Skip if already delivered via event\n if (deliveredChunkIds.has(chunkId)) {\n continue;\n }\n\n const ext = fileExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n if (chunk?.eof === true) {\n isComplete = true;\n break;\n }\n // Track as handled so polling doesn't re-deliver\n deliveredChunkIds.add(chunkId);\n if (chunk.chunk.byteLength) {\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n\n // Finished reading from disk - now deliver buffered event chunks in chronological order\n isReadingFromDisk = false;\n\n // Sort buffered chunks by ULID (chronological order)\n bufferedEventChunks.sort((a, b) =>\n a.chunkId.localeCompare(b.chunkId)\n );\n for (const buffered of bufferedEventChunks) {\n // Create a copy for defense in depth (already copied at storage, but be extra safe)\n controller.enqueue(Uint8Array.from(buffered.chunkData));\n }\n\n if (isComplete) {\n removeListeners();\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from closeListener event)\n }\n return;\n }\n\n // Process any pending close event that arrived during disk reading\n if (pendingClose) {\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Track pre-startIndex chunks so polling doesn't re-deliver them\n for (\n let i = 0;\n i < resolvedStartIndex && i < chunkFiles.length;\n i++\n ) {\n const file = chunkFiles[i];\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n deliveredChunkIds.add(chunkId);\n }\n\n // Start filesystem polling for cross-process streaming support.\n // The EventEmitter only works in-process; when the writer is in a\n // separate process (e.g. e2e test runner ↔ workbench app), polling\n // the shared filesystem is the fallback delivery mechanism.\n let isPolling = false;\n pollInterval = setInterval(async () => {\n if (isPolling) return;\n isPolling = true;\n try {\n const { files: currentFiles, extMap: currentExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n for (const file of currentFiles) {\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n if (deliveredChunkIds.has(chunkId)) continue;\n deliveredChunkIds.add(chunkId);\n\n const ext = currentExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n\n if (chunk?.eof === true) {\n streamClosed = true;\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Guard against enqueue-after-close: closeListener may have\n // fired between our readBuffer() yield and this point.\n if (streamClosed) return;\n\n if (chunk.chunk.byteLength) {\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n } catch (err: unknown) {\n // Silently ignore transient filesystem errors (ENOENT, EACCES, etc.)\n // Surface unexpected errors so bugs aren't hidden\n if (!(err instanceof Error && 'code' in err)) {\n console.error('[world-local] Unexpected polling error:', err);\n }\n } finally {\n isPolling = false;\n }\n }, 100);\n },\n\n cancel() {\n removeListeners();\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n },\n });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\nimport {\n assertSafeEntityId,\n readBuffer,\n readFirstByte,\n readJSONWithFallback,\n taggedPath,\n write,\n writeJSON,\n} from './fs.js';\n\n// Create a monotonic ULID factory that ensures ULIDs are always increasing\n// even when generated within the same millisecond\nconst monotonicUlid = monotonicFactory(() => Math.random());\n\n// Schema for the run-to-streams mapping file\nconst RunStreamsSchema = z.object({\n streams: z.array(z.string()),\n});\n\n/**\n * A chunk consists of a boolean `eof` indicating if it's the last chunk,\n * and a `chunk` which is a Buffer of data.\n * The serialized format is:\n * - 1 byte for `eof` (0 or 1)\n * - and the rest is the chunk data.\n */\nexport interface Chunk {\n eof: boolean;\n chunk: Buffer;\n}\n\nconst EOF_MARKER = 1;\n\nfunction isEofByte(byte: number | undefined): boolean {\n return byte === EOF_MARKER;\n}\n\nexport function serializeChunk(chunk: Chunk) {\n const eofByte = Buffer.from([chunk.eof ? EOF_MARKER : 0]);\n return Buffer.concat([eofByte, chunk.chunk]);\n}\n\n/** Check only the EOF flag byte without copying chunk payload. */\nexport function isEofChunk(serialized: Buffer): boolean {\n return isEofByte(serialized[0]);\n}\n\nexport function deserializeChunk(serialized: Buffer) {\n const eof = isEofChunk(serialized);\n // Create a copy instead of a view to prevent ArrayBuffer detachment\n const chunk = Buffer.from(serialized.subarray(1));\n return { eof, chunk };\n}\n\nasync function listChunkEntries(chunksDir: string): Promise<string[]> {\n try {\n return await fs.readdir(chunksDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw error;\n }\n}\n\nfunction addChunkFilesByExtension(\n extMap: Map<string, string>,\n entries: string[],\n sourceExtension: string,\n fileExtension = sourceExtension,\n include: (file: string) => boolean = () => true\n): void {\n for (const entry of entries) {\n if (!entry.endsWith(sourceExtension)) continue;\n const file = entry.slice(0, -sourceExtension.length);\n if (include(file)) extMap.set(file, fileExtension);\n }\n}\n\n/**\n * List chunk files for a stream, sorted chronologically (ULID order).\n * Returns both the sorted file names and a map of file → extension for\n * resolving the full path. Handles tagged and legacy (.json) formats.\n */\nasync function listChunkFilesForStream(\n chunksDir: string,\n name: string,\n tag?: string\n): Promise<{ files: string[]; extMap: Map<string, string> }> {\n // Name is used as a filename prefix below; validate it can't escape chunksDir.\n assertSafeEntityId('streamName', name);\n const entries = await listChunkEntries(chunksDir);\n const extMap = new Map<string, string>();\n addChunkFilesByExtension(extMap, entries, '.json');\n addChunkFilesByExtension(\n extMap,\n entries,\n '.bin',\n '.bin',\n tag ? (file) => !file.endsWith(`.${tag}`) : undefined\n );\n\n if (tag) {\n const taggedExtension = `.${tag}.bin`;\n addChunkFilesByExtension(extMap, entries, taggedExtension);\n }\n\n const files = [...extMap.keys()]\n .filter((file) => file.startsWith(`${name}-`))\n .sort();\n\n return { files, extMap };\n}\n\nexport function createStreamer(basedir: string, tag?: string): Streamer {\n const tagSuffix = tag ? `.${tag}` : '';\n const streamEmitter = new EventEmitter<{\n [key: `chunk:${string}`]: [\n {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n },\n ];\n [key: `close:${string}`]: [\n {\n streamName: string;\n },\n ];\n }>();\n\n // Track which streams have already been registered for a run (in-memory cache)\n const registeredStreams = new Set<string>();\n\n // Helper to record the runId <> streamId association\n async function registerStreamForRun(\n runId: string,\n streamName: string\n ): Promise<void> {\n assertSafeEntityId('runId', runId);\n assertSafeEntityId('streamName', streamName);\n const cacheKey = `${runId}:${streamName}`;\n if (registeredStreams.has(cacheKey)) {\n return; // Already registered in this session\n }\n\n const runStreamsPath = taggedPath(basedir, 'streams/runs', runId, tag);\n\n // Read existing streams for this run (try tagged first, fall back to untagged)\n const existing = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n const streams = existing?.streams ?? [];\n\n // Add stream if not already present\n if (!streams.includes(streamName)) {\n streams.push(streamName);\n await writeJSON(runStreamsPath, { streams }, { overwrite: true });\n }\n\n registeredStreams.add(cacheKey);\n }\n\n // Helper to convert a chunk to a Buffer\n function toBuffer(chunk: string | Uint8Array): Buffer {\n if (typeof chunk === 'string') {\n return Buffer.from(new TextEncoder().encode(chunk));\n } else if (chunk instanceof Buffer) {\n return chunk;\n } else {\n return Buffer.from(chunk);\n }\n }\n\n return {\n async writeToStream(\n name: string,\n _runId: string | Promise<string>,\n chunk: string | Uint8Array\n ) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n // This ensures that chunks written in sequence maintain their order even\n // when runId is a promise that multiple writes are waiting on.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Convert chunk to buffer for serialization\n const chunkBuffer = toBuffer(chunk);\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Emit real-time event with Uint8Array (create copy to prevent ArrayBuffer detachment)\n const chunkData = Uint8Array.from(chunkBuffer);\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n },\n\n async writeToStreamMulti(\n name: string,\n _runId: string | Promise<string>,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Generate all ULIDs synchronously BEFORE any await to preserve call order.\n // This ensures that chunks maintain their order even when runId is a promise.\n const chunkIds = chunks.map(() => `chnk_${monotonicUlid()}`);\n\n // Await runId if it's a promise\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Prepare chunk data for parallel writes\n const chunkBuffers = chunks.map((chunk) => toBuffer(chunk));\n\n // Write all chunks in parallel for efficiency, but track individual completion\n const writePromises = chunkBuffers.map(async (chunkBuffer, i) => {\n const chunkId = chunkIds[i];\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Return data needed for event emission\n return {\n chunkId,\n chunkData: Uint8Array.from(chunkBuffer),\n };\n });\n\n // Emit events in order, waiting for each chunk's write to complete\n // This ensures events are emitted in order while writes happen in parallel\n for (const writePromise of writePromises) {\n const { chunkId, chunkData } = await writePromise;\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n }\n },\n\n async closeStream(name: string, _runId: string | Promise<string>) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run (in case writeToStream wasn't called)\n await registerStreamForRun(runId, name);\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(\n chunkPath,\n serializeChunk({ chunk: Buffer.from([]), eof: true })\n );\n\n streamEmitter.emit(`close:${name}` as const, { streamName: name });\n },\n\n async listStreamsByRunId(runId: string) {\n assertSafeEntityId('runId', runId);\n const data = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n return data?.streams ?? [];\n },\n\n async getStreamChunks(\n name: string,\n _runId: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const limit = options?.limit ?? 100;\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Decode cursor\n let startIndex = 0;\n if (options?.cursor) {\n try {\n const decoded = JSON.parse(\n Buffer.from(options.cursor, 'base64').toString('utf-8')\n );\n startIndex = decoded.i;\n } catch {\n startIndex = 0;\n }\n }\n\n // Walk from startIndex, reading only the files we need.\n // Files before the cursor are skipped entirely.\n let streamDone = false;\n const resultChunks: { index: number; data: Uint8Array }[] = [];\n let dataIndex = 0; // running count of data (non-EOF) files seen\n\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n const filePath = path.join(chunksDir, `${file}${ext}`);\n\n // Before the cursor: only need to check EOF (1 byte), skip content\n if (dataIndex < startIndex) {\n if (isEofByte(await readFirstByte(filePath))) {\n streamDone = true;\n break;\n }\n dataIndex++;\n continue;\n }\n\n // Collected enough data chunks — peek at the next file for EOF/hasMore\n if (resultChunks.length >= limit) {\n if (isEofByte(await readFirstByte(filePath))) {\n streamDone = true;\n } else {\n // More data files exist beyond this page\n dataIndex++;\n }\n break;\n }\n\n // In the page window: deserialize fully\n const chunk = deserializeChunk(await readBuffer(filePath));\n if (chunk.eof) {\n streamDone = true;\n break;\n }\n resultChunks.push({\n index: dataIndex,\n data: Uint8Array.from(chunk.chunk),\n });\n dataIndex++;\n }\n\n // hasMore = we know there are data files beyond this page\n const hasMore =\n !streamDone && dataIndex > startIndex + resultChunks.length;\n const nextIndex = startIndex + resultChunks.length;\n const nextCursor = hasMore\n ? Buffer.from(JSON.stringify({ i: nextIndex })).toString('base64')\n : null;\n\n return {\n data: resultChunks,\n cursor: nextCursor,\n hasMore,\n done: streamDone,\n };\n },\n\n async getStreamInfo(\n name: string,\n _runId: string\n ): Promise<StreamInfoResponse> {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Read only the EOF marker byte because metadata never needs payloads.\n let streamDone = false;\n let dataCount = 0;\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n if (\n isEofByte(await readFirstByte(path.join(chunksDir, `${file}${ext}`)))\n ) {\n streamDone = true;\n break;\n }\n dataCount++;\n }\n\n return { tailIndex: dataCount - 1, done: streamDone };\n },\n\n async readFromStream(name: string, startIndex = 0) {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n let removeListeners = () => {};\n let pollInterval: ReturnType<typeof setInterval> | null = null;\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n // Track chunks delivered via events to prevent duplicates and maintain order.\n const deliveredChunkIds = new Set<string>();\n // Buffer for chunks that arrive via events during disk reading\n const bufferedEventChunks: Array<{\n chunkId: string;\n chunkData: Uint8Array;\n }> = [];\n let isReadingFromDisk = true;\n // Buffer close event if it arrives during disk reading\n let pendingClose = false;\n // Set when the controller is closed; guards against enqueue-after-close\n // in the polling callback when closeListener fires mid-iteration.\n let streamClosed = false;\n\n const chunkListener = (event: {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n }) => {\n // Skip empty chunks to maintain consistency with disk reading behavior\n if (event.chunkData.byteLength === 0) {\n deliveredChunkIds.add(event.chunkId);\n return;\n }\n\n if (isReadingFromDisk) {\n deliveredChunkIds.add(event.chunkId);\n // Buffer chunks that arrive during disk reading to maintain order\n // Create a copy to prevent ArrayBuffer detachment when enqueued later\n bufferedEventChunks.push({\n chunkId: event.chunkId,\n chunkData: Uint8Array.from(event.chunkData),\n });\n } else if (!deliveredChunkIds.has(event.chunkId)) {\n // Guard against duplicates: polling may have already claimed this\n // chunk between its has() check and readBuffer() yield.\n deliveredChunkIds.add(event.chunkId);\n // After disk reading is complete, deliver chunks immediately\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(event.chunkData));\n }\n };\n\n const closeListener = () => {\n // Buffer close event if disk reading is still in progress\n if (isReadingFromDisk) {\n pendingClose = true;\n return;\n }\n // Remove listeners before closing\n streamClosed = true;\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from cancel() or EOF)\n }\n };\n removeListeners = closeListener;\n\n // Set up listeners FIRST to avoid missing events\n streamEmitter.on(`chunk:${name}` as const, chunkListener);\n streamEmitter.on(`close:${name}` as const, closeListener);\n\n // Now load existing chunks from disk.\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Resolve negative startIndex relative to the number of data chunks\n // (excluding the trailing EOF marker chunk, if present).\n let dataChunkCount = chunkFiles.length;\n if (\n typeof startIndex === 'number' &&\n startIndex < 0 &&\n chunkFiles.length > 0\n ) {\n const lastFile = chunkFiles[chunkFiles.length - 1];\n const lastExt = fileExtMap.get(lastFile) ?? '.bin';\n if (\n isEofByte(\n await readFirstByte(\n path.join(chunksDir, `${lastFile}${lastExt}`)\n )\n )\n ) {\n dataChunkCount--;\n }\n }\n const resolvedStartIndex =\n typeof startIndex === 'number' && startIndex < 0\n ? Math.max(0, dataChunkCount + startIndex)\n : startIndex;\n\n // Process existing chunks, skipping any already delivered via events\n let isComplete = false;\n for (let i = resolvedStartIndex; i < chunkFiles.length; i++) {\n const file = chunkFiles[i];\n // Extract chunk ID from filename: \"streamName-chunkId\" or \"streamName-chunkId.tag\"\n const rawChunkId = file.substring(name.length + 1);\n // Strip tag suffix (e.g., \"chnk_ULID.vitest-0\" → \"chnk_ULID\")\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n // Skip if already delivered via event\n if (deliveredChunkIds.has(chunkId)) {\n continue;\n }\n\n const ext = fileExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n if (chunk?.eof === true) {\n isComplete = true;\n break;\n }\n // Track as handled so polling doesn't re-deliver\n deliveredChunkIds.add(chunkId);\n if (chunk.chunk.byteLength) {\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n\n // Finished reading from disk - now deliver buffered event chunks in chronological order\n isReadingFromDisk = false;\n\n // Sort buffered chunks by ULID (chronological order)\n bufferedEventChunks.sort((a, b) =>\n a.chunkId.localeCompare(b.chunkId)\n );\n for (const buffered of bufferedEventChunks) {\n // Create a copy for defense in depth (already copied at storage, but be extra safe)\n controller.enqueue(Uint8Array.from(buffered.chunkData));\n }\n\n if (isComplete) {\n removeListeners();\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from closeListener event)\n }\n return;\n }\n\n // Process any pending close event that arrived during disk reading\n if (pendingClose) {\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Track pre-startIndex chunks so polling doesn't re-deliver them\n for (\n let i = 0;\n i < resolvedStartIndex && i < chunkFiles.length;\n i++\n ) {\n const file = chunkFiles[i];\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n deliveredChunkIds.add(chunkId);\n }\n\n // Start filesystem polling for cross-process streaming support.\n // The EventEmitter only works in-process; when the writer is in a\n // separate process (e.g. e2e test runner ↔ workbench app), polling\n // the shared filesystem is the fallback delivery mechanism.\n let isPolling = false;\n pollInterval = setInterval(async () => {\n if (isPolling) return;\n isPolling = true;\n try {\n const { files: currentFiles, extMap: currentExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n for (const file of currentFiles) {\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n if (deliveredChunkIds.has(chunkId)) continue;\n deliveredChunkIds.add(chunkId);\n\n const ext = currentExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n\n if (chunk?.eof === true) {\n streamClosed = true;\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Guard against enqueue-after-close: closeListener may have\n // fired between our readBuffer() yield and this point.\n if (streamClosed) return;\n\n if (chunk.chunk.byteLength) {\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n } catch (err: unknown) {\n // Silently ignore transient filesystem errors (ENOENT, EACCES, etc.)\n // Surface unexpected errors so bugs aren't hidden\n if (!(err instanceof Error && 'code' in err)) {\n console.error('[world-local] Unexpected polling error:', err);\n }\n } finally {\n isPolling = false;\n }\n }, 100);\n },\n\n cancel() {\n removeListeners();\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n },\n });\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow/world-local",
3
- "version": "4.1.2",
3
+ "version": "4.1.3",
4
4
  "description": "Local development World implementation for Workflow SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,7 @@
30
30
  "zod": "4.3.6",
31
31
  "@workflow/errors": "4.1.2",
32
32
  "@workflow/utils": "4.1.2",
33
- "@workflow/world": "4.1.2"
33
+ "@workflow/world": "4.1.3"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@opentelemetry/api": "1.9.0",